Merge "Fix ColorSpace#get allocating"
diff --git a/.mailmap b/.mailmap
index b061ccf..40c295e 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1 +1 @@
-Ember Rose <emberr@google.com> <ashleyrose@google.com>
+Ember Rose <emberrose@google.com> <ashleyrose@google.com>
diff --git a/Android.bp b/Android.bp
index 9ed0d37..0547481 100644
--- a/Android.bp
+++ b/Android.bp
@@ -413,6 +413,8 @@
"framework-platform-compat-config",
"libcore-platform-compat-config",
"services-platform-compat-config",
+ "media-provider-platform-compat-config",
+ "services-devicepolicy-platform-compat-config",
],
sdk_version: "core_platform",
}
@@ -452,7 +454,9 @@
srcs: [
"core/java/android/annotation/IntDef.java",
"core/java/android/annotation/UnsupportedAppUsage.java",
- ":unsupportedappusage_annotation_files",
+ ],
+ static_libs: [
+ "art.module.api.annotations",
],
sdk_version: "core_current",
@@ -953,6 +957,7 @@
"test-base/src/**/*.java",
":opt-telephony-srcs",
":opt-net-voip-srcs",
+ ":core-current-stubs-source",
":core_public_api_files",
":updatable-media-srcs",
"test-mock/src/**/*.java",
@@ -1017,6 +1022,7 @@
"core/java/**/*.logtags",
":opt-telephony-srcs",
":opt-net-voip-srcs",
+ ":core-current-stubs-source",
":core_public_api_files",
":updatable-media-srcs",
":jobscheduler-framework-source",
@@ -1456,6 +1462,11 @@
removed_api_file: "api/removed.txt",
baseline_file: ":public-api-incompatibilities-with-last-released",
},
+ api_lint: {
+ enabled: true,
+ new_since: ":last-released-public-api",
+ baseline_file: "api/lint-baseline.txt",
+ },
},
jdiff_enabled: true,
}
@@ -1482,6 +1493,11 @@
removed_api_file: "api/system-removed.txt",
baseline_file: ":system-api-incompatibilities-with-last-released"
},
+ api_lint: {
+ enabled: true,
+ new_since: ":last-released-system-api",
+ baseline_file: "api/system-lint-baseline.txt",
+ },
},
jdiff_enabled: true,
}
@@ -1580,4 +1596,4 @@
"core/java/com/android/internal/util/State.java",
"core/java/com/android/internal/util/StateMachine.java",
],
-}
\ No newline at end of file
+}
diff --git a/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java b/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java
index 6979f0f..48ce8ab 100644
--- a/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java
+++ b/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java
@@ -20,9 +20,9 @@
import android.os.Looper;
import android.perftests.utils.PerfStatusReporter;
+import android.perftests.utils.PerfTestActivity;
import android.perftests.utils.SettingsHelper;
import android.perftests.utils.SettingsStateKeeperRule;
-import android.perftests.utils.StubActivity;
import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
@@ -46,8 +46,8 @@
Settings.Secure.AUTOFILL_SERVICE);
@Rule
- public ActivityTestRule<StubActivity> mActivityRule =
- new ActivityTestRule<StubActivity>(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -68,7 +68,7 @@
Looper.getMainLooper().getThread() == Thread.currentThread());
assertTrue("We should be running on the main thread",
Looper.myLooper() == Looper.getMainLooper());
- StubActivity activity = mActivityRule.getActivity();
+ PerfTestActivity activity = mActivityRule.getActivity();
activity.setContentView(mLayoutId);
onCreate(activity);
});
@@ -89,9 +89,9 @@
}
/**
- * Initializes the {@link StubActivity} after it was launched.
+ * Initializes the {@link PerfTestActivity} after it was launched.
*/
- protected abstract void onCreate(StubActivity activity);
+ protected abstract void onCreate(PerfTestActivity activity);
/**
* Uses the {@code settings} binary to set the autofill service.
diff --git a/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java b/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java
index 8090826..fb5ea80 100644
--- a/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java
+++ b/apct-tests/perftests/autofill/src/android/view/autofill/LoginTest.java
@@ -20,7 +20,7 @@
import static android.view.autofill.AutofillManager.AutofillCallback.EVENT_INPUT_SHOWN;
import android.perftests.utils.BenchmarkState;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import android.view.View;
import android.widget.EditText;
@@ -39,7 +39,7 @@
}
@Override
- protected void onCreate(StubActivity activity) {
+ protected void onCreate(PerfTestActivity activity) {
View root = activity.getWindow().getDecorView();
mUsername = root.findViewById(R.id.username);
mPassword = root.findViewById(R.id.password);
diff --git a/apct-tests/perftests/core/AndroidManifest.xml b/apct-tests/perftests/core/AndroidManifest.xml
index 525975d..290f178 100644
--- a/apct-tests/perftests/core/AndroidManifest.xml
+++ b/apct-tests/perftests/core/AndroidManifest.xml
@@ -13,7 +13,7 @@
<application>
<uses-library android:name="android.test.runner" />
- <activity android:name="android.perftests.utils.StubActivity">
+ <activity android:name="android.perftests.utils.PerfTestActivity">
<intent-filter>
<action android:name="com.android.perftests.core.PERFTEST" />
</intent-filter>
diff --git a/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java b/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java
index b3f8359..a320514 100644
--- a/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java
+++ b/apct-tests/perftests/core/src/android/app/PendingIntentPerfTest.java
@@ -20,7 +20,7 @@
import android.content.Intent;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.LargeTest;
@@ -48,7 +48,7 @@
@Before
public void setUp() {
mContext = InstrumentationRegistry.getTargetContext();
- mIntent = StubActivity.createLaunchIntent(mContext);
+ mIntent = PerfTestActivity.createLaunchIntent(mContext);
}
/**
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/PaintHasGlyphPerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/PaintHasGlyphPerfTest.java
index 3a80020..b9c7af4 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/PaintHasGlyphPerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/PaintHasGlyphPerfTest.java
@@ -19,7 +19,7 @@
import android.graphics.Paint;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
@@ -58,7 +58,8 @@
}
@Rule
- public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/VectorDrawablePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/VectorDrawablePerfTest.java
index 3b2b8a9..d14e93e 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/VectorDrawablePerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/VectorDrawablePerfTest.java
@@ -26,7 +26,7 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.BitmapUtils;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import android.test.suitebuilder.annotation.LargeTest;
import androidx.test.rule.ActivityTestRule;
@@ -48,8 +48,8 @@
private int[] mTestHeights = {512, 1024};
@Rule
- public ActivityTestRule<StubActivity> mActivityRule =
- new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java b/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java
index 5be99d9..6b295e5 100644
--- a/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/DynamicLayoutPerfTest.java
@@ -23,7 +23,7 @@
import android.graphics.Paint.FontMetricsInt;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import android.text.style.ReplacementSpan;
import android.util.ArraySet;
@@ -75,7 +75,8 @@
}
@Rule
- public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java b/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java
index b34001d..b0edb11 100644
--- a/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java
+++ b/apct-tests/perftests/core/src/android/view/ViewShowHidePerfTest.java
@@ -23,7 +23,7 @@
import android.graphics.drawable.ColorDrawable;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import android.view.View.MeasureSpec;
import android.widget.FrameLayout;
import android.widget.ImageView;
@@ -46,7 +46,8 @@
public class ViewShowHidePerfTest {
@Rule
- public ActivityTestRule mActivityRule = new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java b/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java
index b3ea62a..270b4e5 100644
--- a/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/EditTextBackspacePerfTest.java
@@ -18,7 +18,7 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import android.text.Selection;
import android.view.KeyEvent;
import android.view.View.MeasureSpec;
@@ -80,7 +80,8 @@
}
@Rule
- public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java b/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java
index aa47d5b..8028f11 100644
--- a/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/EditTextCursorMovementPerfTest.java
@@ -18,7 +18,7 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import android.text.Selection;
import android.view.KeyEvent;
import android.view.View.MeasureSpec;
@@ -74,7 +74,8 @@
}
@Rule
- public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java b/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
index e50016c..f4ad5dd 100644
--- a/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/EditTextLongTextPerfTest.java
@@ -19,7 +19,7 @@
import android.app.Activity;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import android.view.KeyEvent;
import android.view.View.MeasureSpec;
import android.view.ViewGroup;
@@ -59,7 +59,8 @@
}
@Rule
- public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java b/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java
index 644095b..223a316 100644
--- a/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/LayoutPerfTest.java
@@ -28,7 +28,7 @@
import android.os.Looper;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import android.view.View;
import android.view.ViewGroup;
@@ -72,8 +72,8 @@
}
@Rule
- public ActivityTestRule<StubActivity> mActivityRule =
- new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/widget/TextViewAutoSizeLayoutPerfTest.java b/apct-tests/perftests/core/src/android/widget/TextViewAutoSizeLayoutPerfTest.java
index bed173b..694e1f4 100644
--- a/apct-tests/perftests/core/src/android/widget/TextViewAutoSizeLayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/TextViewAutoSizeLayoutPerfTest.java
@@ -22,7 +22,7 @@
import android.os.Looper;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
@@ -64,8 +64,8 @@
}
@Rule
- public ActivityTestRule<StubActivity> mActivityRule =
- new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java b/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java
index 00bd8db..a546667 100644
--- a/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java
+++ b/apct-tests/perftests/core/src/android/widget/TextViewSetTextLocalePerfTest.java
@@ -18,7 +18,7 @@
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
@@ -56,7 +56,8 @@
}
@Rule
- public ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule(StubActivity.class);
+ public ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
diff --git a/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java b/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java
new file mode 100644
index 0000000..c096cd2
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/wm/InternalWindowOperationPerfTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wm;
+
+import static android.perftests.utils.ManualBenchmarkState.StatsReport;
+
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.perftests.utils.ManualBenchmarkState;
+import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest;
+import android.perftests.utils.PerfManualStatusReporter;
+import android.perftests.utils.TraceMarkParser;
+import android.perftests.utils.TraceMarkParser.TraceMarkSlice;
+import android.util.Log;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.lifecycle.Stage;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.concurrent.TimeUnit;
+
+/** Measure the performance of internal methods in window manager service by trace tag. */
+@LargeTest
+public class InternalWindowOperationPerfTest extends WindowManagerPerfTestBase {
+ private static final String TAG = InternalWindowOperationPerfTest.class.getSimpleName();
+
+ @Rule
+ public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
+
+ @Rule
+ public final PerfTestActivityRule mActivityRule = new PerfTestActivityRule();
+
+ private final TraceMarkParser mTraceMarkParser = new TraceMarkParser(
+ "applyPostLayoutPolicy",
+ "applySurfaceChanges",
+ "AppTransitionReady",
+ "closeSurfaceTransactiom",
+ "openSurfaceTransaction",
+ "performLayout",
+ "performSurfacePlacement",
+ "prepareSurfaces",
+ "updateInputWindows",
+ "WSA#startAnimation",
+ "activityIdle",
+ "activityPaused",
+ "activityStopped",
+ "activityDestroyed",
+ "finishActivity",
+ "startActivityInner");
+
+ @Test
+ @ManualBenchmarkTest(
+ targetTestDurationNs = 20 * TIME_1_S_IN_NS,
+ statsReport = @StatsReport(
+ flags = StatsReport.FLAG_ITERATION | StatsReport.FLAG_MEAN
+ | StatsReport.FLAG_MAX | StatsReport.FLAG_COEFFICIENT_VAR))
+ public void testLaunchAndFinishActivity() throws Throwable {
+ final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ long measuredTimeNs = 0;
+ boolean isTraceStarted = false;
+
+ while (state.keepRunning(measuredTimeNs)) {
+ if (!isTraceStarted && !state.isWarmingUp()) {
+ startAsyncAtrace();
+ isTraceStarted = true;
+ }
+ final long startTime = SystemClock.elapsedRealtimeNanos();
+ mActivityRule.launchActivity();
+ mActivityRule.finishActivity();
+ mActivityRule.waitForIdleSync(Stage.DESTROYED);
+ measuredTimeNs = SystemClock.elapsedRealtimeNanos() - startTime;
+ }
+
+ stopAsyncAtrace();
+
+ mTraceMarkParser.forAllSlices((key, slices) -> {
+ for (TraceMarkSlice slice : slices) {
+ state.addExtraResult(key, (long) (slice.getDurarionInSeconds() * NANOS_PER_S));
+ }
+ });
+
+ Log.i(TAG, String.valueOf(mTraceMarkParser));
+ }
+
+ private void startAsyncAtrace() throws IOException {
+ sUiAutomation.executeShellCommand("atrace -b 32768 --async_start wm");
+ // Avoid atrace isn't ready immediately.
+ SystemClock.sleep(TimeUnit.NANOSECONDS.toMillis(TIME_1_S_IN_NS));
+ }
+
+ private void stopAsyncAtrace() throws IOException {
+ final ParcelFileDescriptor pfd = sUiAutomation.executeShellCommand("atrace --async_stop");
+ final InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ mTraceMarkParser.visit(line);
+ }
+ }
+ }
+}
diff --git a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
index 9cfc3d2..73b4a19 100644
--- a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
+++ b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java
@@ -16,16 +16,13 @@
package android.wm;
-import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_COEFFICIENT_VAR;
-import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_ITERATION;
-import static android.perftests.utils.ManualBenchmarkState.STATS_REPORT_MEAN;
+import static android.perftests.utils.ManualBenchmarkState.StatsReport;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.hamcrest.core.AnyOf.anyOf;
import static org.hamcrest.core.Is.is;
-import android.app.Activity;
import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
@@ -39,23 +36,16 @@
import android.perftests.utils.ManualBenchmarkState;
import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest;
import android.perftests.utils.PerfManualStatusReporter;
-import android.perftests.utils.StubActivity;
import android.util.Pair;
import android.view.IRecentsAnimationController;
import android.view.IRecentsAnimationRunner;
import android.view.RemoteAnimationTarget;
-import android.view.WindowManager;
import androidx.test.filters.LargeTest;
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
-import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import androidx.test.runner.lifecycle.Stage;
-import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assume;
-import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
@@ -77,11 +67,10 @@
public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
@Rule
- public final ActivityTestRule<StubActivity> mActivityRule = new ActivityTestRule<>(
- StubActivity.class, false /* initialTouchMode */, false /* launchActivity */);
+ public final PerfTestActivityRule mActivityRule =
+ new PerfTestActivityRule(true /* launchActivity */);
private long mMeasuredTimeNs;
- private LifecycleListener mLifecycleListener;
@Parameterized.Parameter(0)
public int intervalBetweenOperations;
@@ -127,24 +116,6 @@
sUiAutomation.dropShellPermissionIdentity();
}
- @Before
- @Override
- public void setUp() {
- super.setUp();
- final Activity testActivity = mActivityRule.launchActivity(null /* intent */);
- try {
- mActivityRule.runOnUiThread(() -> testActivity.getWindow()
- .addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON));
- } catch (Throwable ignored) { }
- mLifecycleListener = new LifecycleListener(testActivity);
- ActivityLifecycleMonitorRegistry.getInstance().addLifecycleCallback(mLifecycleListener);
- }
-
- @After
- public void tearDown() {
- ActivityLifecycleMonitorRegistry.getInstance().removeLifecycleCallback(mLifecycleListener);
- }
-
/** Simulate the timing of touch. */
private void makeInterval() {
SystemClock.sleep(intervalBetweenOperations);
@@ -167,8 +138,8 @@
@ManualBenchmarkTest(
warmupDurationNs = TIME_1_S_IN_NS,
targetTestDurationNs = TIME_5_S_IN_NS,
- statsReportFlags =
- STATS_REPORT_ITERATION | STATS_REPORT_MEAN | STATS_REPORT_COEFFICIENT_VAR)
+ statsReport = @StatsReport(flags = StatsReport.FLAG_ITERATION | StatsReport.FLAG_MEAN
+ | StatsReport.FLAG_COEFFICIENT_VAR))
public void testRecentsAnimation() throws Throwable {
final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
final IActivityTaskManager atm = ActivityTaskManager.getService();
@@ -201,7 +172,7 @@
state.addExtraResult(finishCase.first, elapsedTimeNsOfFinish);
if (moveRecentsToTop) {
- mLifecycleListener.waitForIdleSync(Stage.STOPPED);
+ mActivityRule.waitForIdleSync(Stage.STOPPED);
startTime = SystemClock.elapsedRealtimeNanos();
atm.startActivityFromRecents(testActivityTaskId, null /* options */);
@@ -209,7 +180,7 @@
mMeasuredTimeNs += elapsedTimeNs;
state.addExtraResult("startFromRecents", elapsedTimeNs);
- mLifecycleListener.waitForIdleSync(Stage.RESUMED);
+ mActivityRule.waitForIdleSync(Stage.RESUMED);
}
makeInterval();
@@ -223,55 +194,18 @@
}
};
+ recentsSemaphore.tryAcquire();
while (state.keepRunning(mMeasuredTimeNs)) {
- Assume.assumeTrue(recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS));
+ mMeasuredTimeNs = 0;
final long startTime = SystemClock.elapsedRealtimeNanos();
atm.startRecentsActivity(sRecentsIntent, null /* unused */, anim);
final long elapsedTimeNsOfStart = SystemClock.elapsedRealtimeNanos() - startTime;
mMeasuredTimeNs += elapsedTimeNsOfStart;
state.addExtraResult("start", elapsedTimeNsOfStart);
- }
- // Ensure the last round of animation callback is done.
- recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS);
- recentsSemaphore.release();
- }
-
- private static class LifecycleListener implements ActivityLifecycleCallback {
- private final Activity mTargetActivity;
- private Stage mWaitingStage;
- private Stage mReceivedStage;
-
- LifecycleListener(Activity activity) {
- mTargetActivity = activity;
- }
-
- void waitForIdleSync(Stage state) {
- synchronized (this) {
- if (state != mReceivedStage) {
- mWaitingStage = state;
- try {
- wait(TimeUnit.NANOSECONDS.toMillis(TIME_5_S_IN_NS));
- } catch (InterruptedException impossible) { }
- }
- mWaitingStage = mReceivedStage = null;
- }
- getInstrumentation().waitForIdleSync();
- }
-
- @Override
- public void onActivityLifecycleChanged(Activity activity, Stage stage) {
- if (mTargetActivity != activity) {
- return;
- }
-
- synchronized (this) {
- mReceivedStage = stage;
- if (mWaitingStage == mReceivedStage) {
- notifyAll();
- }
- }
+ // Ensure the animation callback is done.
+ Assume.assumeTrue(recentsSemaphore.tryAcquire(TIME_5_S_IN_NS, TimeUnit.NANOSECONDS));
}
}
}
diff --git a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java
index f0c474b..f43bdf8 100644
--- a/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java
+++ b/apct-tests/perftests/core/src/android/wm/RelayoutPerfTest.java
@@ -24,7 +24,7 @@
import android.os.RemoteException;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
-import android.perftests.utils.StubActivity;
+import android.perftests.utils.PerfTestActivity;
import android.util.MergedConfiguration;
import android.view.DisplayCutout;
import android.view.IWindow;
@@ -57,8 +57,8 @@
public final PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@Rule
- public final ActivityTestRule<StubActivity> mActivityRule =
- new ActivityTestRule<>(StubActivity.class);
+ public final ActivityTestRule<PerfTestActivity> mActivityRule =
+ new ActivityTestRule<>(PerfTestActivity.class);
/** This is only a placement to match the input parameters from {@link #getParameters}. */
@Parameterized.Parameter(0)
diff --git a/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java b/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java
index 4864da4..4d278c3 100644
--- a/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java
+++ b/apct-tests/perftests/core/src/android/wm/WindowManagerPerfTestBase.java
@@ -18,9 +18,21 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import android.app.Activity;
import android.app.UiAutomation;
+import android.content.Intent;
+import android.perftests.utils.PerfTestActivity;
-import org.junit.Before;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
+import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
+import androidx.test.runner.lifecycle.Stage;
+
+import org.junit.BeforeClass;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.concurrent.TimeUnit;
public class WindowManagerPerfTestBase {
static final UiAutomation sUiAutomation = getInstrumentation().getUiAutomation();
@@ -28,10 +40,102 @@
static final long TIME_1_S_IN_NS = 1 * NANOS_PER_S;
static final long TIME_5_S_IN_NS = 5 * NANOS_PER_S;
- @Before
- public void setUp() {
+ @BeforeClass
+ public static void setUpOnce() {
// In order to be closer to the real use case.
sUiAutomation.executeShellCommand("input keyevent KEYCODE_WAKEUP");
sUiAutomation.executeShellCommand("wm dismiss-keyguard");
+ getInstrumentation().getContext().startActivity(new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ }
+
+ /**
+ * Provides an activity that keeps screen on and is able to wait for a stable lifecycle stage.
+ */
+ static class PerfTestActivityRule extends ActivityTestRule<PerfTestActivity> {
+ private final Intent mStartIntent =
+ new Intent().putExtra(PerfTestActivity.INTENT_EXTRA_KEEP_SCREEN_ON, true);
+ private final LifecycleListener mLifecycleListener = new LifecycleListener();
+
+ PerfTestActivityRule() {
+ this(false /* launchActivity */);
+ }
+
+ PerfTestActivityRule(boolean launchActivity) {
+ super(PerfTestActivity.class, false /* initialTouchMode */, launchActivity);
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ final Statement wrappedStatement = new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ ActivityLifecycleMonitorRegistry.getInstance()
+ .addLifecycleCallback(mLifecycleListener);
+ base.evaluate();
+ ActivityLifecycleMonitorRegistry.getInstance()
+ .removeLifecycleCallback(mLifecycleListener);
+ }
+ };
+ return super.apply(wrappedStatement, description);
+ }
+
+ @Override
+ protected Intent getActivityIntent() {
+ return mStartIntent;
+ }
+
+ @Override
+ public PerfTestActivity launchActivity(Intent intent) {
+ final PerfTestActivity activity = super.launchActivity(intent);
+ mLifecycleListener.setTargetActivity(activity);
+ return activity;
+ }
+
+ PerfTestActivity launchActivity() {
+ return launchActivity(mStartIntent);
+ }
+
+ void waitForIdleSync(Stage state) {
+ mLifecycleListener.waitForIdleSync(state);
+ }
+ }
+
+ static class LifecycleListener implements ActivityLifecycleCallback {
+ private Activity mTargetActivity;
+ private Stage mWaitingStage;
+ private Stage mReceivedStage;
+
+ void setTargetActivity(Activity activity) {
+ mTargetActivity = activity;
+ mReceivedStage = mWaitingStage = null;
+ }
+
+ void waitForIdleSync(Stage stage) {
+ synchronized (this) {
+ if (stage != mReceivedStage) {
+ mWaitingStage = stage;
+ try {
+ wait(TimeUnit.NANOSECONDS.toMillis(TIME_5_S_IN_NS));
+ } catch (InterruptedException impossible) { }
+ }
+ mWaitingStage = mReceivedStage = null;
+ }
+ getInstrumentation().waitForIdleSync();
+ }
+
+ @Override
+ public void onActivityLifecycleChanged(Activity activity, Stage stage) {
+ if (mTargetActivity != activity) {
+ return;
+ }
+
+ synchronized (this) {
+ mReceivedStage = stage;
+ if (mWaitingStage == mReceivedStage) {
+ notifyAll();
+ }
+ }
+ }
}
}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
index ffe39e8..a83254b 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/ManualBenchmarkState.java
@@ -59,27 +59,37 @@
public final class ManualBenchmarkState {
private static final String TAG = ManualBenchmarkState.class.getSimpleName();
- @IntDef(prefix = {"STATS_REPORT"}, value = {
- STATS_REPORT_MEDIAN,
- STATS_REPORT_MEAN,
- STATS_REPORT_MIN,
- STATS_REPORT_MAX,
- STATS_REPORT_PERCENTILE90,
- STATS_REPORT_PERCENTILE95,
- STATS_REPORT_STDDEV,
- STATS_REPORT_ITERATION,
- })
- public @interface StatsReport {}
+ @Target(ElementType.ANNOTATION_TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface StatsReport {
+ int FLAG_MEDIAN = 0x00000001;
+ int FLAG_MEAN = 0x00000002;
+ int FLAG_MIN = 0x00000004;
+ int FLAG_MAX = 0x00000008;
+ int FLAG_STDDEV = 0x00000010;
+ int FLAG_COEFFICIENT_VAR = 0x00000020;
+ int FLAG_ITERATION = 0x00000040;
- public static final int STATS_REPORT_MEDIAN = 0x00000001;
- public static final int STATS_REPORT_MEAN = 0x00000002;
- public static final int STATS_REPORT_MIN = 0x00000004;
- public static final int STATS_REPORT_MAX = 0x00000008;
- public static final int STATS_REPORT_PERCENTILE90 = 0x00000010;
- public static final int STATS_REPORT_PERCENTILE95 = 0x00000020;
- public static final int STATS_REPORT_STDDEV = 0x00000040;
- public static final int STATS_REPORT_COEFFICIENT_VAR = 0x00000080;
- public static final int STATS_REPORT_ITERATION = 0x00000100;
+ @Retention(RetentionPolicy.RUNTIME)
+ @IntDef(value = {
+ FLAG_MEDIAN,
+ FLAG_MEAN,
+ FLAG_MIN,
+ FLAG_MAX,
+ FLAG_STDDEV,
+ FLAG_COEFFICIENT_VAR,
+ FLAG_ITERATION,
+ })
+ @interface Flag {}
+
+ /** Defines which type of statistics should output. */
+ @Flag int flags() default -1;
+ /** An array with value 0~100 to provide the percentiles. */
+ int[] percentiles() default {};
+ }
+
+ /** It means the entire {@link StatsReport} is not given. */
+ private static final int DEFAULT_STATS_REPORT = -2;
// TODO: Tune these values.
// warm-up for duration
@@ -116,8 +126,9 @@
// The computation needs double precision, but long int is fine for final reporting.
private Stats mStats;
- private int mStatsReportFlags = STATS_REPORT_MEDIAN | STATS_REPORT_MEAN
- | STATS_REPORT_PERCENTILE90 | STATS_REPORT_PERCENTILE95 | STATS_REPORT_STDDEV;
+ private int mStatsReportFlags =
+ StatsReport.FLAG_MEDIAN | StatsReport.FLAG_MEAN | StatsReport.FLAG_STDDEV;
+ private int[] mStatsReportPercentiles = {90 , 95};
private boolean shouldReport(int statsReportFlag) {
return (mStatsReportFlags & statsReportFlag) != 0;
@@ -136,9 +147,10 @@
if (targetTestDurationNs >= 0) {
mTargetTestDurationNs = targetTestDurationNs;
}
- final int statsReportFlags = testAnnotation.statsReportFlags();
- if (statsReportFlags >= 0) {
- mStatsReportFlags = statsReportFlags;
+ final StatsReport statsReport = testAnnotation.statsReport();
+ if (statsReport != null && statsReport.flags() != DEFAULT_STATS_REPORT) {
+ mStatsReportFlags = statsReport.flags();
+ mStatsReportPercentiles = statsReport.percentiles();
}
}
@@ -189,11 +201,20 @@
}
/**
- * Adds additional result while this benchmark is running. It is used when a sequence of
+ * @return {@code true} if the benchmark is in warmup state. It can be used to skip the
+ * operations or measurements that are unnecessary while the test isn't running the
+ * actual benchmark.
+ */
+ public boolean isWarmingUp() {
+ return mState == WARMUP;
+ }
+
+ /**
+ * Adds additional result while this benchmark isn't warming up. It is used when a sequence of
* operations is executed consecutively, the duration of each operation can also be recorded.
*/
public void addExtraResult(String key, long duration) {
- if (mState != RUNNING) {
+ if (isWarmingUp()) {
return;
}
if (mExtraResults == null) {
@@ -221,31 +242,30 @@
}
private void fillStatus(Bundle status, String key, Stats stats) {
- if (shouldReport(STATS_REPORT_ITERATION)) {
+ if (shouldReport(StatsReport.FLAG_ITERATION)) {
status.putLong(key + "_iteration", stats.getSize());
}
- if (shouldReport(STATS_REPORT_MEDIAN)) {
+ if (shouldReport(StatsReport.FLAG_MEDIAN)) {
status.putLong(key + "_median", stats.getMedian());
}
- if (shouldReport(STATS_REPORT_MEAN)) {
+ if (shouldReport(StatsReport.FLAG_MEAN)) {
status.putLong(key + "_mean", Math.round(stats.getMean()));
}
- if (shouldReport(STATS_REPORT_MIN)) {
+ if (shouldReport(StatsReport.FLAG_MIN)) {
status.putLong(key + "_min", stats.getMin());
}
- if (shouldReport(STATS_REPORT_MAX)) {
+ if (shouldReport(StatsReport.FLAG_MAX)) {
status.putLong(key + "_max", stats.getMax());
}
- if (shouldReport(STATS_REPORT_PERCENTILE90)) {
- status.putLong(key + "_percentile90", stats.getPercentile90());
+ if (mStatsReportPercentiles != null) {
+ for (int percentile : mStatsReportPercentiles) {
+ status.putLong(key + "_percentile" + percentile, stats.getPercentile(percentile));
+ }
}
- if (shouldReport(STATS_REPORT_PERCENTILE95)) {
- status.putLong(key + "_percentile95", stats.getPercentile95());
- }
- if (shouldReport(STATS_REPORT_STDDEV)) {
+ if (shouldReport(StatsReport.FLAG_STDDEV)) {
status.putLong(key + "_stddev", Math.round(stats.getStandardDeviation()));
}
- if (shouldReport(STATS_REPORT_COEFFICIENT_VAR)) {
+ if (shouldReport(StatsReport.FLAG_COEFFICIENT_VAR)) {
status.putLong(key + "_cv",
Math.round((100 * stats.getStandardDeviation() / stats.getMean())));
}
@@ -276,6 +296,6 @@
public @interface ManualBenchmarkTest {
long warmupDurationNs() default -1;
long targetTestDurationNs() default -1;
- @StatsReport int statsReportFlags() default -1;
+ StatsReport statsReport() default @StatsReport(flags = DEFAULT_STATS_REPORT);
}
}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java b/apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java
new file mode 100644
index 0000000..e934feb
--- /dev/null
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.perftests.utils;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+/**
+ * A simple activity used for testing, e.g. performance of activity switching, or as a base
+ * container of testing view.
+ */
+public class PerfTestActivity extends Activity {
+ public static final String INTENT_EXTRA_KEEP_SCREEN_ON = "keep_screen_on";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (getIntent().getBooleanExtra(INTENT_EXTRA_KEEP_SCREEN_ON, false)) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+
+ public static Intent createLaunchIntent(Context context) {
+ final Intent intent = new Intent();
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClass(context, PerfTestActivity.class);
+ return intent;
+ }
+}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java b/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java
index f650e81..fb516a8 100644
--- a/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/Stats.java
@@ -16,34 +16,34 @@
package android.perftests.utils;
+import android.annotation.IntRange;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Stats {
- private long mMedian, mMin, mMax, mPercentile90, mPercentile95;
+ private long mMedian, mMin, mMax;
private double mMean, mStandardDeviation;
- private final int mSize;
+ private final List<Long> mValues;
/* 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.");
}
+ // Make a copy since we're modifying it.
+ mValues = values = new ArrayList<>(values);
+
Collections.sort(values);
- mSize = size;
mMin = values.get(0);
mMax = values.get(values.size() - 1);
mMedian = size % 2 == 0 ? (values.get(size / 2) + values.get(size / 2 - 1)) / 2 :
values.get(size / 2);
- mPercentile90 = getPercentile(values, 90);
- mPercentile95 = getPercentile(values, 95);
for (int i = 0; i < size; ++i) {
long result = values.get(i);
@@ -59,7 +59,7 @@
}
public int getSize() {
- return mSize;
+ return mValues.size();
}
public double getMean() {
@@ -82,12 +82,8 @@
return mStandardDeviation;
}
- public long getPercentile90() {
- return mPercentile90;
- }
-
- public long getPercentile95() {
- return mPercentile95;
+ public long getPercentile(@IntRange(from = 0, to = 100) int percentile) {
+ return getPercentile(mValues, percentile);
}
private static long getPercentile(List<Long> values, int percentile) {
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java b/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java
deleted file mode 100644
index 8f03f7e..0000000
--- a/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.perftests.utils;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-
-public class StubActivity extends Activity {
- public static Intent createLaunchIntent(Context context) {
- final Intent intent = new Intent();
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.setClass(context, StubActivity.class);
- return intent;
- }
-}
diff --git a/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java b/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java
new file mode 100644
index 0000000..1afed3a
--- /dev/null
+++ b/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Predicate;
+
+/**
+ * Utility to get the slice of tracing_mark_write S,F,B,E (Async: start, finish, Sync: begin, end).
+ * Use {@link #visit(String)} to process the trace in text form. The filtered results can be
+ * obtained by {@link #forAllSlices(BiConsumer)}.
+ *
+ * @see android.os.Trace
+ */
+public class TraceMarkParser {
+ /** All slices by the name of {@link TraceMarkLine}. */
+ private final Map<String, List<TraceMarkSlice>> mSlicesMap = new HashMap<>();
+ /** The nested depth of each task-pid. */
+ private final Map<String, Integer> mDepthMap = new HashMap<>();
+ /** The start trace lines that haven't matched the corresponding end. */
+ private final Map<String, TraceMarkLine> mPendingStarts = new HashMap<>();
+
+ private final Predicate<TraceMarkLine> mTraceLineFilter;
+
+ public TraceMarkParser(Predicate<TraceMarkLine> traceLineFilter) {
+ mTraceLineFilter = traceLineFilter;
+ }
+
+ /** Only accept the trace event with the given names. */
+ public TraceMarkParser(String... traceNames) {
+ this(line -> {
+ for (String name : traceNames) {
+ if (name.equals(line.name)) {
+ return true;
+ }
+ }
+ return false;
+ });
+ }
+
+ /** Computes {@link TraceMarkSlice} by the given trace line. */
+ public void visit(String textTraceLine) {
+ final TraceMarkLine line = TraceMarkLine.parse(textTraceLine);
+ if (line == null) {
+ return;
+ }
+
+ if (line.isAsync) {
+ // Async-trace contains name in the start and finish event.
+ if (mTraceLineFilter.test(line)) {
+ if (line.isBegin) {
+ mPendingStarts.put(line.name, line);
+ } else {
+ final TraceMarkLine start = mPendingStarts.remove(line.name);
+ if (start != null) {
+ addSlice(start, line);
+ }
+ }
+ }
+ return;
+ }
+
+ int depth = 1;
+ if (line.isBegin) {
+ final Integer existingDepth = mDepthMap.putIfAbsent(line.taskPid, 1);
+ if (existingDepth != null) {
+ mDepthMap.put(line.taskPid, depth = existingDepth + 1);
+ }
+ // Sync-trace only contains name in the begin event.
+ if (mTraceLineFilter.test(line)) {
+ mPendingStarts.put(getSyncPendingStartKey(line, depth), line);
+ }
+ } else {
+ final Integer existingDepth = mDepthMap.get(line.taskPid);
+ if (existingDepth != null) {
+ depth = existingDepth;
+ mDepthMap.put(line.taskPid, existingDepth - 1);
+ }
+ final TraceMarkLine begin = mPendingStarts.remove(getSyncPendingStartKey(line, depth));
+ if (begin != null) {
+ addSlice(begin, line);
+ }
+ }
+ }
+
+ private static String getSyncPendingStartKey(TraceMarkLine line, int depth) {
+ return line.taskPid + "@" + depth;
+ }
+
+ private void addSlice(TraceMarkLine begin, TraceMarkLine end) {
+ mSlicesMap.computeIfAbsent(
+ begin.name, k -> new ArrayList<>()).add(new TraceMarkSlice(begin, end));
+ }
+
+ public void forAllSlices(BiConsumer<String, List<TraceMarkSlice>> consumer) {
+ for (Map.Entry<String, List<TraceMarkSlice>> entry : mSlicesMap.entrySet()) {
+ consumer.accept(entry.getKey(), entry.getValue());
+ }
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ forAllSlices((key, slices) -> {
+ double totalMs = 0;
+ for (TraceMarkSlice s : slices) {
+ totalMs += s.getDurarionInSeconds() * 1000;
+ }
+ sb.append(key).append(" count=").append(slices.size()).append(" avg=")
+ .append(totalMs / slices.size()).append("ms\n");
+ });
+
+ if (!mPendingStarts.isEmpty()) {
+ sb.append("[Warning] Unresolved events:").append(mPendingStarts).append("\n");
+ }
+ return sb.toString();
+ }
+
+ public static class TraceMarkSlice {
+ public final TraceMarkLine begin;
+ public final TraceMarkLine end;
+
+ TraceMarkSlice(TraceMarkLine begin, TraceMarkLine end) {
+ this.begin = begin;
+ this.end = end;
+ }
+
+ public double getDurarionInSeconds() {
+ return end.timestamp - begin.timestamp;
+ }
+ }
+
+ // taskPid timestamp name
+ // # Async:
+ // Binder:129_F-349 ( 1296) [003] ...1 12.2776: tracing_mark_write: S|1296|launching: a.test|0
+ // android.anim-135 ( 1296) [005] ...1 12.3361: tracing_mark_write: F|1296|launching: a.test|0
+ // # Normal:
+ // Binder:129_6-315 ( 1296) [007] ...1 97.4576: tracing_mark_write: B|1296|relayoutWindow: xxx
+ // ... there may have other nested begin/end
+ // Binder:129_6-315 ( 1296) [007] ...1 97.4580: tracing_mark_write: E|1296
+ public static class TraceMarkLine {
+ static final String EVENT_KEYWORD = ": tracing_mark_write: ";
+ static final char ASYNC_START = 'S';
+ static final char ASYNC_FINISH = 'F';
+ static final char SYNC_BEGIN = 'B';
+ static final char SYNC_END = 'E';
+
+ public final String taskPid;
+ public final double timestamp;
+ public final String name;
+ public final boolean isAsync;
+ public final boolean isBegin;
+
+ TraceMarkLine(String rawLine, int typePos, int type) throws IllegalArgumentException {
+ taskPid = rawLine.substring(0, rawLine.indexOf('(')).trim();
+ final int timeEnd = rawLine.indexOf(':', taskPid.length());
+ if (timeEnd < 0) {
+ throw new IllegalArgumentException("Timestamp end not found");
+ }
+ final int timeBegin = rawLine.lastIndexOf(' ', timeEnd);
+ if (timeBegin < 0) {
+ throw new IllegalArgumentException("Timestamp start not found");
+ }
+ timestamp = Double.parseDouble(rawLine.substring(timeBegin, timeEnd));
+ isAsync = type == ASYNC_START || type == ASYNC_FINISH;
+ isBegin = type == ASYNC_START || type == SYNC_BEGIN;
+
+ if (!isAsync && !isBegin) {
+ name = "";
+ } else {
+ // Get the position of the second '|' from "S|1234|name".
+ final int nameBegin = rawLine.indexOf('|', typePos + 2) + 1;
+ if (nameBegin == 0) {
+ throw new IllegalArgumentException("Name begin not found");
+ }
+ if (isAsync) {
+ // Get the name from "S|1234|name|0".
+ name = rawLine.substring(nameBegin, rawLine.lastIndexOf('|'));
+ } else {
+ name = rawLine.substring(nameBegin);
+ }
+ }
+ }
+
+ static TraceMarkLine parse(String rawLine) {
+ final int eventPos = rawLine.indexOf(EVENT_KEYWORD);
+ if (eventPos < 0) {
+ return null;
+ }
+ final int typePos = eventPos + EVENT_KEYWORD.length();
+ if (typePos >= rawLine.length()) {
+ return null;
+ }
+ final int type = rawLine.charAt(typePos);
+ if (type != ASYNC_START && type != ASYNC_FINISH
+ && type != SYNC_BEGIN && type != SYNC_END) {
+ return null;
+ }
+
+ try {
+ return new TraceMarkLine(rawLine, typePos, type);
+ } catch (IllegalArgumentException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "TraceMarkLine{pid=" + taskPid + " time=" + timestamp + " name=" + name
+ + " async=" + isAsync + " begin=" + isBegin + "}";
+ }
+ }
+}
diff --git a/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java b/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java
index 9039f92..e27670c 100644
--- a/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java
+++ b/apex/jobscheduler/framework/java/android/os/DeviceIdleManager.java
@@ -17,10 +17,13 @@
package android.os;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.content.Context;
+import java.util.List;
+
/**
* Access to the service that keeps track of device idleness and drives low power mode based on
* that.
@@ -66,4 +69,19 @@
return new String[0];
}
}
+
+ /**
+ * Add the specified packages to the power save whitelist.
+ *
+ * @return the number of packages that were successfully added to the whitelist
+ */
+ @RequiresPermission(android.Manifest.permission.DEVICE_POWER)
+ public int addPowerSaveWhitelistApps(@NonNull List<String> packageNames) {
+ try {
+ return mService.addPowerSaveWhitelistApps(packageNames);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return 0;
+ }
+ }
}
diff --git a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
index 9d5becb..20fb000 100644
--- a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
+++ b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl
@@ -21,6 +21,7 @@
/** @hide */
interface IDeviceIdleController {
void addPowerSaveWhitelistApp(String name);
+ int addPowerSaveWhitelistApps(in List<String> packageNames);
void removePowerSaveWhitelistApp(String name);
/* Removes an app from the system whitelist. Calling restoreSystemPowerWhitelistApp will add
the app back into the system whitelist */
diff --git a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java
index 1273249..6475f57 100644
--- a/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/DeviceIdleInternal.java
@@ -49,4 +49,24 @@
int[] getPowerSaveWhitelistUserAppIds();
int[] getPowerSaveTempWhitelistAppIds();
+
+ /**
+ * Listener to be notified when DeviceIdleController determines that the device has moved or is
+ * stationary.
+ */
+ interface StationaryListener {
+ void onDeviceStationaryChanged(boolean isStationary);
+ }
+
+ /**
+ * Registers a listener that will be notified when the system has detected that the device is
+ * stationary or in motion.
+ */
+ void registerStationaryListener(StationaryListener listener);
+
+ /**
+ * Unregisters a registered stationary listener from being notified when the system has detected
+ * that the device is stationary or in motion.
+ */
+ void unregisterStationaryListener(StationaryListener listener);
}
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index 3cfb080..c036c77 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -35,8 +35,6 @@
void onBootPhase(int phase);
- boolean isParoledOrCharging();
-
void postCheckIdleStates(int userId);
/**
@@ -59,13 +57,15 @@
int getAppId(String packageName);
- boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime,
+ /**
+ * @see #isAppIdleFiltered(String, int, int, long)
+ */
+ boolean isAppIdleFiltered(String packageName, int userId, long elapsedRealtime,
boolean shouldObfuscateInstantApps);
/**
* Checks if an app has been idle for a while and filters out apps that are excluded.
* It returns false if the current system state allows all apps to be considered active.
- * This happens if the device is plugged in or temporarily allowed to make exceptions.
* Called by interface impls.
*/
boolean isAppIdleFiltered(String packageName, int appId, int userId,
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index d6661c2..4ee46f4 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -48,7 +48,6 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
-import android.os.FileUtils;
import android.os.Handler;
import android.os.IDeviceIdleController;
import android.os.Looper;
@@ -106,6 +105,8 @@
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
import java.util.stream.Collectors;
/**
@@ -275,6 +276,7 @@
private IBatteryStats mBatteryStats;
private ActivityManagerInternal mLocalActivityManager;
private ActivityTaskManagerInternal mLocalActivityTaskManager;
+ private DeviceIdleInternal mLocalService;
private PowerManagerInternal mLocalPowerManager;
private PowerManager mPowerManager;
private INetworkPolicyManager mNetworkPolicyManager;
@@ -289,6 +291,7 @@
private boolean mLightEnabled;
private boolean mDeepEnabled;
private boolean mQuickDozeActivated;
+ private boolean mQuickDozeActivatedWhileIdling;
private boolean mForceIdle;
private boolean mNetworkConnected;
private boolean mScreenOn;
@@ -300,6 +303,10 @@
private boolean mHasNetworkLocation;
private Location mLastGenericLocation;
private Location mLastGpsLocation;
+
+ /** Time in the elapsed realtime timebase when this listener last received a motion event. */
+ private long mLastMotionEventElapsed;
+
// Current locked state of the screen
private boolean mScreenLocked;
private int mNumBlockingConstraints = 0;
@@ -549,6 +556,9 @@
*/
private ArrayMap<String, Integer> mRemovedFromSystemWhitelistApps = new ArrayMap<>();
+ private final ArraySet<DeviceIdleInternal.StationaryListener> mStationaryListeners =
+ new ArraySet<>();
+
private static final int EVENT_NULL = 0;
private static final int EVENT_NORMAL = 1;
private static final int EVENT_LIGHT_IDLE = 2;
@@ -607,6 +617,22 @@
}
};
+
+ private final AlarmManager.OnAlarmListener mMotionTimeoutAlarmListener = () -> {
+ synchronized (DeviceIdleController.this) {
+ if (!isStationaryLocked()) {
+ // If the device keeps registering motion, then the alarm should be
+ // rescheduled, so this shouldn't go off until the device is stationary.
+ // This case may happen in a race condition (alarm goes off right before
+ // motion is detected, but handleMotionDetectedLocked is called before
+ // we enter this block).
+ Slog.w(TAG, "motion timeout went off and device isn't stationary");
+ return;
+ }
+ }
+ postStationaryStatusUpdated();
+ };
+
private final AlarmManager.OnAlarmListener mSensingTimeoutAlarmListener
= new AlarmManager.OnAlarmListener() {
@Override
@@ -656,12 +682,70 @@
}
};
+ /** Post stationary status only to this listener. */
+ private void postStationaryStatus(DeviceIdleInternal.StationaryListener listener) {
+ mHandler.obtainMessage(MSG_REPORT_STATIONARY_STATUS, listener).sendToTarget();
+ }
+
+ /** Post stationary status to all registered listeners. */
+ private void postStationaryStatusUpdated() {
+ mHandler.sendEmptyMessage(MSG_REPORT_STATIONARY_STATUS);
+ }
+
+ private boolean isStationaryLocked() {
+ final long now = mInjector.getElapsedRealtime();
+ return mMotionListener.active
+ // Listening for motion for long enough and last motion was long enough ago.
+ && now - Math.max(mMotionListener.activatedTimeElapsed, mLastMotionEventElapsed)
+ >= mConstants.MOTION_INACTIVE_TIMEOUT;
+ }
+
+ @VisibleForTesting
+ void registerStationaryListener(DeviceIdleInternal.StationaryListener listener) {
+ synchronized (this) {
+ if (!mStationaryListeners.add(listener)) {
+ // Listener already registered.
+ return;
+ }
+ postStationaryStatus(listener);
+ if (mMotionListener.active) {
+ if (!isStationaryLocked() && mStationaryListeners.size() == 1) {
+ // First listener to be registered and the device isn't stationary, so we
+ // need to register the alarm to report the device is stationary.
+ scheduleMotionTimeoutAlarmLocked();
+ }
+ } else {
+ startMonitoringMotionLocked();
+ scheduleMotionTimeoutAlarmLocked();
+ }
+ }
+ }
+
+ private void unregisterStationaryListener(DeviceIdleInternal.StationaryListener listener) {
+ synchronized (this) {
+ if (mStationaryListeners.remove(listener) && mStationaryListeners.size() == 0
+ // Motion detection is started when transitioning from INACTIVE to IDLE_PENDING
+ // and so doesn't need to be on for ACTIVE or INACTIVE states.
+ // Motion detection isn't needed when idling due to Quick Doze.
+ && (mState == STATE_ACTIVE || mState == STATE_INACTIVE
+ || mQuickDozeActivated)) {
+ maybeStopMonitoringMotionLocked();
+ }
+ }
+ }
+
@VisibleForTesting
final class MotionListener extends TriggerEventListener
implements SensorEventListener {
boolean active = false;
+ /**
+ * Time in the elapsed realtime timebase when this listener was activated. Only valid if
+ * {@link #active} is true.
+ */
+ long activatedTimeElapsed;
+
public boolean isActive() {
return active;
}
@@ -669,7 +753,6 @@
@Override
public void onTrigger(TriggerEvent event) {
synchronized (DeviceIdleController.this) {
- active = false;
motionLocked();
}
}
@@ -677,8 +760,6 @@
@Override
public void onSensorChanged(SensorEvent event) {
synchronized (DeviceIdleController.this) {
- mSensorManager.unregisterListener(this, mMotionSensor);
- active = false;
motionLocked();
}
}
@@ -696,6 +777,7 @@
}
if (success) {
active = true;
+ activatedTimeElapsed = mInjector.getElapsedRealtime();
} else {
Slog.e(TAG, "Unable to register for " + mMotionSensor);
}
@@ -1303,6 +1385,8 @@
private static final int MSG_REPORT_IDLE_OFF = 4;
private static final int MSG_REPORT_ACTIVE = 5;
private static final int MSG_TEMP_APP_WHITELIST_TIMEOUT = 6;
+ @VisibleForTesting
+ static final int MSG_REPORT_STATIONARY_STATUS = 7;
private static final int MSG_FINISH_IDLE_OP = 8;
private static final int MSG_REPORT_TEMP_APP_WHITELIST_CHANGED = 9;
private static final int MSG_SEND_CONSTRAINT_MONITORING = 10;
@@ -1428,6 +1512,32 @@
updatePreIdleFactor();
maybeDoImmediateMaintenance();
} break;
+ case MSG_REPORT_STATIONARY_STATUS: {
+ final DeviceIdleInternal.StationaryListener newListener =
+ (DeviceIdleInternal.StationaryListener) msg.obj;
+ final DeviceIdleInternal.StationaryListener[] listeners;
+ final boolean isStationary;
+ synchronized (DeviceIdleController.this) {
+ isStationary = isStationaryLocked();
+ if (newListener == null) {
+ // Only notify all listeners if we aren't directing to one listener.
+ listeners = mStationaryListeners.toArray(
+ new DeviceIdleInternal.StationaryListener[
+ mStationaryListeners.size()]);
+ } else {
+ listeners = null;
+ }
+ }
+ if (listeners != null) {
+ for (DeviceIdleInternal.StationaryListener listener : listeners) {
+ listener.onDeviceStationaryChanged(isStationary);
+ }
+ }
+ if (newListener != null) {
+ newListener.onDeviceStationaryChanged(isStationary);
+ }
+ }
+ break;
}
}
}
@@ -1441,11 +1551,20 @@
if (DEBUG) {
Slog.i(TAG, "addPowerSaveWhitelistApp(name = " + name + ")");
}
+ addPowerSaveWhitelistApps(Collections.singletonList(name));
+ }
+
+ @Override
+ public int addPowerSaveWhitelistApps(List<String> packageNames) {
+ if (DEBUG) {
+ Slog.i(TAG,
+ "addPowerSaveWhitelistApps(name = " + packageNames + ")");
+ }
getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
null);
long ident = Binder.clearCallingIdentity();
try {
- addPowerSaveWhitelistAppInternal(name);
+ return addPowerSaveWhitelistAppsInternal(packageNames);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -1680,6 +1799,16 @@
public int[] getPowerSaveTempWhitelistAppIds() {
return DeviceIdleController.this.getAppIdTempWhitelistInternal();
}
+
+ @Override
+ public void registerStationaryListener(StationaryListener listener) {
+ DeviceIdleController.this.registerStationaryListener(listener);
+ }
+
+ @Override
+ public void unregisterStationaryListener(StationaryListener listener) {
+ DeviceIdleController.this.unregisterStationaryListener(listener);
+ }
}
static class Injector {
@@ -1863,7 +1992,8 @@
mBinderService = new BinderService();
publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService);
- publishLocalService(DeviceIdleInternal.class, new LocalService());
+ mLocalService = new LocalService();
+ publishLocalService(DeviceIdleInternal.class, mLocalService);
}
@Override
@@ -2069,21 +2199,35 @@
}
}
- public boolean addPowerSaveWhitelistAppInternal(String name) {
+ private int addPowerSaveWhitelistAppsInternal(List<String> pkgNames) {
+ int numAdded = 0;
+ int numErrors = 0;
synchronized (this) {
- try {
- ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(name,
- PackageManager.MATCH_ANY_USER);
- if (mPowerSaveWhitelistUserApps.put(name, UserHandle.getAppId(ai.uid)) == null) {
- reportPowerSaveWhitelistChangedLocked();
- updateWhitelistAppIdsLocked();
- writeConfigFileLocked();
+ for (int i = pkgNames.size() - 1; i >= 0; --i) {
+ final String name = pkgNames.get(i);
+ if (name == null) {
+ numErrors++;
+ continue;
}
- return true;
- } catch (PackageManager.NameNotFoundException e) {
- return false;
+ try {
+ ApplicationInfo ai = getContext().getPackageManager().getApplicationInfo(name,
+ PackageManager.MATCH_ANY_USER);
+ if (mPowerSaveWhitelistUserApps.put(name, UserHandle.getAppId(ai.uid))
+ == null) {
+ numAdded++;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.e(TAG, "Tried to add unknown package to power save whitelist: " + name);
+ numErrors++;
+ }
+ }
+ if (numAdded > 0) {
+ reportPowerSaveWhitelistChangedLocked();
+ updateWhitelistAppIdsLocked();
+ writeConfigFileLocked();
}
}
+ return pkgNames.size() - numErrors;
}
public boolean removePowerSaveWhitelistAppInternal(String name) {
@@ -2594,6 +2738,8 @@
void updateQuickDozeFlagLocked(boolean enabled) {
if (DEBUG) Slog.i(TAG, "updateQuickDozeFlagLocked: enabled=" + enabled);
mQuickDozeActivated = enabled;
+ mQuickDozeActivatedWhileIdling =
+ mQuickDozeActivated && (mState == STATE_IDLE || mState == STATE_IDLE_MAINTENANCE);
if (enabled) {
// If Quick Doze is enabled, see if we should go straight into it.
becomeInactiveIfAppropriateLocked();
@@ -2778,10 +2924,11 @@
mNextIdlePendingDelay = 0;
mNextIdleDelay = 0;
mIdleStartTime = 0;
+ mQuickDozeActivatedWhileIdling = false;
cancelAlarmLocked();
cancelSensingTimeoutAlarmLocked();
cancelLocatingLocked();
- stopMonitoringMotionLocked();
+ maybeStopMonitoringMotionLocked();
mAnyMotionDetector.stop();
updateActiveConstraintsLocked();
}
@@ -3268,11 +3415,23 @@
void motionLocked() {
if (DEBUG) Slog.d(TAG, "motionLocked()");
- // The motion sensor will have been disabled at this point
+ mLastMotionEventElapsed = mInjector.getElapsedRealtime();
handleMotionDetectedLocked(mConstants.MOTION_INACTIVE_TIMEOUT, "motion");
}
void handleMotionDetectedLocked(long timeout, String type) {
+ if (mStationaryListeners.size() > 0) {
+ postStationaryStatusUpdated();
+ scheduleMotionTimeoutAlarmLocked();
+ }
+ if (mQuickDozeActivated && !mQuickDozeActivatedWhileIdling) {
+ // Don't exit idle due to motion if quick doze is enabled.
+ // However, if the device started idling due to the normal progression (going through
+ // all the states) and then had quick doze activated, come out briefly on motion so the
+ // user can get slightly fresher content.
+ return;
+ }
+ maybeStopMonitoringMotionLocked();
// The device is not yet active, so we want to go back to the pending idle
// state to wait again for no motion. Note that we only monitor for motion
// after moving out of the inactive state, so no need to worry about that.
@@ -3324,10 +3483,15 @@
}
}
- void stopMonitoringMotionLocked() {
- if (DEBUG) Slog.d(TAG, "stopMonitoringMotionLocked()");
- if (mMotionSensor != null && mMotionListener.active) {
+ /**
+ * Stops motion monitoring. Will not stop monitoring if there are registered stationary
+ * listeners.
+ */
+ private void maybeStopMonitoringMotionLocked() {
+ if (DEBUG) Slog.d(TAG, "maybeStopMonitoringMotionLocked()");
+ if (mMotionSensor != null && mMotionListener.active && mStationaryListeners.size() == 0) {
mMotionListener.unregisterLocked();
+ cancelMotionTimeoutAlarmLocked();
}
}
@@ -3354,6 +3518,10 @@
}
}
+ private void cancelMotionTimeoutAlarmLocked() {
+ mAlarmManager.cancel(mMotionTimeoutAlarmListener);
+ }
+
void cancelSensingTimeoutAlarmLocked() {
if (mNextSensingTimeoutAlarmTime != 0) {
mNextSensingTimeoutAlarmTime = 0;
@@ -3394,6 +3562,14 @@
mNextLightAlarmTime, "DeviceIdleController.light", mLightAlarmListener, mHandler);
}
+ private void scheduleMotionTimeoutAlarmLocked() {
+ if (DEBUG) Slog.d(TAG, "scheduleMotionAlarmLocked");
+ long nextMotionTimeoutAlarmTime =
+ mInjector.getElapsedRealtime() + mConstants.MOTION_INACTIVE_TIMEOUT;
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, nextMotionTimeoutAlarmTime,
+ "DeviceIdleController.motion", mMotionTimeoutAlarmListener, mHandler);
+ }
+
void scheduleSensingTimeoutAlarmLocked(long delay) {
if (DEBUG) Slog.d(TAG, "scheduleSensingAlarmLocked(" + delay + ")");
mNextSensingTimeoutAlarmTime = SystemClock.elapsedRealtime() + delay;
@@ -3597,9 +3773,6 @@
try {
stream = mConfigFile.startWrite();
memStream.writeTo(stream);
- stream.flush();
- FileUtils.sync(stream);
- stream.close();
mConfigFile.finishWrite(stream);
} catch (IOException e) {
Slog.w(TAG, "Error writing config file", e);
@@ -3922,7 +4095,8 @@
char op = arg.charAt(0);
String pkg = arg.substring(1);
if (op == '+') {
- if (addPowerSaveWhitelistAppInternal(pkg)) {
+ if (addPowerSaveWhitelistAppsInternal(Collections.singletonList(pkg))
+ == 1) {
pw.println("Added: " + pkg);
} else {
pw.println("Unknown package: " + pkg);
@@ -4314,9 +4488,14 @@
}
pw.println(" }");
}
- if (mUseMotionSensor) {
+ if (mUseMotionSensor || mStationaryListeners.size() > 0) {
pw.print(" mMotionActive="); pw.println(mMotionListener.active);
pw.print(" mNotMoving="); pw.println(mNotMoving);
+ pw.print(" mMotionListener.activatedTimeElapsed=");
+ pw.println(mMotionListener.activatedTimeElapsed);
+ pw.print(" mLastMotionEventElapsed="); pw.println(mLastMotionEventElapsed);
+ pw.print(" "); pw.print(mStationaryListeners.size());
+ pw.println(" stationary listeners registered");
}
pw.print(" mLocating="); pw.print(mLocating); pw.print(" mHasGps=");
pw.print(mHasGps); pw.print(" mHasNetwork=");
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 329d4b7..c3ffad6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -109,6 +109,9 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -144,10 +147,53 @@
@VisibleForTesting
public static Clock sSystemClock = Clock.systemUTC();
+
+ private abstract static class MySimpleClock extends Clock {
+ private final ZoneId mZoneId;
+
+ MySimpleClock(ZoneId zoneId) {
+ this.mZoneId = zoneId;
+ }
+
+ @Override
+ public ZoneId getZone() {
+ return mZoneId;
+ }
+
+ @Override
+ public Clock withZone(ZoneId zone) {
+ return new MySimpleClock(zone) {
+ @Override
+ public long millis() {
+ return MySimpleClock.this.millis();
+ }
+ };
+ }
+
+ @Override
+ public abstract long millis();
+
+ @Override
+ public Instant instant() {
+ return Instant.ofEpochMilli(millis());
+ }
+ }
+
@VisibleForTesting
- public static Clock sUptimeMillisClock = SystemClock.uptimeClock();
+ public static Clock sUptimeMillisClock = new MySimpleClock(ZoneOffset.UTC) {
+ @Override
+ public long millis() {
+ return SystemClock.uptimeMillis();
+ }
+ };
+
@VisibleForTesting
- public static Clock sElapsedRealtimeClock = SystemClock.elapsedRealtimeClock();
+ public static Clock sElapsedRealtimeClock = new MySimpleClock(ZoneOffset.UTC) {
+ @Override
+ public long millis() {
+ return SystemClock.elapsedRealtime();
+ }
+ };
/** Global local for all job scheduler state. */
final Object mLock = new Object();
@@ -220,11 +266,6 @@
boolean mReportedActive;
/**
- * Are we currently in device-wide standby parole?
- */
- volatile boolean mInParole;
-
- /**
* A mapping of which uids are currently in the foreground to their effective priority.
*/
final SparseIntArray mUidPriorityOverride = new SparseIntArray();
@@ -979,17 +1020,18 @@
// This may throw a SecurityException.
jobStatus.prepareLocked();
- if (work != null) {
- // If work has been supplied, enqueue it into the new job.
- jobStatus.enqueueWorkLocked(work);
- }
-
if (toCancel != null) {
// Implicitly replaces the existing job record with the new instance
cancelJobImplLocked(toCancel, jobStatus, "job rescheduled by app");
} else {
startTrackingJobLocked(jobStatus, null);
}
+
+ if (work != null) {
+ // If work has been supplied, enqueue it into the new job.
+ jobStatus.enqueueWorkLocked(work);
+ }
+
StatsLog.write_non_chained(StatsLog.SCHEDULED_JOB_STATE_CHANGED,
uId, null, jobStatus.getBatteryName(),
StatsLog.SCHEDULED_JOB_STATE_CHANGED__STATE__SCHEDULED,
@@ -2126,7 +2168,7 @@
job.getServiceComponent(), PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
job.getUserId());
} catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
+ throw new RuntimeException(e);
}
if (service == null) {
@@ -2314,14 +2356,6 @@
}
@Override
- public void onParoleStateChanged(boolean isParoleOn) {
- if (DEBUG_STANDBY) {
- Slog.i(TAG, "Global parole state now " + (isParoleOn ? "ON" : "OFF"));
- }
- mInParole = isParoleOn;
- }
-
- @Override
public void onUserInteractionStarted(String packageName, int userId) {
final int uid = mLocalPM.getPackageUid(packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
@@ -2984,10 +3018,6 @@
}
pw.println();
- pw.print(" In parole?: ");
- pw.print(mInParole);
- pw.println();
-
for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
pw.print(" ");
mJobRestrictions.get(i).dumpConstants(pw);
@@ -3175,7 +3205,6 @@
}
proto.end(settingsToken);
- proto.write(JobSchedulerServiceDumpProto.IN_PAROLE, mInParole);
for (int i = mJobRestrictions.size() - 1; i >= 0; i--) {
mJobRestrictions.get(i).dumpConstants(proto);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 782e646..26db4a3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -251,7 +251,7 @@
binding = mContext.bindServiceAsUser(intent, this,
Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
| Context.BIND_NOT_PERCEPTIBLE,
- new UserHandle(job.getUserId()));
+ UserHandle.of(job.getUserId()));
} catch (SecurityException e) {
// Some permission policy, for example INTERACT_ACROSS_USERS and
// android:singleUser, can result in a SecurityException being thrown from
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index adb4314..c76346f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -28,7 +28,7 @@
import android.net.Uri;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.text.format.TimeMigrationUtils;
+import android.text.format.DateFormat;
import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
@@ -1518,7 +1518,7 @@
if (job.getClipData() != null) {
pw.print(prefix); pw.print(" Clip data: ");
StringBuilder b = new StringBuilder(128);
- job.getClipData().toShortString(b);
+ b.append(job.getClipData());
pw.println(b);
}
if (uriPerms != null) {
@@ -1659,14 +1659,18 @@
}
if (mLastSuccessfulRunTime != 0) {
pw.print(prefix); pw.print("Last successful run: ");
- pw.println(TimeMigrationUtils.formatMillisWithFixedFormat(mLastSuccessfulRunTime));
+ pw.println(formatTime(mLastSuccessfulRunTime));
}
if (mLastFailedRunTime != 0) {
pw.print(prefix); pw.print("Last failed run: ");
- pw.println(TimeMigrationUtils.formatMillisWithFixedFormat(mLastFailedRunTime));
+ pw.println(formatTime(mLastFailedRunTime));
}
}
+ private static CharSequence formatTime(long time) {
+ return DateFormat.format("yyyy-MM-dd HH:mm:ss", time);
+ }
+
public void dump(ProtoOutputStream proto, long fieldId, boolean full, long elapsedRealtimeMillis) {
final long token = proto.start(fieldId);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index 400d902..14dce84 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -414,8 +414,6 @@
private final Handler mHandler;
private final QcConstants mQcConstants;
- private volatile boolean mInParole;
-
/** How much time each app will have to run jobs within their standby bucket window. */
private long mAllowedTimePerPeriodMs = QcConstants.DEFAULT_ALLOWED_TIME_PER_PERIOD_MS;
@@ -711,7 +709,6 @@
public long getMaxJobExecutionTimeMsLocked(@NonNull final JobStatus jobStatus) {
// If quota is currently "free", then the job can run for the full amount of time.
if (mChargeTracker.isCharging()
- || mInParole
|| isTopStartedJobLocked(jobStatus)
|| isUidInForeground(jobStatus.getSourceUid())) {
return JobServiceContext.EXECUTING_TIMESLICE_MILLIS;
@@ -737,8 +734,8 @@
final int standbyBucket) {
if (standbyBucket == NEVER_INDEX) return false;
- // Quota constraint is not enforced while charging or when parole is on.
- if (mChargeTracker.isCharging() || mInParole) {
+ // Quota constraint is not enforced while charging.
+ if (mChargeTracker.isCharging()) {
return true;
}
@@ -1780,20 +1777,6 @@
}
});
}
-
- @Override
- public void onParoleStateChanged(final boolean isParoleOn) {
- mInParole = isParoleOn;
- if (DEBUG) {
- Slog.i(TAG, "Global parole state now " + (isParoleOn ? "ON" : "OFF"));
- }
- // Update job bookkeeping out of band.
- BackgroundThread.getHandler().post(() -> {
- synchronized (mLock) {
- maybeUpdateAllConstraintsLocked();
- }
- });
- }
}
private final class DeleteTimingSessionsFunctor implements Consumer<List<TimingSession>> {
@@ -2515,7 +2498,6 @@
public void dumpControllerStateLocked(final IndentingPrintWriter pw,
final Predicate<JobStatus> predicate) {
pw.println("Is charging: " + mChargeTracker.isCharging());
- pw.println("In parole: " + mInParole);
pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis());
pw.println();
@@ -2639,7 +2621,6 @@
final long mToken = proto.start(StateControllerProto.QUOTA);
proto.write(StateControllerProto.QuotaController.IS_CHARGING, mChargeTracker.isCharging());
- proto.write(StateControllerProto.QuotaController.IS_IN_PAROLE, mInParole);
proto.write(StateControllerProto.QuotaController.ELAPSED_REALTIME,
sElapsedRealtimeClock.millis());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
index b97da59..aa7696d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -17,13 +17,8 @@
package com.android.server.job.restrictions;
import android.app.job.JobParameters;
-import android.content.Context;
-import android.os.IThermalService;
-import android.os.IThermalStatusListener;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.Temperature;
-import android.util.Slog;
+import android.os.PowerManager;
+import android.os.PowerManager.OnThermalStatusChangedListener;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.IndentingPrintWriter;
@@ -36,31 +31,29 @@
private volatile boolean mIsThermalRestricted = false;
+ private PowerManager mPowerManager;
+
public ThermalStatusRestriction(JobSchedulerService service) {
super(service, JobParameters.REASON_DEVICE_THERMAL);
}
@Override
public void onSystemServicesReady() {
- final IThermalService thermalService = IThermalService.Stub.asInterface(
- ServiceManager.getService(Context.THERMAL_SERVICE));
- if (thermalService != null) {
- try {
- thermalService.registerThermalStatusListener(new IThermalStatusListener.Stub() {
- @Override
- public void onStatusChange(int status) {
- final boolean shouldBeActive = status >= Temperature.THROTTLING_SEVERE;
- if (mIsThermalRestricted == shouldBeActive) {
- return;
- }
- mIsThermalRestricted = shouldBeActive;
- mService.onControllerStateChanged();
- }
- });
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to register thermal callback.", e);
+ mPowerManager = mService.getContext().getSystemService(PowerManager.class);
+ // Use MainExecutor
+ mPowerManager.addThermalStatusListener(new OnThermalStatusChangedListener() {
+ @Override
+ public void onThermalStatusChanged(int status) {
+ // This is called on the main thread. Do not do any slow operations in it.
+ // mService.onControllerStateChanged() will just post a message, which is okay.
+ final boolean shouldBeActive = status >= PowerManager.THERMAL_STATUS_SEVERE;
+ if (mIsThermalRestricted == shouldBeActive) {
+ return;
+ }
+ mIsThermalRestricted = shouldBeActive;
+ mService.onControllerStateChanged();
}
- }
+ });
}
@Override
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 7c472a9..ecc0459 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -45,7 +45,6 @@
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
-import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
import android.annotation.UserIdInt;
@@ -70,10 +69,8 @@
import android.hardware.display.DisplayManager;
import android.net.ConnectivityManager;
import android.net.Network;
-import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.NetworkScoreManager;
-import android.os.BatteryManager;
import android.os.BatteryStats;
import android.os.Environment;
import android.os.Handler;
@@ -192,21 +189,14 @@
static final int MSG_INFORM_LISTENERS = 3;
static final int MSG_FORCE_IDLE_STATE = 4;
static final int MSG_CHECK_IDLE_STATES = 5;
- static final int MSG_CHECK_PAROLE_TIMEOUT = 6;
- static final int MSG_PAROLE_END_TIMEOUT = 7;
static final int MSG_REPORT_CONTENT_PROVIDER_USAGE = 8;
- static final int MSG_PAROLE_STATE_CHANGED = 9;
static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10;
/** Check the state of one app: arg1 = userId, arg2 = uid, obj = (String) packageName */
static final int MSG_CHECK_PACKAGE_IDLE_STATE = 11;
static final int MSG_REPORT_SYNC_SCHEDULED = 12;
static final int MSG_REPORT_EXEMPTED_SYNC_START = 13;
- static final int MSG_UPDATE_STABLE_CHARGING= 14;
long mCheckIdleIntervalMillis;
- long mAppIdleParoleIntervalMillis;
- long mAppIdleParoleWindowMillis;
- long mAppIdleParoleDurationMillis;
long[] mAppStandbyScreenThresholds = SCREEN_TIME_THRESHOLDS;
long[] mAppStandbyElapsedThresholds = ELAPSED_TIME_THRESHOLDS;
/** Minimum time a strong usage event should keep the bucket elevated. */
@@ -244,20 +234,12 @@
* start is the first usage of the app
*/
long mInitialForegroundServiceStartTimeoutMillis;
- /** The length of time phone must be charging before considered stable enough to run jobs */
- long mStableChargingThresholdMillis;
private volatile boolean mAppIdleEnabled;
- boolean mAppIdleTempParoled;
- boolean mCharging;
- boolean mChargingStable;
- private long mLastAppIdleParoledTime;
private boolean mSystemServicesReady = false;
// There was a system update, defaults need to be initialized after services are ready
private boolean mPendingInitializeDefaults;
- private final DeviceStateReceiver mDeviceStateReceiver;
-
private volatile boolean mPendingOneTimeCheckIdleStates;
private final AppStandbyHandler mHandler;
@@ -267,7 +249,6 @@
private AppWidgetManager mAppWidgetManager;
private ConnectivityManager mConnectivityManager;
- private PowerManager mPowerManager;
private PackageManager mPackageManager;
Injector mInjector;
@@ -329,12 +310,6 @@
mContext = mInjector.getContext();
mHandler = new AppStandbyHandler(mInjector.getLooper());
mPackageManager = mContext.getPackageManager();
- mDeviceStateReceiver = new DeviceStateReceiver();
-
- IntentFilter deviceStates = new IntentFilter(BatteryManager.ACTION_CHARGING);
- deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
- deviceStates.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
- mContext.registerReceiver(mDeviceStateReceiver, deviceStates);
synchronized (mAppIdleLock) {
mAppIdleHistory = new AppIdleHistory(mInjector.getDataSystemDirectory(),
@@ -353,15 +328,7 @@
@VisibleForTesting
void setAppIdleEnabled(boolean enabled) {
- synchronized (mAppIdleLock) {
- if (mAppIdleEnabled != enabled) {
- final boolean oldParoleState = isParoledOrCharging();
- mAppIdleEnabled = enabled;
- if (isParoledOrCharging() != oldParoleState) {
- postParoleStateChanged();
- }
- }
- }
+ mAppIdleEnabled = enabled;
}
@Override
@@ -381,7 +348,6 @@
mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class);
mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
- mPowerManager = mContext.getSystemService(PowerManager.class);
mInjector.registerDisplayListener(mDisplayListener, mHandler);
synchronized (mAppIdleLock) {
@@ -402,8 +368,6 @@
if (mPendingOneTimeCheckIdleStates) {
postOneTimeCheckIdleStates();
}
- } else if (phase == PHASE_BOOT_COMPLETED) {
- setChargingState(mInjector.isCharging());
}
}
@@ -504,93 +468,6 @@
}
}
- @VisibleForTesting
- void setChargingState(boolean charging) {
- synchronized (mAppIdleLock) {
- if (mCharging != charging) {
- mCharging = charging;
- if (DEBUG) Slog.d(TAG, "Setting mCharging to " + charging);
- if (charging) {
- if (DEBUG) {
- Slog.d(TAG, "Scheduling MSG_UPDATE_STABLE_CHARGING delay = "
- + mStableChargingThresholdMillis);
- }
- mHandler.sendEmptyMessageDelayed(MSG_UPDATE_STABLE_CHARGING,
- mStableChargingThresholdMillis);
- } else {
- mHandler.removeMessages(MSG_UPDATE_STABLE_CHARGING);
- updateChargingStableState();
- }
- }
- }
- }
-
- private void updateChargingStableState() {
- synchronized (mAppIdleLock) {
- if (mChargingStable != mCharging) {
- if (DEBUG) Slog.d(TAG, "Setting mChargingStable to " + mCharging);
- mChargingStable = mCharging;
- postParoleStateChanged();
- }
- }
- }
-
- private void setAppIdleParoled(boolean paroled) {
- synchronized (mAppIdleLock) {
- final long now = mInjector.currentTimeMillis();
- if (mAppIdleTempParoled != paroled) {
- mAppIdleTempParoled = paroled;
- if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleTempParoled);
- if (paroled) {
- postParoleEndTimeout();
- } else {
- mLastAppIdleParoledTime = now;
- postNextParoleTimeout(now, false);
- }
- postParoleStateChanged();
- }
- }
- }
-
- @Override
- public boolean isParoledOrCharging() {
- if (!mAppIdleEnabled) return true;
- synchronized (mAppIdleLock) {
- // Only consider stable charging when determining charge state.
- return mAppIdleTempParoled || mChargingStable;
- }
- }
-
- private void postNextParoleTimeout(long now, boolean forced) {
- if (DEBUG) Slog.d(TAG, "Posting MSG_CHECK_PAROLE_TIMEOUT");
- mHandler.removeMessages(MSG_CHECK_PAROLE_TIMEOUT);
- // Compute when the next parole needs to happen. We check more frequently than necessary
- // since the message handler delays are based on elapsedRealTime and not wallclock time.
- // The comparison is done in wallclock time.
- long timeLeft = (mLastAppIdleParoledTime + mAppIdleParoleIntervalMillis) - now;
- if (forced) {
- // Set next timeout for the end of the parole window
- // If parole is not set by the end of the window it will be forced
- timeLeft += mAppIdleParoleWindowMillis;
- }
- if (timeLeft < 0) {
- timeLeft = 0;
- }
- mHandler.sendEmptyMessageDelayed(MSG_CHECK_PAROLE_TIMEOUT, timeLeft);
- }
-
- private void postParoleEndTimeout() {
- if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_END_TIMEOUT");
- mHandler.removeMessages(MSG_PAROLE_END_TIMEOUT);
- mHandler.sendEmptyMessageDelayed(MSG_PAROLE_END_TIMEOUT, mAppIdleParoleDurationMillis);
- }
-
- private void postParoleStateChanged() {
- if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_STATE_CHANGED");
- mHandler.removeMessages(MSG_PAROLE_STATE_CHANGED);
- mHandler.sendEmptyMessage(MSG_PAROLE_STATE_CHANGED);
- }
-
@Override
public void postCheckIdleStates(int userId) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0));
@@ -787,48 +664,6 @@
return THRESHOLD_BUCKETS[bucketIndex];
}
- private void checkParoleTimeout() {
- boolean setParoled = false;
- boolean waitForNetwork = false;
- NetworkInfo activeNetwork = mConnectivityManager.getActiveNetworkInfo();
- boolean networkActive = activeNetwork != null &&
- activeNetwork.isConnected();
-
- synchronized (mAppIdleLock) {
- final long now = mInjector.currentTimeMillis();
- if (!mAppIdleTempParoled) {
- final long timeSinceLastParole = now - mLastAppIdleParoledTime;
- if (timeSinceLastParole > mAppIdleParoleIntervalMillis) {
- if (DEBUG) Slog.d(TAG, "Crossed default parole interval");
- if (networkActive) {
- // If network is active set parole
- setParoled = true;
- } else {
- if (timeSinceLastParole
- > mAppIdleParoleIntervalMillis + mAppIdleParoleWindowMillis) {
- if (DEBUG) Slog.d(TAG, "Crossed end of parole window, force parole");
- setParoled = true;
- } else {
- if (DEBUG) Slog.d(TAG, "Network unavailable, delaying parole");
- waitForNetwork = true;
- postNextParoleTimeout(now, true);
- }
- }
- } else {
- if (DEBUG) Slog.d(TAG, "Not long enough to go to parole");
- postNextParoleTimeout(now, false);
- }
- }
- }
- if (waitForNetwork) {
- mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback);
- }
- if (setParoled) {
- // Set parole if network is available
- setAppIdleParoled(true);
- }
- }
-
private void notifyBatteryStats(String packageName, int userId, boolean idle) {
try {
final int uid = mPackageManager.getPackageUidAsUser(packageName,
@@ -844,30 +679,6 @@
}
}
- private void onDeviceIdleModeChanged() {
- final boolean deviceIdle = mPowerManager.isDeviceIdleMode();
- if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle);
- boolean paroled = false;
- synchronized (mAppIdleLock) {
- final long timeSinceLastParole =
- mInjector.currentTimeMillis() - mLastAppIdleParoledTime;
- if (!deviceIdle
- && timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
- if (DEBUG) {
- Slog.i(TAG,
- "Bringing idle apps out of inactive state due to deviceIdleMode=false");
- }
- paroled = true;
- } else if (deviceIdle) {
- if (DEBUG) Slog.i(TAG, "Device idle, back to prison");
- paroled = false;
- } else {
- return;
- }
- }
- setAppIdleParoled(paroled);
- }
-
@Override
public void reportEvent(UsageEvents.Event event, long elapsedRealtime, int userId) {
if (!mAppIdleEnabled) return;
@@ -1038,11 +849,8 @@
}
@Override
- public boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime,
+ public boolean isAppIdleFiltered(String packageName, int userId, long elapsedRealtime,
boolean shouldObfuscateInstantApps) {
- if (isParoledOrCharging()) {
- return false;
- }
if (shouldObfuscateInstantApps &&
mInjector.isPackageEphemeral(userId, packageName)) {
return false;
@@ -1388,15 +1196,6 @@
}
}
- private void informParoleStateChanged() {
- final boolean paroled = isParoledOrCharging();
- synchronized (mPackageAccessListeners) {
- for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
- listener.onParoleStateChanged(paroled);
- }
- }
- }
-
@Override
public void flushToDisk(int userId) {
synchronized (mAppIdleLock) {
@@ -1517,18 +1316,6 @@
TimeUtils.formatDuration(mCheckIdleIntervalMillis, pw);
pw.println();
- pw.print(" mAppIdleParoleIntervalMillis=");
- TimeUtils.formatDuration(mAppIdleParoleIntervalMillis, pw);
- pw.println();
-
- pw.print(" mAppIdleParoleWindowMillis=");
- TimeUtils.formatDuration(mAppIdleParoleWindowMillis, pw);
- pw.println();
-
- pw.print(" mAppIdleParoleDurationMillis=");
- TimeUtils.formatDuration(mAppIdleParoleDurationMillis, pw);
- pw.println();
-
pw.print(" mStrongUsageTimeoutMillis=");
TimeUtils.formatDuration(mStrongUsageTimeoutMillis, pw);
pw.println();
@@ -1566,22 +1353,11 @@
TimeUtils.formatDuration(mSystemUpdateUsageTimeoutMillis, pw);
pw.println();
- pw.print(" mStableChargingThresholdMillis=");
- TimeUtils.formatDuration(mStableChargingThresholdMillis, pw);
- pw.println();
-
pw.println();
pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
- pw.print(" mAppIdleTempParoled="); pw.print(mAppIdleTempParoled);
- pw.print(" mCharging="); pw.print(mCharging);
- pw.print(" mChargingStable="); pw.print(mChargingStable);
- pw.print(" mLastAppIdleParoledTime=");
- TimeUtils.formatDuration(now - mLastAppIdleParoledTime, pw);
pw.println();
pw.print("mScreenThresholds="); pw.println(Arrays.toString(mAppStandbyScreenThresholds));
pw.print("mElapsedThresholds="); pw.println(Arrays.toString(mAppStandbyElapsedThresholds));
- pw.print("mStableChargingThresholdMillis=");
- TimeUtils.formatDuration(mStableChargingThresholdMillis, pw);
pw.println();
}
@@ -1655,10 +1431,6 @@
return buildFlag && runtimeFlag;
}
- boolean isCharging() {
- return mContext.getSystemService(BatteryManager.class).isCharging();
- }
-
boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
return mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName);
}
@@ -1748,15 +1520,6 @@
checkIdleStates(UserHandle.USER_ALL);
break;
- case MSG_CHECK_PAROLE_TIMEOUT:
- checkParoleTimeout();
- break;
-
- case MSG_PAROLE_END_TIMEOUT:
- if (DEBUG) Slog.d(TAG, "Ending parole");
- setAppIdleParoled(false);
- break;
-
case MSG_REPORT_CONTENT_PROVIDER_USAGE:
SomeArgs args = (SomeArgs) msg.obj;
reportContentProviderUsage((String) args.arg1, // authority name
@@ -1765,11 +1528,6 @@
args.recycle();
break;
- case MSG_PAROLE_STATE_CHANGED:
- if (DEBUG) Slog.d(TAG, "Parole state: " + mAppIdleTempParoled
- + ", Charging state:" + mChargingStable);
- informParoleStateChanged();
- break;
case MSG_CHECK_PACKAGE_IDLE_STATE:
checkAndUpdateStandbyState((String) msg.obj, msg.arg1, msg.arg2,
mInjector.elapsedRealtime());
@@ -1788,10 +1546,6 @@
reportExemptedSyncStart((String) msg.obj, msg.arg1);
break;
- case MSG_UPDATE_STABLE_CHARGING:
- updateChargingStableState();
- break;
-
default:
super.handleMessage(msg);
break;
@@ -1800,23 +1554,6 @@
}
};
- private class DeviceStateReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- switch (intent.getAction()) {
- case BatteryManager.ACTION_CHARGING:
- setChargingState(true);
- break;
- case BatteryManager.ACTION_DISCHARGING:
- setChargingState(false);
- break;
- case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
- onDeviceIdleModeChanged();
- break;
- }
- }
- }
-
private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder().build();
private final ConnectivityManager.NetworkCallback mNetworkCallback
@@ -1824,7 +1561,6 @@
@Override
public void onAvailable(Network network) {
mConnectivityManager.unregisterNetworkCallback(this);
- checkParoleTimeout();
}
};
@@ -1851,9 +1587,6 @@
* Observe settings changes for {@link Global#APP_IDLE_CONSTANTS}.
*/
private class SettingsObserver extends ContentObserver {
- private static final String KEY_PAROLE_INTERVAL = "parole_interval";
- private static final String KEY_PAROLE_WINDOW = "parole_window";
- private static final String KEY_PAROLE_DURATION = "parole_duration";
private static final String KEY_SCREEN_TIME_THRESHOLDS = "screen_thresholds";
private static final String KEY_ELAPSED_TIME_THRESHOLDS = "elapsed_thresholds";
private static final String KEY_STRONG_USAGE_HOLD_DURATION = "strong_usage_duration";
@@ -1875,7 +1608,6 @@
"system_interaction_duration";
private static final String KEY_INITIAL_FOREGROUND_SERVICE_START_HOLD_DURATION =
"initial_foreground_service_start_duration";
- private static final String KEY_STABLE_CHARGING_THRESHOLD = "stable_charging_threshold";
public static final long DEFAULT_STRONG_USAGE_TIMEOUT = 1 * ONE_HOUR;
public static final long DEFAULT_NOTIFICATION_TIMEOUT = 12 * ONE_HOUR;
public static final long DEFAULT_SYSTEM_UPDATE_TIMEOUT = 2 * ONE_HOUR;
@@ -1885,7 +1617,6 @@
public static final long DEFAULT_EXEMPTED_SYNC_SCHEDULED_DOZE_TIMEOUT = 4 * ONE_HOUR;
public static final long DEFAULT_EXEMPTED_SYNC_START_TIMEOUT = 10 * ONE_MINUTE;
public static final long DEFAULT_UNEXEMPTED_SYNC_SCHEDULED_TIMEOUT = 10 * ONE_MINUTE;
- public static final long DEFAULT_STABLE_CHARGING_THRESHOLD = 10 * ONE_MINUTE;
public static final long DEFAULT_INITIAL_FOREGROUND_SERVICE_START_TIMEOUT = 30 * ONE_MINUTE;
private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -1932,17 +1663,6 @@
synchronized (mAppIdleLock) {
- // Default: 24 hours between paroles
- mAppIdleParoleIntervalMillis = mParser.getDurationMillis(KEY_PAROLE_INTERVAL,
- COMPRESS_TIME ? ONE_MINUTE * 10 : 24 * 60 * ONE_MINUTE);
-
- // Default: 2 hours to wait on network
- mAppIdleParoleWindowMillis = mParser.getDurationMillis(KEY_PAROLE_WINDOW,
- COMPRESS_TIME ? ONE_MINUTE * 2 : 2 * 60 * ONE_MINUTE);
-
- mAppIdleParoleDurationMillis = mParser.getDurationMillis(KEY_PAROLE_DURATION,
- COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
-
String screenThresholdsValue = mParser.getString(KEY_SCREEN_TIME_THRESHOLDS, null);
mAppStandbyScreenThresholds = parseLongArray(screenThresholdsValue,
SCREEN_TIME_THRESHOLDS);
@@ -1997,10 +1717,6 @@
KEY_INITIAL_FOREGROUND_SERVICE_START_HOLD_DURATION,
COMPRESS_TIME ? ONE_MINUTE :
DEFAULT_INITIAL_FOREGROUND_SERVICE_START_TIMEOUT);
-
- mStableChargingThresholdMillis = mParser.getDurationMillis(
- KEY_STABLE_CHARGING_THRESHOLD,
- COMPRESS_TIME ? ONE_MINUTE : DEFAULT_STABLE_CHARGING_THRESHOLD);
}
// Check if app_idle_enabled has changed. Do this after getting the rest of the settings
diff --git a/api/current.txt b/api/current.txt
index 3a768ea..dd49a08 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -4481,7 +4481,7 @@
method @IntRange(from=0) public int getNotingUid();
method @NonNull public String getOp();
method @IntRange(from=0) public long getTime();
- method public void writeToParcel(android.os.Parcel, int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.AsyncNotedAppOp> CREATOR;
}
@@ -5872,6 +5872,7 @@
method public android.service.notification.StatusBarNotification[] getActiveNotifications();
method public android.app.AutomaticZenRule getAutomaticZenRule(String);
method public java.util.Map<java.lang.String,android.app.AutomaticZenRule> getAutomaticZenRules();
+ method @Nullable public android.app.NotificationManager.Policy getConsolidatedNotificationPolicy();
method public final int getCurrentInterruptionFilter();
method public int getImportance();
method public android.app.NotificationChannel getNotificationChannel(String);
@@ -9805,7 +9806,7 @@
method public abstract java.io.File[] getExternalCacheDirs();
method @Nullable public abstract java.io.File getExternalFilesDir(@Nullable String);
method public abstract java.io.File[] getExternalFilesDirs(String);
- method public abstract java.io.File[] getExternalMediaDirs();
+ method @Deprecated public abstract java.io.File[] getExternalMediaDirs();
method public abstract java.io.File getFileStreamPath(String);
method public abstract java.io.File getFilesDir();
method public java.util.concurrent.Executor getMainExecutor();
@@ -12402,6 +12403,8 @@
public class Resources {
ctor @Deprecated public Resources(android.content.res.AssetManager, android.util.DisplayMetrics, android.content.res.Configuration);
+ method public void addLoader(@NonNull android.content.res.loader.ResourceLoader, @NonNull android.content.res.loader.ResourcesProvider, @IntRange(from=0) int);
+ method public int addLoader(@NonNull android.content.res.loader.ResourceLoader, @NonNull android.content.res.loader.ResourcesProvider);
method public final void finishPreloading();
method public final void flushLayoutCache();
method @NonNull public android.content.res.XmlResourceParser getAnimation(@AnimatorRes @AnimRes int) throws android.content.res.Resources.NotFoundException;
@@ -12428,6 +12431,7 @@
method @NonNull public int[] getIntArray(@ArrayRes int) throws android.content.res.Resources.NotFoundException;
method public int getInteger(@IntegerRes int) throws android.content.res.Resources.NotFoundException;
method @NonNull public android.content.res.XmlResourceParser getLayout(@LayoutRes int) throws android.content.res.Resources.NotFoundException;
+ method @NonNull public java.util.List<android.util.Pair<android.content.res.loader.ResourceLoader,android.content.res.loader.ResourcesProvider>> getLoaders();
method @Deprecated public android.graphics.Movie getMovie(@RawRes int) throws android.content.res.Resources.NotFoundException;
method @NonNull public String getQuantityString(@PluralsRes int, int, java.lang.Object...) throws android.content.res.Resources.NotFoundException;
method @NonNull public String getQuantityString(@PluralsRes int, int) throws android.content.res.Resources.NotFoundException;
@@ -12455,6 +12459,8 @@
method public android.content.res.AssetFileDescriptor openRawResourceFd(@RawRes int) throws android.content.res.Resources.NotFoundException;
method public void parseBundleExtra(String, android.util.AttributeSet, android.os.Bundle) throws org.xmlpull.v1.XmlPullParserException;
method public void parseBundleExtras(android.content.res.XmlResourceParser, android.os.Bundle) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public int removeLoader(@NonNull android.content.res.loader.ResourceLoader);
+ method public void setLoaders(@Nullable java.util.List<android.util.Pair<android.content.res.loader.ResourceLoader,android.content.res.loader.ResourcesProvider>>);
method @Deprecated public void updateConfiguration(android.content.res.Configuration, android.util.DisplayMetrics);
field @AnyRes public static final int ID_NULL = 0; // 0x0
}
@@ -12522,6 +12528,33 @@
}
+package android.content.res.loader {
+
+ public class DirectoryResourceLoader implements android.content.res.loader.ResourceLoader {
+ ctor public DirectoryResourceLoader(@NonNull java.io.File);
+ method @Nullable public java.io.File findFile(@NonNull String);
+ method @NonNull public java.io.File getDirectory();
+ }
+
+ public interface ResourceLoader {
+ method @Nullable public default java.io.InputStream loadAsset(@NonNull String, int) throws java.io.IOException;
+ method @Nullable public default android.os.ParcelFileDescriptor loadAssetFd(@NonNull String) throws java.io.IOException;
+ method @Nullable public default android.graphics.drawable.Drawable loadDrawable(@NonNull android.util.TypedValue, int, int, @Nullable android.content.res.Resources.Theme);
+ method @Nullable public default android.content.res.XmlResourceParser loadXmlResourceParser(@NonNull String, @AnyRes int);
+ }
+
+ public final class ResourcesProvider implements java.lang.AutoCloseable java.io.Closeable {
+ method public void close();
+ method @NonNull public static android.content.res.loader.ResourcesProvider empty();
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.SharedMemory) throws java.io.IOException;
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromArsc(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromArsc(@NonNull android.os.SharedMemory) throws java.io.IOException;
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromSplit(@NonNull android.content.Context, @NonNull String) throws java.io.IOException;
+ }
+
+}
+
package android.database {
public abstract class AbstractCursor implements android.database.CrossProcessCursor {
@@ -24356,6 +24389,7 @@
field public static final int INFO_OUTPUT_FORMAT_CHANGED = -2; // 0xfffffffe
field public static final int INFO_TRY_AGAIN_LATER = -1; // 0xffffffff
field public static final String PARAMETER_KEY_HDR10_PLUS_INFO = "hdr10-plus-info";
+ field public static final String PARAMETER_KEY_LOW_LATENCY = "low-latency";
field public static final String PARAMETER_KEY_OFFSET_TIME = "time-offset-us";
field public static final String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync";
field public static final String PARAMETER_KEY_SUSPEND = "drop-input-frames";
@@ -24529,6 +24563,7 @@
field public static final String FEATURE_DynamicTimestamp = "dynamic-timestamp";
field public static final String FEATURE_FrameParsing = "frame-parsing";
field public static final String FEATURE_IntraRefresh = "intra-refresh";
+ field public static final String FEATURE_LowLatency = "low-latency";
field public static final String FEATURE_MultipleFrames = "multiple-frames";
field public static final String FEATURE_PartialFrame = "partial-frame";
field public static final String FEATURE_SecurePlayback = "secure-playback";
@@ -24617,6 +24652,7 @@
field public static final int DolbyVisionLevelUhd30 = 64; // 0x40
field public static final int DolbyVisionLevelUhd48 = 128; // 0x80
field public static final int DolbyVisionLevelUhd60 = 256; // 0x100
+ field public static final int DolbyVisionProfileDvav110 = 1024; // 0x400
field public static final int DolbyVisionProfileDvavPen = 2; // 0x2
field public static final int DolbyVisionProfileDvavPer = 1; // 0x1
field public static final int DolbyVisionProfileDvavSe = 512; // 0x200
@@ -25239,6 +25275,7 @@
field public static final String KEY_LANGUAGE = "language";
field public static final String KEY_LATENCY = "latency";
field public static final String KEY_LEVEL = "level";
+ field public static final String KEY_LOW_LATENCY = "low-latency";
field public static final String KEY_MAX_B_FRAMES = "max-bframes";
field public static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder";
field public static final String KEY_MAX_HEIGHT = "max-height";
@@ -25387,6 +25424,7 @@
method public android.graphics.Bitmap getFrameAtIndex(int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
method public android.graphics.Bitmap getFrameAtIndex(int);
method public android.graphics.Bitmap getFrameAtTime(long, int);
+ method public android.graphics.Bitmap getFrameAtTime(long, int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
method public android.graphics.Bitmap getFrameAtTime(long);
method public android.graphics.Bitmap getFrameAtTime();
method @NonNull public java.util.List<android.graphics.Bitmap> getFramesAtIndex(int, int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
@@ -25396,6 +25434,7 @@
method public android.graphics.Bitmap getPrimaryImage(@NonNull android.media.MediaMetadataRetriever.BitmapParams);
method public android.graphics.Bitmap getPrimaryImage();
method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
+ method public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
method public void release();
method public void setDataSource(String) throws java.lang.IllegalArgumentException;
method public void setDataSource(String, java.util.Map<java.lang.String,java.lang.String>) throws java.lang.IllegalArgumentException;
@@ -25410,6 +25449,9 @@
field public static final int METADATA_KEY_BITRATE = 20; // 0x14
field public static final int METADATA_KEY_CAPTURE_FRAMERATE = 25; // 0x19
field public static final int METADATA_KEY_CD_TRACK_NUMBER = 0; // 0x0
+ field public static final int METADATA_KEY_COLOR_RANGE = 37; // 0x25
+ field public static final int METADATA_KEY_COLOR_STANDARD = 35; // 0x23
+ field public static final int METADATA_KEY_COLOR_TRANSFER = 36; // 0x24
field public static final int METADATA_KEY_COMPILATION = 15; // 0xf
field public static final int METADATA_KEY_COMPOSER = 4; // 0x4
field public static final int METADATA_KEY_DATE = 5; // 0x5
@@ -38240,6 +38282,7 @@
field public static final String COLUMN_MIME_TYPE = "mime_type";
field public static final String COLUMN_SIZE = "_size";
field public static final String COLUMN_SUMMARY = "summary";
+ field public static final int FLAG_DIR_BLOCKS_TREE = 32768; // 0x8000
field public static final int FLAG_DIR_PREFERS_GRID = 16; // 0x10
field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20
field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8
@@ -38458,16 +38501,17 @@
public static final class MediaStore.Audio {
ctor public MediaStore.Audio();
- method public static String keyFor(String);
+ method @Deprecated @Nullable public static String keyFor(@Nullable String);
}
public static interface MediaStore.Audio.AlbumColumns {
field public static final String ALBUM = "album";
field @Deprecated public static final String ALBUM_ART = "album_art";
field public static final String ALBUM_ID = "album_id";
- field public static final String ALBUM_KEY = "album_key";
+ field @Deprecated public static final String ALBUM_KEY = "album_key";
field public static final String ARTIST = "artist";
field public static final String ARTIST_ID = "artist_id";
+ field @Deprecated public static final String ARTIST_KEY = "artist_key";
field public static final String FIRST_YEAR = "minyear";
field public static final String LAST_YEAR = "maxyear";
field public static final String NUMBER_OF_SONGS = "numsongs";
@@ -38486,7 +38530,7 @@
public static interface MediaStore.Audio.ArtistColumns {
field public static final String ARTIST = "artist";
- field public static final String ARTIST_KEY = "artist_key";
+ field @Deprecated public static final String ARTIST_KEY = "artist_key";
field public static final String NUMBER_OF_ALBUMS = "number_of_albums";
field public static final String NUMBER_OF_TRACKS = "number_of_tracks";
}
@@ -38509,19 +38553,23 @@
public static interface MediaStore.Audio.AudioColumns extends android.provider.MediaStore.MediaColumns {
field public static final String ALBUM = "album";
field public static final String ALBUM_ID = "album_id";
- field public static final String ALBUM_KEY = "album_key";
+ field @Deprecated public static final String ALBUM_KEY = "album_key";
field public static final String ARTIST = "artist";
field public static final String ARTIST_ID = "artist_id";
- field public static final String ARTIST_KEY = "artist_key";
+ field @Deprecated public static final String ARTIST_KEY = "artist_key";
field public static final String BOOKMARK = "bookmark";
field public static final String COMPOSER = "composer";
+ field public static final String GENRE = "genre";
+ field public static final String GENRE_ID = "genre_id";
+ field @Deprecated public static final String GENRE_KEY = "genre_key";
field public static final String IS_ALARM = "is_alarm";
field public static final String IS_AUDIOBOOK = "is_audiobook";
field public static final String IS_MUSIC = "is_music";
field public static final String IS_NOTIFICATION = "is_notification";
field public static final String IS_PODCAST = "is_podcast";
field public static final String IS_RINGTONE = "is_ringtone";
- field public static final String TITLE_KEY = "title_key";
+ field @Deprecated public static final String TITLE_KEY = "title_key";
+ field public static final String TITLE_RESOURCE_URI = "title_resource_uri";
field public static final String TRACK = "track";
field public static final String YEAR = "year";
}
@@ -38747,6 +38795,9 @@
field public static final String ARTIST = "artist";
field public static final String BOOKMARK = "bookmark";
field public static final String CATEGORY = "category";
+ field public static final String COLOR_RANGE = "color_range";
+ field public static final String COLOR_STANDARD = "color_standard";
+ field public static final String COLOR_TRANSFER = "color_transfer";
field public static final String DESCRIPTION = "description";
field public static final String IS_PRIVATE = "isprivate";
field public static final String LANGUAGE = "language";
@@ -40805,7 +40856,7 @@
field public static final String EXTRA_KEY_ALIAS = "android.security.extra.KEY_ALIAS";
field public static final String EXTRA_NAME = "name";
field public static final String EXTRA_PKCS12 = "PKCS12";
- field public static final String KEY_ALIAS_SELECTION_DENIED = "alias-selection-denied-ef829e15-210a-409d-96c9-ee684fc41972";
+ field public static final String KEY_ALIAS_SELECTION_DENIED = "android:alias-selection-denied";
}
public interface KeyChainAliasCallback {
@@ -41866,6 +41917,7 @@
field public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
field public static final String ACTION_QS_TILE_PREFERENCES = "android.service.quicksettings.action.QS_TILE_PREFERENCES";
field public static final String META_DATA_ACTIVE_TILE = "android.service.quicksettings.ACTIVE_TILE";
+ field public static final String META_DATA_BOOLEAN_TILE = "android.service.quicksettings.BOOLEAN_TILE";
}
}
@@ -44204,9 +44256,11 @@
field public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
field public static final String KEY_DATA_WARNING_THRESHOLD_BYTES_LONG = "data_warning_threshold_bytes_long";
field public static final String KEY_DEFAULT_SIM_CALL_MANAGER_STRING = "default_sim_call_manager_string";
+ field public static final String KEY_DEFAULT_VM_NUMBER_ROAMING_AND_IMS_UNREGISTERED_STRING = "default_vm_number_roaming_and_ims_unregistered_string";
field public static final String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
field public static final String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array";
field public static final String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool";
+ field public static final String KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY = "disconnect_cause_play_busytone_int_array";
field public static final String KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL = "display_hd_audio_property_bool";
field public static final String KEY_DROP_VIDEO_CALL_WHEN_ANSWERING_AUDIO_CALL_BOOL = "drop_video_call_when_answering_audio_call_bool";
field public static final String KEY_DTMF_TYPE_ENABLED_BOOL = "dtmf_type_enabled_bool";
@@ -44552,6 +44606,7 @@
method public int describeContents();
method public int getAsuLevel();
method public int getDbm();
+ method public int getEcNo();
method @IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) public int getLevel();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthWcdma> CREATOR;
@@ -45057,6 +45112,7 @@
method public long getDataLimitBytes();
method public long getDataUsageBytes();
method public long getDataUsageTime();
+ method @Nullable public int[] getNetworkTypes();
method @Nullable public CharSequence getSummary();
method @Nullable public CharSequence getTitle();
method public void writeToParcel(android.os.Parcel, int);
@@ -45076,6 +45132,7 @@
method public static android.telephony.SubscriptionPlan.Builder createRecurring(java.time.ZonedDateTime, java.time.Period);
method public android.telephony.SubscriptionPlan.Builder setDataLimit(long, int);
method public android.telephony.SubscriptionPlan.Builder setDataUsage(long, long);
+ method @NonNull public android.telephony.SubscriptionPlan.Builder setNetworkTypes(@Nullable int[]);
method public android.telephony.SubscriptionPlan.Builder setSummary(@Nullable CharSequence);
method public android.telephony.SubscriptionPlan.Builder setTitle(@Nullable CharSequence);
}
@@ -45085,6 +45142,7 @@
method @Nullable public android.telephony.TelephonyManager createForPhoneAccountHandle(android.telecom.PhoneAccountHandle);
method public android.telephony.TelephonyManager createForSubscriptionId(int);
method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean doesSwitchMultiSimConfigTriggerReboot();
+ method public int getActiveModemCount();
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public java.util.List<android.telephony.CellInfo> getAllCellInfo();
method public int getCallState();
method public int getCardIdForDefaultEuicc();
@@ -45117,7 +45175,7 @@
method public String getNetworkOperatorName();
method public String getNetworkSpecifier();
method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getNetworkType();
- method public int getPhoneCount();
+ method @Deprecated public int getPhoneCount();
method public int getPhoneType();
method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();
@@ -45133,6 +45191,7 @@
method public int getSimState();
method public int getSimState(int);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getSubscriberId();
+ method public int getSupportedModemCount();
method @Nullable public String getTypeAllocationCode();
method @Nullable public String getTypeAllocationCode(int);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @NonNull public java.util.List<android.telephony.UiccCardInfo> getUiccCardsInfo();
@@ -45170,6 +45229,7 @@
method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler);
method public void sendVisualVoicemailSms(String, int, String, android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setForbiddenPlmns(@NonNull java.util.List<java.lang.String>);
method public boolean setLine1NumberForDisplay(String, String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setNetworkSelectionModeAutomatic();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setNetworkSelectionModeManual(String, boolean);
@@ -45236,6 +45296,10 @@
field public static final String EXTRA_SUBSCRIPTION_ID = "android.telephony.extra.SUBSCRIPTION_ID";
field public static final String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER";
field public static final String METADATA_HIDE_VOICEMAIL_SETTINGS_MENU = "android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU";
+ field public static final int MODEM_COUNT_DUAL_MODEM = 2; // 0x2
+ field public static final int MODEM_COUNT_NO_MODEM = 0; // 0x0
+ field public static final int MODEM_COUNT_SINGLE_MODEM = 1; // 0x1
+ field public static final int MODEM_COUNT_TRI_MODEM = 3; // 0x3
field public static final int MULTISIM_ALLOWED = 0; // 0x0
field public static final int MULTISIM_NOT_SUPPORTED_BY_CARRIER = 2; // 0x2
field public static final int MULTISIM_NOT_SUPPORTED_BY_HARDWARE = 1; // 0x1
diff --git a/api/lint-baseline.txt b/api/lint-baseline.txt
new file mode 100644
index 0000000..2ca8cf4
--- /dev/null
+++ b/api/lint-baseline.txt
@@ -0,0 +1,1179 @@
+// Baseline format: 1.0
+AcronymName: android.system.ErrnoException#rethrowAsIOException():
+
+
+
+ActionValue: android.provider.Settings#ACTION_CONDITION_PROVIDER_SETTINGS:
+
+
+
+AllUpper: android.media.MediaCodecInfo.CodecCapabilities#FEATURE_LowLatency:
+
+
+
+ArrayReturn: android.app.Notification.MessagingStyle.Message#getMessagesFromBundleArray(android.os.Parcelable[]) parameter #0:
+
+ArrayReturn: android.content.ContentProviderOperation#resolveExtrasBackReferences(android.content.ContentProviderResult[], int) parameter #0:
+
+
+
+BroadcastBehavior: android.app.AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED:
+
+BroadcastBehavior: android.app.admin.DevicePolicyManager#ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED:
+
+BroadcastBehavior: android.app.admin.DevicePolicyManager#ACTION_MANAGED_PROFILE_PROVISIONED:
+
+BroadcastBehavior: android.bluetooth.BluetoothAdapter#ACTION_DISCOVERY_FINISHED:
+
+BroadcastBehavior: android.bluetooth.BluetoothAdapter#ACTION_DISCOVERY_STARTED:
+
+BroadcastBehavior: android.bluetooth.BluetoothAdapter#ACTION_LOCAL_NAME_CHANGED:
+
+BroadcastBehavior: android.bluetooth.BluetoothAdapter#ACTION_SCAN_MODE_CHANGED:
+
+BroadcastBehavior: android.bluetooth.BluetoothAdapter#ACTION_STATE_CHANGED:
+
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_ACL_CONNECTED:
+
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_ACL_DISCONNECTED:
+
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_ACL_DISCONNECT_REQUESTED:
+
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_BOND_STATE_CHANGED:
+
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_CLASS_CHANGED:
+
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_FOUND:
+
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_NAME_CHANGED:
+
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_PAIRING_REQUEST:
+
+BroadcastBehavior: android.bluetooth.BluetoothDevice#ACTION_UUID:
+
+BroadcastBehavior: android.content.Intent#ACTION_AIRPLANE_MODE_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_APPLICATION_RESTRICTIONS_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_BATTERY_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_BATTERY_LOW:
+
+BroadcastBehavior: android.content.Intent#ACTION_BATTERY_OKAY:
+
+BroadcastBehavior: android.content.Intent#ACTION_CAMERA_BUTTON:
+
+BroadcastBehavior: android.content.Intent#ACTION_CLOSE_SYSTEM_DIALOGS:
+
+BroadcastBehavior: android.content.Intent#ACTION_CONFIGURATION_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_DATE_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_DEVICE_STORAGE_LOW:
+
+BroadcastBehavior: android.content.Intent#ACTION_DEVICE_STORAGE_OK:
+
+BroadcastBehavior: android.content.Intent#ACTION_DOCK_EVENT:
+
+BroadcastBehavior: android.content.Intent#ACTION_DREAMING_STARTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_DREAMING_STOPPED:
+
+BroadcastBehavior: android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_AVAILABLE:
+
+BroadcastBehavior: android.content.Intent#ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE:
+
+BroadcastBehavior: android.content.Intent#ACTION_GTALK_SERVICE_CONNECTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_GTALK_SERVICE_DISCONNECTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_HEADSET_PLUG:
+
+BroadcastBehavior: android.content.Intent#ACTION_INPUT_METHOD_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_LOCALE_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_LOCKED_BOOT_COMPLETED:
+
+BroadcastBehavior: android.content.Intent#ACTION_MANAGE_PACKAGE_STORAGE:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_BAD_REMOVAL:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_BUTTON:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_CHECKING:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_EJECT:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_MOUNTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_NOFS:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_REMOVED:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_SCANNER_FINISHED:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_SCANNER_SCAN_FILE:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_SCANNER_STARTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_SHARED:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_UNMOUNTABLE:
+
+BroadcastBehavior: android.content.Intent#ACTION_MEDIA_UNMOUNTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_MY_PACKAGE_REPLACED:
+
+BroadcastBehavior: android.content.Intent#ACTION_MY_PACKAGE_SUSPENDED:
+
+BroadcastBehavior: android.content.Intent#ACTION_MY_PACKAGE_UNSUSPENDED:
+
+BroadcastBehavior: android.content.Intent#ACTION_NEW_OUTGOING_CALL:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGES_SUSPENDED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGES_UNSUSPENDED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_ADDED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_DATA_CLEARED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_FIRST_LAUNCH:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_FULLY_REMOVED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_INSTALL:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_NEEDS_VERIFICATION:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_REMOVED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_REPLACED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_RESTARTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PACKAGE_VERIFIED:
+
+BroadcastBehavior: android.content.Intent#ACTION_POWER_CONNECTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_POWER_DISCONNECTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_PROVIDER_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_REBOOT:
+
+BroadcastBehavior: android.content.Intent#ACTION_SCREEN_OFF:
+
+BroadcastBehavior: android.content.Intent#ACTION_SCREEN_ON:
+
+BroadcastBehavior: android.content.Intent#ACTION_SHUTDOWN:
+
+BroadcastBehavior: android.content.Intent#ACTION_TIMEZONE_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_TIME_CHANGED:
+
+BroadcastBehavior: android.content.Intent#ACTION_TIME_TICK:
+
+BroadcastBehavior: android.content.Intent#ACTION_UID_REMOVED:
+
+BroadcastBehavior: android.content.Intent#ACTION_UMS_CONNECTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_UMS_DISCONNECTED:
+
+BroadcastBehavior: android.content.Intent#ACTION_USER_PRESENT:
+
+BroadcastBehavior: android.content.Intent#ACTION_USER_UNLOCKED:
+
+BroadcastBehavior: android.content.Intent#ACTION_WALLPAPER_CHANGED:
+
+BroadcastBehavior: android.content.pm.PackageInstaller#ACTION_SESSION_COMMITTED:
+
+BroadcastBehavior: android.content.pm.PackageInstaller#ACTION_SESSION_UPDATED:
+
+BroadcastBehavior: android.hardware.Camera#ACTION_NEW_PICTURE:
+
+BroadcastBehavior: android.hardware.Camera#ACTION_NEW_VIDEO:
+
+BroadcastBehavior: android.hardware.input.InputManager#ACTION_QUERY_KEYBOARD_LAYOUTS:
+
+BroadcastBehavior: android.hardware.usb.UsbManager#ACTION_USB_ACCESSORY_DETACHED:
+
+BroadcastBehavior: android.hardware.usb.UsbManager#ACTION_USB_DEVICE_DETACHED:
+
+BroadcastBehavior: android.media.AudioManager#ACTION_HDMI_AUDIO_PLUG:
+
+BroadcastBehavior: android.media.AudioManager#ACTION_HEADSET_PLUG:
+
+BroadcastBehavior: android.media.AudioManager#ACTION_MICROPHONE_MUTE_CHANGED:
+
+BroadcastBehavior: android.media.AudioManager#ACTION_SPEAKERPHONE_STATE_CHANGED:
+
+BroadcastBehavior: android.media.tv.TvContract#ACTION_INITIALIZE_PROGRAMS:
+
+BroadcastBehavior: android.media.tv.TvContract#ACTION_PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT:
+
+BroadcastBehavior: android.media.tv.TvContract#ACTION_PREVIEW_PROGRAM_BROWSABLE_DISABLED:
+
+BroadcastBehavior: android.media.tv.TvContract#ACTION_WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED:
+
+BroadcastBehavior: android.net.ConnectivityManager#ACTION_BACKGROUND_DATA_SETTING_CHANGED:
+
+BroadcastBehavior: android.net.Proxy#PROXY_CHANGE_ACTION:
+
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_ADAPTER_STATE_CHANGED:
+
+BroadcastBehavior: android.nfc.NfcAdapter#ACTION_TRANSACTION_DETECTED:
+
+BroadcastBehavior: android.os.DropBoxManager#ACTION_DROPBOX_ENTRY_ADDED:
+
+BroadcastBehavior: android.provider.CalendarContract#ACTION_EVENT_REMINDER:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#DATA_SMS_RECEIVED_ACTION:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SECRET_CODE_ACTION:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SIM_FULL_ACTION:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SMS_CB_RECEIVED_ACTION:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SMS_DELIVER_ACTION:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SMS_RECEIVED_ACTION:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SMS_REJECTED_ACTION:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#WAP_PUSH_DELIVER_ACTION:
+
+BroadcastBehavior: android.provider.Telephony.Sms.Intents#WAP_PUSH_RECEIVED_ACTION:
+
+BroadcastBehavior: android.security.KeyChain#ACTION_KEYCHAIN_CHANGED:
+
+BroadcastBehavior: android.security.KeyChain#ACTION_KEY_ACCESS_CHANGED:
+
+BroadcastBehavior: android.security.KeyChain#ACTION_STORAGE_CHANGED:
+
+BroadcastBehavior: android.security.KeyChain#ACTION_TRUST_STORE_CHANGED:
+
+BroadcastBehavior: android.speech.tts.TextToSpeech#ACTION_TTS_QUEUE_PROCESSING_COMPLETED:
+
+BroadcastBehavior: android.speech.tts.TextToSpeech.Engine#ACTION_TTS_DATA_INSTALLED:
+
+BroadcastBehavior: android.telephony.SubscriptionManager#ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED:
+
+BroadcastBehavior: android.telephony.SubscriptionManager#ACTION_DEFAULT_SUBSCRIPTION_CHANGED:
+
+BroadcastBehavior: android.telephony.SubscriptionManager#ACTION_REFRESH_SUBSCRIPTION_PLANS:
+
+BroadcastBehavior: android.telephony.TelephonyManager#ACTION_SECRET_CODE:
+
+BroadcastBehavior: android.telephony.TelephonyManager#ACTION_SUBSCRIPTION_CARRIER_IDENTITY_CHANGED:
+
+BroadcastBehavior: android.telephony.TelephonyManager#ACTION_SUBSCRIPTION_SPECIFIC_CARRIER_IDENTITY_CHANGED:
+
+BroadcastBehavior: android.telephony.euicc.EuiccManager#ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE:
+
+
+
+CompileTimeConstant: android.icu.util.JapaneseCalendar#REIWA:
+
+
+
+DeprecationMismatch: android.accounts.AccountManager#newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, String[], boolean, String, String, String[], android.os.Bundle):
+
+DeprecationMismatch: android.app.Activity#enterPictureInPictureMode():
+
+DeprecationMismatch: android.app.Instrumentation#startAllocCounting():
+
+DeprecationMismatch: android.app.Instrumentation#stopAllocCounting():
+
+DeprecationMismatch: android.app.Notification#bigContentView:
+
+DeprecationMismatch: android.app.Notification#contentView:
+
+DeprecationMismatch: android.app.Notification#headsUpContentView:
+
+DeprecationMismatch: android.app.Notification#tickerView:
+
+DeprecationMismatch: android.app.Notification.Action.Builder#Builder(int, CharSequence, android.app.PendingIntent):
+
+DeprecationMismatch: android.app.Notification.Action.WearableExtender#getCancelLabel():
+
+DeprecationMismatch: android.app.Notification.Action.WearableExtender#getConfirmLabel():
+
+DeprecationMismatch: android.app.Notification.Action.WearableExtender#getInProgressLabel():
+
+DeprecationMismatch: android.app.Notification.Action.WearableExtender#setCancelLabel(CharSequence):
+
+DeprecationMismatch: android.app.Notification.Action.WearableExtender#setConfirmLabel(CharSequence):
+
+DeprecationMismatch: android.app.Notification.Action.WearableExtender#setInProgressLabel(CharSequence):
+
+DeprecationMismatch: android.app.Notification.Builder#setContent(android.widget.RemoteViews):
+
+DeprecationMismatch: android.app.Notification.Builder#setTicker(CharSequence, android.widget.RemoteViews):
+
+DeprecationMismatch: android.app.Notification.WearableExtender#getContentIcon():
+
+DeprecationMismatch: android.app.Notification.WearableExtender#getContentIconGravity():
+
+DeprecationMismatch: android.app.Notification.WearableExtender#getCustomContentHeight():
+
+DeprecationMismatch: android.app.Notification.WearableExtender#getCustomSizePreset():
+
+DeprecationMismatch: android.app.Notification.WearableExtender#getGravity():
+
+DeprecationMismatch: android.app.Notification.WearableExtender#getHintAvoidBackgroundClipping():
+
+DeprecationMismatch: android.app.Notification.WearableExtender#getHintHideIcon():
+
+DeprecationMismatch: android.app.Notification.WearableExtender#getHintScreenTimeout():
+
+DeprecationMismatch: android.app.Notification.WearableExtender#getHintShowBackgroundOnly():
+
+DeprecationMismatch: android.app.Notification.WearableExtender#setContentIcon(int):
+
+DeprecationMismatch: android.app.Notification.WearableExtender#setContentIconGravity(int):
+
+DeprecationMismatch: android.app.Notification.WearableExtender#setCustomContentHeight(int):
+
+DeprecationMismatch: android.app.Notification.WearableExtender#setCustomSizePreset(int):
+
+DeprecationMismatch: android.app.Notification.WearableExtender#setGravity(int):
+
+DeprecationMismatch: android.app.Notification.WearableExtender#setHintAvoidBackgroundClipping(boolean):
+
+DeprecationMismatch: android.app.Notification.WearableExtender#setHintHideIcon(boolean):
+
+DeprecationMismatch: android.app.Notification.WearableExtender#setHintScreenTimeout(int):
+
+DeprecationMismatch: android.app.Notification.WearableExtender#setHintShowBackgroundOnly(boolean):
+
+DeprecationMismatch: android.content.ContextWrapper#clearWallpaper():
+
+DeprecationMismatch: android.content.ContextWrapper#getWallpaper():
+
+DeprecationMismatch: android.content.ContextWrapper#getWallpaperDesiredMinimumHeight():
+
+DeprecationMismatch: android.content.ContextWrapper#getWallpaperDesiredMinimumWidth():
+
+DeprecationMismatch: android.content.ContextWrapper#peekWallpaper():
+
+DeprecationMismatch: android.content.ContextWrapper#removeStickyBroadcast(android.content.Intent):
+
+DeprecationMismatch: android.content.ContextWrapper#removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle):
+
+DeprecationMismatch: android.content.ContextWrapper#sendStickyBroadcast(android.content.Intent):
+
+DeprecationMismatch: android.content.ContextWrapper#sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle):
+
+DeprecationMismatch: android.content.ContextWrapper#sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle):
+
+DeprecationMismatch: android.content.ContextWrapper#sendStickyOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, android.os.Handler, int, String, android.os.Bundle):
+
+DeprecationMismatch: android.content.ContextWrapper#setWallpaper(android.graphics.Bitmap):
+
+DeprecationMismatch: android.content.ContextWrapper#setWallpaper(java.io.InputStream):
+
+DeprecationMismatch: android.database.CursorWrapper#deactivate():
+
+DeprecationMismatch: android.database.CursorWrapper#requery():
+
+DeprecationMismatch: android.graphics.ComposeShader#ComposeShader(android.graphics.Shader, android.graphics.Shader, android.graphics.Xfermode):
+
+DeprecationMismatch: android.graphics.PixelFormat#A_8:
+
+DeprecationMismatch: android.graphics.PixelFormat#LA_88:
+
+DeprecationMismatch: android.graphics.PixelFormat#L_8:
+
+DeprecationMismatch: android.graphics.PixelFormat#RGBA_4444:
+
+DeprecationMismatch: android.graphics.PixelFormat#RGBA_5551:
+
+DeprecationMismatch: android.graphics.PixelFormat#RGB_332:
+
+DeprecationMismatch: android.net.wifi.WifiManager#EXTRA_BSSID:
+
+DeprecationMismatch: android.net.wifi.WifiManager#EXTRA_WIFI_INFO:
+
+DeprecationMismatch: android.opengl.EGL14#eglCreatePixmapSurface(android.opengl.EGLDisplay, android.opengl.EGLConfig, int, int[], int):
+
+DeprecationMismatch: android.opengl.GLES20#GL_STENCIL_INDEX:
+
+DeprecationMismatch: android.opengl.GLSurfaceView#surfaceRedrawNeeded(android.view.SurfaceHolder):
+
+DeprecationMismatch: android.os.UserManager#setUserRestrictions(android.os.Bundle):
+
+DeprecationMismatch: android.os.UserManager#setUserRestrictions(android.os.Bundle, android.os.UserHandle):
+
+DeprecationMismatch: android.provider.Contacts.People#markAsContacted(android.content.ContentResolver, long):
+
+DeprecationMismatch: android.renderscript.Type.CubemapFace#POSITVE_X:
+
+DeprecationMismatch: android.renderscript.Type.CubemapFace#POSITVE_Y:
+
+DeprecationMismatch: android.renderscript.Type.CubemapFace#POSITVE_Z:
+
+DeprecationMismatch: android.speech.tts.TextToSpeech#areDefaultsEnforced():
+
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_12HOUR:
+
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_24HOUR:
+
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_CAP_AMPM:
+
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_CAP_MIDNIGHT:
+
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_CAP_NOON:
+
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_CAP_NOON_MIDNIGHT:
+
+DeprecationMismatch: android.text.format.DateUtils#FORMAT_NO_NOON_MIDNIGHT:
+
+DeprecationMismatch: android.view.ViewGroup.LayoutParams#FILL_PARENT:
+
+DeprecationMismatch: android.view.Window#setTitleColor(int):
+
+DeprecationMismatch: android.view.accessibility.AccessibilityEvent#MAX_TEXT_LENGTH:
+
+DeprecationMismatch: android.webkit.WebSettings#getSaveFormData():
+
+DeprecationMismatch: android.webkit.WebView#shouldDelayChildPressedState():
+
+DeprecationMismatch: android.webkit.WebViewDatabase#clearFormData():
+
+DeprecationMismatch: android.webkit.WebViewDatabase#hasFormData():
+
+DeprecationMismatch: javax.microedition.khronos.egl.EGL10#eglCreatePixmapSurface(javax.microedition.khronos.egl.EGLDisplay, javax.microedition.khronos.egl.EGLConfig, Object, int[]):
+
+
+
+GenericException: android.content.res.loader.ResourcesProvider#finalize():
+ Methods must not throw generic exceptions (`java.lang.Throwable`)
+
+
+HiddenSuperclass: android.content.res.ColorStateList:
+
+HiddenSuperclass: android.graphics.Canvas:
+
+HiddenSuperclass: android.graphics.RecordingCanvas:
+
+HiddenSuperclass: android.hardware.biometrics.BiometricPrompt.AuthenticationCallback:
+
+HiddenSuperclass: android.hardware.biometrics.BiometricPrompt.AuthenticationResult:
+
+HiddenSuperclass: android.hardware.biometrics.BiometricPrompt.CryptoObject:
+
+HiddenSuperclass: android.hardware.fingerprint.FingerprintManager.AuthenticationCallback:
+
+HiddenSuperclass: android.hardware.fingerprint.FingerprintManager.CryptoObject:
+
+HiddenSuperclass: android.media.AudioTrack:
+
+HiddenSuperclass: android.media.MediaPlayer:
+
+HiddenSuperclass: android.media.SoundPool:
+
+HiddenSuperclass: android.service.autofill.CharSequenceTransformation:
+
+HiddenSuperclass: android.service.autofill.DateTransformation:
+
+HiddenSuperclass: android.service.autofill.DateValueSanitizer:
+
+HiddenSuperclass: android.service.autofill.ImageTransformation:
+
+HiddenSuperclass: android.service.autofill.LuhnChecksumValidator:
+
+HiddenSuperclass: android.service.autofill.RegexValidator:
+
+HiddenSuperclass: android.service.autofill.TextValueSanitizer:
+
+HiddenSuperclass: android.service.autofill.VisibilitySetterAction:
+
+HiddenSuperclass: android.util.StatsLog:
+
+
+
+MissingNullability: android.app.AsyncNotedAppOp#equals(Object) parameter #0:
+
+MissingNullability: android.app.AsyncNotedAppOp#writeToParcel(android.os.Parcel, int) parameter #0:
+
+MissingNullability: android.app.SyncNotedAppOp#equals(Object) parameter #0:
+
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#EGYPTIAN_HIEROGLYPH_FORMAT_CONTROLS:
+
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#ELYMAIC:
+
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#NANDINAGARI:
+
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#NYIAKENG_PUACHUE_HMONG:
+
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#OTTOMAN_SIYAQ_NUMBERS:
+
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#SMALL_KANA_EXTENSION:
+
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#SYMBOLS_AND_PICTOGRAPHS_EXTENDED_A:
+
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#TAMIL_SUPPLEMENT:
+
+MissingNullability: android.icu.lang.UCharacter.UnicodeBlock#WANCHO:
+
+MissingNullability: android.icu.text.DateTimePatternGenerator#getFieldDisplayName(int, android.icu.text.DateTimePatternGenerator.DisplayWidth):
+
+MissingNullability: android.icu.text.DateTimePatternGenerator#getFieldDisplayName(int, android.icu.text.DateTimePatternGenerator.DisplayWidth) parameter #1:
+
+MissingNullability: android.icu.util.VersionInfo#UNICODE_12_0:
+
+MissingNullability: android.icu.util.VersionInfo#UNICODE_12_1:
+
+MissingNullability: android.media.MediaMetadataRetriever#getFrameAtTime(long, int, android.media.MediaMetadataRetriever.BitmapParams):
+
+MissingNullability: android.media.MediaMetadataRetriever#getScaledFrameAtTime(long, int, int, int, android.media.MediaMetadataRetriever.BitmapParams):
+
+
+
+RequiresPermission: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler):
+
+RequiresPermission: android.accounts.AccountManager#hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
+
+RequiresPermission: android.app.AlarmManager#setTime(long):
+
+RequiresPermission: android.app.AppOpsManager#isOpActive(String, int, String):
+
+RequiresPermission: android.app.AppOpsManager#startWatchingActive(String[], java.util.concurrent.Executor, android.app.AppOpsManager.OnOpActiveChangedListener):
+
+RequiresPermission: android.app.DownloadManager.Request#setDestinationInExternalPublicDir(String, String):
+
+RequiresPermission: android.app.DownloadManager.Request#setDestinationUri(android.net.Uri):
+
+RequiresPermission: android.app.DownloadManager.Request#setNotificationVisibility(int):
+
+RequiresPermission: android.app.DownloadManager.Request#setShowRunningNotification(boolean):
+
+RequiresPermission: android.app.Notification.Builder#setFullScreenIntent(android.app.PendingIntent, boolean):
+
+RequiresPermission: android.app.Service#startForeground(int, android.app.Notification):
+
+RequiresPermission: android.app.WallpaperInfo#getSettingsSliceUri():
+
+RequiresPermission: android.app.WallpaperManager#clear():
+
+RequiresPermission: android.app.WallpaperManager#clearWallpaper():
+
+RequiresPermission: android.app.WallpaperManager#setBitmap(android.graphics.Bitmap):
+
+RequiresPermission: android.app.WallpaperManager#setBitmap(android.graphics.Bitmap, android.graphics.Rect, boolean):
+
+RequiresPermission: android.app.WallpaperManager#setDisplayPadding(android.graphics.Rect):
+
+RequiresPermission: android.app.WallpaperManager#setResource(int):
+
+RequiresPermission: android.app.WallpaperManager#setStream(java.io.InputStream):
+
+RequiresPermission: android.app.WallpaperManager#setStream(java.io.InputStream, android.graphics.Rect, boolean):
+
+RequiresPermission: android.app.WallpaperManager#suggestDesiredDimensions(int, int):
+
+RequiresPermission: android.app.admin.DevicePolicyManager#bindDeviceAdminServiceAsUser(android.content.ComponentName, android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle):
+
+RequiresPermission: android.app.admin.DevicePolicyManager#getPasswordComplexity():
+
+RequiresPermission: android.app.admin.DevicePolicyManager#setAlwaysOnVpnPackage(android.content.ComponentName, String, boolean):
+
+RequiresPermission: android.app.backup.BackupManager#dataChanged(String):
+
+RequiresPermission: android.app.usage.StorageStatsManager#queryExternalStatsForUser(java.util.UUID, android.os.UserHandle):
+
+RequiresPermission: android.app.usage.StorageStatsManager#queryStatsForPackage(java.util.UUID, String, android.os.UserHandle):
+
+RequiresPermission: android.app.usage.StorageStatsManager#queryStatsForUid(java.util.UUID, int):
+
+RequiresPermission: android.app.usage.StorageStatsManager#queryStatsForUser(java.util.UUID, android.os.UserHandle):
+
+RequiresPermission: android.app.usage.UsageStatsManager#queryAndAggregateUsageStats(long, long):
+
+RequiresPermission: android.app.usage.UsageStatsManager#queryConfigurations(int, long, long):
+
+RequiresPermission: android.app.usage.UsageStatsManager#queryEventStats(int, long, long):
+
+RequiresPermission: android.app.usage.UsageStatsManager#queryEvents(long, long):
+
+RequiresPermission: android.app.usage.UsageStatsManager#queryUsageStats(int, long, long):
+
+RequiresPermission: android.appwidget.AppWidgetManager#bindAppWidgetIdIfAllowed(int, android.os.UserHandle, android.content.ComponentName, android.os.Bundle):
+
+RequiresPermission: android.bluetooth.BluetoothA2dp#isA2dpPlaying(android.bluetooth.BluetoothDevice):
+
+RequiresPermission: android.bluetooth.BluetoothAdapter#getName():
+
+RequiresPermission: android.bluetooth.BluetoothDevice#setPin(byte[]):
+
+RequiresPermission: android.bluetooth.BluetoothGatt#abortReliableWrite():
+
+RequiresPermission: android.bluetooth.BluetoothGatt#beginReliableWrite():
+
+RequiresPermission: android.bluetooth.BluetoothGatt#disconnect():
+
+RequiresPermission: android.bluetooth.BluetoothGatt#discoverServices():
+
+RequiresPermission: android.bluetooth.BluetoothGatt#executeReliableWrite():
+
+RequiresPermission: android.bluetooth.BluetoothGatt#getService(java.util.UUID):
+
+RequiresPermission: android.bluetooth.BluetoothGatt#getServices():
+
+RequiresPermission: android.bluetooth.BluetoothGatt#readCharacteristic(android.bluetooth.BluetoothGattCharacteristic):
+
+RequiresPermission: android.bluetooth.BluetoothGatt#readDescriptor(android.bluetooth.BluetoothGattDescriptor):
+
+RequiresPermission: android.bluetooth.BluetoothGatt#readRemoteRssi():
+
+RequiresPermission: android.bluetooth.BluetoothGatt#requestMtu(int):
+
+RequiresPermission: android.bluetooth.BluetoothGatt#setCharacteristicNotification(android.bluetooth.BluetoothGattCharacteristic, boolean):
+
+RequiresPermission: android.bluetooth.BluetoothGatt#writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic):
+
+RequiresPermission: android.bluetooth.BluetoothGatt#writeDescriptor(android.bluetooth.BluetoothGattDescriptor):
+
+RequiresPermission: android.bluetooth.BluetoothGattCharacteristic#BluetoothGattCharacteristic(java.util.UUID, int, int):
+
+RequiresPermission: android.bluetooth.BluetoothGattCharacteristic#addDescriptor(android.bluetooth.BluetoothGattDescriptor):
+
+RequiresPermission: android.bluetooth.BluetoothGattDescriptor#BluetoothGattDescriptor(java.util.UUID, int):
+
+RequiresPermission: android.bluetooth.BluetoothGattServer#addService(android.bluetooth.BluetoothGattService):
+
+RequiresPermission: android.bluetooth.BluetoothGattServer#cancelConnection(android.bluetooth.BluetoothDevice):
+
+RequiresPermission: android.bluetooth.BluetoothGattServer#clearServices():
+
+RequiresPermission: android.bluetooth.BluetoothGattServer#connect(android.bluetooth.BluetoothDevice, boolean):
+
+RequiresPermission: android.bluetooth.BluetoothGattServer#getService(java.util.UUID):
+
+RequiresPermission: android.bluetooth.BluetoothGattServer#getServices():
+
+RequiresPermission: android.bluetooth.BluetoothGattServer#notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean):
+
+RequiresPermission: android.bluetooth.BluetoothGattServer#removeService(android.bluetooth.BluetoothGattService):
+
+RequiresPermission: android.bluetooth.BluetoothGattServer#sendResponse(android.bluetooth.BluetoothDevice, int, int, int, byte[]):
+
+RequiresPermission: android.bluetooth.BluetoothGattService#BluetoothGattService(java.util.UUID, int):
+
+RequiresPermission: android.bluetooth.BluetoothGattService#addCharacteristic(android.bluetooth.BluetoothGattCharacteristic):
+
+RequiresPermission: android.bluetooth.BluetoothGattService#addService(android.bluetooth.BluetoothGattService):
+
+RequiresPermission: android.bluetooth.BluetoothHeadset#isAudioConnected(android.bluetooth.BluetoothDevice):
+
+RequiresPermission: android.bluetooth.BluetoothHeadset#sendVendorSpecificResultCode(android.bluetooth.BluetoothDevice, String, String):
+
+RequiresPermission: android.bluetooth.BluetoothHeadset#startVoiceRecognition(android.bluetooth.BluetoothDevice):
+
+RequiresPermission: android.bluetooth.BluetoothHeadset#stopVoiceRecognition(android.bluetooth.BluetoothDevice):
+
+RequiresPermission: android.bluetooth.BluetoothHealth#connectChannelToSource(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration):
+
+RequiresPermission: android.bluetooth.BluetoothHealth#disconnectChannel(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration, int):
+
+RequiresPermission: android.bluetooth.BluetoothHealth#getConnectedDevices():
+
+RequiresPermission: android.bluetooth.BluetoothHealth#getConnectionState(android.bluetooth.BluetoothDevice):
+
+RequiresPermission: android.bluetooth.BluetoothHealth#getDevicesMatchingConnectionStates(int[]):
+
+RequiresPermission: android.bluetooth.BluetoothHealth#getMainChannelFd(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothHealthAppConfiguration):
+
+RequiresPermission: android.bluetooth.BluetoothHealth#registerSinkAppConfiguration(String, int, android.bluetooth.BluetoothHealthCallback):
+
+RequiresPermission: android.bluetooth.BluetoothHealth#unregisterAppConfiguration(android.bluetooth.BluetoothHealthAppConfiguration):
+
+RequiresPermission: android.bluetooth.le.AdvertisingSet#enableAdvertising(boolean, int, int):
+
+RequiresPermission: android.bluetooth.le.BluetoothLeAdvertiser#startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback):
+
+RequiresPermission: android.bluetooth.le.BluetoothLeAdvertiser#startAdvertising(android.bluetooth.le.AdvertiseSettings, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseData, android.bluetooth.le.AdvertiseCallback):
+
+RequiresPermission: android.bluetooth.le.BluetoothLeAdvertiser#stopAdvertising(android.bluetooth.le.AdvertiseCallback):
+
+RequiresPermission: android.companion.CompanionDeviceManager#associate(android.companion.AssociationRequest, android.companion.CompanionDeviceManager.Callback, android.os.Handler):
+
+RequiresPermission: android.content.ContentResolver#addPeriodicSync(android.accounts.Account, String, android.os.Bundle, long):
+
+RequiresPermission: android.content.ContentResolver#cancelSync(android.content.SyncRequest):
+
+RequiresPermission: android.content.ContentResolver#getCurrentSync():
+
+RequiresPermission: android.content.ContentResolver#getCurrentSyncs():
+
+RequiresPermission: android.content.ContentResolver#getIsSyncable(android.accounts.Account, String):
+
+RequiresPermission: android.content.ContentResolver#getMasterSyncAutomatically():
+
+RequiresPermission: android.content.ContentResolver#getPeriodicSyncs(android.accounts.Account, String):
+
+RequiresPermission: android.content.ContentResolver#getSyncAutomatically(android.accounts.Account, String):
+
+RequiresPermission: android.content.ContentResolver#isSyncActive(android.accounts.Account, String):
+
+RequiresPermission: android.content.ContentResolver#isSyncPending(android.accounts.Account, String):
+
+RequiresPermission: android.content.ContentResolver#removePeriodicSync(android.accounts.Account, String, android.os.Bundle):
+
+RequiresPermission: android.content.ContentResolver#setIsSyncable(android.accounts.Account, String, int):
+
+RequiresPermission: android.content.ContentResolver#setMasterSyncAutomatically(boolean):
+
+RequiresPermission: android.content.ContentResolver#setSyncAutomatically(android.accounts.Account, String, boolean):
+
+RequiresPermission: android.content.Context#clearWallpaper():
+
+RequiresPermission: android.content.Context#getExternalCacheDir():
+
+RequiresPermission: android.content.Context#getExternalCacheDirs():
+
+RequiresPermission: android.content.Context#getExternalFilesDir(String):
+
+RequiresPermission: android.content.Context#getExternalFilesDirs(String):
+
+RequiresPermission: android.content.Context#getExternalMediaDirs():
+
+RequiresPermission: android.content.Context#getObbDir():
+
+RequiresPermission: android.content.Context#getObbDirs():
+
+RequiresPermission: android.content.Context#removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle):
+
+RequiresPermission: android.content.Context#setWallpaper(android.graphics.Bitmap):
+
+RequiresPermission: android.content.Context#setWallpaper(java.io.InputStream):
+
+RequiresPermission: android.content.pm.LauncherApps.Callback#onPackagesSuspended(String[], android.os.UserHandle, android.os.Bundle):
+
+RequiresPermission: android.content.pm.PackageManager#canRequestPackageInstalls():
+
+RequiresPermission: android.content.pm.PackageManager#getSuspendedPackageAppExtras():
+
+RequiresPermission: android.content.pm.PackageManager#isPackageSuspended():
+
+RequiresPermission: android.hardware.camera2.CameraCharacteristics#getKeysNeedingPermission():
+
+RequiresPermission: android.hardware.usb.UsbManager#hasPermission(android.hardware.usb.UsbDevice):
+
+RequiresPermission: android.hardware.usb.UsbManager#requestPermission(android.hardware.usb.UsbDevice, android.app.PendingIntent):
+
+RequiresPermission: android.location.LocationManager#addGpsStatusListener(android.location.GpsStatus.Listener):
+
+RequiresPermission: android.location.LocationManager#addNmeaListener(android.location.OnNmeaMessageListener):
+
+RequiresPermission: android.location.LocationManager#addNmeaListener(android.location.OnNmeaMessageListener, android.os.Handler):
+
+RequiresPermission: android.location.LocationManager#addNmeaListener(java.util.concurrent.Executor, android.location.OnNmeaMessageListener):
+
+RequiresPermission: android.location.LocationManager#addProximityAlert(double, double, float, long, android.app.PendingIntent):
+
+RequiresPermission: android.location.LocationManager#registerGnssStatusCallback(android.location.GnssStatus.Callback):
+
+RequiresPermission: android.location.LocationManager#registerGnssStatusCallback(android.location.GnssStatus.Callback, android.os.Handler):
+
+RequiresPermission: android.location.LocationManager#registerGnssStatusCallback(java.util.concurrent.Executor, android.location.GnssStatus.Callback):
+
+RequiresPermission: android.media.AudioManager#startBluetoothSco():
+
+RequiresPermission: android.media.AudioManager#stopBluetoothSco():
+
+RequiresPermission: android.media.MediaExtractor#setDataSource(String):
+
+RequiresPermission: android.media.MediaExtractor#setDataSource(String, java.util.Map<java.lang.String,java.lang.String>):
+
+RequiresPermission: android.media.MediaExtractor#setDataSource(android.content.Context, android.net.Uri, java.util.Map<java.lang.String,java.lang.String>):
+
+RequiresPermission: android.media.MediaPlayer#setWakeMode(android.content.Context, int):
+
+RequiresPermission: android.media.MediaSession2Service#onUpdateNotification(android.media.MediaSession2):
+
+RequiresPermission: android.media.RingtoneManager#getCursor():
+
+RequiresPermission: android.media.RingtoneManager#getValidRingtoneUri(android.content.Context):
+
+RequiresPermission: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName):
+
+RequiresPermission: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName, android.os.Handler):
+
+RequiresPermission: android.media.session.MediaSessionManager#getActiveSessions(android.content.ComponentName):
+
+RequiresPermission: android.media.session.MediaSessionManager#isTrustedForMediaControl(android.media.session.MediaSessionManager.RemoteUserInfo):
+
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.app.PendingIntent):
+
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback):
+
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler):
+
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, android.os.Handler, int):
+
+RequiresPermission: android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback, int):
+
+RequiresPermission: android.net.sip.SipAudioCall#setSpeakerMode(boolean):
+
+RequiresPermission: android.net.sip.SipAudioCall#startAudio():
+
+RequiresPermission: android.net.wifi.WifiManager#getScanResults():
+
+RequiresPermission: android.net.wifi.WifiManager#setWifiEnabled(boolean):
+
+RequiresPermission: android.net.wifi.WifiManager#startLocalOnlyHotspot(android.net.wifi.WifiManager.LocalOnlyHotspotCallback, android.os.Handler):
+
+RequiresPermission: android.net.wifi.WifiManager#startScan():
+
+RequiresPermission: android.net.wifi.aware.IdentityChangedListener#onIdentityChanged(byte[]):
+
+RequiresPermission: android.net.wifi.aware.WifiAwareManager#attach(android.net.wifi.aware.AttachCallback, android.net.wifi.aware.IdentityChangedListener, android.os.Handler):
+
+RequiresPermission: android.net.wifi.aware.WifiAwareSession#publish(android.net.wifi.aware.PublishConfig, android.net.wifi.aware.DiscoverySessionCallback, android.os.Handler):
+
+RequiresPermission: android.net.wifi.aware.WifiAwareSession#subscribe(android.net.wifi.aware.SubscribeConfig, android.net.wifi.aware.DiscoverySessionCallback, android.os.Handler):
+
+RequiresPermission: android.nfc.NfcAdapter#disableForegroundDispatch(android.app.Activity):
+
+RequiresPermission: android.nfc.NfcAdapter#disableForegroundNdefPush(android.app.Activity):
+
+RequiresPermission: android.nfc.NfcAdapter#enableForegroundDispatch(android.app.Activity, android.app.PendingIntent, android.content.IntentFilter[], String[][]):
+
+RequiresPermission: android.nfc.NfcAdapter#enableForegroundNdefPush(android.app.Activity, android.nfc.NdefMessage):
+
+RequiresPermission: android.nfc.NfcAdapter#setBeamPushUris(android.net.Uri[], android.app.Activity):
+
+RequiresPermission: android.nfc.NfcAdapter#setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity):
+
+RequiresPermission: android.nfc.NfcAdapter#setNdefPushMessage(android.nfc.NdefMessage, android.app.Activity, android.app.Activity...):
+
+RequiresPermission: android.nfc.NfcAdapter#setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...):
+
+RequiresPermission: android.nfc.NfcAdapter#setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...):
+
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForAid(android.content.ComponentName, String):
+
+RequiresPermission: android.nfc.cardemulation.CardEmulation#isDefaultServiceForCategory(android.content.ComponentName, String):
+
+RequiresPermission: android.nfc.cardemulation.CardEmulation#setOffHostForService(android.content.ComponentName, String):
+
+RequiresPermission: android.nfc.tech.IsoDep#getTimeout():
+
+RequiresPermission: android.nfc.tech.IsoDep#setTimeout(int):
+
+RequiresPermission: android.nfc.tech.IsoDep#transceive(byte[]):
+
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyA(int, byte[]):
+
+RequiresPermission: android.nfc.tech.MifareClassic#authenticateSectorWithKeyB(int, byte[]):
+
+RequiresPermission: android.nfc.tech.MifareClassic#decrement(int, int):
+
+RequiresPermission: android.nfc.tech.MifareClassic#getTimeout():
+
+RequiresPermission: android.nfc.tech.MifareClassic#increment(int, int):
+
+RequiresPermission: android.nfc.tech.MifareClassic#readBlock(int):
+
+RequiresPermission: android.nfc.tech.MifareClassic#restore(int):
+
+RequiresPermission: android.nfc.tech.MifareClassic#setTimeout(int):
+
+RequiresPermission: android.nfc.tech.MifareClassic#transceive(byte[]):
+
+RequiresPermission: android.nfc.tech.MifareClassic#transfer(int):
+
+RequiresPermission: android.nfc.tech.MifareClassic#writeBlock(int, byte[]):
+
+RequiresPermission: android.nfc.tech.MifareUltralight#getTimeout():
+
+RequiresPermission: android.nfc.tech.MifareUltralight#readPages(int):
+
+RequiresPermission: android.nfc.tech.MifareUltralight#setTimeout(int):
+
+RequiresPermission: android.nfc.tech.MifareUltralight#transceive(byte[]):
+
+RequiresPermission: android.nfc.tech.MifareUltralight#writePage(int, byte[]):
+
+RequiresPermission: android.nfc.tech.Ndef#getNdefMessage():
+
+RequiresPermission: android.nfc.tech.Ndef#isWritable():
+
+RequiresPermission: android.nfc.tech.Ndef#makeReadOnly():
+
+RequiresPermission: android.nfc.tech.Ndef#writeNdefMessage(android.nfc.NdefMessage):
+
+RequiresPermission: android.nfc.tech.NdefFormatable#format(android.nfc.NdefMessage):
+
+RequiresPermission: android.nfc.tech.NdefFormatable#formatReadOnly(android.nfc.NdefMessage):
+
+RequiresPermission: android.nfc.tech.NfcA#getTimeout():
+
+RequiresPermission: android.nfc.tech.NfcA#setTimeout(int):
+
+RequiresPermission: android.nfc.tech.NfcA#transceive(byte[]):
+
+RequiresPermission: android.nfc.tech.NfcB#transceive(byte[]):
+
+RequiresPermission: android.nfc.tech.NfcF#getTimeout():
+
+RequiresPermission: android.nfc.tech.NfcF#setTimeout(int):
+
+RequiresPermission: android.nfc.tech.NfcF#transceive(byte[]):
+
+RequiresPermission: android.nfc.tech.NfcV#transceive(byte[]):
+
+RequiresPermission: android.nfc.tech.TagTechnology#close():
+
+RequiresPermission: android.nfc.tech.TagTechnology#connect():
+
+RequiresPermission: android.os.Build#getSerial():
+
+RequiresPermission: android.os.Debug#dumpService(String, java.io.FileDescriptor, String[]):
+
+RequiresPermission: android.os.Environment#getExternalStorageDirectory():
+
+RequiresPermission: android.os.PowerManager#newWakeLock(int, String):
+
+RequiresPermission: android.os.PowerManager#reboot(String):
+
+RequiresPermission: android.os.RecoverySystem#rebootWipeUserData(android.content.Context):
+
+RequiresPermission: android.os.StrictMode.VmPolicy.Builder#detectFileUriExposure():
+
+RequiresPermission: android.os.UserManager#getUserName():
+
+RequiresPermission: android.os.UserManager#isUserUnlocked(android.os.UserHandle):
+
+RequiresPermission: android.os.health.SystemHealthManager#takeUidSnapshot(int):
+
+RequiresPermission: android.os.health.SystemHealthManager#takeUidSnapshots(int[]):
+
+RequiresPermission: android.os.storage.StorageVolume#createAccessIntent(String):
+
+RequiresPermission: android.provider.MediaStore#setRequireOriginal(android.net.Uri):
+
+RequiresPermission: android.provider.Settings#canDrawOverlays(android.content.Context):
+
+RequiresPermission: android.provider.Settings.System#canWrite(android.content.Context):
+
+RequiresPermission: android.telecom.TelecomManager#acceptHandover(android.net.Uri, int, android.telecom.PhoneAccountHandle):
+
+RequiresPermission: android.telecom.TelecomManager#acceptRingingCall():
+
+RequiresPermission: android.telecom.TelecomManager#acceptRingingCall(int):
+
+RequiresPermission: android.telecom.TelecomManager#addNewIncomingCall(android.telecom.PhoneAccountHandle, android.os.Bundle):
+
+RequiresPermission: android.telecom.TelecomManager#cancelMissedCallsNotification():
+
+RequiresPermission: android.telecom.TelecomManager#endCall():
+
+RequiresPermission: android.telecom.TelecomManager#getAdnUriForPhoneAccount(android.telecom.PhoneAccountHandle):
+
+RequiresPermission: android.telecom.TelecomManager#getCallCapablePhoneAccounts():
+
+RequiresPermission: android.telecom.TelecomManager#getDefaultOutgoingPhoneAccount(String):
+
+RequiresPermission: android.telecom.TelecomManager#getLine1Number(android.telecom.PhoneAccountHandle):
+
+RequiresPermission: android.telecom.TelecomManager#getSelfManagedPhoneAccounts():
+
+RequiresPermission: android.telecom.TelecomManager#getVoiceMailNumber(android.telecom.PhoneAccountHandle):
+
+RequiresPermission: android.telecom.TelecomManager#handleMmi(String):
+
+RequiresPermission: android.telecom.TelecomManager#handleMmi(String, android.telecom.PhoneAccountHandle):
+
+RequiresPermission: android.telecom.TelecomManager#isInCall():
+
+RequiresPermission: android.telecom.TelecomManager#isInManagedCall():
+
+RequiresPermission: android.telecom.TelecomManager#isVoiceMailNumber(android.telecom.PhoneAccountHandle, String):
+
+RequiresPermission: android.telecom.TelecomManager#placeCall(android.net.Uri, android.os.Bundle):
+
+RequiresPermission: android.telecom.TelecomManager#showInCallScreen(boolean):
+
+RequiresPermission: android.telecom.TelecomManager#silenceRinger():
+
+RequiresPermission: android.telephony.CarrierConfigManager#getConfig():
+
+RequiresPermission: android.telephony.CarrierConfigManager#getConfigByComponentForSubId(String, int):
+
+RequiresPermission: android.telephony.CarrierConfigManager#getConfigForSubId(int):
+
+RequiresPermission: android.telephony.PhoneStateListener#onCallStateChanged(int, String):
+
+RequiresPermission: android.telephony.SmsManager#injectSmsPdu(byte[], String, android.app.PendingIntent):
+
+RequiresPermission: android.telephony.SmsManager#sendDataMessage(String, String, short, byte[], android.app.PendingIntent, android.app.PendingIntent):
+
+RequiresPermission: android.telephony.SmsManager#sendMultipartTextMessage(String, String, java.util.ArrayList<java.lang.String>, java.util.ArrayList<android.app.PendingIntent>, java.util.ArrayList<android.app.PendingIntent>):
+
+RequiresPermission: android.telephony.SmsManager#sendTextMessage(String, String, String, android.app.PendingIntent, android.app.PendingIntent):
+
+RequiresPermission: android.telephony.SmsManager#sendTextMessageWithoutPersisting(String, String, String, android.app.PendingIntent, android.app.PendingIntent):
+
+RequiresPermission: android.telephony.SubscriptionManager#addSubscriptionsIntoGroup(java.util.List<java.lang.Integer>, android.os.ParcelUuid):
+
+RequiresPermission: android.telephony.SubscriptionManager#createSubscriptionGroup(java.util.List<java.lang.Integer>):
+
+RequiresPermission: android.telephony.SubscriptionManager#getActiveSubscriptionInfo(int):
+
+RequiresPermission: android.telephony.SubscriptionManager#getActiveSubscriptionInfoCount():
+
+RequiresPermission: android.telephony.SubscriptionManager#getActiveSubscriptionInfoForSimSlotIndex(int):
+
+RequiresPermission: android.telephony.SubscriptionManager#getActiveSubscriptionInfoList():
+
+RequiresPermission: android.telephony.SubscriptionManager#getOpportunisticSubscriptions():
+
+RequiresPermission: android.telephony.SubscriptionManager#getSubscriptionsInGroup(android.os.ParcelUuid):
+
+RequiresPermission: android.telephony.SubscriptionManager#removeSubscriptionsFromGroup(java.util.List<java.lang.Integer>, android.os.ParcelUuid):
+
+RequiresPermission: android.telephony.SubscriptionManager#setOpportunistic(boolean, int):
+
+RequiresPermission: android.telephony.TelephonyManager#doesSwitchMultiSimConfigTriggerReboot():
+
+RequiresPermission: android.telephony.TelephonyManager#getCarrierConfig():
+
+RequiresPermission: android.telephony.TelephonyManager#getDataNetworkType():
+
+RequiresPermission: android.telephony.TelephonyManager#getDeviceId():
+
+RequiresPermission: android.telephony.TelephonyManager#getDeviceId(int):
+
+RequiresPermission: android.telephony.TelephonyManager#getDeviceSoftwareVersion():
+
+RequiresPermission: android.telephony.TelephonyManager#getEmergencyNumberList():
+
+RequiresPermission: android.telephony.TelephonyManager#getEmergencyNumberList(int):
+
+RequiresPermission: android.telephony.TelephonyManager#getForbiddenPlmns():
+
+RequiresPermission: android.telephony.TelephonyManager#getGroupIdLevel1():
+
+RequiresPermission: android.telephony.TelephonyManager#getImei(int):
+
+RequiresPermission: android.telephony.TelephonyManager#getLine1Number():
+
+RequiresPermission: android.telephony.TelephonyManager#getMeid():
+
+RequiresPermission: android.telephony.TelephonyManager#getMeid(int):
+
+RequiresPermission: android.telephony.TelephonyManager#getNai():
+
+RequiresPermission: android.telephony.TelephonyManager#getPreferredOpportunisticDataSubscription():
+
+RequiresPermission: android.telephony.TelephonyManager#getServiceState():
+
+RequiresPermission: android.telephony.TelephonyManager#getSimSerialNumber():
+
+RequiresPermission: android.telephony.TelephonyManager#getSubscriberId():
+
+RequiresPermission: android.telephony.TelephonyManager#getVisualVoicemailPackageName():
+
+RequiresPermission: android.telephony.TelephonyManager#getVoiceMailAlphaTag():
+
+RequiresPermission: android.telephony.TelephonyManager#getVoiceMailNumber():
+
+RequiresPermission: android.telephony.TelephonyManager#getVoiceNetworkType():
+
+RequiresPermission: android.telephony.TelephonyManager#iccCloseLogicalChannel(int):
+
+RequiresPermission: android.telephony.TelephonyManager#iccExchangeSimIO(int, int, int, int, int, String):
+
+RequiresPermission: android.telephony.TelephonyManager#iccOpenLogicalChannel(String):
+
+RequiresPermission: android.telephony.TelephonyManager#iccOpenLogicalChannel(String, int):
+
+RequiresPermission: android.telephony.TelephonyManager#iccTransmitApduBasicChannel(int, int, int, int, int, String):
+
+RequiresPermission: android.telephony.TelephonyManager#iccTransmitApduLogicalChannel(int, int, int, int, int, int, String):
+
+RequiresPermission: android.telephony.TelephonyManager#isDataEnabled():
+
+RequiresPermission: android.telephony.TelephonyManager#isDataRoamingEnabled():
+
+RequiresPermission: android.telephony.TelephonyManager#isMultiSimSupported():
+
+RequiresPermission: android.telephony.TelephonyManager#requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback):
+
+RequiresPermission: android.telephony.TelephonyManager#sendEnvelopeWithStatus(String):
+
+RequiresPermission: android.telephony.TelephonyManager#sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler):
+
+RequiresPermission: android.telephony.TelephonyManager#sendVisualVoicemailSms(String, int, String, android.app.PendingIntent):
+
+RequiresPermission: android.telephony.TelephonyManager#setDataEnabled(boolean):
+
+RequiresPermission: android.telephony.TelephonyManager#setNetworkSelectionModeAutomatic():
+
+RequiresPermission: android.telephony.TelephonyManager#setNetworkSelectionModeManual(String, boolean):
+
+RequiresPermission: android.telephony.TelephonyManager#setPreferredOpportunisticDataSubscription(int, boolean, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>):
+
+RequiresPermission: android.telephony.TelephonyManager#setVoicemailRingtoneUri(android.telecom.PhoneAccountHandle, android.net.Uri):
+
+RequiresPermission: android.telephony.TelephonyManager#setVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle, boolean):
+
+RequiresPermission: android.telephony.TelephonyManager#switchMultiSimConfig(int):
+
+RequiresPermission: android.telephony.TelephonyManager#updateAvailableNetworks(java.util.List<android.telephony.AvailableNetworkInfo>, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>):
+
+RequiresPermission: android.telephony.euicc.EuiccManager#deleteSubscription(int, android.app.PendingIntent):
+
+RequiresPermission: android.telephony.euicc.EuiccManager#downloadSubscription(android.telephony.euicc.DownloadableSubscription, boolean, android.app.PendingIntent):
+
+RequiresPermission: android.telephony.euicc.EuiccManager#switchToSubscription(int, android.app.PendingIntent):
+
+RequiresPermission: android.telephony.euicc.EuiccManager#updateSubscriptionNickname(int, String, android.app.PendingIntent):
+
+RequiresPermission: android.view.inputmethod.InputMethodManager#setCurrentInputMethodSubtype(android.view.inputmethod.InputMethodSubtype):
+
+RequiresPermission: android.view.inputmethod.InputMethodManager#setInputMethod(android.os.IBinder, String):
+
+RequiresPermission: android.view.inputmethod.InputMethodManager#setInputMethodAndSubtype(android.os.IBinder, String, android.view.inputmethod.InputMethodSubtype):
+
+RequiresPermission: android.webkit.WebSettings#setBlockNetworkLoads(boolean):
+
+RequiresPermission: android.webkit.WebSettings#setGeolocationEnabled(boolean):
+
+
+
+SamShouldBeLast: android.location.LocationManager#registerGnssMeasurementsCallback(java.util.concurrent.Executor, android.location.GnssMeasurementsEvent.Callback):
+
+SamShouldBeLast: android.location.LocationManager#registerGnssNavigationMessageCallback(java.util.concurrent.Executor, android.location.GnssNavigationMessage.Callback):
+
+SamShouldBeLast: android.location.LocationManager#registerGnssStatusCallback(java.util.concurrent.Executor, android.location.GnssStatus.Callback):
+
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, java.util.concurrent.Executor, android.location.LocationListener):
+
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, java.util.concurrent.Executor, android.location.LocationListener):
+
+
+
+StreamFiles: android.content.res.loader.DirectoryResourceLoader#DirectoryResourceLoader(java.io.File):
+ Methods accepting `File` should also accept `FileDescriptor` or streams: constructor android.content.res.loader.DirectoryResourceLoader(java.io.File)
+
+
+Todo: android.hardware.camera2.params.StreamConfigurationMap:
+
+Todo: android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration(Class<T>, android.util.Size):
+
+Todo: android.hardware.camera2.params.StreamConfigurationMap#getOutputMinFrameDuration(int, android.util.Size):
+
+Todo: android.provider.ContactsContract.RawContacts#newEntityIterator(android.database.Cursor):
+
+Todo: android.telephony.CarrierConfigManager#KEY_USE_OTASP_FOR_PROVISIONING_BOOL:
+
diff --git a/api/system-current.txt b/api/system-current.txt
index 8ed79a3..6e35200 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -172,6 +172,7 @@
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
+ field public static final String RESTORE_RUNTIME_PERMISSIONS = "android.permission.RESTORE_RUNTIME_PERMISSIONS";
field public static final String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS";
field public static final String RETRIEVE_WINDOW_CONTENT = "android.permission.RETRIEVE_WINDOW_CONTENT";
field public static final String REVIEW_ACCESSIBILITY_SERVICES = "android.permission.REVIEW_ACCESSIBILITY_SERVICES";
@@ -226,6 +227,7 @@
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 resourcesMap = 16844297; // 0x1010609
field public static final int supportsAmbientMode = 16844173; // 0x101058d
field public static final int userRestriction = 16844164; // 0x1010584
}
@@ -1358,8 +1360,9 @@
public abstract class Context {
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean bindServiceAsUser(@RequiresPermission android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle);
+ method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int);
method public abstract android.content.Context createCredentialProtectedStorageContext();
- method public android.content.Context createPackageContextAsUser(String, int, android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
method @Nullable public abstract java.io.File getPreloadsFileCache();
method public abstract boolean isCredentialProtectedStorage();
method public abstract void sendBroadcast(android.content.Intent, @Nullable String, @Nullable android.os.Bundle);
@@ -1752,7 +1755,9 @@
field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
+ field public static final int PROTECTION_FLAG_TELEPHONY = 4194304; // 0x400000
field public static final int PROTECTION_FLAG_WELLBEING = 131072; // 0x20000
+ field public static final int PROTECTION_FLAG_WIFI = 8388608; // 0x800000
field @Nullable public final String backgroundPermission;
field @StringRes public int requestRes;
}
@@ -3418,7 +3423,9 @@
public class Location implements android.os.Parcelable {
method public boolean isComplete();
method public void makeComplete();
+ method public void setExtraLocation(@Nullable String, @Nullable android.location.Location);
method public void setIsFromMockProvider(boolean);
+ field public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
}
public class LocationManager {
@@ -5695,7 +5702,10 @@
package android.permission {
public final class PermissionControllerManager {
+ method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @RequiresPermission(android.Manifest.permission.GET_RUNTIME_PERMISSIONS) public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>);
method @RequiresPermission(android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS) public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
+ method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.RESTORE_RUNTIME_PERMISSIONS}) public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[], @NonNull android.os.UserHandle);
field public static final int COUNT_ONLY_WHEN_GRANTED = 1; // 0x1
field public static final int COUNT_WHEN_SYSTEM = 2; // 0x2
field public static final int REASON_INSTALLER_POLICY_VIOLATION = 2; // 0x2
@@ -5709,17 +5719,19 @@
public abstract class PermissionControllerService extends android.app.Service {
ctor public PermissionControllerService();
+ method @BinderThread public void onApplyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @NonNull public final android.os.IBinder onBind(android.content.Intent);
method @BinderThread public abstract void onCountPermissionApps(@NonNull java.util.List<java.lang.String>, int, @NonNull java.util.function.IntConsumer);
method @BinderThread public abstract void onGetAppPermissions(@NonNull String, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionPresentationInfo>>);
method @BinderThread public abstract void onGetPermissionUsages(boolean, long, @NonNull java.util.function.Consumer<java.util.List<android.permission.RuntimePermissionUsageInfo>>);
method @BinderThread public abstract void onGetRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.OutputStream, @NonNull Runnable);
method @BinderThread public abstract void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable);
- method @BinderThread public abstract void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
- method @BinderThread public abstract void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
+ method @Deprecated @BinderThread public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable);
method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>);
method @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable);
method @BinderThread public void onUpdateUserSensitive();
field public static final String SERVICE_INTERFACE = "android.permission.PermissionControllerService";
}
@@ -5875,6 +5887,7 @@
method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static float getFloat(@NonNull String, @NonNull String, float);
method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static int getInt(@NonNull String, @NonNull String, int);
method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static long getLong(@NonNull String, @NonNull String, long);
+ method @NonNull @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static android.provider.DeviceConfig.Properties getProperties(@NonNull String, @NonNull java.lang.String...);
method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getProperty(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(@NonNull String, @NonNull String, @Nullable String);
method public static void removeOnPropertiesChangedListener(@NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
@@ -6557,7 +6570,8 @@
method public abstract int onDeleteSubscription(int, String);
method public android.service.euicc.DownloadSubscriptionResult onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean, @Nullable android.os.Bundle);
method @Deprecated public int onDownloadSubscription(int, @NonNull android.telephony.euicc.DownloadableSubscription, boolean, boolean);
- method public abstract int onEraseSubscriptions(int);
+ method @Deprecated public abstract int onEraseSubscriptions(int);
+ method public int onEraseSubscriptionsWithOptions(int, @android.telephony.euicc.EuiccCardManager.ResetOption int);
method public abstract android.service.euicc.GetDefaultDownloadableSubscriptionListResult onGetDefaultDownloadableSubscriptionList(int, boolean);
method public abstract android.service.euicc.GetDownloadableSubscriptionMetadataResult onGetDownloadableSubscriptionMetadata(int, android.telephony.euicc.DownloadableSubscription, boolean);
method public abstract String onGetEid(int);
@@ -7308,7 +7322,7 @@
public abstract class CellBroadcastService extends android.app.Service {
ctor public CellBroadcastService();
method @CallSuper public android.os.IBinder onBind(android.content.Intent);
- method public abstract void onCdmaCellBroadcastSms(int, byte[]);
+ method public abstract void onCdmaCellBroadcastSms(int, byte[], int);
method public abstract void onGsmCellBroadcastSms(int, byte[]);
field public static final String CELL_BROADCAST_SERVICE_INTERFACE = "android.telephony.CellBroadcastService";
}
@@ -8620,7 +8634,8 @@
public class EuiccManager {
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void continueOperation(android.content.Intent, android.os.Bundle);
- method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void eraseSubscriptions(android.app.PendingIntent);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void eraseSubscriptions(@NonNull android.app.PendingIntent);
+ method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void eraseSubscriptionsWithOptions(@android.telephony.euicc.EuiccCardManager.ResetOption int, @NonNull android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDefaultDownloadableSubscriptionList(android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void getDownloadableSubscriptionMetadata(android.telephony.euicc.DownloadableSubscription, android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public int getOtaStatus();
@@ -9571,17 +9586,17 @@
public class ImsSmsImplBase {
ctor public ImsSmsImplBase();
- method public void acknowledgeSms(int, int, int);
- method public void acknowledgeSmsReport(int, int, int);
+ method public void acknowledgeSms(int, @IntRange(from=0, to=65535) int, int);
+ method public void acknowledgeSmsReport(int, @IntRange(from=0, to=65535) int, int);
method public String getSmsFormat();
method public void onReady();
- method @Deprecated public final void onSendSmsResult(int, int, int, int) throws java.lang.RuntimeException;
- method public final void onSendSmsResultError(int, int, int, int, int) throws java.lang.RuntimeException;
- method public final void onSendSmsResultSuccess(int, int) throws java.lang.RuntimeException;
+ method @Deprecated public final void onSendSmsResult(int, @IntRange(from=0, to=65535) int, int, int) throws java.lang.RuntimeException;
+ method public final void onSendSmsResultError(int, @IntRange(from=0, to=65535) int, int, int, int) throws java.lang.RuntimeException;
+ method public final void onSendSmsResultSuccess(int, @IntRange(from=0, to=65535) int) throws java.lang.RuntimeException;
method public final void onSmsReceived(int, String, byte[]) throws java.lang.RuntimeException;
- method @Deprecated public final void onSmsStatusReportReceived(int, int, String, byte[]) throws java.lang.RuntimeException;
+ method @Deprecated public final void onSmsStatusReportReceived(int, @IntRange(from=0, to=65535) int, String, byte[]) throws java.lang.RuntimeException;
method public final void onSmsStatusReportReceived(int, String, byte[]) throws java.lang.RuntimeException;
- method public void sendSms(int, int, String, String, boolean, byte[]);
+ method public void sendSms(int, @IntRange(from=0, to=65535) int, String, String, boolean, byte[]);
field public static final int DELIVER_STATUS_ERROR_GENERIC = 2; // 0x2
field public static final int DELIVER_STATUS_ERROR_NO_MEMORY = 3; // 0x3
field public static final int DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED = 4; // 0x4
@@ -9753,9 +9768,10 @@
method public final long getUserActivityTimeout();
method public final void setUserActivityTimeout(long);
field @RequiresPermission(android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS) public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 524288; // 0x80000
+ field @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW) public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 16; // 0x10
}
- @IntDef(flag=true, prefix={"SYSTEM_FLAG_"}, value={android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface WindowManager.LayoutParams.SystemFlags {
+ @IntDef(flag=true, prefix={"SYSTEM_FLAG_"}, value={android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface WindowManager.LayoutParams.SystemFlags {
}
}
diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt
new file mode 100644
index 0000000..57a853a
--- /dev/null
+++ b/api/system-lint-baseline.txt
@@ -0,0 +1,391 @@
+// Baseline format: 1.0
+ActionValue: android.location.Location#EXTRA_NO_GPS_LOCATION:
+
+
+
+ArrayReturn: android.view.contentcapture.ViewNode#getAutofillOptions():
+
+
+
+GenericException: android.app.prediction.AppPredictor#finalize():
+
+GenericException: android.hardware.location.ContextHubClient#finalize():
+
+GenericException: android.net.IpSecManager.IpSecTunnelInterface#finalize():
+
+GenericException: android.service.autofill.augmented.FillWindow#finalize():
+
+
+
+InterfaceConstant: android.service.storage.ExternalStorageService#SERVICE_INTERFACE:
+
+
+
+KotlinKeyword: android.app.Notification#when:
+
+
+
+MissingNullability: android.hardware.soundtrigger.SoundTrigger.ModuleProperties#toString():
+
+MissingNullability: android.hardware.soundtrigger.SoundTrigger.ModuleProperties#writeToParcel(android.os.Parcel, int) parameter #0:
+
+MissingNullability: android.media.session.MediaSessionManager.Callback#onAddressedPlayerChanged(android.content.ComponentName) parameter #0:
+
+MissingNullability: android.media.session.MediaSessionManager.Callback#onAddressedPlayerChanged(android.media.session.MediaSession.Token) parameter #0:
+
+MissingNullability: android.media.session.MediaSessionManager.Callback#onMediaKeyEventDispatched(android.view.KeyEvent, android.content.ComponentName) parameter #0:
+
+MissingNullability: android.media.session.MediaSessionManager.Callback#onMediaKeyEventDispatched(android.view.KeyEvent, android.content.ComponentName) parameter #1:
+
+MissingNullability: android.media.session.MediaSessionManager.Callback#onMediaKeyEventDispatched(android.view.KeyEvent, android.media.session.MediaSession.Token) parameter #0:
+
+MissingNullability: android.media.session.MediaSessionManager.Callback#onMediaKeyEventDispatched(android.view.KeyEvent, android.media.session.MediaSession.Token) parameter #1:
+
+MissingNullability: android.media.soundtrigger.SoundTriggerDetectionService#onUnbind(android.content.Intent) parameter #0:
+
+MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #0:
+
+MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #1:
+
+MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #2:
+
+MissingNullability: android.net.wifi.rtt.RangingRequest.Builder#addResponder(android.net.wifi.rtt.ResponderConfig):
+
+MissingNullability: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context) parameter #0:
+
+MissingNullability: android.provider.ContactsContract.MetadataSync#CONTENT_URI:
+
+MissingNullability: android.provider.ContactsContract.MetadataSync#METADATA_AUTHORITY_URI:
+
+MissingNullability: android.provider.ContactsContract.MetadataSyncState#CONTENT_URI:
+
+MissingNullability: android.provider.SearchIndexablesProvider#attachInfo(android.content.Context, android.content.pm.ProviderInfo) parameter #0:
+
+MissingNullability: android.provider.SearchIndexablesProvider#attachInfo(android.content.Context, android.content.pm.ProviderInfo) parameter #1:
+
+MissingNullability: android.service.autofill.augmented.AugmentedAutofillService#onUnbind(android.content.Intent) parameter #0:
+
+MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0:
+
+MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #1:
+
+MissingNullability: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #2:
+
+MissingNullability: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context) parameter #0:
+
+MissingNullability: android.telecom.CallScreeningService.CallResponse.Builder#setShouldScreenCallFurther(boolean):
+
+MissingNullability: android.telephony.CallerInfo#toString():
+
+MissingNullability: android.telephony.CellBroadcastService#onBind(android.content.Intent):
+
+MissingNullability: android.telephony.CellBroadcastService#onBind(android.content.Intent) parameter #0:
+
+MissingNullability: android.telephony.CellBroadcastService#onCdmaCellBroadcastSms(int, byte[]) parameter #1:
+
+MissingNullability: android.telephony.CellBroadcastService#onCdmaCellBroadcastSms(int, byte[], int) parameter #1:
+
+MissingNullability: android.telephony.CellBroadcastService#onGsmCellBroadcastSms(int, byte[]) parameter #1:
+
+MissingNullability: android.telephony.ModemActivityInfo#toString():
+
+MissingNullability: android.telephony.ModemActivityInfo#writeToParcel(android.os.Parcel, int) parameter #0:
+
+MissingNullability: android.telephony.ModemActivityInfo.TransmitPower#toString():
+
+MissingNullability: android.telephony.NetworkService#onUnbind(android.content.Intent) parameter #0:
+
+MissingNullability: android.telephony.SmsCbCmasInfo#toString():
+
+MissingNullability: android.telephony.SmsCbCmasInfo#writeToParcel(android.os.Parcel, int) parameter #0:
+
+MissingNullability: android.telephony.SmsCbEtwsInfo#toString():
+
+MissingNullability: android.telephony.SmsCbEtwsInfo#writeToParcel(android.os.Parcel, int) parameter #0:
+
+MissingNullability: android.telephony.SmsCbLocation#equals(Object) parameter #0:
+
+MissingNullability: android.telephony.SmsCbLocation#toString():
+
+MissingNullability: android.telephony.SmsCbLocation#writeToParcel(android.os.Parcel, int) parameter #0:
+
+MissingNullability: android.telephony.SmsCbMessage#toString():
+
+MissingNullability: android.telephony.SmsCbMessage#writeToParcel(android.os.Parcel, int) parameter #0:
+
+MissingNullability: android.telephony.SubscriptionPlan.Builder#createRecurringDaily(java.time.ZonedDateTime) parameter #0:
+
+MissingNullability: android.telephony.SubscriptionPlan.Builder#createRecurringMonthly(java.time.ZonedDateTime) parameter #0:
+
+MissingNullability: android.telephony.SubscriptionPlan.Builder#createRecurringWeekly(java.time.ZonedDateTime) parameter #0:
+
+MissingNullability: android.telephony.cdma.CdmaSmsCbProgramData#toString():
+
+MissingNullability: android.telephony.cdma.CdmaSmsCbProgramData#writeToParcel(android.os.Parcel, int) parameter #0:
+
+MissingNullability: android.telephony.data.DataService#onUnbind(android.content.Intent) parameter #0:
+
+MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#onSmsStatusReportReceived(int, String, byte[]) parameter #1:
+
+MissingNullability: android.telephony.ims.stub.ImsSmsImplBase#onSmsStatusReportReceived(int, String, byte[]) parameter #2:
+
+MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String):
+
+MissingNullability: android.telephony.mbms.DownloadRequest.Builder#setServiceId(String) parameter #0:
+
+
+
+NoClone: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0:
+
+
+
+ProtectedMember: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context):
+
+ProtectedMember: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]):
+
+ProtectedMember: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context):
+
+
+
+SamShouldBeLast: android.accounts.AccountManager#addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean):
+
+SamShouldBeLast: android.accounts.AccountManager#addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean, String[]):
+
+SamShouldBeLast: android.accounts.AccountManager#confirmCredentials(android.accounts.Account, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#editProperties(String, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#finishSession(android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#getAuthToken(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#getAuthToken(android.accounts.Account, String, android.os.Bundle, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#getAuthToken(android.accounts.Account, String, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#getAuthTokenByFeatures(String, String, String[], android.app.Activity, android.os.Bundle, android.os.Bundle, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#isCredentialsUpdateSuggested(android.accounts.Account, String, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#removeAccount(android.accounts.Account, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#renameAccount(android.accounts.Account, String, android.accounts.AccountManagerCallback<android.accounts.Account>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#startAddAccountSession(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#startUpdateCredentialsSession(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.accounts.AccountManager#updateCredentials(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler):
+
+SamShouldBeLast: android.app.AlarmManager#set(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
+
+SamShouldBeLast: android.app.AlarmManager#setExact(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
+
+SamShouldBeLast: android.app.AlarmManager#setWindow(int, long, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler):
+
+SamShouldBeLast: android.app.WallpaperInfo#dump(android.util.Printer, String):
+
+SamShouldBeLast: android.app.admin.DevicePolicyManager#installSystemUpdate(android.content.ComponentName, android.net.Uri, java.util.concurrent.Executor, android.app.admin.DevicePolicyManager.InstallSystemUpdateCallback):
+
+SamShouldBeLast: android.content.Context#bindIsolatedService(android.content.Intent, int, String, java.util.concurrent.Executor, android.content.ServiceConnection):
+
+SamShouldBeLast: android.content.Context#bindService(android.content.Intent, int, java.util.concurrent.Executor, android.content.ServiceConnection):
+
+SamShouldBeLast: android.content.ContextWrapper#bindIsolatedService(android.content.Intent, int, String, java.util.concurrent.Executor, android.content.ServiceConnection):
+
+SamShouldBeLast: android.content.ContextWrapper#bindService(android.content.Intent, int, java.util.concurrent.Executor, android.content.ServiceConnection):
+
+SamShouldBeLast: android.content.IntentFilter#dump(android.util.Printer, String):
+
+SamShouldBeLast: android.content.pm.ApplicationInfo#dump(android.util.Printer, String):
+
+SamShouldBeLast: android.content.pm.LauncherApps#registerPackageInstallerSessionCallback(java.util.concurrent.Executor, android.content.pm.PackageInstaller.SessionCallback):
+
+SamShouldBeLast: android.content.pm.PackageItemInfo#dumpBack(android.util.Printer, String):
+
+SamShouldBeLast: android.content.pm.PackageItemInfo#dumpFront(android.util.Printer, String):
+
+SamShouldBeLast: android.content.pm.ResolveInfo#dump(android.util.Printer, String):
+
+SamShouldBeLast: android.location.Location#dump(android.util.Printer, String):
+
+SamShouldBeLast: android.location.LocationManager#addNmeaListener(android.location.OnNmeaMessageListener, android.os.Handler):
+
+SamShouldBeLast: android.location.LocationManager#registerGnssMeasurementsCallback(java.util.concurrent.Executor, android.location.GnssMeasurementsEvent.Callback):
+
+SamShouldBeLast: android.location.LocationManager#registerGnssNavigationMessageCallback(java.util.concurrent.Executor, android.location.GnssNavigationMessage.Callback):
+
+SamShouldBeLast: android.location.LocationManager#registerGnssStatusCallback(java.util.concurrent.Executor, android.location.GnssStatus.Callback):
+
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(String, long, float, java.util.concurrent.Executor, android.location.LocationListener):
+
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, java.util.concurrent.Executor, android.location.LocationListener):
+
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(long, float, android.location.Criteria, java.util.concurrent.Executor, android.location.LocationListener):
+
+SamShouldBeLast: android.media.AudioFocusRequest.Builder#setOnAudioFocusChangeListener(android.media.AudioManager.OnAudioFocusChangeListener, android.os.Handler):
+
+SamShouldBeLast: android.media.AudioManager#requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int):
+
+SamShouldBeLast: android.media.AudioRecord#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
+
+SamShouldBeLast: android.media.AudioRecord#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
+
+SamShouldBeLast: android.media.AudioRecordingMonitor#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
+
+SamShouldBeLast: android.media.AudioRouting#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
+
+SamShouldBeLast: android.media.MediaRecorder#addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler):
+
+SamShouldBeLast: android.media.MediaRecorder#registerAudioRecordingCallback(java.util.concurrent.Executor, android.media.AudioManager.AudioRecordingCallback):
+
+SamShouldBeLast: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName):
+
+SamShouldBeLast: android.media.session.MediaSessionManager#addOnActiveSessionsChangedListener(android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, android.content.ComponentName, android.os.Handler):
+
+SamShouldBeLast: android.media.session.MediaSessionManager#addOnSession2TokensChangedListener(android.media.session.MediaSessionManager.OnSession2TokensChangedListener, android.os.Handler):
+
+SamShouldBeLast: android.media.session.MediaSessionManager#registerCallback(java.util.concurrent.Executor, android.media.session.MediaSessionManager.Callback):
+
+SamShouldBeLast: android.net.ConnectivityManager#createSocketKeepalive(android.net.Network, android.net.IpSecManager.UdpEncapsulationSocket, java.net.InetAddress, java.net.InetAddress, java.util.concurrent.Executor, android.net.SocketKeepalive.Callback):
+
+SamShouldBeLast: android.net.wifi.rtt.WifiRttManager#startRanging(android.net.wifi.rtt.RangingRequest, java.util.concurrent.Executor, android.net.wifi.rtt.RangingResultCallback):
+
+SamShouldBeLast: android.nfc.NfcAdapter#enableReaderMode(android.app.Activity, android.nfc.NfcAdapter.ReaderCallback, int, android.os.Bundle):
+
+SamShouldBeLast: android.nfc.NfcAdapter#ignore(android.nfc.Tag, int, android.nfc.NfcAdapter.OnTagRemovedListener, android.os.Handler):
+
+SamShouldBeLast: android.nfc.NfcAdapter#setBeamPushUrisCallback(android.nfc.NfcAdapter.CreateBeamUrisCallback, android.app.Activity):
+
+SamShouldBeLast: android.nfc.NfcAdapter#setNdefPushMessageCallback(android.nfc.NfcAdapter.CreateNdefMessageCallback, android.app.Activity, android.app.Activity...):
+
+SamShouldBeLast: android.nfc.NfcAdapter#setOnNdefPushCompleteCallback(android.nfc.NfcAdapter.OnNdefPushCompleteCallback, android.app.Activity, android.app.Activity...):
+
+SamShouldBeLast: android.os.Binder#attachInterface(android.os.IInterface, String):
+
+SamShouldBeLast: android.os.Binder#linkToDeath(android.os.IBinder.DeathRecipient, int):
+
+SamShouldBeLast: android.os.Binder#unlinkToDeath(android.os.IBinder.DeathRecipient, int):
+
+SamShouldBeLast: android.os.Handler#dump(android.util.Printer, String):
+
+SamShouldBeLast: android.os.Handler#postAtTime(Runnable, Object, long):
+
+SamShouldBeLast: android.os.Handler#postAtTime(Runnable, long):
+
+SamShouldBeLast: android.os.Handler#postDelayed(Runnable, Object, long):
+
+SamShouldBeLast: android.os.Handler#postDelayed(Runnable, long):
+
+SamShouldBeLast: android.os.Handler#removeCallbacks(Runnable, Object):
+
+SamShouldBeLast: android.os.IBinder#linkToDeath(android.os.IBinder.DeathRecipient, int):
+
+SamShouldBeLast: android.os.IBinder#unlinkToDeath(android.os.IBinder.DeathRecipient, int):
+
+SamShouldBeLast: android.os.RecoverySystem#verifyPackage(java.io.File, android.os.RecoverySystem.ProgressListener, java.io.File):
+
+SamShouldBeLast: android.telephony.MbmsDownloadSession#addProgressListener(android.telephony.mbms.DownloadRequest, java.util.concurrent.Executor, android.telephony.mbms.DownloadProgressListener):
+
+SamShouldBeLast: android.telephony.MbmsDownloadSession#addStatusListener(android.telephony.mbms.DownloadRequest, java.util.concurrent.Executor, android.telephony.mbms.DownloadStatusListener):
+
+SamShouldBeLast: android.telephony.MbmsDownloadSession#create(android.content.Context, java.util.concurrent.Executor, android.telephony.mbms.MbmsDownloadSessionCallback):
+
+SamShouldBeLast: android.telephony.MbmsDownloadSession#create(android.content.Context, java.util.concurrent.Executor, int, android.telephony.mbms.MbmsDownloadSessionCallback):
+
+SamShouldBeLast: android.telephony.MbmsGroupCallSession#create(android.content.Context, int, java.util.concurrent.Executor, android.telephony.mbms.MbmsGroupCallSessionCallback):
+
+SamShouldBeLast: android.telephony.MbmsGroupCallSession#create(android.content.Context, java.util.concurrent.Executor, android.telephony.mbms.MbmsGroupCallSessionCallback):
+
+SamShouldBeLast: android.telephony.MbmsGroupCallSession#startGroupCall(long, java.util.List<java.lang.Integer>, java.util.List<java.lang.Integer>, java.util.concurrent.Executor, android.telephony.mbms.GroupCallCallback):
+
+SamShouldBeLast: android.telephony.MbmsStreamingSession#create(android.content.Context, java.util.concurrent.Executor, android.telephony.mbms.MbmsStreamingSessionCallback):
+
+SamShouldBeLast: android.telephony.MbmsStreamingSession#create(android.content.Context, java.util.concurrent.Executor, int, android.telephony.mbms.MbmsStreamingSessionCallback):
+
+SamShouldBeLast: android.telephony.MbmsStreamingSession#startStreaming(android.telephony.mbms.StreamingServiceInfo, java.util.concurrent.Executor, android.telephony.mbms.StreamingServiceCallback):
+
+SamShouldBeLast: android.telephony.SmsManager#getSmsMessagesForFinancialApp(android.os.Bundle, java.util.concurrent.Executor, android.telephony.SmsManager.FinancialSmsCallback):
+
+SamShouldBeLast: android.telephony.SubscriptionManager#addOnOpportunisticSubscriptionsChangedListener(java.util.concurrent.Executor, android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener):
+
+SamShouldBeLast: android.telephony.TelephonyManager#requestCellInfoUpdate(java.util.concurrent.Executor, android.telephony.TelephonyManager.CellInfoCallback):
+
+SamShouldBeLast: android.telephony.TelephonyManager#requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback):
+
+SamShouldBeLast: android.telephony.TelephonyManager#setPreferredOpportunisticDataSubscription(int, boolean, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>):
+
+SamShouldBeLast: android.telephony.TelephonyManager#updateAvailableNetworks(java.util.List<android.telephony.AvailableNetworkInfo>, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Integer>):
+
+SamShouldBeLast: android.view.View#postDelayed(Runnable, long):
+
+SamShouldBeLast: android.view.View#postOnAnimationDelayed(Runnable, long):
+
+SamShouldBeLast: android.view.View#scheduleDrawable(android.graphics.drawable.Drawable, Runnable, long):
+
+SamShouldBeLast: android.view.Window#addOnFrameMetricsAvailableListener(android.view.Window.OnFrameMetricsAvailableListener, android.os.Handler):
+
+SamShouldBeLast: android.view.accessibility.AccessibilityManager#addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, android.os.Handler):
+
+SamShouldBeLast: android.view.accessibility.AccessibilityManager#addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, android.os.Handler):
+
+SamShouldBeLast: android.webkit.WebChromeClient#onShowFileChooser(android.webkit.WebView, android.webkit.ValueCallback<android.net.Uri[]>, android.webkit.WebChromeClient.FileChooserParams):
+
+SamShouldBeLast: android.webkit.WebView#setWebViewRenderProcessClient(java.util.concurrent.Executor, android.webkit.WebViewRenderProcessClient):
+
+
+
+ServiceName: android.Manifest.permission#BIND_ATTENTION_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_AUGMENTED_AUTOFILL_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_CELL_BROADCAST_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_CONTENT_CAPTURE_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_CONTENT_SUGGESTIONS_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_EUICC_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_EXTERNAL_STORAGE_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_IMS_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_NETWORK_RECOMMENDATION_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_NOTIFICATION_ASSISTANT_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_PRINT_RECOMMENDATION_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_RESOLVER_RANKER_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_SETTINGS_SUGGESTIONS_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_SOUND_TRIGGER_DETECTION_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_TELEPHONY_DATA_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_TELEPHONY_NETWORK_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_TEXTCLASSIFIER_SERVICE:
+
+ServiceName: android.Manifest.permission#BIND_TV_REMOTE_SERVICE:
+
+ServiceName: android.Manifest.permission#PROVIDE_RESOLVER_RANKER_SERVICE:
+
+ServiceName: android.Manifest.permission#REQUEST_NOTIFICATION_ASSISTANT_SERVICE:
+
+ServiceName: android.provider.DeviceConfig#NAMESPACE_PACKAGE_MANAGER_SERVICE:
+
diff --git a/api/test-current.txt b/api/test-current.txt
index 70837a8..b9ab375 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -131,7 +131,7 @@
method public void startActivity(@NonNull android.content.Intent);
method public void startActivity(@NonNull android.content.Intent, android.os.UserHandle);
method public void startActivity(@NonNull android.app.PendingIntent);
- method public void startActivity(@NonNull android.app.PendingIntent, @NonNull android.app.ActivityOptions);
+ method public void startActivity(@NonNull android.app.PendingIntent, @Nullable android.content.Intent, @NonNull android.app.ActivityOptions);
}
public abstract static class ActivityView.StateCallback {
@@ -659,7 +659,8 @@
}
public abstract class Context {
- method public android.content.Context createPackageContextAsUser(String, int, android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int);
+ method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
method public abstract android.view.Display getDisplay();
method public abstract int getDisplayId();
method public android.os.UserHandle getUser();
@@ -730,6 +731,7 @@
method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.REVOKE_RUNTIME_PERMISSIONS", "android.permission.GET_RUNTIME_PERMISSIONS"}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
method @NonNull public abstract String getServicesSystemSharedLibraryPackageName();
method @NonNull public abstract String getSharedSystemSharedLibraryPackageName();
+ method @Nullable public String[] getTelephonyPackageNames();
method @Nullable public String getWellbeingPackageName();
method @RequiresPermission("android.permission.GRANT_RUNTIME_PERMISSIONS") public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void removeOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener);
@@ -768,8 +770,10 @@
field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
field public static final int PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER = 65536; // 0x10000
+ field public static final int PROTECTION_FLAG_TELEPHONY = 4194304; // 0x400000
field public static final int PROTECTION_FLAG_VENDOR_PRIVILEGED = 32768; // 0x8000
field public static final int PROTECTION_FLAG_WELLBEING = 131072; // 0x20000
+ field public static final int PROTECTION_FLAG_WIFI = 8388608; // 0x800000
field @Nullable public final String backgroundPermission;
}
@@ -787,7 +791,9 @@
public final class AssetManager implements java.lang.AutoCloseable {
method @NonNull public String[] getApkPaths();
+ method @Nullable public String getLastResourceResolution();
method @Nullable public String getOverlayablesToString(String);
+ method public void setResourceResolutionLoggingEnabled(boolean);
}
public final class Configuration implements java.lang.Comparable<android.content.res.Configuration> android.os.Parcelable {
@@ -1088,11 +1094,14 @@
public class Location implements android.os.Parcelable {
method public void makeComplete();
+ method public void setExtraLocation(@Nullable String, @Nullable android.location.Location);
+ field public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
}
public class LocationManager {
method @NonNull public String[] getBackgroundThrottlingWhitelist();
method @NonNull public String[] getIgnoreSettingsWhitelist();
+ method @Nullable @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public java.util.List<java.lang.String> getProviderPackages(@NonNull String);
method @NonNull public java.util.List<android.location.LocationRequest> getTestProviderCurrentRequests(String);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull android.location.LocationListener, @Nullable android.os.Looper);
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void requestLocationUpdates(@Nullable android.location.LocationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.location.LocationListener);
@@ -1740,6 +1749,7 @@
}
public class DeviceIdleManager {
+ method @RequiresPermission("android.permission.DEVICE_POWER") public int addPowerSaveWhitelistApps(@NonNull java.util.List<java.lang.String>);
method @NonNull public String[] getSystemPowerWhitelist();
method @NonNull public String[] getSystemPowerWhitelistExceptIdle();
}
@@ -2243,8 +2253,11 @@
package android.permission {
public final class PermissionControllerManager {
+ method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.RESTORE_RUNTIME_PERMISSIONS"}) public void applyStagedRuntimePermissionBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @RequiresPermission("android.permission.GET_RUNTIME_PERMISSIONS") public void getAppPermissions(@NonNull String, @NonNull android.permission.PermissionControllerManager.OnGetAppPermissionResultCallback, @Nullable android.os.Handler);
+ method @RequiresPermission("android.permission.GET_RUNTIME_PERMISSIONS") public void getRuntimePermissionBackup(@NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<byte[]>);
method @RequiresPermission("android.permission.REVOKE_RUNTIME_PERMISSIONS") public void revokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull java.util.concurrent.Executor, @NonNull android.permission.PermissionControllerManager.OnRevokeRuntimePermissionsCallback);
+ method @RequiresPermission(anyOf={"android.permission.GRANT_RUNTIME_PERMISSIONS", "android.permission.RESTORE_RUNTIME_PERMISSIONS"}) public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[], @NonNull android.os.UserHandle);
field public static final int COUNT_ONLY_WHEN_GRANTED = 1; // 0x1
field public static final int COUNT_WHEN_SYSTEM = 2; // 0x2
field public static final int REASON_INSTALLER_POLICY_VIOLATION = 2; // 0x2
@@ -2785,6 +2798,10 @@
method public void exitBackgroundAudioProcessing(boolean);
}
+ public static class Call.Details {
+ method public String getTelecomCallId();
+ }
+
public final class CallAudioState implements android.os.Parcelable {
ctor public CallAudioState(boolean, int, int, @Nullable android.bluetooth.BluetoothDevice, @NonNull java.util.Collection<android.bluetooth.BluetoothDevice>);
}
diff --git a/cmds/idmap2/Android.bp b/cmds/idmap2/Android.bp
index 4c77ba4..41a1706 100644
--- a/cmds/idmap2/Android.bp
+++ b/cmds/idmap2/Android.bp
@@ -43,9 +43,10 @@
"libidmap2/Policies.cpp",
"libidmap2/PrettyPrintVisitor.cpp",
"libidmap2/RawPrintVisitor.cpp",
+ "libidmap2/ResourceMapping.cpp",
"libidmap2/ResourceUtils.cpp",
"libidmap2/Result.cpp",
- "libidmap2/Xml.cpp",
+ "libidmap2/XmlParser.cpp",
"libidmap2/ZipFile.cpp",
],
export_include_dirs: ["include"],
@@ -97,9 +98,10 @@
"tests/PoliciesTests.cpp",
"tests/PrettyPrintVisitorTests.cpp",
"tests/RawPrintVisitorTests.cpp",
+ "tests/ResourceMappingTests.cpp",
"tests/ResourceUtilsTests.cpp",
"tests/ResultTests.cpp",
- "tests/XmlTests.cpp",
+ "tests/XmlParserTests.cpp",
"tests/ZipFileTests.cpp",
],
required: [
diff --git a/cmds/idmap2/idmap2/Create.cpp b/cmds/idmap2/idmap2/Create.cpp
index f482191..3ff6d35 100644
--- a/cmds/idmap2/idmap2/Create.cpp
+++ b/cmds/idmap2/idmap2/Create.cpp
@@ -99,8 +99,8 @@
return Error("failed to load apk %s", overlay_apk_path.c_str());
}
- const auto idmap = Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path,
- *overlay_apk, fulfilled_policies, !ignore_overlayable);
+ const auto idmap =
+ Idmap::FromApkAssets(*target_apk, *overlay_apk, fulfilled_policies, !ignore_overlayable);
if (!idmap) {
return Error(idmap.GetError(), "failed to create idmap");
}
diff --git a/cmds/idmap2/idmap2/Lookup.cpp b/cmds/idmap2/idmap2/Lookup.cpp
index b7ae9d0..c5cf980 100644
--- a/cmds/idmap2/idmap2/Lookup.cpp
+++ b/cmds/idmap2/idmap2/Lookup.cpp
@@ -33,9 +33,10 @@
#include "androidfw/Util.h"
#include "idmap2/CommandLineOptions.h"
#include "idmap2/Idmap.h"
+#include "idmap2/ResourceUtils.h"
#include "idmap2/Result.h"
#include "idmap2/SysTrace.h"
-#include "idmap2/Xml.h"
+#include "idmap2/XmlParser.h"
#include "idmap2/ZipFile.h"
#include "utils/String16.h"
#include "utils/String8.h"
@@ -57,8 +58,7 @@
using android::idmap2::ResourceId;
using android::idmap2::Result;
using android::idmap2::Unit;
-using android::idmap2::Xml;
-using android::idmap2::ZipFile;
+using android::idmap2::utils::ExtractOverlayManifestInfo;
using android::util::Utf16ToUtf8;
namespace {
@@ -132,29 +132,6 @@
return out;
}
-Result<std::string> GetTargetPackageNameFromManifest(const std::string& apk_path) {
- const auto zip = ZipFile::Open(apk_path);
- if (!zip) {
- return Error("failed to open %s as zip", apk_path.c_str());
- }
- const auto entry = zip->Uncompress("AndroidManifest.xml");
- if (!entry) {
- return Error("failed to uncompress AndroidManifest.xml in %s", apk_path.c_str());
- }
- const auto xml = Xml::Create(entry->buf, entry->size);
- if (!xml) {
- return Error("failed to create XML buffer");
- }
- const auto tag = xml->FindTag("overlay");
- if (!tag) {
- return Error("failed to find <overlay> tag");
- }
- const auto iter = tag->find("targetPackage");
- if (iter == tag->end()) {
- return Error("failed to find targetPackage attribute");
- }
- return iter->second;
-}
} // namespace
Result<Unit> Lookup(const std::vector<std::string>& args) {
@@ -202,12 +179,12 @@
}
apk_assets.push_back(std::move(target_apk));
- const Result<std::string> package_name =
- GetTargetPackageNameFromManifest(idmap_header->GetOverlayPath().to_string());
- if (!package_name) {
- return Error("failed to parse android:targetPackage from overlay manifest");
+ auto manifest_info = ExtractOverlayManifestInfo(idmap_header->GetOverlayPath().to_string(),
+ true /* assert_overlay */);
+ if (!manifest_info) {
+ return manifest_info.GetError();
}
- target_package_name = *package_name;
+ target_package_name = (*manifest_info).target_package;
} else if (target_path != idmap_header->GetTargetPath()) {
return Error("different target APKs (expected target APK %s but %s has target APK %s)",
target_path.c_str(), idmap_path.c_str(),
diff --git a/cmds/idmap2/idmap2/Scan.cpp b/cmds/idmap2/idmap2/Scan.cpp
index d0530f2..e643ab5 100644
--- a/cmds/idmap2/idmap2/Scan.cpp
+++ b/cmds/idmap2/idmap2/Scan.cpp
@@ -33,7 +33,7 @@
#include "idmap2/ResourceUtils.h"
#include "idmap2/Result.h"
#include "idmap2/SysTrace.h"
-#include "idmap2/Xml.h"
+#include "idmap2/XmlParser.h"
#include "idmap2/ZipFile.h"
using android::idmap2::CommandLineOptions;
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index 8ee79f6..4aabf83 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -137,8 +137,8 @@
return error("failed to load apk " + overlay_apk_path);
}
- const auto idmap = Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path,
- *overlay_apk, policy_bitmask, enforce_overlayable);
+ const auto idmap =
+ Idmap::FromApkAssets(*target_apk, *overlay_apk, policy_bitmask, enforce_overlayable);
if (!idmap) {
return error(idmap.GetErrorMessage());
}
diff --git a/cmds/idmap2/include/idmap2/Idmap.h b/cmds/idmap2/include/idmap2/Idmap.h
index ebbb5ff..f2cae58 100644
--- a/cmds/idmap2/include/idmap2/Idmap.h
+++ b/cmds/idmap2/include/idmap2/Idmap.h
@@ -56,20 +56,14 @@
#include "androidfw/ResourceTypes.h"
#include "androidfw/StringPiece.h"
#include "idmap2/Policies.h"
+#include "idmap2/ResourceMapping.h"
namespace android::idmap2 {
class Idmap;
class Visitor;
-// use typedefs to let the compiler warn us about implicit casts
-typedef uint32_t ResourceId; // 0xpptteeee
-typedef uint8_t PackageId; // pp in 0xpptteeee
-typedef uint8_t TypeId; // tt in 0xpptteeee
-typedef uint16_t EntryId; // eeee in 0xpptteeee
-
static constexpr const ResourceId kPadding = 0xffffffffu;
-
static constexpr const EntryId kNoEntry = 0xffffu;
// magic number: all idmap files start with this
@@ -155,7 +149,7 @@
PackageId target_package_id_;
uint16_t type_count_;
- friend Idmap;
+ friend IdmapData;
DISALLOW_COPY_AND_ASSIGN(Header);
};
@@ -194,12 +188,15 @@
uint16_t entry_offset_;
std::vector<EntryId> entries_;
- friend Idmap;
+ friend IdmapData;
DISALLOW_COPY_AND_ASSIGN(TypeEntry);
};
static std::unique_ptr<const IdmapData> FromBinaryStream(std::istream& stream);
+ static Result<std::unique_ptr<const IdmapData>> FromResourceMapping(
+ const ResourceMapping& resource_mapping);
+
inline const std::unique_ptr<const Header>& GetHeader() const {
return header_;
}
@@ -232,9 +229,7 @@
// file is used; change this in the next version of idmap to use a named
// package instead; also update FromApkAssets to take additional parameters:
// the target and overlay package names
- static Result<std::unique_ptr<const Idmap>> FromApkAssets(const std::string& target_apk_path,
- const ApkAssets& target_apk_assets,
- const std::string& overlay_apk_path,
+ static Result<std::unique_ptr<const Idmap>> FromApkAssets(const ApkAssets& target_apk_assets,
const ApkAssets& overlay_apk_assets,
const PolicyBitmask& fulfilled_policies,
bool enforce_overlayable);
diff --git a/cmds/idmap2/include/idmap2/ResourceMapping.h b/cmds/idmap2/include/idmap2/ResourceMapping.h
new file mode 100644
index 0000000..c3e1ef0
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/ResourceMapping.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_
+#define IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_
+
+#include <map>
+#include <memory>
+#include <utility>
+
+#include "androidfw/ApkAssets.h"
+#include "idmap2/Policies.h"
+#include "idmap2/ResourceUtils.h"
+#include "idmap2/Result.h"
+#include "idmap2/XmlParser.h"
+
+using android::idmap2::utils::OverlayManifestInfo;
+
+namespace android::idmap2 {
+
+struct TargetValue {
+ typedef uint8_t DataType;
+ typedef uint32_t DataValue;
+ DataType data_type;
+ DataValue data_value;
+};
+
+using TargetResourceMap = std::map<ResourceId, TargetValue>;
+using OverlayResourceMap = std::map<ResourceId, ResourceId>;
+
+class ResourceMapping {
+ public:
+ // Creates a ResourceMapping using the target and overlay APKs. Setting enforce_overlayable to
+ // `false` disables all overlayable and policy enforcement: this is intended for backwards
+ // compatibility pre-Q and unit tests.
+ static Result<ResourceMapping> FromApkAssets(const ApkAssets& target_apk_assets,
+ const ApkAssets& overlay_apk_assets,
+ const OverlayManifestInfo& overlay_info,
+ const PolicyBitmask& fulfilled_policies,
+ bool enforce_overlayable);
+
+ // Retrieves the mapping of target resource id to overlay value.
+ inline TargetResourceMap GetTargetToOverlayMap() const {
+ return target_map_;
+ }
+
+ // Retrieves the mapping of overlay resource id to target resource id. This allows a reference to
+ // an overlay resource to appear as a reference to its corresponding target resource at runtime.
+ OverlayResourceMap GetOverlayToTargetMap() const;
+
+ // Retrieves the build-time package id of the target package.
+ inline uint32_t GetTargetPackageId() const {
+ return target_package_id_;
+ }
+
+ // Retrieves the build-time package id of the overlay package.
+ inline uint32_t GetOverlayPackageId() const {
+ return overlay_package_id_;
+ }
+
+ // Retrieves the offset that was added to the index of inline string overlay values so the indices
+ // do not collide with the indices of the overlay resource table string pool.
+ inline uint32_t GetStringPoolOffset() const {
+ return string_pool_offset_;
+ }
+
+ // Retrieves the raw string pool data from the xml referenced in android:resourcesMap.
+ inline const std::pair<const uint8_t*, uint32_t> GetStringPoolData() const {
+ return std::make_pair(string_pool_data_.get(), string_pool_data_length_);
+ }
+
+ private:
+ ResourceMapping() = default;
+
+ // Apps a mapping of target resource id to the type and value of the data that overlays the
+ // target resource. The data_type is the runtime format of the data value (see
+ // Res_value::dataType). If rewrite_overlay_reference is `true` then references to an overlay
+ // resource should appear as a reference to its corresponding target resource at runtime.
+ Result<Unit> AddMapping(ResourceId target_resource, TargetValue::DataType data_type,
+ TargetValue::DataValue data_value, bool rewrite_overlay_reference);
+
+ // Removes the overlay value mapping for the target resource.
+ void RemoveMapping(ResourceId target_resource);
+
+ // Parses the mapping of target resources to overlay resources to generate a ResourceMapping.
+ static Result<ResourceMapping> CreateResourceMapping(const AssetManager2* target_am,
+ const LoadedPackage* target_package,
+ const LoadedPackage* overlay_package,
+ size_t string_pool_offset,
+ const XmlParser& overlay_parser);
+
+ // Generates a ResourceMapping that maps target resources to overlay resources by name. To overlay
+ // a target resource, a resource must exist in the overlay with the same type and entry name as
+ // the target resource.
+ static Result<ResourceMapping> CreateResourceMappingLegacy(const AssetManager2* target_am,
+ const AssetManager2* overlay_am,
+ const LoadedPackage* target_package,
+ const LoadedPackage* overlay_package);
+
+ // Removes resources that do not pass policy or overlayable checks of the target package.
+ void FilterOverlayableResources(const AssetManager2* target_am,
+ const LoadedPackage* target_package,
+ const LoadedPackage* overlay_package,
+ const OverlayManifestInfo& overlay_info,
+ const PolicyBitmask& fulfilled_policies);
+
+ TargetResourceMap target_map_;
+ std::multimap<ResourceId, ResourceId> overlay_map_;
+
+ uint32_t target_package_id_ = 0;
+ uint32_t overlay_package_id_ = 0;
+ uint32_t string_pool_offset_ = 0;
+ uint32_t string_pool_data_length_ = 0;
+ std::unique_ptr<uint8_t[]> string_pool_data_ = nullptr;
+};
+
+} // namespace android::idmap2
+
+#endif // IDMAP2_INCLUDE_IDMAP2_RESOURCEMAPPING_H_
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index 9a0c2ab..abc2df1 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -21,17 +21,28 @@
#include <string>
#include "androidfw/AssetManager2.h"
-#include "idmap2/Idmap.h"
#include "idmap2/Result.h"
#include "idmap2/ZipFile.h"
-namespace android::idmap2::utils {
+namespace android::idmap2 {
+
+// use typedefs to let the compiler warn us about implicit casts
+typedef uint32_t ResourceId; // 0xpptteeee
+typedef uint8_t PackageId; // pp in 0xpptteeee
+typedef uint8_t TypeId; // tt in 0xpptteeee
+typedef uint16_t EntryId; // eeee in 0xpptteeee
+
+#define EXTRACT_TYPE(resid) ((0x00ff0000 & (resid)) >> 16)
+#define EXTRACT_ENTRY(resid) (0x0000ffff & (resid))
+
+namespace utils {
struct OverlayManifestInfo {
std::string target_package; // NOLINT(misc-non-private-member-variables-in-classes)
std::string target_name; // NOLINT(misc-non-private-member-variables-in-classes)
std::string requiredSystemPropertyName; // NOLINT(misc-non-private-member-variables-in-classes)
std::string requiredSystemPropertyValue; // NOLINT(misc-non-private-member-variables-in-classes)
+ uint32_t resource_mapping; // NOLINT(misc-non-private-member-variables-in-classes)
bool is_static; // NOLINT(misc-non-private-member-variables-in-classes)
int priority = -1; // NOLINT(misc-non-private-member-variables-in-classes)
};
@@ -41,6 +52,8 @@
Result<std::string> ResToTypeEntryName(const AssetManager2& am, ResourceId resid);
-} // namespace android::idmap2::utils
+} // namespace utils
+
+} // namespace android::idmap2
#endif // IDMAP2_INCLUDE_IDMAP2_RESOURCEUTILS_H_
diff --git a/cmds/idmap2/include/idmap2/Xml.h b/cmds/idmap2/include/idmap2/Xml.h
deleted file mode 100644
index dd89dee..0000000
--- a/cmds/idmap2/include/idmap2/Xml.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef IDMAP2_INCLUDE_IDMAP2_XML_H_
-#define IDMAP2_INCLUDE_IDMAP2_XML_H_
-
-#include <map>
-#include <memory>
-#include <string>
-
-#include "android-base/macros.h"
-#include "androidfw/ResourceTypes.h"
-#include "utils/String16.h"
-
-namespace android::idmap2 {
-
-class Xml {
- public:
- static std::unique_ptr<const Xml> Create(const uint8_t* data, size_t size, bool copyData = false);
-
- std::unique_ptr<std::map<std::string, std::string>> FindTag(const std::string& name) const;
-
- ~Xml();
-
- private:
- Xml() {
- }
-
- mutable ResXMLTree xml_;
-
- DISALLOW_COPY_AND_ASSIGN(Xml);
-};
-
-} // namespace android::idmap2
-
-#endif // IDMAP2_INCLUDE_IDMAP2_XML_H_
diff --git a/cmds/idmap2/include/idmap2/XmlParser.h b/cmds/idmap2/include/idmap2/XmlParser.h
new file mode 100644
index 0000000..972a6d7
--- /dev/null
+++ b/cmds/idmap2/include/idmap2/XmlParser.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef IDMAP2_INCLUDE_IDMAP2_XMLPARSER_H_
+#define IDMAP2_INCLUDE_IDMAP2_XMLPARSER_H_
+
+#include <iostream>
+#include <map>
+#include <memory>
+#include <string>
+
+#include "Result.h"
+#include "android-base/macros.h"
+#include "androidfw/ResourceTypes.h"
+#include "utils/String16.h"
+
+namespace android::idmap2 {
+
+class XmlParser {
+ public:
+ using Event = ResXMLParser::event_code_t;
+ class iterator;
+
+ class Node {
+ public:
+ Event event() const;
+ std::string name() const;
+
+ Result<std::string> GetAttributeStringValue(const std::string& name) const;
+ Result<Res_value> GetAttributeValue(const std::string& name) const;
+
+ bool operator==(const Node& rhs) const;
+ bool operator!=(const Node& rhs) const;
+
+ private:
+ explicit Node(const ResXMLTree& tree);
+ Node(const ResXMLTree& tree, const ResXMLParser::ResXMLPosition& pos);
+
+ // Retrieves/Sets the position of the position of the xml parser in the xml tree.
+ ResXMLParser::ResXMLPosition get_position() const;
+ void set_position(const ResXMLParser::ResXMLPosition& pos);
+
+ // If `inner_child` is true, seek advances the parser to the first inner child of the current
+ // node. Otherwise, seek advances the parser to the following node. Returns false if there is
+ // no node to seek to.
+ bool Seek(bool inner_child);
+
+ ResXMLParser parser_;
+ friend iterator;
+ };
+
+ class iterator {
+ public:
+ iterator(const iterator& other) : iterator(other.tree_, other.iter_) {
+ }
+
+ inline iterator& operator=(const iterator& rhs) {
+ iter_.set_position(rhs.iter_.get_position());
+ return *this;
+ }
+
+ inline bool operator==(const iterator& rhs) const {
+ return iter_ == rhs.iter_;
+ }
+
+ inline bool operator!=(const iterator& rhs) const {
+ return !(*this == rhs);
+ }
+
+ inline iterator operator++() {
+ // Seek to the following xml node.
+ iter_.Seek(false /* inner_child */);
+ return *this;
+ }
+
+ iterator begin() const {
+ iterator child_it(*this);
+ // Seek to the first inner child of the current node.
+ child_it.iter_.Seek(true /* inner_child */);
+ return child_it;
+ }
+
+ iterator end() const {
+ iterator child_it = begin();
+ while (child_it.iter_.Seek(false /* inner_child */)) {
+ // Continue iterating until the end tag is found.
+ }
+
+ return child_it;
+ }
+
+ inline const Node operator*() {
+ return Node(tree_, iter_.get_position());
+ }
+
+ inline const Node* operator->() {
+ return &iter_;
+ }
+
+ private:
+ explicit iterator(const ResXMLTree& tree) : tree_(tree), iter_(Node(tree)) {
+ }
+ iterator(const ResXMLTree& tree, const Node& node)
+ : tree_(tree), iter_(Node(tree, node.get_position())) {
+ }
+
+ const ResXMLTree& tree_;
+ Node iter_;
+ friend XmlParser;
+ };
+
+ // Creates a new xml parser beginning at the first tag.
+ static Result<std::unique_ptr<const XmlParser>> Create(const void* data, size_t size,
+ bool copy_data = false);
+ ~XmlParser();
+
+ inline iterator tree_iterator() const {
+ return iterator(tree_);
+ }
+
+ inline const ResStringPool& get_strings() const {
+ return tree_.getStrings();
+ }
+
+ private:
+ XmlParser() = default;
+ mutable ResXMLTree tree_;
+
+ DISALLOW_COPY_AND_ASSIGN(XmlParser);
+};
+
+} // namespace android::idmap2
+
+#endif // IDMAP2_INCLUDE_IDMAP2_XMLPARSER_H_
diff --git a/cmds/idmap2/libidmap2/Idmap.cpp b/cmds/idmap2/libidmap2/Idmap.cpp
index 4649675..389ade5 100644
--- a/cmds/idmap2/libidmap2/Idmap.cpp
+++ b/cmds/idmap2/libidmap2/Idmap.cpp
@@ -30,6 +30,7 @@
#include "android-base/macros.h"
#include "android-base/stringprintf.h"
#include "androidfw/AssetManager2.h"
+#include "idmap2/ResourceMapping.h"
#include "idmap2/ResourceUtils.h"
#include "idmap2/Result.h"
#include "idmap2/SysTrace.h"
@@ -41,10 +42,6 @@
namespace {
-#define EXTRACT_TYPE(resid) ((0x00ff0000 & (resid)) >> 16)
-
-#define EXTRACT_ENTRY(resid) (0x0000ffff & (resid))
-
class MatchingResources {
public:
void Add(ResourceId target_resid, ResourceId overlay_resid) {
@@ -97,28 +94,6 @@
return true;
}
-ResourceId NameToResid(const AssetManager2& am, const std::string& name) {
- return am.GetResourceId(name);
-}
-
-// TODO(martenkongstad): scan for package name instead of assuming package at index 0
-//
-// idmap version 0x01 naively assumes that the package to use is always the first ResTable_package
-// in the resources.arsc blob. In most cases, there is only a single ResTable_package anyway, so
-// this assumption tends to work out. That said, the correct thing to do is to scan
-// resources.arsc for a package with a given name as read from the package manifest instead of
-// relying on a hard-coded index. This however requires storing the package name in the idmap
-// header, which in turn requires incrementing the idmap version. Because the initial version of
-// idmap2 is compatible with idmap, this will have to wait for now.
-const LoadedPackage* GetPackageAtIndex0(const LoadedArsc& loaded_arsc) {
- const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc.GetPackages();
- if (packages.empty()) {
- return nullptr;
- }
- int id = packages[0]->GetPackageId();
- return loaded_arsc.GetPackageById(id);
-}
-
Result<uint32_t> GetCrc(const ZipFile& zip) {
const Result<uint32_t> a = zip.Crc("resources.arsc");
const Result<uint32_t> b = zip.Crc("AndroidManifest.xml");
@@ -266,95 +241,57 @@
return {std::move(idmap)};
}
-std::string ConcatPolicies(const std::vector<std::string>& policies) {
- std::string message;
- for (const std::string& policy : policies) {
- if (!message.empty()) {
- message.append("|");
+Result<std::unique_ptr<const IdmapData>> IdmapData::FromResourceMapping(
+ const ResourceMapping& resource_mapping) {
+ if (resource_mapping.GetTargetToOverlayMap().empty()) {
+ return Error("no resources were overlaid");
+ }
+
+ MatchingResources matching_resources;
+ for (const auto mapping : resource_mapping.GetTargetToOverlayMap()) {
+ if (mapping.second.data_type != Res_value::TYPE_REFERENCE) {
+ // The idmap format must change to support non-references.
+ continue;
}
- message.append(policy);
+
+ matching_resources.Add(mapping.first, mapping.second.data_value);
}
- return message;
+ // encode idmap data
+ std::unique_ptr<IdmapData> data(new IdmapData());
+ const auto types_end = matching_resources.Map().cend();
+ for (auto ti = matching_resources.Map().cbegin(); ti != types_end; ++ti) {
+ auto ei = ti->second.cbegin();
+ std::unique_ptr<IdmapData::TypeEntry> type(new IdmapData::TypeEntry());
+ type->target_type_id_ = EXTRACT_TYPE(ei->first);
+ type->overlay_type_id_ = EXTRACT_TYPE(ei->second);
+ type->entry_offset_ = EXTRACT_ENTRY(ei->first);
+ EntryId last_target_entry = kNoEntry;
+ for (; ei != ti->second.cend(); ++ei) {
+ if (last_target_entry != kNoEntry) {
+ int count = EXTRACT_ENTRY(ei->first) - last_target_entry - 1;
+ type->entries_.insert(type->entries_.end(), count, kNoEntry);
+ }
+ type->entries_.push_back(EXTRACT_ENTRY(ei->second));
+ last_target_entry = EXTRACT_ENTRY(ei->first);
+ }
+ data->type_entries_.push_back(std::move(type));
+ }
+
+ std::unique_ptr<IdmapData::Header> data_header(new IdmapData::Header());
+ data_header->target_package_id_ = resource_mapping.GetTargetPackageId();
+ data_header->type_count_ = data->type_entries_.size();
+ data->header_ = std::move(data_header);
+ return {std::move(data)};
}
-Result<Unit> CheckOverlayable(const LoadedPackage& target_package,
- const utils::OverlayManifestInfo& overlay_info,
- const PolicyBitmask& fulfilled_policies, const ResourceId& resid) {
- static constexpr const PolicyBitmask sDefaultPolicies =
- PolicyFlags::POLICY_ODM_PARTITION | PolicyFlags::POLICY_OEM_PARTITION |
- PolicyFlags::POLICY_SYSTEM_PARTITION | PolicyFlags::POLICY_VENDOR_PARTITION |
- PolicyFlags::POLICY_PRODUCT_PARTITION | PolicyFlags::POLICY_SIGNATURE;
-
- // If the resource does not have an overlayable definition, allow the resource to be overlaid if
- // the overlay is preinstalled or signed with the same signature as the target.
- if (!target_package.DefinesOverlayable()) {
- return (sDefaultPolicies & fulfilled_policies) != 0
- ? Result<Unit>({})
- : Error(
- "overlay must be preinstalled or signed with the same signature as the "
- "target");
- }
-
- const OverlayableInfo* overlayable_info = target_package.GetOverlayableInfo(resid);
- if (overlayable_info == nullptr) {
- // Do not allow non-overlayable resources to be overlaid.
- return Error("resource has no overlayable declaration");
- }
-
- if (overlay_info.target_name != overlayable_info->name) {
- // If the overlay supplies a target overlayable name, the resource must belong to the
- // overlayable defined with the specified name to be overlaid.
- return Error("<overlay> android:targetName '%s' does not match overlayable name '%s'",
- overlay_info.target_name.c_str(), overlayable_info->name.c_str());
- }
-
- // Enforce policy restrictions if the resource is declared as overlayable.
- if ((overlayable_info->policy_flags & fulfilled_policies) == 0) {
- return Error("overlay with policies '%s' does not fulfill any overlayable policies '%s'",
- ConcatPolicies(BitmaskToPolicies(fulfilled_policies)).c_str(),
- ConcatPolicies(BitmaskToPolicies(overlayable_info->policy_flags)).c_str());
- }
-
- return Result<Unit>({});
-}
-
-Result<std::unique_ptr<const Idmap>> Idmap::FromApkAssets(const std::string& target_apk_path,
- const ApkAssets& target_apk_assets,
- const std::string& overlay_apk_path,
+Result<std::unique_ptr<const Idmap>> Idmap::FromApkAssets(const ApkAssets& target_apk_assets,
const ApkAssets& overlay_apk_assets,
const PolicyBitmask& fulfilled_policies,
bool enforce_overlayable) {
SYSTRACE << "Idmap::FromApkAssets";
- AssetManager2 target_asset_manager;
- if (!target_asset_manager.SetApkAssets({&target_apk_assets}, true, false)) {
- return Error("failed to create target asset manager");
- }
-
- AssetManager2 overlay_asset_manager;
- if (!overlay_asset_manager.SetApkAssets({&overlay_apk_assets}, true, false)) {
- return Error("failed to create overlay asset manager");
- }
-
- const LoadedArsc* target_arsc = target_apk_assets.GetLoadedArsc();
- if (target_arsc == nullptr) {
- return Error("failed to load target resources.arsc");
- }
-
- const LoadedArsc* overlay_arsc = overlay_apk_assets.GetLoadedArsc();
- if (overlay_arsc == nullptr) {
- return Error("failed to load overlay resources.arsc");
- }
-
- const LoadedPackage* target_pkg = GetPackageAtIndex0(*target_arsc);
- if (target_pkg == nullptr) {
- return Error("failed to load target package from resources.arsc");
- }
-
- const LoadedPackage* overlay_pkg = GetPackageAtIndex0(*overlay_arsc);
- if (overlay_pkg == nullptr) {
- return Error("failed to load overlay package from resources.arsc");
- }
+ const std::string& target_apk_path = target_apk_assets.GetPath();
+ const std::string& overlay_apk_path = overlay_apk_assets.GetPath();
const std::unique_ptr<const ZipFile> target_zip = ZipFile::Open(target_apk_path);
if (!target_zip) {
@@ -366,11 +303,6 @@
return Error("failed to open overlay as zip");
}
- auto overlay_info = utils::ExtractOverlayManifestInfo(overlay_apk_path);
- if (!overlay_info) {
- return overlay_info.GetError();
- }
-
std::unique_ptr<IdmapHeader> header(new IdmapHeader());
header->magic_ = kIdmapMagic;
header->version_ = kIdmapCurrentVersion;
@@ -395,7 +327,7 @@
memcpy(header->target_path_, target_apk_path.data(), target_apk_path.size());
if (overlay_apk_path.size() > sizeof(header->overlay_path_)) {
- return Error("overlay apk path \"%s\" longer than maximum size %zu", target_apk_path.c_str(),
+ return Error("overlay apk path \"%s\" longer than maximum size %zu", overlay_apk_path.c_str(),
sizeof(header->target_path_));
}
memset(header->overlay_path_, 0, sizeof(header->overlay_path_));
@@ -404,70 +336,24 @@
std::unique_ptr<Idmap> idmap(new Idmap());
idmap->header_ = std::move(header);
- // find the resources that exist in both packages
- MatchingResources matching_resources;
- const auto end = overlay_pkg->end();
- for (auto iter = overlay_pkg->begin(); iter != end; ++iter) {
- const ResourceId overlay_resid = *iter;
- Result<std::string> name = utils::ResToTypeEntryName(overlay_asset_manager, overlay_resid);
- if (!name) {
- continue;
- }
- // prepend "<package>:" to turn name into "<package>:<type>/<name>"
- const std::string full_name =
- base::StringPrintf("%s:%s", target_pkg->GetPackageName().c_str(), name->c_str());
- const ResourceId target_resid = NameToResid(target_asset_manager, full_name);
- if (target_resid == 0) {
- continue;
- }
-
- if (enforce_overlayable) {
- Result<Unit> success =
- CheckOverlayable(*target_pkg, *overlay_info, fulfilled_policies, target_resid);
- if (!success) {
- LOG(WARNING) << "overlay \"" << overlay_apk_path
- << "\" is not allowed to overlay resource \"" << full_name
- << "\": " << success.GetErrorMessage();
- continue;
- }
- }
-
- matching_resources.Add(target_resid, overlay_resid);
+ auto overlay_info = utils::ExtractOverlayManifestInfo(overlay_apk_path);
+ if (!overlay_info) {
+ return overlay_info.GetError();
}
- if (matching_resources.Map().empty()) {
- return Error("overlay \"%s\" does not successfully overlay any resource",
- overlay_apk_path.c_str());
+ auto resource_mapping =
+ ResourceMapping::FromApkAssets(target_apk_assets, overlay_apk_assets, *overlay_info,
+ fulfilled_policies, enforce_overlayable);
+ if (!resource_mapping) {
+ return resource_mapping.GetError();
}
- // encode idmap data
- std::unique_ptr<IdmapData> data(new IdmapData());
- const auto types_end = matching_resources.Map().cend();
- for (auto ti = matching_resources.Map().cbegin(); ti != types_end; ++ti) {
- auto ei = ti->second.cbegin();
- std::unique_ptr<IdmapData::TypeEntry> type(new IdmapData::TypeEntry());
- type->target_type_id_ = EXTRACT_TYPE(ei->first);
- type->overlay_type_id_ = EXTRACT_TYPE(ei->second);
- type->entry_offset_ = EXTRACT_ENTRY(ei->first);
- EntryId last_target_entry = kNoEntry;
- for (; ei != ti->second.cend(); ++ei) {
- if (last_target_entry != kNoEntry) {
- int count = EXTRACT_ENTRY(ei->first) - last_target_entry - 1;
- type->entries_.insert(type->entries_.end(), count, kNoEntry);
- }
- type->entries_.push_back(EXTRACT_ENTRY(ei->second));
- last_target_entry = EXTRACT_ENTRY(ei->first);
- }
- data->type_entries_.push_back(std::move(type));
+ auto idmap_data = IdmapData::FromResourceMapping(*resource_mapping);
+ if (!idmap_data) {
+ return idmap_data.GetError();
}
- std::unique_ptr<IdmapData::Header> data_header(new IdmapData::Header());
- data_header->target_package_id_ = target_pkg->GetPackageId();
- data_header->type_count_ = data->type_entries_.size();
- data->header_ = std::move(data_header);
-
- idmap->data_.push_back(std::move(data));
-
+ idmap->data_.push_back(std::move(*idmap_data));
return {std::move(idmap)};
}
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
new file mode 100644
index 0000000..95ae626
--- /dev/null
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "idmap2/ResourceMapping.h"
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "android-base/stringprintf.h"
+#include "idmap2/ResourceUtils.h"
+
+using android::base::StringPrintf;
+using android::idmap2::utils::ResToTypeEntryName;
+
+namespace android::idmap2 {
+
+namespace {
+
+#define EXTRACT_PACKAGE(resid) ((0xff000000 & (resid)) >> 24)
+
+std::string ConcatPolicies(const std::vector<std::string>& policies) {
+ std::string message;
+ for (const std::string& policy : policies) {
+ if (!message.empty()) {
+ message.append("|");
+ }
+ message.append(policy);
+ }
+
+ return message;
+}
+
+Result<Unit> CheckOverlayable(const LoadedPackage& target_package,
+ const OverlayManifestInfo& overlay_info,
+ const PolicyBitmask& fulfilled_policies,
+ const ResourceId& target_resource) {
+ static constexpr const PolicyBitmask sDefaultPolicies =
+ PolicyFlags::POLICY_ODM_PARTITION | PolicyFlags::POLICY_OEM_PARTITION |
+ PolicyFlags::POLICY_SYSTEM_PARTITION | PolicyFlags::POLICY_VENDOR_PARTITION |
+ PolicyFlags::POLICY_PRODUCT_PARTITION | PolicyFlags::POLICY_SIGNATURE;
+
+ // If the resource does not have an overlayable definition, allow the resource to be overlaid if
+ // the overlay is preinstalled or signed with the same signature as the target.
+ if (!target_package.DefinesOverlayable()) {
+ return (sDefaultPolicies & fulfilled_policies) != 0
+ ? Result<Unit>({})
+ : Error(
+ "overlay must be preinstalled or signed with the same signature as the "
+ "target");
+ }
+
+ const OverlayableInfo* overlayable_info = target_package.GetOverlayableInfo(target_resource);
+ if (overlayable_info == nullptr) {
+ // Do not allow non-overlayable resources to be overlaid.
+ return Error("target resource has no overlayable declaration");
+ }
+
+ if (overlay_info.target_name != overlayable_info->name) {
+ // If the overlay supplies a target overlayable name, the resource must belong to the
+ // overlayable defined with the specified name to be overlaid.
+ return Error(R"(<overlay> android:targetName "%s" does not match overlayable name "%s")",
+ overlay_info.target_name.c_str(), overlayable_info->name.c_str());
+ }
+
+ // Enforce policy restrictions if the resource is declared as overlayable.
+ if ((overlayable_info->policy_flags & fulfilled_policies) == 0) {
+ return Error(R"(overlay with policies "%s" does not fulfill any overlayable policies "%s")",
+ ConcatPolicies(BitmaskToPolicies(fulfilled_policies)).c_str(),
+ ConcatPolicies(BitmaskToPolicies(overlayable_info->policy_flags)).c_str());
+ }
+
+ return Result<Unit>({});
+}
+
+// TODO(martenkongstad): scan for package name instead of assuming package at index 0
+//
+// idmap version 0x01 naively assumes that the package to use is always the first ResTable_package
+// in the resources.arsc blob. In most cases, there is only a single ResTable_package anyway, so
+// this assumption tends to work out. That said, the correct thing to do is to scan
+// resources.arsc for a package with a given name as read from the package manifest instead of
+// relying on a hard-coded index. This however requires storing the package name in the idmap
+// header, which in turn requires incrementing the idmap version. Because the initial version of
+// idmap2 is compatible with idmap, this will have to wait for now.
+const LoadedPackage* GetPackageAtIndex0(const LoadedArsc& loaded_arsc) {
+ const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc.GetPackages();
+ if (packages.empty()) {
+ return nullptr;
+ }
+ int id = packages[0]->GetPackageId();
+ return loaded_arsc.GetPackageById(id);
+}
+
+Result<std::unique_ptr<Asset>> OpenNonAssetFromResource(const ResourceId& resource_id,
+ const AssetManager2& asset_manager) {
+ Res_value value{};
+ ResTable_config selected_config{};
+ uint32_t flags;
+ auto cookie =
+ asset_manager.GetResource(resource_id, /* may_be_bag */ false,
+ /* density_override */ 0U, &value, &selected_config, &flags);
+ if (cookie == kInvalidCookie) {
+ return Error("failed to find resource for id 0x%08x", resource_id);
+ }
+
+ if (value.dataType != Res_value::TYPE_STRING) {
+ return Error("resource for is 0x%08x is not a file", resource_id);
+ }
+
+ auto string_pool = asset_manager.GetStringPoolForCookie(cookie);
+ size_t len;
+ auto file_path16 = string_pool->stringAt(value.data, &len);
+ if (file_path16 == nullptr) {
+ return Error("failed to find string for index %d", value.data);
+ }
+
+ // Load the overlay resource mappings from the file specified using android:resourcesMap.
+ auto file_path = String8(String16(file_path16));
+ auto asset = asset_manager.OpenNonAsset(file_path.c_str(), Asset::AccessMode::ACCESS_BUFFER);
+ if (asset == nullptr) {
+ return Error("file \"%s\" not found", file_path.c_str());
+ }
+
+ return asset;
+}
+
+} // namespace
+
+Result<ResourceMapping> ResourceMapping::CreateResourceMapping(const AssetManager2* target_am,
+ const LoadedPackage* target_package,
+ const LoadedPackage* overlay_package,
+ size_t string_pool_offset,
+ const XmlParser& overlay_parser) {
+ ResourceMapping resource_mapping;
+ auto root_it = overlay_parser.tree_iterator();
+ if (root_it->event() != XmlParser::Event::START_TAG || root_it->name() != "overlay") {
+ return Error("root element is not <overlay> tag");
+ }
+
+ const uint8_t overlay_package_id = overlay_package->GetPackageId();
+ auto overlay_it_end = root_it.end();
+ for (auto overlay_it = root_it.begin(); overlay_it != overlay_it_end; ++overlay_it) {
+ if (overlay_it->event() == XmlParser::Event::BAD_DOCUMENT) {
+ return Error("failed to parse overlay xml document");
+ }
+
+ if (overlay_it->event() != XmlParser::Event::START_TAG) {
+ continue;
+ }
+
+ if (overlay_it->name() != "item") {
+ return Error("unexpected tag <%s> in <overlay>", overlay_it->name().c_str());
+ }
+
+ Result<std::string> target_resource = overlay_it->GetAttributeStringValue("target");
+ if (!target_resource) {
+ return Error(R"(<item> tag missing expected attribute "target")");
+ }
+
+ Result<android::Res_value> overlay_resource = overlay_it->GetAttributeValue("value");
+ if (!overlay_resource) {
+ return Error(R"(<item> tag missing expected attribute "value")");
+ }
+
+ ResourceId target_id =
+ target_am->GetResourceId(*target_resource, "", target_package->GetPackageName());
+ if (target_id == 0U) {
+ LOG(WARNING) << "failed to find resource \"" << *target_resource << "\" in target resources";
+ continue;
+ }
+
+ if (overlay_resource->dataType == Res_value::TYPE_STRING) {
+ overlay_resource->data += string_pool_offset;
+ }
+
+ // Only rewrite resources defined within the overlay package to their corresponding target
+ // resource ids at runtime.
+ bool rewrite_overlay_reference =
+ (overlay_resource->dataType == Res_value::TYPE_REFERENCE)
+ ? overlay_package_id == EXTRACT_PACKAGE(overlay_resource->data)
+ : false;
+
+ resource_mapping.AddMapping(target_id, overlay_resource->dataType, overlay_resource->data,
+ rewrite_overlay_reference);
+ }
+
+ return resource_mapping;
+}
+
+Result<ResourceMapping> ResourceMapping::CreateResourceMappingLegacy(
+ const AssetManager2* target_am, const AssetManager2* overlay_am,
+ const LoadedPackage* target_package, const LoadedPackage* overlay_package) {
+ ResourceMapping resource_mapping;
+ const auto end = overlay_package->end();
+ for (auto iter = overlay_package->begin(); iter != end; ++iter) {
+ const ResourceId overlay_resid = *iter;
+ Result<std::string> name = utils::ResToTypeEntryName(*overlay_am, overlay_resid);
+ if (!name) {
+ continue;
+ }
+
+ // Find the resource with the same type and entry name within the target package.
+ const std::string full_name =
+ base::StringPrintf("%s:%s", target_package->GetPackageName().c_str(), name->c_str());
+ const ResourceId target_resource = target_am->GetResourceId(full_name);
+ if (target_resource == 0U) {
+ continue;
+ }
+
+ resource_mapping.AddMapping(target_resource, Res_value::TYPE_REFERENCE, overlay_resid,
+ /* rewrite_overlay_reference */ true);
+ }
+
+ return resource_mapping;
+}
+
+void ResourceMapping::FilterOverlayableResources(const AssetManager2* target_am,
+ const LoadedPackage* target_package,
+ const LoadedPackage* overlay_package,
+ const OverlayManifestInfo& overlay_info,
+ const PolicyBitmask& fulfilled_policies) {
+ std::set<ResourceId> remove_ids;
+ for (const auto& target_map : target_map_) {
+ const ResourceId target_resid = target_map.first;
+ Result<Unit> success =
+ CheckOverlayable(*target_package, overlay_info, fulfilled_policies, target_resid);
+ if (success) {
+ continue;
+ }
+
+ // Attempting to overlay a resource that is not allowed to be overlaid is treated as a
+ // warning.
+ Result<std::string> name = utils::ResToTypeEntryName(*target_am, target_resid);
+ if (!name) {
+ name = StringPrintf("0x%08x", target_resid);
+ }
+
+ LOG(WARNING) << "overlay \"" << overlay_package->GetPackageName()
+ << "\" is not allowed to overlay resource \"" << *name
+ << "\" in target: " << success.GetErrorMessage();
+
+ remove_ids.insert(target_resid);
+ }
+
+ for (const ResourceId target_resid : remove_ids) {
+ RemoveMapping(target_resid);
+ }
+}
+
+Result<ResourceMapping> ResourceMapping::FromApkAssets(const ApkAssets& target_apk_assets,
+ const ApkAssets& overlay_apk_assets,
+ const OverlayManifestInfo& overlay_info,
+ const PolicyBitmask& fulfilled_policies,
+ bool enforce_overlayable) {
+ AssetManager2 target_asset_manager;
+ if (!target_asset_manager.SetApkAssets({&target_apk_assets}, true /* invalidate_caches */,
+ false /* filter_incompatible_configs*/)) {
+ return Error("failed to create target asset manager");
+ }
+
+ AssetManager2 overlay_asset_manager;
+ if (!overlay_asset_manager.SetApkAssets({&overlay_apk_assets}, true /* invalidate_caches */,
+ false /* filter_incompatible_configs */)) {
+ return Error("failed to create overlay asset manager");
+ }
+
+ const LoadedArsc* target_arsc = target_apk_assets.GetLoadedArsc();
+ if (target_arsc == nullptr) {
+ return Error("failed to load target resources.arsc");
+ }
+
+ const LoadedArsc* overlay_arsc = overlay_apk_assets.GetLoadedArsc();
+ if (overlay_arsc == nullptr) {
+ return Error("failed to load overlay resources.arsc");
+ }
+
+ const LoadedPackage* target_pkg = GetPackageAtIndex0(*target_arsc);
+ if (target_pkg == nullptr) {
+ return Error("failed to load target package from resources.arsc");
+ }
+
+ const LoadedPackage* overlay_pkg = GetPackageAtIndex0(*overlay_arsc);
+ if (overlay_pkg == nullptr) {
+ return Error("failed to load overlay package from resources.arsc");
+ }
+
+ size_t string_pool_data_length = 0U;
+ size_t string_pool_offset = 0U;
+ std::unique_ptr<uint8_t[]> string_pool_data;
+ Result<ResourceMapping> resource_mapping = {{}};
+ if (overlay_info.resource_mapping != 0U) {
+ // Load the overlay resource mappings from the file specified using android:resourcesMap.
+ auto asset = OpenNonAssetFromResource(overlay_info.resource_mapping, overlay_asset_manager);
+ if (!asset) {
+ return Error("failed opening xml for android:resourcesMap: %s",
+ asset.GetErrorMessage().c_str());
+ }
+
+ auto parser =
+ XmlParser::Create((*asset)->getBuffer(true /* wordAligned*/), (*asset)->getLength());
+ if (!parser) {
+ return Error("failed opening ResXMLTree");
+ }
+
+ // Copy the xml string pool data before the parse goes out of scope.
+ auto& string_pool = (*parser)->get_strings();
+ string_pool_data_length = string_pool.bytes();
+ string_pool_data.reset(new uint8_t[string_pool_data_length]);
+ memcpy(string_pool_data.get(), string_pool.data(), string_pool_data_length);
+
+ // Offset string indices by the size of the overlay resource table string pool.
+ string_pool_offset = overlay_arsc->GetStringPool()->size();
+
+ resource_mapping = CreateResourceMapping(&target_asset_manager, target_pkg, overlay_pkg,
+ string_pool_offset, *(*parser));
+ } else {
+ // If no file is specified using android:resourcesMap, it is assumed that the overlay only
+ // defines resources intended to override target resources of the same type and name.
+ resource_mapping = CreateResourceMappingLegacy(&target_asset_manager, &overlay_asset_manager,
+ target_pkg, overlay_pkg);
+ }
+
+ if (!resource_mapping) {
+ return resource_mapping.GetError();
+ }
+
+ if (enforce_overlayable) {
+ // Filter out resources the overlay is not allowed to override.
+ (*resource_mapping)
+ .FilterOverlayableResources(&target_asset_manager, target_pkg, overlay_pkg, overlay_info,
+ fulfilled_policies);
+ }
+
+ resource_mapping->target_package_id_ = target_pkg->GetPackageId();
+ resource_mapping->overlay_package_id_ = overlay_pkg->GetPackageId();
+ resource_mapping->string_pool_offset_ = string_pool_offset;
+ resource_mapping->string_pool_data_ = std::move(string_pool_data);
+ resource_mapping->string_pool_data_length_ = string_pool_data_length;
+ return std::move(*resource_mapping);
+}
+
+OverlayResourceMap ResourceMapping::GetOverlayToTargetMap() const {
+ // An overlay resource can override multiple target resources at once. Rewrite the overlay
+ // resource as the first target resource it overrides.
+ OverlayResourceMap map;
+ for (const auto& mappings : overlay_map_) {
+ map.insert(std::make_pair(mappings.first, mappings.second));
+ }
+ return map;
+}
+
+Result<Unit> ResourceMapping::AddMapping(ResourceId target_resource,
+ TargetValue::DataType data_type,
+ TargetValue::DataValue data_value,
+ bool rewrite_overlay_reference) {
+ if (target_map_.find(target_resource) != target_map_.end()) {
+ return Error(R"(target resource id "0x%08x" mapped to multiple values)", target_resource);
+ }
+
+ // TODO(141485591): Ensure that the overlay type is compatible with the target type. If the
+ // runtime types are not compatible, it could cause runtime crashes when the resource is resolved.
+
+ target_map_.insert(std::make_pair(target_resource, TargetValue{data_type, data_value}));
+
+ if (rewrite_overlay_reference && data_type == Res_value::TYPE_REFERENCE) {
+ overlay_map_.insert(std::make_pair(data_value, target_resource));
+ }
+
+ return Result<Unit>({});
+}
+
+void ResourceMapping::RemoveMapping(ResourceId target_resource) {
+ auto target_iter = target_map_.find(target_resource);
+ if (target_iter == target_map_.end()) {
+ return;
+ }
+
+ const TargetValue value = target_iter->second;
+ target_map_.erase(target_iter);
+
+ // Remove rewriting of overlay resource id to target resource id.
+ if (value.data_type != Res_value::TYPE_REFERENCE) {
+ return;
+ }
+
+ auto overlay_iter = overlay_map_.equal_range(value.data_value);
+ for (auto i = overlay_iter.first; i != overlay_iter.second; ++i) {
+ if (i->second == target_resource) {
+ overlay_map_.erase(i);
+ return;
+ }
+ }
+}
+
+} // namespace android::idmap2
diff --git a/cmds/idmap2/libidmap2/ResourceUtils.cpp b/cmds/idmap2/libidmap2/ResourceUtils.cpp
index dce83e3..9d32692 100644
--- a/cmds/idmap2/libidmap2/ResourceUtils.cpp
+++ b/cmds/idmap2/libidmap2/ResourceUtils.cpp
@@ -22,18 +22,18 @@
#include "androidfw/StringPiece.h"
#include "androidfw/Util.h"
#include "idmap2/Result.h"
-#include "idmap2/Xml.h"
+#include "idmap2/XmlParser.h"
#include "idmap2/ZipFile.h"
using android::StringPiece16;
using android::idmap2::Result;
-using android::idmap2::Xml;
+using android::idmap2::XmlParser;
using android::idmap2::ZipFile;
using android::util::Utf16ToUtf8;
namespace android::idmap2::utils {
-Result<std::string> ResToTypeEntryName(const AssetManager2& am, ResourceId resid) {
+Result<std::string> ResToTypeEntryName(const AssetManager2& am, uint32_t resid) {
AssetManager2::ResourceName name;
if (!am.GetResourceName(resid, &name)) {
return Error("no resource 0x%08x in asset manager", resid);
@@ -65,52 +65,72 @@
return Error("failed to uncompress AndroidManifest.xml from %s", path.c_str());
}
- std::unique_ptr<const Xml> xml = Xml::Create(entry->buf, entry->size);
+ Result<std::unique_ptr<const XmlParser>> xml = XmlParser::Create(entry->buf, entry->size);
if (!xml) {
return Error("failed to parse AndroidManifest.xml from %s", path.c_str());
}
+ auto manifest_it = (*xml)->tree_iterator();
+ if (manifest_it->event() != XmlParser::Event::START_TAG || manifest_it->name() != "manifest") {
+ return Error("root element tag is not <manifest> in AndroidManifest.xml of %s", path.c_str());
+ }
+
+ auto overlay_it = std::find_if(manifest_it.begin(), manifest_it.end(), [](const auto& it) {
+ return it.event() == XmlParser::Event::START_TAG && it.name() == "overlay";
+ });
+
OverlayManifestInfo info{};
- const auto tag = xml->FindTag("overlay");
- if (!tag) {
- if (assert_overlay) {
- return Error("<overlay> missing from AndroidManifest.xml of %s", path.c_str());
+ if (overlay_it == manifest_it.end()) {
+ if (!assert_overlay) {
+ return info;
}
- return info;
+ return Error("<overlay> missing from AndroidManifest.xml of %s", path.c_str());
}
- auto iter = tag->find("targetPackage");
- if (iter == tag->end()) {
- if (assert_overlay) {
- return Error("android:targetPackage missing from <overlay> of %s", path.c_str());
- }
+ if (auto result_str = overlay_it->GetAttributeStringValue("targetPackage")) {
+ info.target_package = *result_str;
} else {
- info.target_package = iter->second;
+ return Error("android:targetPackage missing from <overlay> of %s: %s", path.c_str(),
+ result_str.GetErrorMessage().c_str());
}
- iter = tag->find("targetName");
- if (iter != tag->end()) {
- info.target_name = iter->second;
+ if (auto result_str = overlay_it->GetAttributeStringValue("targetName")) {
+ info.target_name = *result_str;
}
- iter = tag->find("isStatic");
- if (iter != tag->end()) {
- info.is_static = std::stoul(iter->second) != 0U;
+ if (auto result_value = overlay_it->GetAttributeValue("resourcesMap")) {
+ if ((*result_value).dataType == Res_value::TYPE_REFERENCE) {
+ info.resource_mapping = (*result_value).data;
+ } else {
+ return Error("android:resourcesMap is not a reference in AndroidManifest.xml of %s",
+ path.c_str());
+ }
}
- iter = tag->find("priority");
- if (iter != tag->end()) {
- info.priority = std::stoi(iter->second);
+ if (auto result_value = overlay_it->GetAttributeValue("isStatic")) {
+ if ((*result_value).dataType >= Res_value::TYPE_FIRST_INT &&
+ (*result_value).dataType <= Res_value::TYPE_LAST_INT) {
+ info.is_static = (*result_value).data != 0U;
+ } else {
+ return Error("android:isStatic is not a boolean in AndroidManifest.xml of %s", path.c_str());
+ }
}
- iter = tag->find("requiredSystemPropertyName");
- if (iter != tag->end()) {
- info.requiredSystemPropertyName = iter->second;
+ if (auto result_value = overlay_it->GetAttributeValue("priority")) {
+ if ((*result_value).dataType >= Res_value::TYPE_FIRST_INT &&
+ (*result_value).dataType <= Res_value::TYPE_LAST_INT) {
+ info.priority = (*result_value).data;
+ } else {
+ return Error("android:priority is not an integer in AndroidManifest.xml of %s", path.c_str());
+ }
}
- iter = tag->find("requiredSystemPropertyValue");
- if (iter != tag->end()) {
- info.requiredSystemPropertyValue = iter->second;
+ if (auto result_str = overlay_it->GetAttributeStringValue("requiredSystemPropertyName")) {
+ info.requiredSystemPropertyName = *result_str;
+ }
+
+ if (auto result_str = overlay_it->GetAttributeStringValue("requiredSystemPropertyValue")) {
+ info.requiredSystemPropertyValue = *result_str;
}
return info;
diff --git a/cmds/idmap2/libidmap2/Xml.cpp b/cmds/idmap2/libidmap2/Xml.cpp
deleted file mode 100644
index 2645868..0000000
--- a/cmds/idmap2/libidmap2/Xml.cpp
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "idmap2/Xml.h"
-
-#include <map>
-#include <memory>
-#include <string>
-#include <utility>
-
-namespace android::idmap2 {
-
-std::unique_ptr<const Xml> Xml::Create(const uint8_t* data, size_t size, bool copyData) {
- std::unique_ptr<Xml> xml(new Xml());
- if (xml->xml_.setTo(data, size, copyData) != NO_ERROR) {
- return nullptr;
- }
- return xml;
-}
-
-std::unique_ptr<std::map<std::string, std::string>> Xml::FindTag(const std::string& name) const {
- const String16 tag_to_find(name.c_str(), name.size());
- xml_.restart();
- ResXMLParser::event_code_t type;
- do {
- type = xml_.next();
- if (type == ResXMLParser::START_TAG) {
- size_t len;
- const String16 tag(xml_.getElementName(&len));
- if (tag == tag_to_find) {
- std::unique_ptr<std::map<std::string, std::string>> map(
- new std::map<std::string, std::string>());
- for (size_t i = 0; i < xml_.getAttributeCount(); i++) {
- const String16 key16(xml_.getAttributeName(i, &len));
- std::string key = String8(key16).c_str();
-
- std::string value;
- switch (xml_.getAttributeDataType(i)) {
- case Res_value::TYPE_STRING: {
- const String16 value16(xml_.getAttributeStringValue(i, &len));
- value = String8(value16).c_str();
- } break;
- case Res_value::TYPE_INT_DEC:
- case Res_value::TYPE_INT_HEX:
- case Res_value::TYPE_INT_BOOLEAN: {
- Res_value resValue;
- xml_.getAttributeValue(i, &resValue);
- value = std::to_string(resValue.data);
- } break;
- default:
- return nullptr;
- }
-
- map->emplace(std::make_pair(key, value));
- }
- return map;
- }
- }
- } while (type != ResXMLParser::BAD_DOCUMENT && type != ResXMLParser::END_DOCUMENT);
- return nullptr;
-}
-
-Xml::~Xml() {
- xml_.uninit();
-}
-
-} // namespace android::idmap2
diff --git a/cmds/idmap2/libidmap2/XmlParser.cpp b/cmds/idmap2/libidmap2/XmlParser.cpp
new file mode 100644
index 0000000..526a560
--- /dev/null
+++ b/cmds/idmap2/libidmap2/XmlParser.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 "idmap2/XmlParser.h"
+
+#include <iostream>
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+
+namespace android::idmap2 {
+
+template <typename T>
+ResXMLParser::ResXMLPosition get_tree_position(const T& tree) {
+ ResXMLParser::ResXMLPosition pos{};
+ tree.getPosition(&pos);
+ return pos;
+}
+
+XmlParser::Node::Node(const ResXMLTree& tree) : Node(tree, get_tree_position(tree)) {
+}
+XmlParser::Node::Node(const ResXMLTree& tree, const ResXMLParser::ResXMLPosition& pos)
+ : parser_(tree) {
+ set_position(pos);
+}
+
+bool XmlParser::Node::operator==(const XmlParser::Node& rhs) const {
+ ResXMLParser::ResXMLPosition pos = get_position();
+ ResXMLParser::ResXMLPosition rhs_pos = rhs.get_position();
+ return pos.curExt == rhs_pos.curExt && pos.curNode == rhs_pos.curNode &&
+ pos.eventCode == rhs_pos.eventCode;
+}
+
+bool XmlParser::Node::operator!=(const XmlParser::Node& rhs) const {
+ return !(*this == rhs);
+}
+
+ResXMLParser::ResXMLPosition XmlParser::Node::get_position() const {
+ return get_tree_position(parser_);
+}
+
+void XmlParser::Node::set_position(const ResXMLParser::ResXMLPosition& pos) {
+ parser_.setPosition(pos);
+}
+
+bool XmlParser::Node::Seek(bool inner_child) {
+ if (parser_.getEventType() == XmlParser::Event::END_TAG) {
+ return false;
+ }
+
+ ssize_t depth = 0;
+ XmlParser::Event code;
+ while ((code = parser_.next()) != XmlParser::Event::BAD_DOCUMENT &&
+ code != XmlParser::Event::END_DOCUMENT) {
+ if (code == XmlParser::Event::START_TAG) {
+ if (++depth == (inner_child ? 1 : 0)) {
+ return true;
+ }
+ } else if (code == XmlParser::Event::END_TAG) {
+ if (--depth == (inner_child ? -1 : -2)) {
+ return false;
+ }
+ }
+ }
+
+ return false;
+}
+
+XmlParser::Event XmlParser::Node::event() const {
+ return parser_.getEventType();
+}
+
+std::string XmlParser::Node::name() const {
+ size_t len;
+ const String16 key16(parser_.getElementName(&len));
+ return String8(key16).c_str();
+}
+
+Result<std::string> XmlParser::Node::GetAttributeStringValue(const std::string& name) const {
+ auto value = GetAttributeValue(name);
+ if (!value) {
+ return value.GetError();
+ }
+
+ switch ((*value).dataType) {
+ case Res_value::TYPE_STRING: {
+ size_t len;
+ const String16 value16(parser_.getStrings().stringAt((*value).data, &len));
+ return std::string(String8(value16).c_str());
+ }
+ case Res_value::TYPE_INT_DEC:
+ case Res_value::TYPE_INT_HEX:
+ case Res_value::TYPE_INT_BOOLEAN: {
+ return std::to_string((*value).data);
+ }
+ default:
+ return Error(R"(Failed to convert attribute "%s" value to a string)", name.c_str());
+ }
+}
+
+Result<Res_value> XmlParser::Node::GetAttributeValue(const std::string& name) const {
+ size_t len;
+ for (size_t i = 0; i < parser_.getAttributeCount(); i++) {
+ const String16 key16(parser_.getAttributeName(i, &len));
+ std::string key = String8(key16).c_str();
+ if (key != name) {
+ continue;
+ }
+
+ Res_value res_value{};
+ if (parser_.getAttributeValue(i, &res_value) == BAD_TYPE) {
+ return Error(R"(Bad value for attribute "%s")", name.c_str());
+ }
+
+ return res_value;
+ }
+
+ return Error(R"(Failed to find attribute "%s")", name.c_str());
+}
+
+Result<std::unique_ptr<const XmlParser>> XmlParser::Create(const void* data, size_t size,
+ bool copy_data) {
+ auto parser = std::unique_ptr<const XmlParser>(new XmlParser());
+ if (parser->tree_.setTo(data, size, copy_data) != NO_ERROR) {
+ return Error("Malformed xml block");
+ }
+
+ // Find the beginning of the first tag.
+ XmlParser::Event event;
+ while ((event = parser->tree_.next()) != XmlParser::Event::BAD_DOCUMENT &&
+ event != XmlParser::Event::END_DOCUMENT && event != XmlParser::Event::START_TAG) {
+ }
+
+ if (event == XmlParser::Event::END_DOCUMENT) {
+ return Error("Root tag was not be found");
+ }
+
+ if (event == XmlParser::Event::BAD_DOCUMENT) {
+ return Error("Bad xml document");
+ }
+
+ return parser;
+}
+
+XmlParser::~XmlParser() {
+ tree_.uninit();
+}
+
+} // namespace android::idmap2
diff --git a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
index 9348ab7..43fdc9a 100644
--- a/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
+++ b/cmds/idmap2/tests/BinaryStreamVisitorTests.cpp
@@ -75,9 +75,8 @@
std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
ASSERT_THAT(overlay_apk, NotNull());
- const auto idmap =
- Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk,
- PolicyFlags::POLICY_PUBLIC, /* enforce_overlayable */ true);
+ const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::POLICY_PUBLIC,
+ /* enforce_overlayable */ true);
ASSERT_TRUE(idmap);
std::stringstream stream;
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index 0f47f1e..47e5b17 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -179,8 +179,7 @@
ASSERT_THAT(overlay_apk, NotNull());
auto result =
- Idmap::FromApkAssets(target_apk_path.to_string(), *target_apk, overlay_apk_path.to_string(),
- *overlay_apk, fulfilled_policies, enforce_overlayable);
+ Idmap::FromApkAssets(*target_apk, *overlay_apk, fulfilled_policies, enforce_overlayable);
*out_idmap = result ? std::move(*result) : nullptr;
}
@@ -195,7 +194,7 @@
ASSERT_EQ(idmap->GetHeader()->GetMagic(), 0x504d4449U);
ASSERT_EQ(idmap->GetHeader()->GetVersion(), 0x01U);
ASSERT_EQ(idmap->GetHeader()->GetTargetCrc(), 0x76a20829);
- ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0x8635c2ed);
+ ASSERT_EQ(idmap->GetHeader()->GetOverlayCrc(), 0xc054fb26);
ASSERT_EQ(idmap->GetHeader()->GetTargetPath().to_string(), target_apk_path);
ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), overlay_apk_path);
ASSERT_EQ(idmap->GetHeader()->GetOverlayPath(), overlay_apk_path);
@@ -480,9 +479,8 @@
std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
ASSERT_THAT(overlay_apk, NotNull());
- const auto result =
- Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk,
- PolicyFlags::POLICY_PUBLIC, /* enforce_overlayable */ true);
+ const auto result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::POLICY_PUBLIC,
+ /* enforce_overlayable */ true);
ASSERT_FALSE(result);
}
@@ -497,8 +495,7 @@
std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
ASSERT_THAT(overlay_apk, NotNull());
- auto result = Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk,
- PolicyFlags::POLICY_PUBLIC,
+ auto result = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::POLICY_PUBLIC,
/* enforce_overlayable */ true);
ASSERT_TRUE(result);
const auto idmap = std::move(*result);
diff --git a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
index c412504..1d34e42 100644
--- a/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/PrettyPrintVisitorTests.cpp
@@ -43,9 +43,8 @@
std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
ASSERT_THAT(overlay_apk, NotNull());
- const auto idmap =
- Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk,
- PolicyFlags::POLICY_PUBLIC, /* enforce_overlayable */ true);
+ const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::POLICY_PUBLIC,
+ /* enforce_overlayable */ true);
ASSERT_TRUE(idmap);
std::stringstream stream;
diff --git a/cmds/idmap2/tests/RawPrintVisitorTests.cpp b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
index 2695176..c243d74 100644
--- a/cmds/idmap2/tests/RawPrintVisitorTests.cpp
+++ b/cmds/idmap2/tests/RawPrintVisitorTests.cpp
@@ -38,9 +38,8 @@
std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
ASSERT_THAT(overlay_apk, NotNull());
- const auto idmap =
- Idmap::FromApkAssets(target_apk_path, *target_apk, overlay_apk_path, *overlay_apk,
- PolicyFlags::POLICY_PUBLIC, /* enforce_overlayable */ true);
+ const auto idmap = Idmap::FromApkAssets(*target_apk, *overlay_apk, PolicyFlags::POLICY_PUBLIC,
+ /* enforce_overlayable */ true);
ASSERT_TRUE(idmap);
std::stringstream stream;
@@ -50,7 +49,7 @@
ASSERT_NE(stream.str().find("00000000: 504d4449 magic\n"), std::string::npos);
ASSERT_NE(stream.str().find("00000004: 00000001 version\n"), std::string::npos);
ASSERT_NE(stream.str().find("00000008: 76a20829 target crc\n"), std::string::npos);
- ASSERT_NE(stream.str().find("0000000c: 8635c2ed overlay crc\n"), std::string::npos);
+ ASSERT_NE(stream.str().find("0000000c: c054fb26 overlay crc\n"), std::string::npos);
ASSERT_NE(stream.str().find("0000021c: 00000000 0x7f010000 -> 0x7f010000 integer/int1\n"),
std::string::npos);
}
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
new file mode 100644
index 0000000..1ef41de
--- /dev/null
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdio> // fclose
+#include <fstream>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "TestHelpers.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "idmap2/ResourceMapping.h"
+
+using android::idmap2::utils::ExtractOverlayManifestInfo;
+
+namespace android::idmap2 {
+
+#define ASSERT_RESULT(r) \
+ do { \
+ auto result = r; \
+ ASSERT_TRUE(result) << result.GetErrorMessage(); \
+ } while (0)
+
+Result<ResourceMapping> TestGetResourceMapping(const android::StringPiece& local_target_apk_path,
+ const android::StringPiece& local_overlay_apk_path,
+ const OverlayManifestInfo& overlay_info,
+ const PolicyBitmask& fulfilled_policies,
+ bool enforce_overlayable) {
+ const std::string target_apk_path(GetTestDataPath() + local_target_apk_path.data());
+ std::unique_ptr<const ApkAssets> target_apk = ApkAssets::Load(target_apk_path);
+ if (!target_apk) {
+ return Error(R"(Failed to load target apk "%s")", target_apk_path.data());
+ }
+
+ const std::string overlay_apk_path(GetTestDataPath() + local_overlay_apk_path.data());
+ std::unique_ptr<const ApkAssets> overlay_apk = ApkAssets::Load(overlay_apk_path);
+ if (!overlay_apk) {
+ return Error(R"(Failed to load overlay apk "%s")", overlay_apk_path.data());
+ }
+
+ return ResourceMapping::FromApkAssets(*target_apk, *overlay_apk, overlay_info, fulfilled_policies,
+ enforce_overlayable);
+}
+
+Result<ResourceMapping> TestGetResourceMapping(const android::StringPiece& local_target_apk_path,
+ const android::StringPiece& local_overlay_apk_path,
+ const PolicyBitmask& fulfilled_policies,
+ bool enforce_overlayable) {
+ auto overlay_info = ExtractOverlayManifestInfo(GetTestDataPath() + local_overlay_apk_path.data());
+ if (!overlay_info) {
+ return overlay_info.GetError();
+ }
+ return TestGetResourceMapping(local_target_apk_path, local_overlay_apk_path, *overlay_info,
+ fulfilled_policies, enforce_overlayable);
+}
+
+Result<Unit> MappingExists(const ResourceMapping& mapping, const ResourceId& target_resource,
+ const uint8_t type, const uint32_t value, bool rewrite) {
+ auto target_map = mapping.GetTargetToOverlayMap();
+ auto entry_map = target_map.find(target_resource);
+ if (entry_map == target_map.end()) {
+ return Error("Failed to find mapping for target resource");
+ }
+
+ if (entry_map->second.data_type != type) {
+ return Error(R"(Expected type: "0x%02x" Actual type: "0x%02x")", type,
+ entry_map->second.data_type);
+ }
+
+ if (entry_map->second.data_value != value) {
+ return Error(R"(Expected value: "0x%08x" Actual value: "0x%08x")", type,
+ entry_map->second.data_value);
+ }
+
+ auto overlay_map = mapping.GetOverlayToTargetMap();
+ auto overlay_iter = overlay_map.find(entry_map->second.data_value);
+ if ((overlay_iter != overlay_map.end()) != rewrite) {
+ return Error(R"(Expected rewriting: "%s")", rewrite ? "true" : "false");
+ }
+
+ return Result<Unit>({});
+}
+
+TEST(ResourceMappingTests, ResourcesFromApkAssetsLegacy) {
+ OverlayManifestInfo info{};
+ info.target_package = "test.target";
+ info.target_name = "TestResources";
+ info.resource_mapping = 0U; // no xml
+ auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info,
+ PolicyFlags::POLICY_PUBLIC,
+ /* enforce_overlayable */ false);
+
+ ASSERT_TRUE(resources) << resources.GetErrorMessage();
+ auto& res = *resources;
+ ASSERT_EQ(res.GetTargetToOverlayMap().size(), 4U);
+ ASSERT_RESULT(MappingExists(res, 0x7f010000, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010000,
+ true /* rewrite */)); // integer/int1
+ ASSERT_RESULT(MappingExists(res, 0x7f02000c, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f020000,
+ true /* rewrite */)); // string/str1
+ ASSERT_RESULT(MappingExists(res, 0x7f02000e, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f020001,
+ true /* rewrite */)); // string/str3
+ ASSERT_RESULT(MappingExists(res, 0x7f02000f, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f020002,
+ true /* rewrite */)); // string/str4
+}
+
+TEST(ResourceMappingTests, ResourcesFromApkAssetsNonMatchingNames) {
+ OverlayManifestInfo info{};
+ info.target_package = "test.target";
+ info.target_name = "TestResources";
+ info.resource_mapping = 0x7f030003; // xml/overlays_swap
+ auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info,
+ PolicyFlags::POLICY_PUBLIC,
+ /* enforce_overlayable */ false);
+
+ ASSERT_TRUE(resources) << resources.GetErrorMessage();
+ auto& res = *resources;
+ ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U);
+ ASSERT_RESULT(MappingExists(res, 0x7f02000c, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f020002,
+ true /* rewrite */)); // string/str1 -> string/str4
+ ASSERT_RESULT(MappingExists(res, 0x7f02000e, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f020000,
+ true /* rewrite */)); // string/str3 -> string/str1
+ ASSERT_RESULT(MappingExists(res, 0x7f02000f, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f020001,
+ true /* rewrite */)); // string/str4 -> string/str3
+}
+
+TEST(ResourceMappingTests, DoNotRewriteNonResourceMapping) {
+ OverlayManifestInfo info{};
+ info.target_package = "test.target";
+ info.target_name = "TestResources";
+ info.resource_mapping = 0x7f030001; // xml/overlays_different_packages
+ auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info,
+ PolicyFlags::POLICY_PUBLIC,
+ /* enforce_overlayable */ false);
+
+ ASSERT_TRUE(resources) << resources.GetErrorMessage();
+ auto& res = *resources;
+ ASSERT_EQ(res.GetTargetToOverlayMap().size(), 2U);
+ ASSERT_EQ(res.GetOverlayToTargetMap().size(), 1U);
+ ASSERT_RESULT(MappingExists(res, 0x7f02000c, 0x01 /* Res_value::TYPE_REFERENCE */, 0x0104000a,
+ false /* rewrite */)); // string/str1 -> android:string/ok
+ ASSERT_RESULT(MappingExists(res, 0x7f02000e, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f020001,
+ true /* rewrite */)); // string/str3 -> string/str4
+}
+
+TEST(ResourceMappingTests, InlineResources) {
+ OverlayManifestInfo info{};
+ info.target_package = "test.target";
+ info.target_name = "TestResources";
+ info.resource_mapping = 0x7f030002; // xml/overlays_inline
+ auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay.apk", info,
+ PolicyFlags::POLICY_PUBLIC,
+ /* enforce_overlayable */ false);
+
+ constexpr size_t overlay_string_pool_size = 8U;
+ ASSERT_TRUE(resources) << resources.GetErrorMessage();
+ auto& res = *resources;
+ ASSERT_EQ(res.GetTargetToOverlayMap().size(), 2U);
+ ASSERT_EQ(res.GetOverlayToTargetMap().size(), 0U);
+ ASSERT_RESULT(MappingExists(res, 0x7f02000c, 0x03 /* Res_value::TYPE_STRING */,
+ overlay_string_pool_size + 0U,
+ false /* rewrite */)); // string/str1 -> "Hello World"
+ ASSERT_RESULT(MappingExists(res, 0x7f010000, 0x10 /* Res_value::TYPE_INT_DEC */, 73U,
+ false /* rewrite */)); // string/str1 -> "Hello World"
+}
+
+TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublic) {
+ auto resources =
+ TestGetResourceMapping("/target/target.apk", "/system-overlay/system-overlay.apk",
+ PolicyFlags::POLICY_SYSTEM_PARTITION | PolicyFlags::POLICY_PUBLIC,
+ /* enforce_overlayable */ true);
+
+ ASSERT_TRUE(resources) << resources.GetErrorMessage();
+ auto& res = *resources;
+ ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U);
+ ASSERT_RESULT(MappingExists(res, 0x7f020008, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010000,
+ true /* rewrite */)); // string/policy_public
+ ASSERT_RESULT(MappingExists(res, 0x7f02000a, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010001,
+ true /* rewrite */)); // string/policy_system
+ ASSERT_RESULT(MappingExists(res, 0x7f02000b, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010002,
+ true /* rewrite */)); // string/policy_system_vendor
+}
+
+// Resources that are not declared as overlayable and resources that a protected by policies the
+// overlay does not fulfill must not map to overlay resources.
+TEST(ResourceMappingTests, CreateIdmapFromApkAssetsPolicySystemPublicInvalid) {
+ auto resources = TestGetResourceMapping(
+ "/target/target.apk", "/system-overlay-invalid/system-overlay-invalid.apk",
+ PolicyFlags::POLICY_SYSTEM_PARTITION | PolicyFlags::POLICY_PUBLIC,
+ /* enforce_overlayable */ true);
+
+ ASSERT_TRUE(resources) << resources.GetErrorMessage();
+ auto& res = *resources;
+ ASSERT_EQ(res.GetTargetToOverlayMap().size(), 3U);
+ ASSERT_RESULT(MappingExists(res, 0x7f020008, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010005,
+ true /* rewrite */)); // string/policy_public
+ ASSERT_RESULT(MappingExists(res, 0x7f02000a, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010007,
+ true /* rewrite */)); // string/policy_system
+ ASSERT_RESULT(MappingExists(res, 0x7f02000b, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010008,
+ true /* rewrite */)); // string/policy_system_vendor
+}
+
+// Resources that are not declared as overlayable and resources that a protected by policies the
+// overlay does not fulfilled can map to overlay resources when overlayable enforcement is turned
+// off.
+TEST(ResourceMappingTests, ResourcesFromApkAssetsPolicySystemPublicInvalidIgnoreOverlayable) {
+ auto resources = TestGetResourceMapping(
+ "/target/target.apk", "/system-overlay-invalid/system-overlay-invalid.apk",
+ PolicyFlags::POLICY_SYSTEM_PARTITION | PolicyFlags::POLICY_PUBLIC,
+ /* enforce_overlayable */ false);
+
+ ASSERT_TRUE(resources) << resources.GetErrorMessage();
+ auto& res = *resources;
+ ASSERT_EQ(res.GetTargetToOverlayMap().size(), 9U);
+ ASSERT_RESULT(MappingExists(res, 0x7f020003, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010000,
+ true /* rewrite */)); // string/not_overlayable
+ ASSERT_RESULT(MappingExists(res, 0x7f020004, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010001,
+ true /* rewrite */)); // string/other
+ ASSERT_RESULT(MappingExists(res, 0x7f020005, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010002,
+ true /* rewrite */)); // string/policy_odm
+ ASSERT_RESULT(MappingExists(res, 0x7f020006, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010003,
+ true /* rewrite */)); // string/policy_oem
+ ASSERT_RESULT(MappingExists(res, 0x7f020007, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010004,
+ true /* rewrite */)); // string/policy_product
+ ASSERT_RESULT(MappingExists(res, 0x7f020008, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010005,
+ true /* rewrite */)); // string/policy_public
+ ASSERT_RESULT(MappingExists(res, 0x7f020009, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010006,
+ true /* rewrite */)); // string/policy_signature
+ ASSERT_RESULT(MappingExists(res, 0x7f02000a, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010007,
+ true /* rewrite */)); // string/policy_system
+ ASSERT_RESULT(MappingExists(res, 0x7f02000b, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010008,
+ true /* rewrite */)); // string/policy_system_vendor
+}
+
+// Overlays that do not target an <overlayable> tag can overlay resources defined within any
+// <overlayable> tag.
+TEST(ResourceMappingTests, ResourcesFromApkAssetsNoDefinedOverlayableAndNoTargetName) {
+ auto resources = TestGetResourceMapping("/target/target.apk", "/overlay/overlay-no-name.apk",
+ PolicyFlags::POLICY_PUBLIC,
+ /* enforce_overlayable */ false);
+
+ ASSERT_TRUE(resources) << resources.GetErrorMessage();
+ auto& res = *resources;
+ ASSERT_EQ(res.GetTargetToOverlayMap().size(), 4U);
+ ASSERT_RESULT(MappingExists(res, 0x7f010000, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010000,
+ true /* rewrite */)); // integer/int1
+ ASSERT_RESULT(MappingExists(res, 0x7f02000c, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f020000,
+ true /* rewrite */)); // string/str1
+ ASSERT_RESULT(MappingExists(res, 0x7f02000e, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f020001,
+ true /* rewrite */)); // string/str3
+ ASSERT_RESULT(MappingExists(res, 0x7f02000f, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f020002,
+ true /* rewrite */)); // string/str4
+}
+
+// Overlays that are neither pre-installed nor signed with the same signature as the target cannot
+// overlay packages that have not defined overlayable resources.
+TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPoliciesPublicFail) {
+ auto resources =
+ TestGetResourceMapping("/target/target-no-overlayable.apk", "/overlay/overlay-no-name.apk",
+ PolicyFlags::POLICY_PUBLIC,
+ /* enforce_overlayable */ true);
+
+ ASSERT_TRUE(resources) << resources.GetErrorMessage();
+ ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 0U);
+}
+
+// Overlays that are pre-installed or are signed with the same signature as the target can overlay
+// packages that have not defined overlayable resources.
+TEST(ResourceMappingTests, ResourcesFromApkAssetsDefaultPolicies) {
+ auto CheckEntries = [&](const PolicyBitmask& fulfilled_policies) -> void {
+ auto resources = TestGetResourceMapping("/target/target-no-overlayable.apk",
+ "/system-overlay-invalid/system-overlay-invalid.apk",
+ fulfilled_policies,
+ /* enforce_overlayable */ true);
+
+ ASSERT_TRUE(resources) << resources.GetErrorMessage();
+ auto& res = *resources;
+ ASSERT_EQ(resources->GetTargetToOverlayMap().size(), 9U);
+ ASSERT_RESULT(MappingExists(res, 0x7f020003, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010000,
+ true /* rewrite */)); // string/not_overlayable
+ ASSERT_RESULT(MappingExists(res, 0x7f020004, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010001,
+ true /* rewrite */)); // string/other
+ ASSERT_RESULT(MappingExists(res, 0x7f020005, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010002,
+ true /* rewrite */)); // string/policy_odm
+ ASSERT_RESULT(MappingExists(res, 0x7f020006, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010003,
+ true /* rewrite */)); // string/policy_oem
+ ASSERT_RESULT(MappingExists(res, 0x7f020007, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010004,
+ true /* rewrite */)); // string/policy_product
+ ASSERT_RESULT(MappingExists(res, 0x7f020008, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010005,
+ true /* rewrite */)); // string/policy_public
+ ASSERT_RESULT(MappingExists(res, 0x7f020009, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010006,
+ true /* rewrite */)); // string/policy_signature
+ ASSERT_RESULT(MappingExists(res, 0x7f02000a, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010007,
+ true /* rewrite */)); // string/policy_system
+ ASSERT_RESULT(MappingExists(res, 0x7f02000b, 0x01 /* Res_value::TYPE_REFERENCE */, 0x7f010008,
+ true /* rewrite */)); // string/policy_system_vendor
+ };
+
+ CheckEntries(PolicyFlags::POLICY_SIGNATURE);
+ CheckEntries(PolicyFlags::POLICY_PRODUCT_PARTITION);
+ CheckEntries(PolicyFlags::POLICY_SYSTEM_PARTITION);
+ CheckEntries(PolicyFlags::POLICY_VENDOR_PARTITION);
+ CheckEntries(PolicyFlags::POLICY_ODM_PARTITION);
+ CheckEntries(PolicyFlags::POLICY_OEM_PARTITION);
+}
+
+} // namespace android::idmap2
diff --git a/cmds/idmap2/tests/XmlParserTests.cpp b/cmds/idmap2/tests/XmlParserTests.cpp
new file mode 100644
index 0000000..1a7eaca
--- /dev/null
+++ b/cmds/idmap2/tests/XmlParserTests.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cstdio> // fclose
+#include <memory>
+#include <string>
+
+#include "TestHelpers.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "idmap2/XmlParser.h"
+#include "idmap2/ZipFile.h"
+
+namespace android::idmap2 {
+
+Result<std::unique_ptr<const XmlParser>> CreateTestParser(const std::string& test_file) {
+ auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
+ if (zip == nullptr) {
+ return Error("Failed to open zip file");
+ }
+
+ auto data = zip->Uncompress(test_file);
+ if (data == nullptr) {
+ return Error("Failed to open xml file");
+ }
+
+ return XmlParser::Create(data->buf, data->size, /* copy_data */ true);
+}
+
+TEST(XmlParserTests, Create) {
+ auto xml = CreateTestParser("AndroidManifest.xml");
+ ASSERT_TRUE(xml) << xml.GetErrorMessage();
+
+ fclose(stderr); // silence expected warnings from libandroidfw
+ const char* not_xml = "foo";
+ auto fail = XmlParser::Create(reinterpret_cast<const uint8_t*>(not_xml), strlen(not_xml));
+ ASSERT_FALSE(fail);
+}
+
+TEST(XmlParserTests, NextChild) {
+ auto xml = CreateTestParser("res/xml/test.xml");
+ ASSERT_TRUE(xml) << xml.GetErrorMessage();
+
+ auto root_iter = (*xml)->tree_iterator();
+ ASSERT_EQ(root_iter->event(), XmlParser::Event::START_TAG);
+ ASSERT_EQ(root_iter->name(), "a");
+
+ auto a_iter = root_iter.begin();
+ ASSERT_EQ(a_iter->event(), XmlParser::Event::START_TAG);
+ ASSERT_EQ(a_iter->name(), "b");
+
+ auto c_iter = a_iter.begin();
+ ASSERT_EQ(c_iter->event(), XmlParser::Event::START_TAG);
+ ASSERT_EQ(c_iter->name(), "c");
+
+ ++c_iter;
+ ASSERT_EQ(c_iter->event(), XmlParser::Event::END_TAG);
+ ASSERT_EQ(c_iter, a_iter.end());
+
+ ++a_iter;
+ ASSERT_EQ(a_iter->event(), XmlParser::Event::START_TAG);
+ ASSERT_EQ(a_iter->name(), "d");
+
+ // Skip the <e> tag.
+ ++a_iter;
+ ASSERT_EQ(a_iter->event(), XmlParser::Event::END_TAG);
+ ASSERT_EQ(a_iter, root_iter.end());
+}
+
+TEST(XmlParserTests, AttributeValues) {
+ auto xml = CreateTestParser("res/xml/test.xml");
+ ASSERT_TRUE(xml) << xml.GetErrorMessage();
+
+ // Start at the <a> tag.
+ auto root_iter = (*xml)->tree_iterator();
+
+ // Start at the <b> tag.
+ auto a_iter = root_iter.begin();
+ auto attribute_str = a_iter->GetAttributeStringValue("type_string");
+ ASSERT_TRUE(attribute_str);
+ ASSERT_EQ(*attribute_str, "fortytwo");
+
+ auto attribute_value = a_iter->GetAttributeValue("type_int_dec");
+ ASSERT_TRUE(attribute_value);
+ ASSERT_EQ(attribute_value->data, 42);
+
+ attribute_value = a_iter->GetAttributeValue("type_int_hex");
+ ASSERT_TRUE(attribute_value);
+ ASSERT_EQ(attribute_value->data, 42);
+
+ attribute_value = a_iter->GetAttributeValue("type_int_boolean");
+ ASSERT_TRUE(attribute_value);
+ ASSERT_EQ(attribute_value->data, 0xffffffff);
+}
+
+TEST(XmlParserTests, IteratorEquality) {
+ auto xml = CreateTestParser("res/xml/test.xml");
+ ASSERT_TRUE(xml) << xml.GetErrorMessage();
+
+ // Start at the <a> tag.
+ auto root_iter_1 = (*xml)->tree_iterator();
+ auto root_iter_2 = (*xml)->tree_iterator();
+ ASSERT_EQ(root_iter_1, root_iter_2);
+ ASSERT_EQ(*root_iter_1, *root_iter_2);
+
+ // Start at the <b> tag.
+ auto a_iter_1 = root_iter_1.begin();
+ auto a_iter_2 = root_iter_2.begin();
+ ASSERT_NE(a_iter_1, root_iter_1.end());
+ ASSERT_NE(a_iter_2, root_iter_2.end());
+ ASSERT_EQ(a_iter_1, a_iter_2);
+ ASSERT_EQ(*a_iter_1, *a_iter_2);
+
+ // Move to the <d> tag.
+ ++a_iter_1;
+ ++a_iter_2;
+ ASSERT_NE(a_iter_1, root_iter_1.end());
+ ASSERT_NE(a_iter_2, root_iter_2.end());
+ ASSERT_EQ(a_iter_1, a_iter_2);
+ ASSERT_EQ(*a_iter_1, *a_iter_2);
+
+ // Move to the end of the <a> tag.
+ ++a_iter_1;
+ ++a_iter_2;
+ ASSERT_EQ(a_iter_1, root_iter_1.end());
+ ASSERT_EQ(a_iter_2, root_iter_2.end());
+ ASSERT_EQ(a_iter_1, a_iter_2);
+ ASSERT_EQ(*a_iter_1, *a_iter_2);
+}
+
+TEST(XmlParserTests, Backtracking) {
+ auto xml = CreateTestParser("res/xml/test.xml");
+ ASSERT_TRUE(xml) << xml.GetErrorMessage();
+
+ // Start at the <a> tag.
+ auto root_iter_1 = (*xml)->tree_iterator();
+
+ // Start at the <b> tag.
+ auto a_iter_1 = root_iter_1.begin();
+
+ // Start a second iterator at the <a> tag.
+ auto root_iter_2 = root_iter_1;
+ ASSERT_EQ(root_iter_1, root_iter_2);
+ ASSERT_EQ(*root_iter_1, *root_iter_2);
+
+ // Move the first iterator to the end of the <a> tag.
+ auto root_iter_end_1 = root_iter_1.end();
+ ++root_iter_1;
+ ASSERT_NE(root_iter_1, root_iter_2);
+ ASSERT_NE(*root_iter_1, *root_iter_2);
+
+ // Move to the <d> tag.
+ ++a_iter_1;
+ ASSERT_NE(a_iter_1, root_iter_end_1);
+
+ // Move to the end of the <a> tag.
+ ++a_iter_1;
+ ASSERT_EQ(a_iter_1, root_iter_end_1);
+}
+
+} // namespace android::idmap2
diff --git a/cmds/idmap2/tests/XmlTests.cpp b/cmds/idmap2/tests/XmlTests.cpp
deleted file mode 100644
index df63211..0000000
--- a/cmds/idmap2/tests/XmlTests.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <cstdio> // fclose
-
-#include "TestHelpers.h"
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
-#include "idmap2/Xml.h"
-#include "idmap2/ZipFile.h"
-
-using ::testing::IsNull;
-using ::testing::NotNull;
-
-namespace android::idmap2 {
-
-TEST(XmlTests, Create) {
- auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
- ASSERT_THAT(zip, NotNull());
-
- auto data = zip->Uncompress("AndroidManifest.xml");
- ASSERT_THAT(data, NotNull());
-
- auto xml = Xml::Create(data->buf, data->size);
- ASSERT_THAT(xml, NotNull());
-
- fclose(stderr); // silence expected warnings from libandroidfw
- const char* not_xml = "foo";
- auto fail = Xml::Create(reinterpret_cast<const uint8_t*>(not_xml), strlen(not_xml));
- ASSERT_THAT(fail, IsNull());
-}
-
-TEST(XmlTests, FindTag) {
- auto zip = ZipFile::Open(GetTestDataPath() + "/target/target.apk");
- ASSERT_THAT(zip, NotNull());
-
- auto data = zip->Uncompress("res/xml/test.xml");
- ASSERT_THAT(data, NotNull());
-
- auto xml = Xml::Create(data->buf, data->size);
- ASSERT_THAT(xml, NotNull());
-
- auto attrs = xml->FindTag("c");
- ASSERT_THAT(attrs, NotNull());
- ASSERT_EQ(attrs->size(), 4U);
- ASSERT_EQ(attrs->at("type_string"), "fortytwo");
- ASSERT_EQ(std::stoi(attrs->at("type_int_dec")), 42);
- ASSERT_EQ(std::stoi(attrs->at("type_int_hex")), 42);
- ASSERT_NE(std::stoul(attrs->at("type_int_boolean")), 0U);
-
- auto fail = xml->FindTag("does-not-exist");
- ASSERT_THAT(fail, IsNull());
-}
-
-} // namespace android::idmap2
diff --git a/cmds/idmap2/tests/data/overlay/AndroidManifest.xml b/cmds/idmap2/tests/data/overlay/AndroidManifest.xml
index 619bb6c..cf3691c 100644
--- a/cmds/idmap2/tests/data/overlay/AndroidManifest.xml
+++ b/cmds/idmap2/tests/data/overlay/AndroidManifest.xml
@@ -16,8 +16,11 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="test.overlay">
+
<application android:hasCode="false"/>
+
<overlay
android:targetPackage="test.target"
- android:targetName="TestResources"/>
+ android:targetName="TestResources"
+ android:resourcesMap="@xml/overlays"/>
</manifest>
diff --git a/cmds/idmap2/tests/data/overlay/build b/cmds/idmap2/tests/data/overlay/build
index 68b9f50..b921b0d 100755
--- a/cmds/idmap2/tests/data/overlay/build
+++ b/cmds/idmap2/tests/data/overlay/build
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-FRAMEWORK_RES_APK=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/public/android.jar
+FRAMEWORK_RES_APK=${ANDROID_PRODUCT_OUT}/system/framework/framework-res.apk
aapt2 compile --dir res -o compiled.flata
diff --git a/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk b/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk
index 18ee43d..7c25985 100644
--- a/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk
+++ b/cmds/idmap2/tests/data/overlay/overlay-no-name-static.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay-no-name.apk b/cmds/idmap2/tests/data/overlay/overlay-no-name.apk
index 6425190..c75f3e1 100644
--- a/cmds/idmap2/tests/data/overlay/overlay-no-name.apk
+++ b/cmds/idmap2/tests/data/overlay/overlay-no-name.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-1.apk b/cmds/idmap2/tests/data/overlay/overlay-static-1.apk
index 642ab90..5b8a6e4 100644
--- a/cmds/idmap2/tests/data/overlay/overlay-static-1.apk
+++ b/cmds/idmap2/tests/data/overlay/overlay-static-1.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay-static-2.apk b/cmds/idmap2/tests/data/overlay/overlay-static-2.apk
index 2ec5602..698a1fd 100644
--- a/cmds/idmap2/tests/data/overlay/overlay-static-2.apk
+++ b/cmds/idmap2/tests/data/overlay/overlay-static-2.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/overlay.apk b/cmds/idmap2/tests/data/overlay/overlay.apk
index 5842da4..1db303f 100644
--- a/cmds/idmap2/tests/data/overlay/overlay.apk
+++ b/cmds/idmap2/tests/data/overlay/overlay.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/overlay/res/xml/overlays.xml b/cmds/idmap2/tests/data/overlay/res/xml/overlays.xml
new file mode 100644
index 0000000..edd33f7
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/res/xml/overlays.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<overlay>
+ <item target="string/str1" value="@string/str1"/>
+ <item target="string/str3" value="@string/str3" />
+ <item target="string/str4" value="@string/str4" />
+ <item target="integer/int1" value="@integer/int1" />
+ <item target="integer/not_in_target" value="@integer/not_in_target" />
+</overlay>
+
diff --git a/cmds/idmap2/tests/data/overlay/res/xml/overlays_different_package.xml b/cmds/idmap2/tests/data/overlay/res/xml/overlays_different_package.xml
new file mode 100644
index 0000000..aa7fefa
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/res/xml/overlays_different_package.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<overlay>
+ <item target="string/str1" value="@android:string/ok"/>
+ <item target="string/str3" value="@string/str3" />
+</overlay>
+
diff --git a/cmds/idmap2/tests/data/overlay/res/xml/overlays_inline.xml b/cmds/idmap2/tests/data/overlay/res/xml/overlays_inline.xml
new file mode 100644
index 0000000..e12b823
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/res/xml/overlays_inline.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<overlay>
+ <item target="string/str1" value="Hello World"/>
+ <item target="integer/int1" value="73" />
+</overlay>
+
diff --git a/cmds/idmap2/tests/data/overlay/res/xml/overlays_swap.xml b/cmds/idmap2/tests/data/overlay/res/xml/overlays_swap.xml
new file mode 100644
index 0000000..5728e67
--- /dev/null
+++ b/cmds/idmap2/tests/data/overlay/res/xml/overlays_swap.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<overlay>
+ <item target="string/str1" value="@string/str4"/>
+ <item target="string/str3" value="@string/str1" />
+ <item target="string/str4" value="@string/str3" />
+ <item target="integer/int_not_in_target" value="@integer/int1" />
+</overlay>
diff --git a/cmds/idmap2/tests/data/target/res/xml/test.xml b/cmds/idmap2/tests/data/target/res/xml/test.xml
index 0fe21c6..56a3f7f 100644
--- a/cmds/idmap2/tests/data/target/res/xml/test.xml
+++ b/cmds/idmap2/tests/data/target/res/xml/test.xml
@@ -14,12 +14,15 @@
limitations under the License.
-->
<a>
- <b>
- <c
- type_string="fortytwo"
- type_int_dec="42"
- type_int_hex="0x2a"
- type_int_boolean="true"
- />
+ <b type_string="fortytwo"
+ type_int_dec="42"
+ type_int_hex="0x2a"
+ type_int_boolean="true">
+
+ <c />
</b>
-</a>
+
+ <d>
+ <e />
+ </d>
+</a>
\ No newline at end of file
diff --git a/cmds/idmap2/tests/data/target/target-no-overlayable.apk b/cmds/idmap2/tests/data/target/target-no-overlayable.apk
index 033305a..2eb7c47 100644
--- a/cmds/idmap2/tests/data/target/target-no-overlayable.apk
+++ b/cmds/idmap2/tests/data/target/target-no-overlayable.apk
Binary files differ
diff --git a/cmds/idmap2/tests/data/target/target.apk b/cmds/idmap2/tests/data/target/target.apk
index 9bcd6dc..251cf46 100644
--- a/cmds/idmap2/tests/data/target/target.apk
+++ b/cmds/idmap2/tests/data/target/target.apk
Binary files differ
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index cb27325..8af925a 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -230,6 +230,7 @@
"tests/e2e/Anomaly_duration_sum_e2e_test.cpp",
"tests/e2e/Attribution_e2e_test.cpp",
"tests/e2e/ConfigTtl_e2e_test.cpp",
+ "tests/e2e/CountMetric_e2e_test.cpp",
"tests/e2e/DurationMetric_e2e_test.cpp",
"tests/e2e/GaugeMetric_e2e_pull_test.cpp",
"tests/e2e/GaugeMetric_e2e_push_test.cpp",
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index ff7416c..6c3dff2 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -16,24 +16,26 @@
#define DEBUG false // STOPSHIP if true
#include "Log.h"
-#include "statslog.h"
+
+#include "StatsLogProcessor.h"
#include <android-base/file.h>
#include <dirent.h>
#include <frameworks/base/cmds/statsd/src/active_config_list.pb.h>
-#include "StatsLogProcessor.h"
+#include <log/log_event_list.h>
+#include <utils/Errors.h>
+#include <utils/SystemClock.h>
+
#include "android-base/stringprintf.h"
#include "external/StatsPullerManager.h"
#include "guardrail/StatsdStats.h"
#include "metrics/CountMetricProducer.h"
+#include "state/StateManager.h"
#include "stats_log_util.h"
#include "stats_util.h"
+#include "statslog.h"
#include "storage/StorageManager.h"
-#include <log/log_event_list.h>
-#include <utils/Errors.h>
-#include <utils/SystemClock.h>
-
using namespace android;
using android::base::StringPrintf;
using android::util::FIELD_COUNT_REPEATED;
@@ -218,6 +220,8 @@
onIsolatedUidChangedEventLocked(*event);
}
+ StateManager::getInstance().onLogEvent(*event);
+
if (mMetricsManagers.empty()) {
return;
}
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 3d002d2..8292a3a 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -262,6 +262,10 @@
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithSameDeactivation);
FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithTwoMetricsTwoDeactivations);
+ FRIEND_TEST(CountMetricE2eTest, TestWithSimpleState);
+ FRIEND_TEST(CountMetricE2eTest, TestWithMappedState);
+ FRIEND_TEST(CountMetricE2eTest, TestWithMultipleStates);
+
FRIEND_TEST(DurationMetricE2eTest, TestOneBucket);
FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets);
FRIEND_TEST(DurationMetricE2eTest, TestWithActivation);
diff --git a/cmds/statsd/src/anomaly/subscriber_util.cpp b/cmds/statsd/src/anomaly/subscriber_util.cpp
index e09d575..4c30c4c 100644
--- a/cmds/statsd/src/anomaly/subscriber_util.cpp
+++ b/cmds/statsd/src/anomaly/subscriber_util.cpp
@@ -40,7 +40,7 @@
for (const Subscription& subscription : subscriptions) {
if (subscription.probability_of_informing() < 1
- && ((float)rand() / RAND_MAX) >= subscription.probability_of_informing()) {
+ && ((float)rand() / (float)RAND_MAX) >= subscription.probability_of_informing()) {
// Note that due to float imprecision, 0.0 and 1.0 might not truly mean never/always.
// The config writer was advised to use -0.1 and 1.1 for never/always.
ALOGI("Fate decided that a subscriber would not be informed.");
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 43e33f5..5a76d1f 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -122,9 +122,10 @@
{.puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_ACTIVITY_INFO)}},
// system_elapsed_realtime
{android::util::SYSTEM_ELAPSED_REALTIME,
- {.pullTimeoutNs = NS_PER_SEC / 2,
- .coolDownNs = NS_PER_SEC,
- .puller = new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME)}},
+ {.coolDownNs = NS_PER_SEC,
+ .puller = new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME),
+ .pullTimeoutNs = NS_PER_SEC / 2,
+ }},
// system_uptime
{android::util::SYSTEM_UPTIME,
{.puller = new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}},
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 1d0d2fb..4a06387 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -18,13 +18,15 @@
#include "Log.h"
#include "CountMetricProducer.h"
-#include "guardrail/StatsdStats.h"
-#include "stats_util.h"
-#include "stats_log_util.h"
+#include <inttypes.h>
#include <limits.h>
#include <stdlib.h>
+#include "guardrail/StatsdStats.h"
+#include "stats_log_util.h"
+#include "stats_util.h"
+
using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_BOOL;
using android::util::FIELD_TYPE_FLOAT;
@@ -37,6 +39,7 @@
using std::string;
using std::unordered_map;
using std::vector;
+using std::shared_ptr;
namespace android {
namespace os {
@@ -64,11 +67,16 @@
const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5;
const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6;
-CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric& metric,
- const int conditionIndex,
- const sp<ConditionWizard>& wizard,
- const int64_t timeBaseNs, const int64_t startTimeNs)
- : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard) {
+CountMetricProducer::CountMetricProducer(
+ const ConfigKey& key, const CountMetric& metric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard, const int64_t timeBaseNs, const int64_t startTimeNs,
+
+ const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
+ const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+ const vector<int>& slicedStateAtoms,
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
+ : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard, eventActivationMap,
+ eventDeactivationMap, slicedStateAtoms, stateGroupMap) {
if (metric.has_bucket()) {
mBucketSizeNs =
TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000;
@@ -94,6 +102,8 @@
mConditionSliced = true;
}
+ // TODO(tsaichristine): b/142124705 handle metric state links
+
flushIfNeededLocked(startTimeNs);
// Adjust start for partial bucket
mCurrentBucketStartTimeNs = startTimeNs;
@@ -106,6 +116,12 @@
VLOG("~CountMetricProducer() called");
}
+void CountMetricProducer::onStateChanged(int atomId, const HashableDimensionKey& primaryKey,
+ int oldState, int newState) {
+ VLOG("CountMetric %lld onStateChanged State%d, key %s, %d -> %d", (long long)mMetricId, atomId,
+ primaryKey.toString().c_str(), oldState, newState);
+}
+
void CountMetricProducer::dumpStatesLocked(FILE* out, bool verbose) const {
if (mCurrentSlicedCounter == nullptr ||
mCurrentSlicedCounter->size() == 0) {
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index b4a910c..61e0892 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -17,15 +17,16 @@
#ifndef COUNT_METRIC_PRODUCER_H
#define COUNT_METRIC_PRODUCER_H
-#include <unordered_map>
-
#include <android/util/ProtoOutputStream.h>
#include <gtest/gtest_prod.h>
-#include "../anomaly/AnomalyTracker.h"
-#include "../condition/ConditionTracker.h"
-#include "../matchers/matcher_util.h"
+
+#include <unordered_map>
+
#include "MetricProducer.h"
+#include "anomaly/AnomalyTracker.h"
+#include "condition/ConditionTracker.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "matchers/matcher_util.h"
#include "stats_util.h"
namespace android {
@@ -40,12 +41,20 @@
class CountMetricProducer : public MetricProducer {
public:
- CountMetricProducer(const ConfigKey& key, const CountMetric& countMetric,
- const int conditionIndex, const sp<ConditionWizard>& wizard,
- const int64_t timeBaseNs, const int64_t startTimeNs);
+ CountMetricProducer(
+ const ConfigKey& key, const CountMetric& countMetric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard, const int64_t timeBaseNs, const int64_t startTimeNs,
+ const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
+ const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+ eventDeactivationMap = {},
+ const vector<int>& slicedStateAtoms = {},
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
virtual ~CountMetricProducer();
+ void onStateChanged(int atomId, const HashableDimensionKey& primaryKey, int oldState,
+ int newState) override;
+
protected:
void onMatchedLogEventInternalLocked(
const size_t matcherIndex, const MetricDimensionKey& eventKey,
@@ -102,6 +111,7 @@
FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgrade);
FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket);
FRIEND_TEST(CountMetricProducerTest, TestFirstBucket);
+ FRIEND_TEST(CountMetricProducerTest, TestOneWeekTimeUnit);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index d7b46d1..ab2a1c3 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -36,6 +36,7 @@
using std::string;
using std::unordered_map;
using std::vector;
+using std::shared_ptr;
namespace android {
namespace os {
@@ -62,14 +63,17 @@
const int FIELD_ID_START_BUCKET_ELAPSED_MILLIS = 5;
const int FIELD_ID_END_BUCKET_ELAPSED_MILLIS = 6;
-DurationMetricProducer::DurationMetricProducer(const ConfigKey& key, const DurationMetric& metric,
- const int conditionIndex, const size_t startIndex,
- const size_t stopIndex, const size_t stopAllIndex,
- const bool nesting,
- const sp<ConditionWizard>& wizard,
- const FieldMatcher& internalDimensions,
- const int64_t timeBaseNs, const int64_t startTimeNs)
- : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard),
+DurationMetricProducer::DurationMetricProducer(
+ const ConfigKey& key, const DurationMetric& metric, const int conditionIndex,
+ const size_t startIndex, const size_t stopIndex, const size_t stopAllIndex,
+ const bool nesting, const sp<ConditionWizard>& wizard,
+ const FieldMatcher& internalDimensions, const int64_t timeBaseNs, const int64_t startTimeNs,
+ const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
+ const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+ const vector<int>& slicedStateAtoms,
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
+ : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard, eventActivationMap,
+ eventDeactivationMap, slicedStateAtoms, stateGroupMap),
mAggregationType(metric.aggregation_type()),
mStartIndex(startIndex),
mStopIndex(stopIndex),
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 56c9fd6..7457d7f 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -38,11 +38,16 @@
class DurationMetricProducer : public MetricProducer {
public:
- DurationMetricProducer(const ConfigKey& key, const DurationMetric& durationMetric,
- const int conditionIndex, const size_t startIndex,
- const size_t stopIndex, const size_t stopAllIndex, const bool nesting,
- const sp<ConditionWizard>& wizard,
- const FieldMatcher& internalDimensions, const int64_t timeBaseNs, const int64_t startTimeNs);
+ DurationMetricProducer(
+ const ConfigKey& key, const DurationMetric& durationMetric, const int conditionIndex,
+ const size_t startIndex, const size_t stopIndex, const size_t stopAllIndex,
+ const bool nesting, const sp<ConditionWizard>& wizard,
+ const FieldMatcher& internalDimensions, const int64_t timeBaseNs,
+ const int64_t startTimeNs,
+ const unordered_map<int, shared_ptr<Activation>>& eventActivationMap = {},
+ const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap = {},
+ const vector<int>& slicedStateAtoms = {},
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
virtual ~DurationMetricProducer();
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index 96133bd..32eb077 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -36,6 +36,7 @@
using std::string;
using std::unordered_map;
using std::vector;
+using std::shared_ptr;
namespace android {
namespace os {
@@ -51,11 +52,15 @@
const int FIELD_ID_ELAPSED_TIMESTAMP_NANOS = 1;
const int FIELD_ID_ATOMS = 2;
-EventMetricProducer::EventMetricProducer(const ConfigKey& key, const EventMetric& metric,
- const int conditionIndex,
- const sp<ConditionWizard>& wizard,
- const int64_t startTimeNs)
- : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard) {
+EventMetricProducer::EventMetricProducer(
+ const ConfigKey& key, const EventMetric& metric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard, const int64_t startTimeNs,
+ const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
+ const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+ const vector<int>& slicedStateAtoms,
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
+ : MetricProducer(metric.id(), key, startTimeNs, conditionIndex, wizard, eventActivationMap,
+ eventDeactivationMap, slicedStateAtoms, stateGroupMap) {
if (metric.links().size() > 0) {
for (const auto& link : metric.links()) {
Metric2Condition mc;
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index 74e6bc8..dca37e8 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -33,9 +33,14 @@
class EventMetricProducer : public MetricProducer {
public:
- EventMetricProducer(const ConfigKey& key, const EventMetric& eventMetric,
- const int conditionIndex, const sp<ConditionWizard>& wizard,
- const int64_t startTimeNs);
+ EventMetricProducer(
+ const ConfigKey& key, const EventMetric& eventMetric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard, const int64_t startTimeNs,
+ const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
+ const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+ eventDeactivationMap = {},
+ const vector<int>& slicedStateAtoms = {},
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
virtual ~EventMetricProducer();
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index efd05dc..d0f88a8 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -72,8 +72,13 @@
const sp<ConditionWizard>& wizard, const int whatMatcherIndex,
const sp<EventMatcherWizard>& matcherWizard, const int pullTagId, const int triggerAtomId,
const int atomId, const int64_t timeBaseNs, const int64_t startTimeNs,
- const sp<StatsPullerManager>& pullerManager)
- : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard),
+ const sp<StatsPullerManager>& pullerManager,
+ const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
+ const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+ const vector<int>& slicedStateAtoms,
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
+ : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, wizard, eventActivationMap,
+ eventDeactivationMap, slicedStateAtoms, stateGroupMap),
mWhatMatcherIndex(whatMatcherIndex),
mEventMatcherWizard(matcherWizard),
mPullerManager(pullerManager),
@@ -133,8 +138,11 @@
mBucketSizeNs);
}
- // Adjust start for partial bucket
+ // Adjust start for partial first bucket and then pull if needed
mCurrentBucketStartTimeNs = startTimeNs;
+ if (mIsActive && mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
+ pullAndMatchEventsLocked(mCurrentBucketStartTimeNs);
+ }
VLOG("Gauge metric %lld created. bucket size %lld start_time: %lld sliced %d",
(long long)metric.id(), (long long)mBucketSizeNs, (long long)mTimeBaseNs,
@@ -295,11 +303,6 @@
}
}
-void GaugeMetricProducer::prepareFirstBucketLocked() {
- if (mIsActive && mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
- pullAndMatchEventsLocked(mCurrentBucketStartTimeNs);
- }
-}
void GaugeMetricProducer::pullAndMatchEventsLocked(const int64_t timestampNs) {
bool triggerPuller = false;
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index a612adf..640a02a 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -56,13 +56,17 @@
// producer always reports the guage at the earliest time of the bucket when the condition is met.
class GaugeMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
public:
- GaugeMetricProducer(const ConfigKey& key, const GaugeMetric& gaugeMetric,
- const int conditionIndex, const sp<ConditionWizard>& conditionWizard,
- const int whatMatcherIndex,
- const sp<EventMatcherWizard>& matcherWizard,
- const int pullTagId, const int triggerAtomId, const int atomId,
- const int64_t timeBaseNs, const int64_t startTimeNs,
- const sp<StatsPullerManager>& pullerManager);
+ GaugeMetricProducer(
+ const ConfigKey& key, const GaugeMetric& gaugeMetric, const int conditionIndex,
+ const sp<ConditionWizard>& conditionWizard, const int whatMatcherIndex,
+ const sp<EventMatcherWizard>& matcherWizard, const int pullTagId,
+ const int triggerAtomId, const int atomId, const int64_t timeBaseNs,
+ const int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager,
+ const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
+ const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+ eventDeactivationMap = {},
+ const vector<int>& slicedStateAtoms = {},
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
virtual ~GaugeMetricProducer();
@@ -125,8 +129,6 @@
void flushCurrentBucketLocked(const int64_t& eventTimeNs,
const int64_t& nextBucketStartTimeNs) override;
- void prepareFirstBucketLocked() override;
-
void pullAndMatchEventsLocked(const int64_t timestampNs);
const int mWhatMatcherIndex;
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index 1ab4fdf..2a700ef 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -40,6 +40,33 @@
const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_REMAINING_TTL_NANOS = 2;
const int FIELD_ID_ACTIVE_EVENT_ACTIVATION_STATE = 3;
+MetricProducer::MetricProducer(
+ const int64_t& metricId, const ConfigKey& key, const int64_t timeBaseNs,
+ const int conditionIndex, const sp<ConditionWizard>& wizard,
+ const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap,
+ const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+ eventDeactivationMap,
+ const vector<int>& slicedStateAtoms,
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
+ : mMetricId(metricId),
+ mConfigKey(key),
+ mTimeBaseNs(timeBaseNs),
+ mCurrentBucketStartTimeNs(timeBaseNs),
+ mCurrentBucketNum(0),
+ mCondition(initialCondition(conditionIndex)),
+ mConditionTrackerIndex(conditionIndex),
+ mConditionSliced(false),
+ mWizard(wizard),
+ mContainANYPositionInDimensionsInWhat(false),
+ mSliceByPositionALL(false),
+ mHasLinksToAllConditionDimensionsInTracker(false),
+ mEventActivationMap(eventActivationMap),
+ mEventDeactivationMap(eventDeactivationMap),
+ mIsActive(mEventActivationMap.empty()),
+ mSlicedStateAtoms(slicedStateAtoms),
+ mStateGroupMap(stateGroupMap) {
+}
+
void MetricProducer::onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) {
if (!mIsActive) {
return;
@@ -97,24 +124,6 @@
}
}
-void MetricProducer::addActivation(int activationTrackerIndex, const ActivationType& activationType,
- int64_t ttl_seconds, int deactivationTrackerIndex) {
- std::lock_guard<std::mutex> lock(mMutex);
- // When a metric producer does not depend on any activation, its mIsActive is true.
- // Therefore, if this is the 1st activation, mIsActive will turn to false. Otherwise it does not
- // change.
- if (mEventActivationMap.empty()) {
- mIsActive = false;
- }
- std::shared_ptr<Activation> activation =
- std::make_shared<Activation>(activationType, ttl_seconds * NS_PER_SEC);
- mEventActivationMap.emplace(activationTrackerIndex, activation);
- if (-1 != deactivationTrackerIndex) {
- auto& deactivationList = mEventDeactivationMap[deactivationTrackerIndex];
- deactivationList.push_back(activation);
- }
-}
-
void MetricProducer::activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs) {
auto it = mEventActivationMap.find(activationTrackerIndex);
if (it == mEventActivationMap.end()) {
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index fdbdc83..a72de22 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -17,19 +17,19 @@
#ifndef METRIC_PRODUCER_H
#define METRIC_PRODUCER_H
-#include <shared_mutex>
-
#include <frameworks/base/cmds/statsd/src/active_config_list.pb.h>
+#include <log/logprint.h>
+#include <utils/RefBase.h>
+
+#include <unordered_map>
+
#include "HashableDimensionKey.h"
#include "anomaly/AnomalyTracker.h"
#include "condition/ConditionWizard.h"
#include "config/ConfigKey.h"
#include "matchers/matcher_util.h"
#include "packages/PackageInfoListener.h"
-
-#include <log/logprint.h>
-#include <utils/RefBase.h>
-#include <unordered_map>
+#include "state/StateListener.h"
namespace android {
namespace os {
@@ -69,28 +69,32 @@
NO_TIME_CONSTRAINTS = 2
};
+struct Activation {
+ Activation(const ActivationType& activationType, const int64_t ttlNs)
+ : ttl_ns(ttlNs),
+ start_ns(0),
+ state(ActivationState::kNotActive),
+ activationType(activationType) {}
+
+ const int64_t ttl_ns;
+ int64_t start_ns;
+ ActivationState state;
+ const ActivationType activationType;
+};
+
// A MetricProducer is responsible for compute one single metrics, creating stats log report, and
// writing the report to dropbox. MetricProducers should respond to package changes as required in
// PackageInfoListener, but if none of the metrics are slicing by package name, then the update can
// be a no-op.
-class MetricProducer : public virtual PackageInfoListener {
+class MetricProducer : public virtual PackageInfoListener, public virtual StateListener {
public:
MetricProducer(const int64_t& metricId, const ConfigKey& key, const int64_t timeBaseNs,
- const int conditionIndex, const sp<ConditionWizard>& wizard)
- : mMetricId(metricId),
- mConfigKey(key),
- mTimeBaseNs(timeBaseNs),
- mCurrentBucketStartTimeNs(timeBaseNs),
- mCurrentBucketNum(0),
- mCondition(initialCondition(conditionIndex)),
- mConditionTrackerIndex(conditionIndex),
- mConditionSliced(false),
- mWizard(wizard),
- mContainANYPositionInDimensionsInWhat(false),
- mSliceByPositionALL(false),
- mHasLinksToAllConditionDimensionsInTracker(false),
- mIsActive(true) {
- }
+ const int conditionIndex, const sp<ConditionWizard>& wizard,
+ const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap,
+ const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+ eventDeactivationMap,
+ const vector<int>& slicedStateAtoms,
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap);
virtual ~MetricProducer(){};
@@ -149,6 +153,9 @@
return mConditionSliced;
};
+ void onStateChanged(int atomId, const HashableDimensionKey& primaryKey, int oldState,
+ int newState){};
+
// Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp.
// This method clears all the past buckets.
void onDumpReport(const int64_t dumpTimeNs,
@@ -188,11 +195,6 @@
dropDataLocked(dropTimeNs);
}
- void prepareFirstBucket() {
- std::lock_guard<std::mutex> lock(mMutex);
- prepareFirstBucketLocked();
- }
-
void loadActiveMetric(const ActiveMetric& activeMetric, int64_t currentTimeNs) {
std::lock_guard<std::mutex> lock(mMutex);
loadActiveMetricLocked(activeMetric, currentTimeNs);
@@ -215,9 +217,6 @@
void flushIfExpire(int64_t elapsedTimestampNs);
- void addActivation(int activationTrackerIndex, const ActivationType& activationType,
- int64_t ttl_seconds, int deactivationTrackerIndex = -1);
-
void writeActiveMetricToProtoOutputStream(
int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto);
@@ -236,6 +235,11 @@
return mBucketSizeNs;
}
+ inline const std::vector<int> getSlicedStateAtoms() {
+ std::lock_guard<std::mutex> lock(mMutex);
+ return mSlicedStateAtoms;
+ }
+
/* If alert is valid, adds an AnomalyTracker and returns it. If invalid, returns nullptr. */
virtual sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
const sp<AlarmMonitor>& anomalyAlarmMonitor) {
@@ -310,7 +314,6 @@
virtual size_t byteSizeLocked() const = 0;
virtual void dumpStatesLocked(FILE* out, bool verbose) const = 0;
virtual void dropDataLocked(const int64_t dropTimeNs) = 0;
- virtual void prepareFirstBucketLocked() {};
void loadActiveMetricLocked(const ActiveMetric& activeMetric, int64_t currentTimeNs);
void activateLocked(int activationTrackerIndex, int64_t elapsedTimestampNs);
void cancelEventActivationLocked(int deactivationTrackerIndex);
@@ -379,19 +382,6 @@
mutable std::mutex mMutex;
- struct Activation {
- Activation(const ActivationType& activationType, const int64_t ttlNs)
- : ttl_ns(ttlNs),
- start_ns(0),
- state(ActivationState::kNotActive),
- activationType(activationType) {}
-
- const int64_t ttl_ns;
- int64_t start_ns;
- ActivationState state;
- const ActivationType activationType;
- };
-
// When the metric producer has multiple activations, these activations are ORed to determine
// whether the metric producer is ready to generate metrics.
std::unordered_map<int, std::shared_ptr<Activation>> mEventActivationMap;
@@ -401,6 +391,16 @@
bool mIsActive;
+ // The slice_by_state atom ids defined in statsd_config.
+ std::vector<int> mSlicedStateAtoms;
+
+ // Maps atom ids and state values to group_ids (<atom_id, <value, group_id>>).
+ std::unordered_map<int, std::unordered_map<int, int64_t>> mStateGroupMap;
+
+ FRIEND_TEST(CountMetricE2eTest, TestWithSimpleState);
+ FRIEND_TEST(CountMetricE2eTest, TestWithMappedState);
+ FRIEND_TEST(CountMetricE2eTest, TestWithMultipleStates);
+
FRIEND_TEST(DurationMetricE2eTest, TestOneBucket);
FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets);
FRIEND_TEST(DurationMetricE2eTest, TestWithActivation);
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 963205e..7bae4b9 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -15,8 +15,12 @@
*/
#define DEBUG false // STOPSHIP if true
#include "Log.h"
+
#include "MetricsManager.h"
-#include "statslog.h"
+
+#include <log/logprint.h>
+#include <private/android_filesystem_config.h>
+#include <utils/SystemClock.h>
#include "CountMetricProducer.h"
#include "condition/CombinationConditionTracker.h"
@@ -25,12 +29,10 @@
#include "matchers/CombinationLogMatchingTracker.h"
#include "matchers/SimpleLogMatchingTracker.h"
#include "metrics_manager_util.h"
-#include "stats_util.h"
+#include "state/StateManager.h"
#include "stats_log_util.h"
-
-#include <log/logprint.h>
-#include <private/android_filesystem_config.h>
-#include <utils/SystemClock.h>
+#include "stats_util.h"
+#include "statslog.h"
using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_INT32;
@@ -149,6 +151,12 @@
}
MetricsManager::~MetricsManager() {
+ for (auto it : mAllMetricProducers) {
+ for (int atomId : it->getSlicedStateAtoms()) {
+ StateManager::getInstance().unregisterListener(atomId, it);
+ }
+ }
+
VLOG("~MetricsManager()");
}
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 3dad614..d184121 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -282,6 +282,10 @@
TestActivationOnBootMultipleActivationsDifferentActivationTypes);
FRIEND_TEST(StatsLogProcessorTest, TestActivationsPersistAcrossSystemServerRestart);
+ FRIEND_TEST(CountMetricE2eTest, TestWithSimpleState);
+ FRIEND_TEST(CountMetricE2eTest, TestWithMappedState);
+ FRIEND_TEST(CountMetricE2eTest, TestWithMultipleStates);
+
FRIEND_TEST(DurationMetricE2eTest, TestOneBucket);
FRIEND_TEST(DurationMetricE2eTest, TestTwoBuckets);
FRIEND_TEST(DurationMetricE2eTest, TestWithActivation);
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index bc16024..6fd0327 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -81,8 +81,13 @@
const ConfigKey& key, const ValueMetric& metric, const int conditionIndex,
const sp<ConditionWizard>& conditionWizard, const int whatMatcherIndex,
const sp<EventMatcherWizard>& matcherWizard, const int pullTagId, const int64_t timeBaseNs,
- const int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager)
- : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, conditionWizard),
+ const int64_t startTimeNs, const sp<StatsPullerManager>& pullerManager,
+ const unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
+ const unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap,
+ const vector<int>& slicedStateAtoms,
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap)
+ : MetricProducer(metric.id(), key, timeBaseNs, conditionIndex, conditionWizard,
+ eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap),
mWhatMatcherIndex(whatMatcherIndex),
mEventMatcherWizard(matcherWizard),
mPullerManager(pullerManager),
@@ -108,7 +113,7 @@
mMaxPullDelayNs(metric.max_pull_delay_sec() > 0 ? metric.max_pull_delay_sec() * NS_PER_SEC
: StatsdStats::kPullMaxDelayNs),
mSplitBucketForAppUpgrade(metric.split_bucket_for_app_upgrade()),
- // Condition timer will be set in prepareFirstBucketLocked.
+ // Condition timer will be set later within the constructor after pulling events
mConditionTimer(false, timeBaseNs) {
int64_t bucketSizeMills = 0;
if (metric.has_bucket()) {
@@ -154,6 +159,15 @@
// Adjust start for partial bucket
mCurrentBucketStartTimeNs = startTimeNs;
mConditionTimer.newBucketStart(mCurrentBucketStartTimeNs);
+
+ // Kicks off the puller immediately if condition is true and diff based.
+ if (mIsActive && mIsPulled && mCondition == ConditionState::kTrue && mUseDiff) {
+ pullAndMatchEventsLocked(mCurrentBucketStartTimeNs, mCondition);
+ }
+ // Now that activations are processed, start the condition timer if needed.
+ mConditionTimer.onConditionChanged(mIsActive && mCondition == ConditionState::kTrue,
+ mCurrentBucketStartTimeNs);
+
VLOG("value metric %lld created. bucket size %lld start_time: %lld", (long long)metric.id(),
(long long)mBucketSizeNs, (long long)mTimeBaseNs);
}
@@ -165,16 +179,6 @@
}
}
-void ValueMetricProducer::prepareFirstBucketLocked() {
- // Kicks off the puller immediately if condition is true and diff based.
- if (mIsActive && mIsPulled && mCondition == ConditionState::kTrue && mUseDiff) {
- pullAndMatchEventsLocked(mCurrentBucketStartTimeNs, mCondition);
- }
- // Now that activations are processed, start the condition timer if needed.
- mConditionTimer.onConditionChanged(mIsActive && mCondition == ConditionState::kTrue,
- mCurrentBucketStartTimeNs);
-}
-
void ValueMetricProducer::onSlicedConditionMayChangeLocked(bool overallCondition,
const int64_t eventTime) {
VLOG("Metric %lld onSlicedConditionMayChange", (long long)mMetricId);
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 739f6ef..206e602 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -52,12 +52,17 @@
// - an alarm set to the end of the bucket
class ValueMetricProducer : public virtual MetricProducer, public virtual PullDataReceiver {
public:
- ValueMetricProducer(const ConfigKey& key, const ValueMetric& valueMetric,
- const int conditionIndex, const sp<ConditionWizard>& conditionWizard,
- const int whatMatcherIndex,
- const sp<EventMatcherWizard>& matcherWizard,
- const int pullTagId, const int64_t timeBaseNs, const int64_t startTimeNs,
- const sp<StatsPullerManager>& pullerManager);
+ ValueMetricProducer(
+ const ConfigKey& key, const ValueMetric& valueMetric, const int conditionIndex,
+ const sp<ConditionWizard>& conditionWizard, const int whatMatcherIndex,
+ const sp<EventMatcherWizard>& matcherWizard, const int pullTagId,
+ const int64_t timeBaseNs, const int64_t startTimeNs,
+ const sp<StatsPullerManager>& pullerManager,
+ const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap = {},
+ const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>&
+ eventDeactivationMap = {},
+ const vector<int>& slicedStateAtoms = {},
+ const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap = {});
virtual ~ValueMetricProducer();
@@ -116,8 +121,6 @@
void flushCurrentBucketLocked(const int64_t& eventTimeNs,
const int64_t& nextBucketStartTimeNs) override;
- void prepareFirstBucketLocked() override;
-
void dropDataLocked(const int64_t dropTimeNs) override;
// Calculate previous bucket end time based on current time.
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 40484f4..33e162e 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -18,25 +18,26 @@
#include "Log.h"
#include "metrics_manager_util.h"
-
-#include "../condition/CombinationConditionTracker.h"
-#include "../condition/SimpleConditionTracker.h"
-#include "../condition/StateConditionTracker.h"
-#include "../external/StatsPullerManager.h"
-#include "../matchers/CombinationLogMatchingTracker.h"
-#include "../matchers/SimpleLogMatchingTracker.h"
-#include "../matchers/EventMatcherWizard.h"
-#include "../metrics/CountMetricProducer.h"
-#include "../metrics/DurationMetricProducer.h"
-#include "../metrics/EventMetricProducer.h"
-#include "../metrics/GaugeMetricProducer.h"
-#include "../metrics/ValueMetricProducer.h"
-
-#include "stats_util.h"
-#include "statslog.h"
+#include "MetricProducer.h"
#include <inttypes.h>
+#include "condition/CombinationConditionTracker.h"
+#include "condition/SimpleConditionTracker.h"
+#include "condition/StateConditionTracker.h"
+#include "external/StatsPullerManager.h"
+#include "matchers/CombinationLogMatchingTracker.h"
+#include "matchers/EventMatcherWizard.h"
+#include "matchers/SimpleLogMatchingTracker.h"
+#include "metrics/CountMetricProducer.h"
+#include "metrics/DurationMetricProducer.h"
+#include "metrics/EventMetricProducer.h"
+#include "metrics/GaugeMetricProducer.h"
+#include "metrics/ValueMetricProducer.h"
+#include "state/StateManager.h"
+#include "stats_util.h"
+#include "statslog.h"
+
using std::set;
using std::string;
using std::unordered_map;
@@ -137,6 +138,97 @@
return true;
}
+// Initializes state data structures for a metric.
+// input:
+// [config]: the input config
+// [stateIds]: the slice_by_state ids for this metric
+// [stateAtomIdMap]: this map contains the mapping from all state ids to atom ids
+// [allStateGroupMaps]: this map contains the mapping from state ids and state
+// values to state group ids for all states
+// output:
+// [slicedStateAtoms]: a vector of atom ids of all the slice_by_states
+// [stateGroupMap]: this map should contain the mapping from states ids and state
+// values to state group ids for all states that this metric
+// is interested in
+bool handleMetricWithStates(
+ const StatsdConfig& config, const ::google::protobuf::RepeatedField<int64_t>& stateIds,
+ const unordered_map<int64_t, int>& stateAtomIdMap,
+ const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
+ vector<int>& slicedStateAtoms,
+ unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap) {
+ for (const auto& stateId : stateIds) {
+ auto it = stateAtomIdMap.find(stateId);
+ if (it == stateAtomIdMap.end()) {
+ ALOGW("cannot find State %" PRId64 " in the config", stateId);
+ return false;
+ }
+ int atomId = it->second;
+ slicedStateAtoms.push_back(atomId);
+
+ auto stateIt = allStateGroupMaps.find(stateId);
+ if (stateIt != allStateGroupMaps.end()) {
+ stateGroupMap[atomId] = stateIt->second;
+ }
+ }
+ return true;
+}
+
+// Validates a metricActivation and populates state.
+// EventActivationMap and EventDeactivationMap are supplied to a MetricProducer
+// to provide the producer with state about its activators and deactivators.
+// Returns false if there are errors.
+bool handleMetricActivation(
+ const StatsdConfig& config,
+ const int64_t metricId,
+ const int metricIndex,
+ const unordered_map<int64_t, int>& metricToActivationMap,
+ const unordered_map<int64_t, int>& logTrackerMap,
+ unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+ unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+ vector<int>& metricsWithActivation,
+ unordered_map<int, shared_ptr<Activation>>& eventActivationMap,
+ unordered_map<int, vector<shared_ptr<Activation>>>& eventDeactivationMap) {
+ // Check if metric has an associated activation
+ auto itr = metricToActivationMap.find(metricId);
+ if (itr == metricToActivationMap.end()) return true;
+
+ int activationIndex = itr->second;
+ const MetricActivation& metricActivation = config.metric_activation(activationIndex);
+
+ for (int i = 0; i < metricActivation.event_activation_size(); i++) {
+ const EventActivation& activation = metricActivation.event_activation(i);
+
+ auto itr = logTrackerMap.find(activation.atom_matcher_id());
+ if (itr == logTrackerMap.end()) {
+ ALOGE("Atom matcher not found for event activation.");
+ return false;
+ }
+
+ ActivationType activationType = (activation.has_activation_type()) ?
+ activation.activation_type() : metricActivation.activation_type();
+ std::shared_ptr<Activation> activationWrapper = std::make_shared<Activation>(
+ activationType, activation.ttl_seconds() * NS_PER_SEC);
+
+ int atomMatcherIndex = itr->second;
+ activationAtomTrackerToMetricMap[atomMatcherIndex].push_back(metricIndex);
+ eventActivationMap.emplace(atomMatcherIndex, activationWrapper);
+
+ if (activation.has_deactivation_atom_matcher_id()) {
+ itr = logTrackerMap.find(activation.deactivation_atom_matcher_id());
+ if (itr == logTrackerMap.end()) {
+ ALOGE("Atom matcher not found for event deactivation.");
+ return false;
+ }
+ int deactivationAtomMatcherIndex = itr->second;
+ deactivationAtomTrackerToMetricMap[deactivationAtomMatcherIndex].push_back(metricIndex);
+ eventDeactivationMap[deactivationAtomMatcherIndex].push_back(activationWrapper);
+ }
+ }
+
+ metricsWithActivation.push_back(metricIndex);
+ return true;
+}
+
bool initLogTrackers(const StatsdConfig& config, const UidMap& uidMap,
unordered_map<int64_t, int>& logTrackerMap,
vector<sp<LogMatchingTracker>>& allAtomMatchers, set<int>& allTagIds) {
@@ -285,24 +377,61 @@
return true;
}
+bool initStates(const StatsdConfig& config, unordered_map<int64_t, int>& stateAtomIdMap,
+ unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps) {
+ for (int i = 0; i < config.state_size(); i++) {
+ const State& state = config.state(i);
+ const int64_t stateId = state.id();
+ stateAtomIdMap[stateId] = state.atom_id();
+
+ const StateMap& stateMap = state.map();
+ for (auto group : stateMap.group()) {
+ for (auto value : group.value()) {
+ allStateGroupMaps[stateId][value] = group.group_id();
+ }
+ }
+ }
+
+ return true;
+}
+
bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t timeBaseTimeNs,
const int64_t currentTimeNs, UidMap& uidMap,
const sp<StatsPullerManager>& pullerManager,
const unordered_map<int64_t, int>& logTrackerMap,
const unordered_map<int64_t, int>& conditionTrackerMap,
const vector<sp<LogMatchingTracker>>& allAtomMatchers,
+ const unordered_map<int64_t, int>& stateAtomIdMap,
+ const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
vector<sp<ConditionTracker>>& allConditionTrackers,
vector<sp<MetricProducer>>& allMetricProducers,
- unordered_map<int, std::vector<int>>& conditionToMetricMap,
- unordered_map<int, std::vector<int>>& trackerToMetricMap,
- unordered_map<int64_t, int>& metricMap, std::set<int64_t>& noReportMetricIds) {
+ unordered_map<int, vector<int>>& conditionToMetricMap,
+ unordered_map<int, vector<int>>& trackerToMetricMap,
+ unordered_map<int64_t, int>& metricMap, std::set<int64_t>& noReportMetricIds,
+ unordered_map<int, vector<int>>& activationAtomTrackerToMetricMap,
+ unordered_map<int, vector<int>>& deactivationAtomTrackerToMetricMap,
+ vector<int>& metricsWithActivation) {
sp<ConditionWizard> wizard = new ConditionWizard(allConditionTrackers);
sp<EventMatcherWizard> matcherWizard = new EventMatcherWizard(allAtomMatchers);
const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() +
- config.event_metric_size() + config.value_metric_size();
+ config.event_metric_size() + config.gauge_metric_size() +
+ config.value_metric_size();
allMetricProducers.reserve(allMetricsCount);
StatsPullerManager statsPullerManager;
+ // Construct map from metric id to metric activation index. The map will be used to determine
+ // the metric activation corresponding to a metric.
+ unordered_map<int64_t, int> metricToActivationMap;
+ for (int i = 0; i < config.metric_activation_size(); i++) {
+ const MetricActivation& metricActivation = config.metric_activation(i);
+ int64_t metricId = metricActivation.metric_id();
+ if (metricToActivationMap.find(metricId) != metricToActivationMap.end()) {
+ ALOGE("Metric %lld has multiple MetricActivations", (long long) metricId);
+ return false;
+ }
+ metricToActivationMap.insert({metricId, i});
+ }
+
// Build MetricProducers for each metric defined in config.
// build CountMetricProducer
for (int i = 0; i < config.count_metric_size(); i++) {
@@ -324,10 +453,9 @@
int conditionIndex = -1;
if (metric.has_condition()) {
- bool good = handleMetricWithConditions(
- metric.condition(), metricIndex, conditionTrackerMap, metric.links(),
- allConditionTrackers, conditionIndex, conditionToMetricMap);
- if (!good) {
+ if (!handleMetricWithConditions(metric.condition(), metricIndex, conditionTrackerMap,
+ metric.links(), allConditionTrackers, conditionIndex,
+ conditionToMetricMap)) {
return false;
}
} else {
@@ -337,8 +465,29 @@
}
}
- sp<MetricProducer> countProducer =
- new CountMetricProducer(key, metric, conditionIndex, wizard, timeBaseTimeNs, currentTimeNs);
+ std::vector<int> slicedStateAtoms;
+ unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
+ if (metric.slice_by_state_size() > 0) {
+ if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
+ allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
+ return false;
+ }
+ }
+
+ // TODO(tsaichristine): add check for unequal number of MetricStateLinks
+ // and slice_by_states
+
+ unordered_map<int, shared_ptr<Activation>> eventActivationMap;
+ unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
+ bool success = handleMetricActivation(config, metric.id(), metricIndex,
+ metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap,
+ eventDeactivationMap);
+ if (!success) return false;
+
+ sp<MetricProducer> countProducer = new CountMetricProducer(
+ key, metric, conditionIndex, wizard, timeBaseTimeNs, currentTimeNs,
+ eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap);
allMetricProducers.push_back(countProducer);
}
@@ -406,9 +555,18 @@
}
}
+ unordered_map<int, shared_ptr<Activation>> eventActivationMap;
+ unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
+ bool success = handleMetricActivation(config, metric.id(), metricIndex,
+ metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap,
+ eventDeactivationMap);
+ if (!success) return false;
+
sp<MetricProducer> durationMetric = new DurationMetricProducer(
key, metric, conditionIndex, trackerIndices[0], trackerIndices[1],
- trackerIndices[2], nesting, wizard, internalDimensions, timeBaseTimeNs, currentTimeNs);
+ trackerIndices[2], nesting, wizard, internalDimensions, timeBaseTimeNs,
+ currentTimeNs, eventActivationMap, eventDeactivationMap);
allMetricProducers.push_back(durationMetric);
}
@@ -443,8 +601,17 @@
}
}
- sp<MetricProducer> eventMetric =
- new EventMetricProducer(key, metric, conditionIndex, wizard, timeBaseTimeNs);
+ unordered_map<int, shared_ptr<Activation>> eventActivationMap;
+ unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
+ bool success = handleMetricActivation(config, metric.id(), metricIndex,
+ metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap,
+ eventDeactivationMap);
+ if (!success) return false;
+
+ sp<MetricProducer> eventMetric = new EventMetricProducer(
+ key, metric, conditionIndex, wizard, timeBaseTimeNs, eventActivationMap,
+ eventDeactivationMap);
allMetricProducers.push_back(eventMetric);
}
@@ -500,9 +667,18 @@
}
}
+ unordered_map<int, shared_ptr<Activation>> eventActivationMap;
+ unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
+ bool success = handleMetricActivation(config, metric.id(), metricIndex,
+ metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap,
+ eventDeactivationMap);
+ if (!success) return false;
+
sp<MetricProducer> valueProducer = new ValueMetricProducer(
key, metric, conditionIndex, wizard, trackerIndex, matcherWizard, pullTagId,
- timeBaseTimeNs, currentTimeNs, pullerManager);
+ timeBaseTimeNs, currentTimeNs, pullerManager, eventActivationMap,
+ eventDeactivationMap);
allMetricProducers.push_back(valueProducer);
}
@@ -586,10 +762,19 @@
}
}
+ unordered_map<int, shared_ptr<Activation>> eventActivationMap;
+ unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
+ bool success = handleMetricActivation(config, metric.id(), metricIndex,
+ metricToActivationMap, logTrackerMap, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation, eventActivationMap,
+ eventDeactivationMap);
+ if (!success) return false;
+
sp<MetricProducer> gaugeProducer = new GaugeMetricProducer(
key, metric, conditionIndex, wizard,
trackerIndex, matcherWizard, pullTagId, triggerAtomId, atomTagId,
- timeBaseTimeNs, currentTimeNs, pullerManager);
+ timeBaseTimeNs, currentTimeNs, pullerManager,
+ eventActivationMap, eventDeactivationMap);
allMetricProducers.push_back(gaugeProducer);
}
for (int i = 0; i < config.no_report_metric_size(); ++i) {
@@ -602,6 +787,13 @@
}
for (const auto& it : allMetricProducers) {
uidMap.addListener(it);
+
+ // Register metrics to StateTrackers
+ for (int atomId : it->getSlicedStateAtoms()) {
+ if (!StateManager::getInstance().registerListener(atomId, it)) {
+ return false;
+ }
+ }
}
return true;
}
@@ -707,73 +899,6 @@
return true;
}
-bool initMetricActivations(const ConfigKey& key, const StatsdConfig& config,
- const int64_t currentTimeNs,
- const unordered_map<int64_t, int> &logEventTrackerMap,
- const unordered_map<int64_t, int> &metricProducerMap,
- vector<sp<MetricProducer>>& allMetricProducers,
- unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
- unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
- vector<int>& metricsWithActivation) {
- for (int i = 0; i < config.metric_activation_size(); ++i) {
- const MetricActivation& metric_activation = config.metric_activation(i);
- auto itr = metricProducerMap.find(metric_activation.metric_id());
- if (itr == metricProducerMap.end()) {
- ALOGE("Metric id not found in metric activation: %lld",
- (long long)metric_activation.metric_id());
- return false;
- }
- const int metricTrackerIndex = itr->second;
- if (metricTrackerIndex < 0 || metricTrackerIndex >= (int)allMetricProducers.size()) {
- ALOGE("Invalid metric tracker index.");
- return false;
- }
- const sp<MetricProducer>& metric = allMetricProducers[metricTrackerIndex];
- metricsWithActivation.push_back(metricTrackerIndex);
- for (int j = 0; j < metric_activation.event_activation_size(); ++j) {
- const EventActivation& activation = metric_activation.event_activation(j);
- auto logTrackerIt = logEventTrackerMap.find(activation.atom_matcher_id());
- if (logTrackerIt == logEventTrackerMap.end()) {
- ALOGE("Atom matcher not found for event activation.");
- return false;
- }
- const int atomMatcherIndex = logTrackerIt->second;
- activationAtomTrackerToMetricMap[atomMatcherIndex].push_back(
- metricTrackerIndex);
-
- ActivationType activationType;
- if (activation.has_activation_type()) {
- activationType = activation.activation_type();
- } else {
- activationType = metric_activation.activation_type();
- }
-
- if (activation.has_deactivation_atom_matcher_id()) {
- auto deactivationAtomMatcherIt =
- logEventTrackerMap.find(activation.deactivation_atom_matcher_id());
- if (deactivationAtomMatcherIt == logEventTrackerMap.end()) {
- ALOGE("Atom matcher not found for event deactivation.");
- return false;
- }
- const int deactivationMatcherIndex = deactivationAtomMatcherIt->second;
- deactivationAtomTrackerToMetricMap[deactivationMatcherIndex]
- .push_back(metricTrackerIndex);
- metric->addActivation(atomMatcherIndex, activationType, activation.ttl_seconds(),
- deactivationMatcherIndex);
- } else {
- metric->addActivation(atomMatcherIndex, activationType, activation.ttl_seconds());
- }
- }
- }
- return true;
-}
-
-void prepareFirstBucket(const vector<sp<MetricProducer>>& allMetricProducers) {
- for (const auto& metric: allMetricProducers) {
- metric->prepareFirstBucket();
- }
-}
-
bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& uidMap,
const sp<StatsPullerManager>& pullerManager,
const sp<AlarmMonitor>& anomalyAlarmMonitor,
@@ -794,6 +919,8 @@
unordered_map<int64_t, int> logTrackerMap;
unordered_map<int64_t, int> conditionTrackerMap;
unordered_map<int64_t, int> metricProducerMap;
+ unordered_map<int64_t, int> stateAtomIdMap;
+ unordered_map<int64_t, unordered_map<int, int64_t>> allStateGroupMaps;
if (!initLogTrackers(config, uidMap, logTrackerMap, allAtomMatchers, allTagIds)) {
ALOGE("initLogMatchingTrackers failed");
@@ -806,11 +933,16 @@
ALOGE("initConditionTrackers failed");
return false;
}
-
+ if (!initStates(config, stateAtomIdMap, allStateGroupMaps)) {
+ ALOGE("initStates failed");
+ return false;
+ }
if (!initMetrics(key, config, timeBaseNs, currentTimeNs, uidMap, pullerManager, logTrackerMap,
- conditionTrackerMap, allAtomMatchers, allConditionTrackers, allMetricProducers,
+ conditionTrackerMap, allAtomMatchers, stateAtomIdMap, allStateGroupMaps,
+ allConditionTrackers, allMetricProducers,
conditionToMetricMap, trackerToMetricMap, metricProducerMap,
- noReportMetricIds)) {
+ noReportMetricIds, activationAtomTrackerToMetricMap,
+ deactivationAtomTrackerToMetricMap, metricsWithActivation)) {
ALOGE("initMetricProducers failed");
return false;
}
@@ -824,14 +956,6 @@
ALOGE("initAlarms failed");
return false;
}
- if (!initMetricActivations(key, config, currentTimeNs, logTrackerMap, metricProducerMap,
- allMetricProducers, activationAtomTrackerToMetricMap,
- deactivationAtomTrackerToMetricMap, metricsWithActivation)) {
- ALOGE("initMetricActivations failed");
- return false;
- }
-
- prepareFirstBucket(allMetricProducers);
return true;
}
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index 3704969..95b2ab8 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -68,6 +68,17 @@
std::unordered_map<int, std::vector<int>>& trackerToConditionMap,
std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks);
+// Initialize State maps using State protos in the config. These maps will
+// eventually be passed to MetricProducers to initialize their state info.
+// input:
+// [config]: the input config
+// output:
+// [stateAtomIdMap]: this map should contain the mapping from state ids to atom ids
+// [allStateGroupMaps]: this map should contain the mapping from states ids and state
+// values to state group ids for all states
+bool initStates(const StatsdConfig& config, unordered_map<int64_t, int>& stateAtomIdMap,
+ unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps);
+
// Initialize MetricProducers.
// input:
// [key]: the config key that this config belongs to
@@ -75,6 +86,9 @@
// [timeBaseSec]: start time base for all metrics
// [logTrackerMap]: LogMatchingTracker name to index mapping from previous step.
// [conditionTrackerMap]: condition name to index mapping
+// [stateAtomIdMap]: contains the mapping from state ids to atom ids
+// [allStateGroupMaps]: contains the mapping from atom ids and state values to
+// state group ids for all states
// output:
// [allMetricProducers]: contains the list of sp to the MetricProducers created.
// [conditionToMetricMap]: contains the mapping from condition tracker index to
@@ -87,11 +101,16 @@
const std::unordered_map<int64_t, int>& conditionTrackerMap,
const std::unordered_map<int, std::vector<MetricConditionLink>>& eventConditionLinks,
const vector<sp<LogMatchingTracker>>& allAtomMatchers,
+ const unordered_map<int64_t, int>& stateAtomIdMap,
+ const unordered_map<int64_t, unordered_map<int, int64_t>>& allStateGroupMaps,
vector<sp<ConditionTracker>>& allConditionTrackers,
std::vector<sp<MetricProducer>>& allMetricProducers,
std::unordered_map<int, std::vector<int>>& conditionToMetricMap,
std::unordered_map<int, std::vector<int>>& trackerToMetricMap,
- std::set<int64_t>& noReportMetricIds);
+ std::set<int64_t>& noReportMetricIds,
+ std::unordered_map<int, std::vector<int>>& activationAtomTrackerToMetricMap,
+ std::unordered_map<int, std::vector<int>>& deactivationAtomTrackerToMetricMap,
+ std::vector<int>& metricsWithActivation);
// Initialize MetricsManager from StatsdConfig.
// Parameters are the members of MetricsManager. See MetricsManager for declaration.
diff --git a/cmds/statsd/src/state/StateManager.cpp b/cmds/statsd/src/state/StateManager.cpp
index a3059c5..95b2c76 100644
--- a/cmds/statsd/src/state/StateManager.cpp
+++ b/cmds/statsd/src/state/StateManager.cpp
@@ -35,26 +35,25 @@
}
}
-bool StateManager::registerListener(int stateAtomId, wp<StateListener> listener) {
+bool StateManager::registerListener(int atomId, wp<StateListener> listener) {
std::lock_guard<std::mutex> lock(mMutex);
// Check if state tracker already exists
- if (mStateTrackers.find(stateAtomId) == mStateTrackers.end()) {
+ if (mStateTrackers.find(atomId) == mStateTrackers.end()) {
// Create a new state tracker iff atom is a state atom
- auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find(stateAtomId);
+ auto it = android::util::AtomsInfo::kStateAtomsFieldOptions.find(atomId);
if (it != android::util::AtomsInfo::kStateAtomsFieldOptions.end()) {
- mStateTrackers[stateAtomId] = new StateTracker(stateAtomId, it->second);
+ mStateTrackers[atomId] = new StateTracker(atomId, it->second);
} else {
- ALOGE("StateManager cannot register listener, Atom %d is not a state atom",
- stateAtomId);
+ ALOGE("StateManager cannot register listener, Atom %d is not a state atom", atomId);
return false;
}
}
- mStateTrackers[stateAtomId]->registerListener(listener);
+ mStateTrackers[atomId]->registerListener(listener);
return true;
}
-void StateManager::unregisterListener(int stateAtomId, wp<StateListener> listener) {
+void StateManager::unregisterListener(int atomId, wp<StateListener> listener) {
std::unique_lock<std::mutex> lock(mMutex);
// Hold the sp<> until the lock is released so that ~StateTracker() is
@@ -62,7 +61,7 @@
sp<StateTracker> toRemove;
// Unregister listener from correct StateTracker
- auto it = mStateTrackers.find(stateAtomId);
+ auto it = mStateTrackers.find(atomId);
if (it != mStateTrackers.end()) {
it->second->unregisterListener(listener);
@@ -73,15 +72,15 @@
}
} else {
ALOGE("StateManager cannot unregister listener, StateTracker for atom %d does not exist",
- stateAtomId);
+ atomId);
}
lock.unlock();
}
-int StateManager::getState(int stateAtomId, const HashableDimensionKey& key) {
+int StateManager::getStateValue(int atomId, const HashableDimensionKey& key) {
std::lock_guard<std::mutex> lock(mMutex);
- if (mStateTrackers.find(stateAtomId) != mStateTrackers.end()) {
- return mStateTrackers[stateAtomId]->getState(key);
+ if (mStateTrackers.find(atomId) != mStateTrackers.end()) {
+ return mStateTrackers[atomId]->getStateValue(key);
}
return StateTracker::kStateUnknown;
diff --git a/cmds/statsd/src/state/StateManager.h b/cmds/statsd/src/state/StateManager.h
index ce60f14..89ee6c0 100644
--- a/cmds/statsd/src/state/StateManager.h
+++ b/cmds/statsd/src/state/StateManager.h
@@ -15,10 +15,11 @@
*/
#pragma once
-//#include <utils/Log.h>
+#include <gtest/gtest_prod.h>
+#include <inttypes.h>
#include <utils/RefBase.h>
-#include "HashableDimensionKey.h"
+#include "HashableDimensionKey.h"
#include "state/StateListener.h"
#include "state/StateTracker.h"
@@ -38,29 +39,29 @@
// Notifies the correct StateTracker of an event.
void onLogEvent(const LogEvent& event);
- // Returns true if stateAtomId is the id of a state atom and notifies the
- // correct StateTracker to register the listener. If the correct
- // StateTracker does not exist, a new StateTracker is created.
- bool registerListener(int stateAtomId, wp<StateListener> listener);
+ // Returns true if atomId is being tracked and is associated with a state
+ // atom. StateManager notifies the correct StateTracker to register listener.
+ // If the correct StateTracker does not exist, a new StateTracker is created.
+ bool registerListener(int atomId, wp<StateListener> listener);
// Notifies the correct StateTracker to unregister a listener
// and removes the tracker if it no longer has any listeners.
- void unregisterListener(int stateAtomId, wp<StateListener> listener);
+ void unregisterListener(int atomId, wp<StateListener> listener);
- // Queries the correct StateTracker for the state that is mapped to the given
- // query key.
+ // Queries the correct StateTracker for the original/un-mapped state value
+ // that is mapped to the given query key.
// If the StateTracker doesn't exist, returns StateTracker::kStateUnknown.
- int getState(int stateAtomId, const HashableDimensionKey& queryKey);
+ int getStateValue(int atomId, const HashableDimensionKey& queryKey);
inline int getStateTrackersCount() {
std::lock_guard<std::mutex> lock(mMutex);
return mStateTrackers.size();
}
- inline int getListenersCount(int stateAtomId) {
+ inline int getListenersCount(int atomId) {
std::lock_guard<std::mutex> lock(mMutex);
- if (mStateTrackers.find(stateAtomId) != mStateTrackers.end()) {
- return mStateTrackers[stateAtomId]->getListenersCount();
+ if (mStateTrackers.find(atomId) != mStateTrackers.end()) {
+ return mStateTrackers[atomId]->getListenersCount();
}
return -1;
}
diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp
index 5a91950..323fc0e 100644
--- a/cmds/statsd/src/state/StateTracker.cpp
+++ b/cmds/statsd/src/state/StateTracker.cpp
@@ -30,13 +30,13 @@
: mAtomId(atomId),
mStateField(getSimpleMatcher(atomId, stateAtomInfo.exclusiveField)) {
// create matcher for each primary field
- // TODO(tsaichristine): handle when primary field is first uid in chain
+ // TODO(tsaichristine): b/142108433 handle when primary field is first uid in chain
for (const auto& primary : stateAtomInfo.primaryFields) {
Matcher matcher = getSimpleMatcher(atomId, primary);
mPrimaryFields.push_back(matcher);
}
- // TODO(tsaichristine): set default state, reset state, and nesting
+ // TODO(tsaichristine): b/142108433 set default state, reset state, and nesting
}
void StateTracker::onLogEvent(const LogEvent& event) {
@@ -96,7 +96,7 @@
mListeners.erase(listener);
}
-int StateTracker::getState(const HashableDimensionKey& queryKey) const {
+int StateTracker::getStateValue(const HashableDimensionKey& queryKey) const {
if (queryKey.getValues().size() == mPrimaryFields.size()) {
auto it = mStateMap.find(queryKey);
if (it != mStateMap.end()) {
diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h
index f22706c..cfa9fd8 100644
--- a/cmds/statsd/src/state/StateTracker.h
+++ b/cmds/statsd/src/state/StateTracker.h
@@ -48,7 +48,7 @@
// Returns the state value mapped to the given query key.
// If the key isn't mapped to a state or the key size doesn't match the
// primary key size, the default state is returned.
- int getState(const HashableDimensionKey& queryKey) const;
+ int getStateValue(const HashableDimensionKey& queryKey) const;
inline int getListenersCount() const {
return mListeners.size();
diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp
index 67625eb..c22e3cc 100644
--- a/cmds/statsd/src/stats_log_util.cpp
+++ b/cmds/statsd/src/stats_log_util.cpp
@@ -445,6 +445,8 @@
return 12 * 60 * 60 * 1000LL;
case ONE_DAY:
return 24 * 60 * 60 * 1000LL;
+ case ONE_WEEK:
+ return 7 * 24 * 60 * 60 * 1000LL;
case CTS:
return 1000;
case TIME_UNIT_UNSPECIFIED:
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index c107397..c98b2cf 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -44,6 +44,7 @@
SIX_HOURS = 7;
TWELVE_HOURS = 8;
ONE_DAY = 9;
+ ONE_WEEK = 10;
CTS = 1000;
}
diff --git a/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp
new file mode 100644
index 0000000..6591d69
--- /dev/null
+++ b/cmds/statsd/tests/e2e/CountMetric_e2e_test.cpp
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2019, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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/state/StateManager.h"
+#include "tests/statsd_test_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+#ifdef __ANDROID__
+
+TEST(CountMetricE2eTest, TestWithSimpleState) {
+ // Initialize config
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+
+ auto syncStartMatcher = CreateSyncStartAtomMatcher();
+ *config.add_atom_matcher() = syncStartMatcher;
+
+ auto state = CreateScreenState();
+ *config.add_state() = state;
+
+ // Create count metric that slices by screen state
+ int64_t metricId = 123456;
+ auto countMetric = config.add_count_metric();
+ countMetric->set_id(metricId);
+ countMetric->set_what(syncStartMatcher.id());
+ countMetric->set_bucket(TimeUnit::ONE_MINUTE);
+ countMetric->add_slice_by_state(state.id());
+
+ // Initialize StatsLogProcessor
+ const int64_t baseTimeNs = 0; // 0:00
+ const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01
+ const int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL;
+
+ int uid = 12345;
+ int64_t cfgId = 98765;
+ ConfigKey cfgKey(uid, cfgId);
+
+ auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey);
+
+ // Check that StateTrackers were properly initialized.
+ EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_EQ(1,
+ StateManager::getInstance().getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+ // Check that CountMetricProducer was initialized correctly.
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+ EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+ sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), android::util::SCREEN_STATE_CHANGED);
+ EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0);
+}
+
+TEST(CountMetricE2eTest, TestWithMappedState) {
+ // Initialize config
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+
+ auto syncStartMatcher = CreateSyncStartAtomMatcher();
+ *config.add_atom_matcher() = syncStartMatcher;
+
+ auto state = CreateScreenStateWithOnOffMap();
+ *config.add_state() = state;
+
+ // Create count metric that slices by screen state with on/off map
+ int64_t metricId = 123456;
+ auto countMetric = config.add_count_metric();
+ countMetric->set_id(metricId);
+ countMetric->set_what(syncStartMatcher.id());
+ countMetric->set_bucket(TimeUnit::ONE_MINUTE);
+ countMetric->add_slice_by_state(state.id());
+
+ // Initialize StatsLogProcessor
+ const int64_t baseTimeNs = 0; // 0:00
+ const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01
+ const int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL;
+
+ int uid = 12345;
+ int64_t cfgId = 98765;
+ ConfigKey cfgKey(uid, cfgId);
+
+ auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey);
+
+ // Check that StateTrackers were properly initialized.
+ EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_EQ(1,
+ StateManager::getInstance().getListenersCount(android::util::SCREEN_STATE_CHANGED));
+
+ // Check that CountMetricProducer was initialized correctly.
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+ EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+ sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), android::util::SCREEN_STATE_CHANGED);
+ EXPECT_EQ(metricProducer->mStateGroupMap.size(), 1);
+
+ StateMap map = state.map();
+ for (auto group : map.group()) {
+ for (auto value : group.value()) {
+ EXPECT_EQ(metricProducer->mStateGroupMap[android::util::SCREEN_STATE_CHANGED][value],
+ group.group_id());
+ }
+ }
+}
+
+TEST(CountMetricE2eTest, TestWithMultipleStates) {
+ // Initialize config
+ StatsdConfig config;
+ config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
+
+ auto syncStartMatcher = CreateSyncStartAtomMatcher();
+ *config.add_atom_matcher() = syncStartMatcher;
+
+ auto state1 = CreateScreenStateWithOnOffMap();
+ *config.add_state() = state1;
+ auto state2 = CreateUidProcessState();
+ *config.add_state() = state2;
+
+ // Create count metric that slices by screen state with on/off map
+ int64_t metricId = 123456;
+ auto countMetric = config.add_count_metric();
+ countMetric->set_id(metricId);
+ countMetric->set_what(syncStartMatcher.id());
+ countMetric->set_bucket(TimeUnit::ONE_MINUTE);
+ countMetric->add_slice_by_state(state1.id());
+ countMetric->add_slice_by_state(state2.id());
+
+ // Initialize StatsLogProcessor
+ const int64_t baseTimeNs = 0; // 0:00
+ const int64_t configAddedTimeNs = baseTimeNs + 1 * NS_PER_SEC; // 0:01
+ const int64_t bucketSizeNs =
+ TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000LL * 1000LL;
+
+ int uid = 12345;
+ int64_t cfgId = 98765;
+ ConfigKey cfgKey(uid, cfgId);
+
+ auto processor = CreateStatsLogProcessor(baseTimeNs, configAddedTimeNs, config, cfgKey);
+
+ // Check that StateTrackers were properly initialized.
+ EXPECT_EQ(2, StateManager::getInstance().getStateTrackersCount());
+ EXPECT_EQ(1,
+ StateManager::getInstance().getListenersCount(android::util::SCREEN_STATE_CHANGED));
+ EXPECT_EQ(1, StateManager::getInstance().getListenersCount(
+ android::util::UID_PROCESS_STATE_CHANGED));
+
+ // Check that CountMetricProducer was initialized correctly.
+ EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+ sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+ EXPECT_TRUE(metricsManager->isConfigValid());
+ EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+ sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 2);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), android::util::SCREEN_STATE_CHANGED);
+ EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(1), android::util::UID_PROCESS_STATE_CHANGED);
+ EXPECT_EQ(metricProducer->mStateGroupMap.size(), 1);
+
+ StateMap map = state1.map();
+ for (auto group : map.group()) {
+ for (auto value : group.value()) {
+ EXPECT_EQ(metricProducer->mStateGroupMap[android::util::SCREEN_STATE_CHANGED][value],
+ group.group_id());
+ }
+ }
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index 839daa4..8915c73 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -43,8 +43,8 @@
metric.set_bucket(ONE_MINUTE);
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
- CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
- 5, 600 * NS_PER_SEC + NS_PER_SEC/2);
+ CountMetricProducer countProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard, 5,
+ 600 * NS_PER_SEC + NS_PER_SEC / 2);
EXPECT_EQ(600500000000, countProducer.mCurrentBucketStartTimeNs);
EXPECT_EQ(10, countProducer.mCurrentBucketNum);
EXPECT_EQ(660000000005, countProducer.getCurrentBucketEndTimeNs());
@@ -131,7 +131,8 @@
sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
- CountMetricProducer countProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs, bucketStartTimeNs);
+ CountMetricProducer countProducer(kConfigKey, metric, 1, wizard, bucketStartTimeNs,
+ bucketStartTimeNs);
countProducer.onConditionChanged(true, bucketStartTimeNs);
countProducer.onMatchedLogEvent(1 /*matcher index*/, event1);
@@ -396,6 +397,26 @@
std::ceil(1.0 * event7.GetElapsedTimestampNs() / NS_PER_SEC + refPeriodSec));
}
+TEST(CountMetricProducerTest, TestOneWeekTimeUnit) {
+ CountMetric metric;
+ metric.set_id(1);
+ metric.set_bucket(ONE_WEEK);
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+
+ int64_t oneDayNs = 24 * 60 * 60 * 1e9;
+ int64_t fiveWeeksNs = 5 * 7 * oneDayNs;
+
+ CountMetricProducer countProducer(
+ kConfigKey, metric, -1 /* meaning no condition */, wizard, oneDayNs, fiveWeeksNs);
+
+ int64_t fiveWeeksOneDayNs = fiveWeeksNs + oneDayNs;
+
+ EXPECT_EQ(fiveWeeksNs, countProducer.mCurrentBucketStartTimeNs);
+ EXPECT_EQ(4, countProducer.mCurrentBucketNum);
+ EXPECT_EQ(fiveWeeksOneDayNs, countProducer.getCurrentBucketEndTimeNs());
+}
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index 47c21aa..b027e8e 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -79,8 +79,6 @@
logEventMatcherIndex, eventMatcherWizard,
-1, -1, tagId, 5, 600 * NS_PER_SEC + NS_PER_SEC / 2,
pullerManager);
- gaugeProducer.prepareFirstBucket();
-
EXPECT_EQ(600500000000, gaugeProducer.mCurrentBucketStartTimeNs);
EXPECT_EQ(10, gaugeProducer.mCurrentBucketNum);
@@ -126,8 +124,6 @@
tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs,
pullerManager);
- gaugeProducer.prepareFirstBucket();
-
vector<shared_ptr<LogEvent>> allData;
allData.clear();
shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
@@ -211,7 +207,6 @@
logEventMatcherIndex, eventMatcherWizard,
-1 /* -1 means no pulling */, -1, tagId, bucketStartTimeNs,
bucketStartTimeNs, pullerManager);
- gaugeProducer.prepareFirstBucket();
sp<AnomalyTracker> anomalyTracker = gaugeProducer.addAnomalyTracker(alert, alarmMonitor);
EXPECT_TRUE(anomalyTracker != nullptr);
@@ -303,7 +298,6 @@
GaugeMetricProducer gaugeProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId,
bucketStartTimeNs, bucketStartTimeNs, pullerManager);
- gaugeProducer.prepareFirstBucket();
vector<shared_ptr<LogEvent>> allData;
shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
@@ -370,7 +364,6 @@
logEventMatcherIndex, eventMatcherWizard,
tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs,
pullerManager);
- gaugeProducer.prepareFirstBucket();
vector<shared_ptr<LogEvent>> allData;
shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
@@ -431,7 +424,6 @@
GaugeMetricProducer gaugeProducer(kConfigKey, metric, 1, wizard,
logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId,
bucketStartTimeNs, bucketStartTimeNs, pullerManager);
- gaugeProducer.prepareFirstBucket();
gaugeProducer.onConditionChanged(true, bucketStartTimeNs + 8);
EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
@@ -521,7 +513,6 @@
GaugeMetricProducer gaugeProducer(kConfigKey, metric, 1, wizard,
logEventMatcherIndex, eventMatcherWizard, tagId, -1, tagId,
bucketStartTimeNs, bucketStartTimeNs, pullerManager);
- gaugeProducer.prepareFirstBucket();
gaugeProducer.onSlicedConditionMayChange(true, bucketStartTimeNs + 8);
@@ -572,7 +563,6 @@
logEventMatcherIndex, eventMatcherWizard,
tagId, -1, tagId, bucketStartTimeNs, bucketStartTimeNs,
pullerManager);
- gaugeProducer.prepareFirstBucket();
Alert alert;
alert.set_id(101);
@@ -681,7 +671,6 @@
logEventMatcherIndex, eventMatcherWizard,
tagId, triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs,
pullerManager);
- gaugeProducer.prepareFirstBucket();
vector<shared_ptr<LogEvent>> allData;
@@ -766,7 +755,6 @@
logEventMatcherIndex, eventMatcherWizard,
tagId, triggerId, tagId, bucketStartTimeNs, bucketStartTimeNs,
pullerManager);
- gaugeProducer.prepareFirstBucket();
vector<shared_ptr<LogEvent>> allData;
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 2262c76..4b9d0c0 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -105,7 +105,6 @@
kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
logEventMatcherIndex, eventMatcherWizard, tagId,
bucketStartTimeNs, bucketStartTimeNs, pullerManager);
- valueProducer->prepareFirstBucket();
return valueProducer;
}
@@ -125,7 +124,6 @@
new ValueMetricProducer(kConfigKey, metric, 1, wizard, logEventMatcherIndex,
eventMatcherWizard, tagId, bucketStartTimeNs,
bucketStartTimeNs, pullerManager);
- valueProducer->prepareFirstBucket();
valueProducer->mCondition = ConditionState::kFalse;
return valueProducer;
}
@@ -169,7 +167,6 @@
ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
logEventMatcherIndex, eventMatcherWizard, -1, startTimeBase,
22, pullerManager);
- valueProducer.prepareFirstBucket();
EXPECT_EQ(startTimeBase, valueProducer.calcPreviousBucketEndTime(60 * NS_PER_SEC + 10));
EXPECT_EQ(startTimeBase, valueProducer.calcPreviousBucketEndTime(60 * NS_PER_SEC + 10));
@@ -199,7 +196,6 @@
ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
logEventMatcherIndex, eventMatcherWizard, -1, 5,
600 * NS_PER_SEC + NS_PER_SEC / 2, pullerManager);
- valueProducer.prepareFirstBucket();
EXPECT_EQ(600500000000, valueProducer.mCurrentBucketStartTimeNs);
EXPECT_EQ(10, valueProducer.mCurrentBucketNum);
@@ -381,7 +377,6 @@
kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
logEventMatcherIndex, eventMatcherWizard, tagId,
bucketStartTimeNs, bucketStartTimeNs, pullerManager);
- valueProducer->prepareFirstBucket();
vector<shared_ptr<LogEvent>> allData;
allData.clear();
@@ -670,7 +665,6 @@
ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs,
pullerManager);
- valueProducer.prepareFirstBucket();
shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
event1->write(1);
@@ -728,7 +722,6 @@
ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
eventMatcherWizard, tagId, bucketStartTimeNs,
bucketStartTimeNs, pullerManager);
- valueProducer.prepareFirstBucket();
vector<shared_ptr<LogEvent>> allData;
allData.clear();
@@ -779,7 +772,6 @@
ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
eventMatcherWizard, tagId, bucketStartTimeNs,
bucketStartTimeNs, pullerManager);
- valueProducer.prepareFirstBucket();
vector<shared_ptr<LogEvent>> allData;
allData.clear();
@@ -854,7 +846,6 @@
ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs,
pullerManager);
- valueProducer.prepareFirstBucket();
shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
event1->write(1);
@@ -897,7 +888,6 @@
ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, logEventMatcherIndex,
eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs,
pullerManager);
- valueProducer.prepareFirstBucket();
valueProducer.mCondition = ConditionState::kFalse;
shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
@@ -972,7 +962,6 @@
ValueMetricProducer valueProducer(kConfigKey, metric, -1 /*-1 meaning no condition*/, wizard,
logEventMatcherIndex, eventMatcherWizard, -1 /*not pulled*/,
bucketStartTimeNs, bucketStartTimeNs, pullerManager);
- valueProducer.prepareFirstBucket();
sp<AnomalyTracker> anomalyTracker = valueProducer.addAnomalyTracker(alert, alarmMonitor);
@@ -1269,7 +1258,6 @@
ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs,
pullerManager);
- valueProducer.prepareFirstBucket();
shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
event1->write(1);
@@ -1314,7 +1302,6 @@
ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs,
pullerManager);
- valueProducer.prepareFirstBucket();
shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
event1->write(1);
@@ -1361,7 +1348,6 @@
ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs,
pullerManager);
- valueProducer.prepareFirstBucket();
shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
event1->write(1);
@@ -1412,7 +1398,6 @@
ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs,
pullerManager);
- valueProducer.prepareFirstBucket();
shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
event1->write(1);
@@ -1458,7 +1443,6 @@
ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs,
pullerManager);
- valueProducer.prepareFirstBucket();
shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
event1->write(1);
@@ -1532,7 +1516,6 @@
ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
eventMatcherWizard, -1, bucketStartTimeNs, bucketStartTimeNs,
pullerManager);
- valueProducer.prepareFirstBucket();
shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
event1->write(1);
@@ -2081,7 +2064,6 @@
ValueMetricProducer valueProducer(kConfigKey, metric, 1, wizard, logEventMatcherIndex,
eventMatcherWizard, tagId, bucket2StartTimeNs,
bucket2StartTimeNs, pullerManager);
- valueProducer.prepareFirstBucket();
valueProducer.mCondition = ConditionState::kFalse;
// Event should be skipped since it is from previous bucket.
@@ -2862,7 +2844,6 @@
ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
eventMatcherWizard, tagId, bucketStartTimeNs,
bucketStartTimeNs, pullerManager);
- valueProducer.prepareFirstBucket();
ProtoOutputStream output;
std::set<string> strSet;
@@ -2905,7 +2886,6 @@
ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
eventMatcherWizard, tagId, bucketStartTimeNs,
bucketStartTimeNs, pullerManager);
- valueProducer.prepareFirstBucket();
vector<shared_ptr<LogEvent>> allData;
allData.clear();
@@ -2969,7 +2949,6 @@
ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
eventMatcherWizard, tagId, bucketStartTimeNs,
bucketStartTimeNs, pullerManager);
- valueProducer.prepareFirstBucket();
ProtoOutputStream output;
std::set<string> strSet;
diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp
index c89ffea..8d38000 100644
--- a/cmds/statsd/tests/state/StateTracker_test.cpp
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -44,7 +44,7 @@
std::vector<Update> updates;
- void onStateChanged(int stateAtomId, const HashableDimensionKey& primaryKey, int oldState,
+ void onStateChanged(int atomId, const HashableDimensionKey& primaryKey, int oldState,
int newState) {
updates.emplace_back(primaryKey, newState);
}
@@ -82,6 +82,7 @@
return event;
}
+// Incorrect event - missing fields
std::shared_ptr<LogEvent> buildIncorrectOverlayEvent(int uid, const std::string& packageName, int state) {
std::shared_ptr<LogEvent> event =
std::make_shared<LogEvent>(android::util::OVERLAY_STATE_CHANGED, 1000 /*timestamp*/);
@@ -91,6 +92,18 @@
event->init();
return event;
}
+
+// Incorrect event - exclusive state has wrong type
+std::shared_ptr<LogEvent> buildOverlayEventBadStateType(int uid, const std::string& packageName) {
+ std::shared_ptr<LogEvent> event =
+ std::make_shared<LogEvent>(android::util::OVERLAY_STATE_CHANGED, 1000 /*timestamp*/);
+ event->write((int32_t)uid);
+ event->write(packageName);
+ event->write(true);
+ event->write("string"); // exclusive state: string instead of int
+ event->init();
+ return event;
+}
// END: build event functions.
// START: get primary key functions
@@ -148,22 +161,22 @@
// Register listener to non-existing StateTracker
EXPECT_EQ(0, mgr.getStateTrackersCount());
- mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
+ EXPECT_TRUE(mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1));
EXPECT_EQ(1, mgr.getStateTrackersCount());
EXPECT_EQ(1, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
// Register listener to existing StateTracker
- mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2);
+ EXPECT_TRUE(mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2));
EXPECT_EQ(1, mgr.getStateTrackersCount());
EXPECT_EQ(2, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
// Register already registered listener to existing StateTracker
- mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2);
+ EXPECT_TRUE(mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener2));
EXPECT_EQ(1, mgr.getStateTrackersCount());
EXPECT_EQ(2, mgr.getListenersCount(android::util::SCREEN_STATE_CHANGED));
// Register listener to non-state atom
- mgr.registerListener(android::util::BATTERY_LEVEL_CHANGED, listener2);
+ EXPECT_FALSE(mgr.registerListener(android::util::BATTERY_LEVEL_CHANGED, listener2));
EXPECT_EQ(1, mgr.getStateTrackersCount());
}
@@ -227,12 +240,12 @@
// check StateTracker was updated by querying for state
HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY;
- EXPECT_EQ(2, mgr.getState(android::util::SCREEN_STATE_CHANGED, queryKey));
+ EXPECT_EQ(2, mgr.getStateValue(android::util::SCREEN_STATE_CHANGED, queryKey));
}
/**
* Test StateManager's onLogEvent and StateListener's onStateChanged correctly
- * updates listener for states with primary keys.
+ * updates listener for states with one primary key.
*/
TEST(StateTrackerTest, TestStateChangeOnePrimaryField) {
sp<TestStateListener> listener1 = new TestStateListener();
@@ -240,9 +253,8 @@
mgr.registerListener(android::util::UID_PROCESS_STATE_CHANGED, listener1);
// log event
- std::shared_ptr<LogEvent> event = buildUidProcessEvent(
- 1000,
- android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002
+ std::shared_ptr<LogEvent> event =
+ buildUidProcessEvent(1000 /* uid */, android::app::ProcessStateEnum::PROCESS_STATE_TOP);
mgr.onLogEvent(*event);
// check listener was updated
@@ -252,23 +264,33 @@
// check StateTracker was updated by querying for state
HashableDimensionKey queryKey;
- getUidProcessKey(1000, &queryKey);
- EXPECT_EQ(1002, mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey));
+ getUidProcessKey(1000 /* uid */, &queryKey);
+ EXPECT_EQ(1002, mgr.getStateValue(android::util::UID_PROCESS_STATE_CHANGED, queryKey));
}
+/**
+ * Test StateManager's onLogEvent and StateListener's onStateChanged correctly
+ * updates listener for states with multiple primary keys.
+ */
TEST(StateTrackerTest, TestStateChangeMultiplePrimaryFields) {
sp<TestStateListener> listener1 = new TestStateListener();
StateManager mgr;
mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener1);
// log event
- std::shared_ptr<LogEvent> event = buildOverlayEvent(1000, "package1", 1); // state: ENTERED
+ std::shared_ptr<LogEvent> event =
+ buildOverlayEvent(1000 /* uid */, "package1", 1); // state: ENTERED
mgr.onLogEvent(*event);
- // check listener update
+ // check listener was updated
EXPECT_EQ(1, listener1->updates.size());
EXPECT_EQ(1000, listener1->updates[0].mKey.getValues()[0].mValue.int_value);
EXPECT_EQ(1, listener1->updates[0].mState);
+
+ // check StateTracker was updated by querying for state
+ HashableDimensionKey queryKey;
+ getOverlayKey(1000 /* uid */, "package1", &queryKey);
+ EXPECT_EQ(1, mgr.getStateValue(android::util::OVERLAY_STATE_CHANGED, queryKey));
}
/**
@@ -282,11 +304,14 @@
mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener1);
// log event
- std::shared_ptr<LogEvent> event =
- buildIncorrectOverlayEvent(1000, "package1", 1); // state: ENTERED
- mgr.onLogEvent(*event);
+ std::shared_ptr<LogEvent> event1 =
+ buildIncorrectOverlayEvent(1000 /* uid */, "package1", 1 /* state */);
+ std::shared_ptr<LogEvent> event2 = buildOverlayEventBadStateType(1001 /* uid */, "package2");
- // check listener update
+ // check listener was updated
+ mgr.onLogEvent(*event1);
+ EXPECT_EQ(0, listener1->updates.size());
+ mgr.onLogEvent(*event2);
EXPECT_EQ(0, listener1->updates.size());
}
@@ -301,18 +326,19 @@
std::shared_ptr<LogEvent> event1 = buildUidProcessEvent(
1000,
- android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002
+ android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002
std::shared_ptr<LogEvent> event2 = buildUidProcessEvent(
1001,
- android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE); // state value: 1003
+ android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE); // state value:
+ // 1003
std::shared_ptr<LogEvent> event3 = buildUidProcessEvent(
1002,
- android::app::ProcessStateEnum::PROCESS_STATE_PERSISTENT); // state value: 1000
+ android::app::ProcessStateEnum::PROCESS_STATE_PERSISTENT); // state value: 1000
std::shared_ptr<LogEvent> event4 = buildUidProcessEvent(
1001,
- android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002
+ android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002
std::shared_ptr<LogEvent> event5 =
- buildScreenEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON); // state value:
+ buildScreenEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON);
std::shared_ptr<LogEvent> event6 = buildOverlayEvent(1000, "package1", 1);
std::shared_ptr<LogEvent> event7 = buildOverlayEvent(1000, "package2", 2);
@@ -327,25 +353,25 @@
// Query for UidProcessState of uid 1001
HashableDimensionKey queryKey1;
getUidProcessKey(1001, &queryKey1);
- EXPECT_EQ(1003, mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
+ EXPECT_EQ(1003, mgr.getStateValue(android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
// Query for UidProcessState of uid 1004 - not in state map
HashableDimensionKey queryKey2;
getUidProcessKey(1004, &queryKey2);
- EXPECT_EQ(-1,
- mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey2)); // default state
+ EXPECT_EQ(-1, mgr.getStateValue(android::util::UID_PROCESS_STATE_CHANGED,
+ queryKey2)); // default state
// Query for UidProcessState of uid 1001 - after change in state
mgr.onLogEvent(*event4);
- EXPECT_EQ(1002, mgr.getState(android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
+ EXPECT_EQ(1002, mgr.getStateValue(android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
// Query for ScreenState
- EXPECT_EQ(2, mgr.getState(android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY));
+ EXPECT_EQ(2, mgr.getStateValue(android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY));
// Query for OverlayState of uid 1000, package name "package2"
HashableDimensionKey queryKey3;
getOverlayKey(1000, "package2", &queryKey3);
- EXPECT_EQ(2, mgr.getState(android::util::OVERLAY_STATE_CHANGED, queryKey3));
+ EXPECT_EQ(2, mgr.getStateValue(android::util::OVERLAY_STATE_CHANGED, queryKey3));
}
} // namespace statsd
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 2c4f3c7..38c22ab 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -251,6 +251,101 @@
return predicate;
}
+State CreateScreenState() {
+ State state;
+ state.set_id(StringToId("ScreenState"));
+ state.set_atom_id(29);
+ return state;
+}
+
+State CreateUidProcessState() {
+ State state;
+ state.set_id(StringToId("UidProcessState"));
+ state.set_atom_id(27);
+ return state;
+}
+
+State CreateOverlayState() {
+ State state;
+ state.set_id(StringToId("OverlayState"));
+ state.set_atom_id(59);
+ return state;
+}
+
+State CreateScreenStateWithOnOffMap() {
+ State state;
+ state.set_id(StringToId("ScreenStateOnOff"));
+ state.set_atom_id(29);
+
+ auto map = CreateScreenStateOnOffMap();
+ *state.mutable_map() = map;
+
+ return state;
+}
+
+State CreateScreenStateWithInDozeMap() {
+ State state;
+ state.set_id(StringToId("ScreenStateInDoze"));
+ state.set_atom_id(29);
+
+ auto map = CreateScreenStateInDozeMap();
+ *state.mutable_map() = map;
+
+ return state;
+}
+
+StateMap_StateGroup CreateScreenStateOnGroup() {
+ StateMap_StateGroup group;
+ group.set_group_id(StringToId("SCREEN_ON"));
+ group.add_value(2);
+ group.add_value(5);
+ group.add_value(6);
+ return group;
+}
+
+StateMap_StateGroup CreateScreenStateOffGroup() {
+ StateMap_StateGroup group;
+ group.set_group_id(StringToId("SCREEN_OFF"));
+ group.add_value(0);
+ group.add_value(1);
+ group.add_value(3);
+ group.add_value(4);
+ return group;
+}
+
+StateMap CreateScreenStateOnOffMap() {
+ StateMap map;
+ *map.add_group() = CreateScreenStateOnGroup();
+ *map.add_group() = CreateScreenStateOffGroup();
+ return map;
+}
+
+StateMap_StateGroup CreateScreenStateInDozeGroup() {
+ StateMap_StateGroup group;
+ group.set_group_id(StringToId("SCREEN_DOZE"));
+ group.add_value(3);
+ group.add_value(4);
+ return group;
+}
+
+StateMap_StateGroup CreateScreenStateNotDozeGroup() {
+ StateMap_StateGroup group;
+ group.set_group_id(StringToId("SCREEN_NOT_DOZE"));
+ group.add_value(0);
+ group.add_value(1);
+ group.add_value(2);
+ group.add_value(5);
+ group.add_value(6);
+ return group;
+}
+
+StateMap CreateScreenStateInDozeMap() {
+ StateMap map;
+ *map.add_group() = CreateScreenStateInDozeGroup();
+ *map.add_group() = CreateScreenStateNotDozeGroup();
+ return map;
+}
+
void addPredicateToPredicateCombination(const Predicate& predicate,
Predicate* combinationPredicate) {
combinationPredicate->mutable_combination()->add_predicate(predicate.id());
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 635c583..c026105 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -104,6 +104,37 @@
// Create a Predicate proto for app is in background.
Predicate CreateIsInBackgroundPredicate();
+// Create State proto for screen state atom.
+State CreateScreenState();
+
+// Create State proto for uid process state atom.
+State CreateUidProcessState();
+
+// Create State proto for overlay state atom.
+State CreateOverlayState();
+
+State CreateScreenStateWithOnOffMap();
+
+State CreateScreenStateWithInDozeMap();
+
+// Create StateGroup proto for ScreenState ON group
+StateMap_StateGroup CreateScreenStateOnGroup();
+
+// Create StateGroup proto for ScreenState OFF group
+StateMap_StateGroup CreateScreenStateOffGroup();
+
+// Create StateMap proto for ScreenState ON/OFF map
+StateMap CreateScreenStateOnOffMap();
+
+// Create StateGroup proto for ScreenState IN DOZE group
+StateMap_StateGroup CreateScreenStateInDozeGroup();
+
+// Create StateGroup proto for ScreenState NOT IN DOZE group
+StateMap_StateGroup CreateScreenStateNotDozeGroup();
+
+// Create StateMap proto for ScreenState IN DOZE map
+StateMap CreateScreenStateInDozeMap();
+
// Add a predicate to the predicate combination.
void addPredicateToPredicateCombination(const Predicate& predicate, Predicate* combination);
@@ -319,4 +350,4 @@
}
} // namespace statsd
} // namespace os
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 38aac1b..7f27368 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1163,6 +1163,10 @@
sendMessage(H.ATTACH_AGENT, agent);
}
+ public void attachStartupAgents(String dataDir) {
+ sendMessage(H.ATTACH_STARTUP_AGENTS, dataDir);
+ }
+
public void setSchedulingGroup(int group) {
// Note: do this immediately, since going into the foreground
// should happen regardless of what pending work we have to do
@@ -1812,6 +1816,7 @@
public static final int EXECUTE_TRANSACTION = 159;
public static final int RELAUNCH_ACTIVITY = 160;
public static final int PURGE_RESOURCES = 161;
+ public static final int ATTACH_STARTUP_AGENTS = 162;
String codeToString(int code) {
if (DEBUG_MESSAGES) {
@@ -1855,6 +1860,7 @@
case EXECUTE_TRANSACTION: return "EXECUTE_TRANSACTION";
case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
case PURGE_RESOURCES: return "PURGE_RESOURCES";
+ case ATTACH_STARTUP_AGENTS: return "ATTACH_STARTUP_AGENTS";
}
}
return Integer.toString(code);
@@ -2043,6 +2049,9 @@
case PURGE_RESOURCES:
schedulePurgeIdler();
break;
+ case ATTACH_STARTUP_AGENTS:
+ handleAttachStartupAgents((String) msg.obj);
+ break;
}
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
@@ -3779,6 +3788,27 @@
}
}
+ static void handleAttachStartupAgents(String dataDir) {
+ try {
+ Path code_cache = ContextImpl.getCodeCacheDirBeforeBind(new File(dataDir)).toPath();
+ if (!Files.exists(code_cache)) {
+ return;
+ }
+ Path startup_path = code_cache.resolve("startup_agents");
+ if (Files.exists(startup_path)) {
+ for (Path p : Files.newDirectoryStream(startup_path)) {
+ handleAttachAgent(
+ p.toAbsolutePath().toString()
+ + "="
+ + dataDir,
+ null);
+ }
+ }
+ } catch (Exception e) {
+ // Ignored.
+ }
+ }
+
private static final ThreadLocal<Intent> sCurrentBroadcastIntent = new ThreadLocal<Intent>();
/**
@@ -6427,26 +6457,6 @@
NetworkSecurityConfigProvider.install(appContext);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-
- if (isAppDebuggable) {
- try {
- // Load all the agents in the code_cache/startup_agents directory.
- // We pass the absolute path to the data_dir as an argument.
- Path startup_path = appContext.getCodeCacheDir().toPath().resolve("startup_agents");
- if (Files.exists(startup_path)) {
- for (Path p : Files.newDirectoryStream(startup_path)) {
- handleAttachAgent(
- p.toAbsolutePath().toString()
- + "="
- + appContext.getDataDir().toPath().toAbsolutePath().toString(),
- data.info);
- }
- }
- } catch (Exception e) {
- // Ignored.
- }
- }
-
// Continue loading instrumentation.
if (ii != null) {
ApplicationInfo instrApp;
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index b56c00e..fbf1f59 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -22,6 +22,7 @@
import static android.view.Display.INVALID_DISPLAY;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.ActivityManager.StackInfo;
import android.content.ComponentName;
@@ -324,16 +325,17 @@
* this method can be called.
*
* @param pendingIntent Intent used to launch an activity.
+ * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()}.
* @param options options for the activity
*
* @see StateCallback
* @see #startActivity(Intent)
*/
- public void startActivity(@NonNull PendingIntent pendingIntent,
+ public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent,
@NonNull ActivityOptions options) {
options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
try {
- pendingIntent.send(null /* context */, 0 /* code */, null /* intent */,
+ pendingIntent.send(getContext(), 0 /* code */, fillInIntent,
null /* onFinished */, null /* handler */, null /* requiredPermission */,
options.toBundle());
} catch (PendingIntent.CanceledException e) {
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index a201307..03ef286 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -3160,6 +3160,15 @@
}
@Override
+ public String[] getTelephonyPackageNames() {
+ try {
+ return mPM.getTelephonyPackageNames();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
public String getSystemCaptionsServicePackageName() {
try {
return mPM.getSystemCaptionsServicePackageName();
@@ -3169,6 +3178,15 @@
}
@Override
+ public String getSetupWizardPackageName() {
+ try {
+ return mPM.getSetupWizardPackageName();
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ @Override
public String getIncidentReportApproverPackageName() {
try {
return mPM.getIncidentReportApproverPackageName();
diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java
index df6533a..241895c 100644
--- a/core/java/android/app/AsyncNotedAppOp.java
+++ b/core/java/android/app/AsyncNotedAppOp.java
@@ -66,14 +66,18 @@
- // Code below generated by codegen v1.0.0.
+ // Code below generated by codegen v1.0.9.
//
// DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/AsyncNotedAppOp.java
//
- // CHECKSTYLE:OFF Generated code
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
/**
* Creates a new AsyncNotedAppOp.
@@ -83,7 +87,8 @@
* @param notingUid
* Uid that noted the op
* @param notingPackageName
- * Package that noted the op
+ * Package that noted the op. {@code null} if the package name that noted the op could be not
+ * be determined (e.g. when the op is noted from native code).
* @param message
* Message associated with the noteOp. This message is set by the app noting the op
* @param time
@@ -127,7 +132,8 @@
}
/**
- * Package that noted the op
+ * Package that noted the op. {@code null} if the package name that noted the op could be not
+ * be determined (e.g. when the op is noted from native code).
*/
@DataClass.Generated.Member
public @Nullable String getNotingPackageName() {
@@ -152,7 +158,7 @@
@Override
@DataClass.Generated.Member
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(AsyncNotedAppOp other) { ... }
// boolean fieldNameEquals(FieldType otherValue) { ... }
@@ -187,7 +193,7 @@
@Override
@DataClass.Generated.Member
- public void writeToParcel(android.os.Parcel dest, int flags) {
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
@@ -205,6 +211,41 @@
@DataClass.Generated.Member
public int describeContents() { return 0; }
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ AsyncNotedAppOp(@NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ int opCode = in.readInt();
+ int notingUid = in.readInt();
+ String notingPackageName = (flg & 0x4) == 0 ? null : in.readString();
+ String message = in.readString();
+ long time = in.readLong();
+
+ this.mOpCode = opCode;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mOpCode,
+ "from", 0,
+ "to", AppOpsManager._NUM_OP - 1);
+ this.mNotingUid = notingUid;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mNotingUid,
+ "from", 0);
+ this.mNotingPackageName = notingPackageName;
+ this.mMessage = message;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mMessage);
+ this.mTime = time;
+ com.android.internal.util.AnnotationValidations.validate(
+ IntRange.class, null, mTime,
+ "from", 0);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
@DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<AsyncNotedAppOp> CREATOR
= new Parcelable.Creator<AsyncNotedAppOp>() {
@@ -214,29 +255,14 @@
}
@Override
- @SuppressWarnings({"unchecked", "RedundantCast"})
- public AsyncNotedAppOp createFromParcel(android.os.Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- byte flg = in.readByte();
- int opCode = in.readInt();
- int notingUid = in.readInt();
- String notingPackageName = (flg & 0x4) == 0 ? null : in.readString();
- String message = in.readString();
- long time = in.readLong();
- return new AsyncNotedAppOp(
- opCode,
- notingUid,
- notingPackageName,
- message,
- time);
+ public AsyncNotedAppOp createFromParcel(@NonNull android.os.Parcel in) {
+ return new AsyncNotedAppOp(in);
}
};
@DataClass.Generated(
- time = 1566503083973L,
- codegenVersion = "1.0.0",
+ time = 1571246617363L,
+ codegenVersion = "1.0.9",
sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java",
inputSignatures = "private final @android.annotation.IntRange(from=0L, to=91L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mNotingPackageName\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
@Deprecated
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ef23d5e..d5bc9b0 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -741,12 +741,21 @@
public File getCodeCacheDir() {
synchronized (mSync) {
if (mCodeCacheDir == null) {
- mCodeCacheDir = new File(getDataDir(), "code_cache");
+ mCodeCacheDir = getCodeCacheDirBeforeBind(getDataDir());
}
return ensurePrivateCacheDirExists(mCodeCacheDir, XATTR_INODE_CODE_CACHE);
}
}
+ /**
+ * Helper for getting code-cache dir potentially before application bind.
+ *
+ * @hide
+ */
+ static File getCodeCacheDirBeforeBind(File dataDir) {
+ return new File(dataDir, "code_cache");
+ }
+
@Override
public File getExternalCacheDir() {
// Operates on primary external storage
@@ -2203,6 +2212,15 @@
}
@Override
+ public Context createContextAsUser(UserHandle user, @CreatePackageOptions int flags) {
+ try {
+ return createPackageContextAsUser(getPackageName(), flags, user);
+ } catch (NameNotFoundException e) {
+ throw new IllegalStateException("Own package not found: package=" + getPackageName());
+ }
+ }
+
+ @Override
public Context createContextForSplit(String splitName) throws NameNotFoundException {
if (!mPackageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) {
// All Splits are always loaded.
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index dda3bb5..f591441 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -215,7 +215,6 @@
void releaseSomeActivities(in IApplicationThread app);
Bitmap getTaskDescriptionIcon(in String filename, int userId);
- void startInPlaceAnimationOnFrontMostApplication(in Bundle opts);
void registerTaskStackListener(in ITaskStackListener listener);
void unregisterTaskStackListener(in ITaskStackListener listener);
void setTaskResizeable(int taskId, int resizeableMode);
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index cfa065b..51a64ff 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -137,6 +137,7 @@
IVoiceInteractor voiceInteractor);
void handleTrustStorageUpdate();
void attachAgent(String path);
+ void attachStartupAgents(String dataDir);
void scheduleApplicationInfoChanged(in ApplicationInfo ai);
void setNetworkBlockSeq(long procStateSeq);
void scheduleTransaction(in ClientTransaction transaction);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 316cab8..58f6741 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -997,8 +997,13 @@
}
/**
- * @hide
+ * <p>
+ * Gets the currently applied notification policy. If {@link #getCurrentInterruptionFilter}
+ * is equal to {@link #INTERRUPTION_FILTER_ALL}, then the consolidated notification policy
+ * will match the default notification policy returned by {@link #getNotificationPolicy}.
+ * </p>
*/
+ @Nullable
public NotificationManager.Policy getConsolidatedNotificationPolicy() {
INotificationManager service = getService();
try {
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index cb9ebac..68ab89c 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -32,6 +32,7 @@
import android.content.res.Resources;
import android.content.res.ResourcesImpl;
import android.content.res.ResourcesKey;
+import android.content.res.loader.ResourceLoader;
import android.hardware.display.DisplayManagerGlobal;
import android.os.IBinder;
import android.os.Process;
@@ -45,6 +46,7 @@
import android.view.Display;
import android.view.DisplayAdjustments;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
@@ -53,7 +55,11 @@
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap;
import java.util.function.Predicate;
@@ -92,6 +98,52 @@
new ArrayMap<>();
/**
+ * A list of {@link Resources} that contain unique {@link ResourcesImpl}s.
+ *
+ * These are isolated so that {@link ResourceLoader}s can be added and removed without
+ * affecting other instances.
+ *
+ * When a reference is added here, it is guaranteed that the {@link ResourcesImpl}
+ * it contains is unique to itself and will never be set to a shared reference.
+ */
+ @GuardedBy("this")
+ private List<ResourcesWithLoaders> mResourcesWithLoaders = Collections.emptyList();
+
+ private static class ResourcesWithLoaders {
+
+ private WeakReference<Resources> mResources;
+ private ResourcesKey mResourcesKey;
+
+ @Nullable
+ private WeakReference<IBinder> mActivityToken;
+
+ ResourcesWithLoaders(Resources resources, ResourcesKey resourcesKey,
+ IBinder activityToken) {
+ this.mResources = new WeakReference<>(resources);
+ this.mResourcesKey = resourcesKey;
+ this.mActivityToken = new WeakReference<>(activityToken);
+ }
+
+ @Nullable
+ Resources resources() {
+ return mResources.get();
+ }
+
+ @Nullable
+ IBinder activityToken() {
+ return mActivityToken == null ? null : mActivityToken.get();
+ }
+
+ ResourcesKey resourcesKey() {
+ return mResourcesKey;
+ }
+
+ void updateKey(ResourcesKey newKey) {
+ mResourcesKey = newKey;
+ }
+ }
+
+ /**
* A list of Resource references that can be reused.
*/
@UnsupportedAppUsage
@@ -182,15 +234,36 @@
public void invalidatePath(String path) {
synchronized (this) {
int count = 0;
- for (int i = 0; i < mResourceImpls.size();) {
+
+ for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
final ResourcesKey key = mResourceImpls.keyAt(i);
if (key.isPathReferenced(path)) {
- cleanupResourceImpl(key);
+ ResourcesImpl impl = mResourceImpls.removeAt(i).get();
+ if (impl != null) {
+ impl.flushLayoutCache();
+ }
count++;
- } else {
- i++;
}
}
+
+ for (int i = mResourcesWithLoaders.size() - 1; i >= 0; i--) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(i);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ continue;
+ }
+
+ final ResourcesKey key = resourcesWithLoaders.resourcesKey();
+ if (key.isPathReferenced(path)) {
+ mResourcesWithLoaders.remove(i);
+ ResourcesImpl impl = resources.getImpl();
+ if (impl != null) {
+ impl.flushLayoutCache();
+ }
+ count++;
+ }
+ }
+
Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path);
for (int i = mCachedApkAssets.size() - 1; i >= 0; i--) {
@@ -317,15 +390,6 @@
}
}
- private void cleanupResourceImpl(ResourcesKey removedKey) {
- // Remove resource key to resource impl mapping and flush cache
- final ResourcesImpl res = mResourceImpls.remove(removedKey).get();
-
- if (res != null) {
- res.flushLayoutCache();
- }
- }
-
private static String overlayPathToIdmapPath(String path) {
return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap";
}
@@ -499,6 +563,16 @@
pw.print("resource impls: ");
pw.println(countLiveReferences(mResourceImpls.values()));
+
+ int resourcesWithLoadersCount = 0;
+ for (int index = 0; index < mResourcesWithLoaders.size(); index++) {
+ if (mResourcesWithLoaders.get(index).resources() != null) {
+ resourcesWithLoadersCount++;
+ }
+ }
+
+ pw.print("resources with loaders: ");
+ pw.println(resourcesWithLoadersCount);
}
}
@@ -579,11 +653,24 @@
*/
private @Nullable ResourcesKey findKeyForResourceImplLocked(
@NonNull ResourcesImpl resourceImpl) {
- final int refCount = mResourceImpls.size();
+ int size = mResourcesWithLoaders.size();
+ for (int index = 0; index < size; index++) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ continue;
+ }
+
+ if (resourceImpl == resources.getImpl()) {
+ return resourcesWithLoaders.resourcesKey();
+ }
+ }
+
+ int refCount = mResourceImpls.size();
for (int i = 0; i < refCount; i++) {
WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
- if (impl != null && resourceImpl == impl) {
+ if (resourceImpl == impl) {
return mResourceImpls.keyAt(i);
}
}
@@ -625,31 +712,55 @@
return activityResources;
}
- /**
- * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist
- * or the class loader is different.
- */
- private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
+ @Nullable
+ private Resources findResourcesForActivityLocked(@NonNull IBinder targetActivityToken,
+ @NonNull ResourcesKey targetKey, @NonNull ClassLoader targetClassLoader) {
+ int size = mResourcesWithLoaders.size();
+ for (int index = 0; index < size; index++) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ continue;
+ }
+
+ IBinder activityToken = resourcesWithLoaders.activityToken();
+ ResourcesKey key = resourcesWithLoaders.resourcesKey();
+
+ ClassLoader classLoader = resources.getClassLoader();
+
+ if (Objects.equals(activityToken, targetActivityToken)
+ && Objects.equals(key, targetKey)
+ && Objects.equals(classLoader, targetClassLoader)) {
+ return resources;
+ }
+ }
+
+ ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
+ targetActivityToken);
+
+ size = activityResources.activityResources.size();
+ for (int index = 0; index < size; index++) {
+ WeakReference<Resources> ref = activityResources.activityResources.get(index);
+ Resources resources = ref.get();
+ ResourcesKey key = resources == null ? null : findKeyForResourceImplLocked(
+ resources.getImpl());
+
+ if (key != null
+ && Objects.equals(resources.getClassLoader(), targetClassLoader)
+ && Objects.equals(key, targetKey)) {
+ return resources;
+ }
+ }
+
+ return null;
+ }
+
+ private @NonNull Resources createResourcesForActivityLocked(@NonNull IBinder activityToken,
@NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
@NonNull CompatibilityInfo compatInfo) {
final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
activityToken);
- final int refCount = activityResources.activityResources.size();
- for (int i = 0; i < refCount; i++) {
- WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i);
- Resources resources = weakResourceRef.get();
-
- if (resources != null
- && Objects.equals(resources.getClassLoader(), classLoader)
- && resources.getImpl() == impl) {
- if (DEBUG) {
- Slog.d(TAG, "- using existing ref=" + resources);
- }
- return resources;
- }
- }
-
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
resources.setImpl(impl);
@@ -661,28 +772,8 @@
return resources;
}
- /**
- * Gets an existing Resources object if the class loader and ResourcesImpl are the same,
- * otherwise creates a new Resources object.
- */
- private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
+ private @NonNull Resources createResourcesLocked(@NonNull ClassLoader classLoader,
@NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
- // Find an existing Resources that has this ResourcesImpl set.
- final int refCount = mResourceReferences.size();
- for (int i = 0; i < refCount; i++) {
- WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
- Resources resources = weakResourceRef.get();
- if (resources != null &&
- Objects.equals(resources.getClassLoader(), classLoader) &&
- resources.getImpl() == impl) {
- if (DEBUG) {
- Slog.d(TAG, "- using existing ref=" + resources);
- }
- return resources;
- }
- }
-
- // Create a new Resources reference and use the existing ResourcesImpl object.
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
resources.setImpl(impl);
@@ -750,16 +841,73 @@
updateResourcesForActivity(activityToken, overrideConfig, displayId,
false /* movedToDifferentDisplay */);
+ cleanupReferences(activityToken);
+ rebaseKeyForActivity(activityToken, key);
+
+ synchronized (this) {
+ Resources resources = findResourcesForActivityLocked(activityToken, key,
+ classLoader);
+ if (resources != null) {
+ return resources;
+ }
+ }
+
// Now request an actual Resources object.
- return getOrCreateResources(activityToken, key, classLoader);
+ return createResources(activityToken, key, classLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
/**
- * Gets an existing Resources object set with a ResourcesImpl object matching the given key,
- * or creates one if it doesn't exist.
+ * Rebases a key's override config on top of the Activity's base override.
+ */
+ private void rebaseKeyForActivity(IBinder activityToken, ResourcesKey key) {
+ final ActivityResources activityResources =
+ getOrCreateActivityResourcesStructLocked(activityToken);
+
+ // Clean up any dead references so they don't pile up.
+ ArrayUtils.unstableRemoveIf(activityResources.activityResources,
+ sEmptyReferencePredicate);
+
+ // Rebase the key's override config on top of the Activity's base override.
+ if (key.hasOverrideConfiguration()
+ && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
+ final Configuration temp = new Configuration(activityResources.overrideConfig);
+ temp.updateFrom(key.mOverrideConfiguration);
+ key.mOverrideConfiguration.setTo(temp);
+ }
+ }
+
+ /**
+ * Check WeakReferences and remove any dead references so they don't pile up.
+ * @param activityToken optional token to clean up Activity resources
+ */
+ private void cleanupReferences(IBinder activityToken) {
+ synchronized (this) {
+ if (activityToken != null) {
+ ActivityResources activityResources = mActivityResourceReferences.get(
+ activityToken);
+ if (activityResources != null) {
+ ArrayUtils.unstableRemoveIf(activityResources.activityResources,
+ sEmptyReferencePredicate);
+ }
+ } else {
+ ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
+ }
+
+ for (int index = mResourcesWithLoaders.size() - 1; index >= 0; index--) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ mResourcesWithLoaders.remove(index);
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a Resources object set with a ResourcesImpl object matching the given key.
*
* @param activityToken The Activity this Resources object should be associated with.
* @param key The key describing the parameters of the ResourcesImpl object.
@@ -769,7 +917,7 @@
* {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)}
* is called.
*/
- private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
+ private @Nullable Resources createResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
synchronized (this) {
if (DEBUG) {
@@ -778,66 +926,17 @@
Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
}
- if (activityToken != null) {
- final ActivityResources activityResources =
- getOrCreateActivityResourcesStructLocked(activityToken);
-
- // Clean up any dead references so they don't pile up.
- ArrayUtils.unstableRemoveIf(activityResources.activityResources,
- sEmptyReferencePredicate);
-
- // Rebase the key's override config on top of the Activity's base override.
- if (key.hasOverrideConfiguration()
- && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
- final Configuration temp = new Configuration(activityResources.overrideConfig);
- temp.updateFrom(key.mOverrideConfiguration);
- key.mOverrideConfiguration.setTo(temp);
- }
-
- ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
- if (resourcesImpl != null) {
- if (DEBUG) {
- Slog.d(TAG, "- using existing impl=" + resourcesImpl);
- }
- return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
- resourcesImpl, key.mCompatInfo);
- }
-
- // We will create the ResourcesImpl object outside of holding this lock.
-
- } else {
- // Clean up any dead references so they don't pile up.
- ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
-
- // Not tied to an Activity, find a shared Resources that has the right ResourcesImpl
- ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
- if (resourcesImpl != null) {
- if (DEBUG) {
- Slog.d(TAG, "- using existing impl=" + resourcesImpl);
- }
- return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
- }
-
- // We will create the ResourcesImpl object outside of holding this lock.
- }
-
- // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
- ResourcesImpl resourcesImpl = createResourcesImpl(key);
+ ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key);
if (resourcesImpl == null) {
return null;
}
- // Add this ResourcesImpl to the cache.
- mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
-
- final Resources resources;
if (activityToken != null) {
- resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
+ return createResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
} else {
- resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
+ return createResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
- return resources;
}
}
@@ -868,7 +967,8 @@
* {@link ClassLoader#getSystemClassLoader()} is used.
* @return a Resources object from which to access resources.
*/
- public @Nullable Resources getResources(@Nullable IBinder activityToken,
+ public @Nullable Resources getResources(
+ @Nullable IBinder activityToken,
@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
@@ -888,7 +988,14 @@
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
compatInfo);
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
- return getOrCreateResources(activityToken, key, classLoader);
+
+ cleanupReferences(activityToken);
+
+ if (activityToken != null) {
+ rebaseKeyForActivity(activityToken, key);
+ }
+
+ return createResources(activityToken, key, classLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
@@ -944,67 +1051,40 @@
here);
}
- final boolean activityHasOverrideConfig =
- !activityResources.overrideConfig.equals(Configuration.EMPTY);
// Rebase each Resources associated with this Activity.
final int refCount = activityResources.activityResources.size();
for (int i = 0; i < refCount; i++) {
WeakReference<Resources> weakResRef = activityResources.activityResources.get(
i);
+
Resources resources = weakResRef.get();
if (resources == null) {
continue;
}
- // Extract the ResourcesKey that was last used to create the Resources for this
- // activity.
- final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
- if (oldKey == null) {
- Slog.e(TAG, "can't find ResourcesKey for resources impl="
- + resources.getImpl());
+ ResourcesKey newKey = rebaseActivityOverrideConfig(resources, oldConfig,
+ overrideConfig, displayId);
+ updateActivityResources(resources, newKey, false);
+ }
+
+ // Also find loaders that are associated with an Activity
+ final int loaderCount = mResourcesWithLoaders.size();
+ for (int index = loaderCount - 1; index >= 0; index--) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(
+ index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null
+ || resourcesWithLoaders.activityToken() != activityToken) {
continue;
}
- // Build the new override configuration for this ResourcesKey.
- final Configuration rebasedOverrideConfig = new Configuration();
- if (overrideConfig != null) {
- rebasedOverrideConfig.setTo(overrideConfig);
- }
+ ResourcesKey newKey = rebaseActivityOverrideConfig(resources, oldConfig,
+ overrideConfig, displayId);
- if (activityHasOverrideConfig && oldKey.hasOverrideConfiguration()) {
- // Generate a delta between the old base Activity override configuration and
- // the actual final override configuration that was used to figure out the
- // real delta this Resources object wanted.
- Configuration overrideOverrideConfig = Configuration.generateDelta(
- oldConfig, oldKey.mOverrideConfiguration);
- rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
- }
+ updateActivityResources(resources, newKey, true);
- // Create the new ResourcesKey with the rebased override config.
- final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
- oldKey.mSplitResDirs,
- oldKey.mOverlayDirs, oldKey.mLibDirs, displayId,
- rebasedOverrideConfig, oldKey.mCompatInfo);
-
- if (DEBUG) {
- Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
- + " to newKey=" + newKey + ", displayId=" + displayId);
- }
-
- ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey);
- if (resourcesImpl == null) {
- resourcesImpl = createResourcesImpl(newKey);
- if (resourcesImpl != null) {
- mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl));
- }
- }
-
- if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
- // Set the ResourcesImpl, updating it for all users of this Resources
- // object.
- resources.setImpl(resourcesImpl);
- }
+ resourcesWithLoaders.updateKey(newKey);
}
}
} finally {
@@ -1012,6 +1092,70 @@
}
}
+ /**
+ * Rebases an updated override config over any old override config and returns the new one
+ * that an Activity's Resources should be set to.
+ */
+ private ResourcesKey rebaseActivityOverrideConfig(Resources resources,
+ Configuration oldOverrideConfig, @Nullable Configuration newOverrideConfig,
+ int displayId) {
+ // Extract the ResourcesKey that was last used to create the Resources for this
+ // activity.
+ final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
+ if (oldKey == null) {
+ Slog.e(TAG, "can't find ResourcesKey for resources impl="
+ + resources.getImpl());
+ return null;
+ }
+
+ // Build the new override configuration for this ResourcesKey.
+ final Configuration rebasedOverrideConfig = new Configuration();
+ if (newOverrideConfig != null) {
+ rebasedOverrideConfig.setTo(newOverrideConfig);
+ }
+
+ final boolean hadOverrideConfig = !oldOverrideConfig.equals(Configuration.EMPTY);
+ if (hadOverrideConfig && oldKey.hasOverrideConfiguration()) {
+ // Generate a delta between the old base Activity override configuration and
+ // the actual final override configuration that was used to figure out the
+ // real delta this Resources object wanted.
+ Configuration overrideOverrideConfig = Configuration.generateDelta(
+ oldOverrideConfig, oldKey.mOverrideConfiguration);
+ rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
+ }
+
+ // Create the new ResourcesKey with the rebased override config.
+ final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
+ oldKey.mSplitResDirs,
+ oldKey.mOverlayDirs, oldKey.mLibDirs, displayId,
+ rebasedOverrideConfig, oldKey.mCompatInfo);
+
+ if (DEBUG) {
+ Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
+ + " to newKey=" + newKey + ", displayId=" + displayId);
+ }
+
+ return newKey;
+ }
+
+ private void updateActivityResources(Resources resources, ResourcesKey newKey,
+ boolean hasLoader) {
+ final ResourcesImpl resourcesImpl;
+
+ if (hasLoader) {
+ // Loaders always get new Impls because they cannot be shared
+ resourcesImpl = createResourcesImpl(newKey);
+ } else {
+ resourcesImpl = findOrCreateResourcesImplForKeyLocked(newKey);
+ }
+
+ if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
+ // Set the ResourcesImpl, updating it for all users of this Resources
+ // object.
+ resources.setImpl(resourcesImpl);
+ }
+ }
+
@TestApi
public final boolean applyConfigurationToResources(@NonNull Configuration config,
@Nullable CompatibilityInfo compat) {
@@ -1050,61 +1194,77 @@
ApplicationPackageManager.configurationChanged();
//Slog.i(TAG, "Configuration changed in " + currentPackageName());
- Configuration tmpConfig = null;
+ Configuration tmpConfig = new Configuration();
for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
ResourcesKey key = mResourceImpls.keyAt(i);
WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
ResourcesImpl r = weakImplRef != null ? weakImplRef.get() : null;
if (r != null) {
- if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
- + r + " config to: " + config);
- int displayId = key.mDisplayId;
- boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
- DisplayMetrics dm = defaultDisplayMetrics;
- final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
- if (!isDefaultDisplay || hasOverrideConfiguration) {
- if (tmpConfig == null) {
- tmpConfig = new Configuration();
- }
- tmpConfig.setTo(config);
-
- // Get new DisplayMetrics based on the DisplayAdjustments given
- // to the ResourcesImpl. Update a copy if the CompatibilityInfo
- // changed, because the ResourcesImpl object will handle the
- // update internally.
- DisplayAdjustments daj = r.getDisplayAdjustments();
- if (compat != null) {
- daj = new DisplayAdjustments(daj);
- daj.setCompatibilityInfo(compat);
- }
- dm = getDisplayMetrics(displayId, daj);
-
- if (!isDefaultDisplay) {
- applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
- }
-
- if (hasOverrideConfiguration) {
- tmpConfig.updateFrom(key.mOverrideConfiguration);
- }
- r.updateConfiguration(tmpConfig, dm, compat);
- } else {
- r.updateConfiguration(config, dm, compat);
- }
- //Slog.i(TAG, "Updated app resources " + v.getKey()
- // + " " + r + ": " + r.getConfiguration());
+ applyConfigurationToResourcesLocked(config, compat, tmpConfig,
+ defaultDisplayMetrics, key, r);
} else {
- //Slog.i(TAG, "Removing old resources " + v.getKey());
mResourceImpls.removeAt(i);
}
}
+ for (int index = mResourcesWithLoaders.size() - 1; index >= 0; index--) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ mResourcesWithLoaders.remove(index);
+ continue;
+ }
+
+ applyConfigurationToResourcesLocked(config, compat, tmpConfig,
+ defaultDisplayMetrics, resourcesWithLoaders.resourcesKey(),
+ resources.getImpl());
+ }
+
return changes != 0;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
+ private void applyConfigurationToResourcesLocked(@NonNull Configuration config,
+ @Nullable CompatibilityInfo compat, Configuration tmpConfig,
+ DisplayMetrics defaultDisplayMetrics, ResourcesKey key, ResourcesImpl resourcesImpl) {
+ if (DEBUG || DEBUG_CONFIGURATION) {
+ Slog.v(TAG, "Changing resources "
+ + resourcesImpl + " config to: " + config);
+ }
+ int displayId = key.mDisplayId;
+ boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
+ DisplayMetrics dm = defaultDisplayMetrics;
+ final boolean hasOverrideConfiguration = key.hasOverrideConfiguration();
+ if (!isDefaultDisplay || hasOverrideConfiguration) {
+ tmpConfig.setTo(config);
+
+ // Get new DisplayMetrics based on the DisplayAdjustments given
+ // to the ResourcesImpl. Update a copy if the CompatibilityInfo
+ // changed, because the ResourcesImpl object will handle the
+ // update internally.
+ DisplayAdjustments daj = resourcesImpl.getDisplayAdjustments();
+ if (compat != null) {
+ daj = new DisplayAdjustments(daj);
+ daj.setCompatibilityInfo(compat);
+ }
+ dm = getDisplayMetrics(displayId, daj);
+
+ if (!isDefaultDisplay) {
+ applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
+ }
+
+ if (hasOverrideConfiguration) {
+ tmpConfig.updateFrom(key.mOverrideConfiguration);
+ }
+ resourcesImpl.updateConfiguration(tmpConfig, dm, compat);
+ } else {
+ resourcesImpl.updateConfiguration(config, dm, compat);
+ }
+ }
+
/**
* Appends the library asset path to any ResourcesImpl object that contains the main
* assetPath.
@@ -1140,7 +1300,7 @@
ArrayUtils.appendElement(String.class, newLibAssets, libAsset);
}
- if (newLibAssets != key.mLibDirs) {
+ if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
updatedResourceKeys.put(impl, new ResourcesKey(
key.mResDir,
key.mSplitResDirs,
@@ -1153,10 +1313,106 @@
}
}
+ final int count = mResourcesWithLoaders.size();
+ for (int index = 0; index < count; index++) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ continue;
+ }
+
+ ResourcesKey key = resourcesWithLoaders.resourcesKey();
+ if (Objects.equals(key.mResDir, assetPath)) {
+ String[] newLibAssets = key.mLibDirs;
+ for (String libAsset : libAssets) {
+ newLibAssets =
+ ArrayUtils.appendElement(String.class, newLibAssets, libAsset);
+ }
+
+ if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
+ updatedResourceKeys.put(resources.getImpl(),
+ new ResourcesKey(
+ key.mResDir,
+ key.mSplitResDirs,
+ key.mOverlayDirs,
+ newLibAssets,
+ key.mDisplayId,
+ key.mOverrideConfiguration,
+ key.mCompatInfo));
+ }
+ }
+ }
+
redirectResourcesToNewImplLocked(updatedResourceKeys);
}
}
+ /**
+ * Mark a {@link Resources} as containing a {@link ResourceLoader}.
+ *
+ * This removes its {@link ResourcesImpl} from the shared pool and creates it a new one. It is
+ * then tracked separately, kept unique, and restored properly across {@link Activity}
+ * recreations.
+ */
+ public void registerForLoaders(Resources resources) {
+ synchronized (this) {
+ boolean found = false;
+ IBinder activityToken = null;
+
+ // Remove the Resources from the reference list as it's now tracked separately
+ int size = mResourceReferences.size();
+ for (int index = 0; index < size; index++) {
+ WeakReference<Resources> reference = mResourceReferences.get(index);
+ if (reference.get() == resources) {
+ mResourceReferences.remove(index);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ // Do the same removal for any Activity Resources
+ for (Map.Entry<IBinder, ActivityResources> entry :
+ mActivityResourceReferences.entrySet()) {
+ ArrayList<WeakReference<Resources>> activityResourcesList =
+ entry.getValue().activityResources;
+ final int resCount = activityResourcesList.size();
+ for (int index = 0; index < resCount; index++) {
+ WeakReference<Resources> reference = activityResourcesList.get(index);
+ if (reference.get() == resources) {
+ activityToken = entry.getKey();
+ activityResourcesList.remove(index);
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ throw new IllegalArgumentException("Resources " + resources
+ + " registered for loaders but was not previously tracked by"
+ + " ResourcesManager");
+ }
+
+ ResourcesKey key = findKeyForResourceImplLocked(resources.getImpl());
+ ResourcesImpl impl = createResourcesImpl(key);
+
+ if (mResourcesWithLoaders == Collections.EMPTY_LIST) {
+ mResourcesWithLoaders = Collections.synchronizedList(new ArrayList<>());
+ }
+
+ mResourcesWithLoaders.add(new ResourcesWithLoaders(resources, key, activityToken));
+
+ // Set the new Impl, which is now guaranteed to be unique per Resources object
+ resources.setImpl(impl);
+ }
+ }
+
// TODO(adamlesinski): Make this accept more than just overlay directories.
final void applyNewResourceDirsLocked(@NonNull final ApplicationInfo appInfo,
@Nullable final String[] oldPaths) {
@@ -1201,6 +1457,32 @@
}
}
+ final int count = mResourcesWithLoaders.size();
+ for (int index = 0; index < count; index++) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ continue;
+ }
+
+ ResourcesKey key = resourcesWithLoaders.resourcesKey();
+
+ if (key.mResDir == null
+ || key.mResDir.equals(baseCodePath)
+ || ArrayUtils.contains(oldPaths, key.mResDir)) {
+ updatedResourceKeys.put(resources.getImpl(),
+ new ResourcesKey(
+ baseCodePath,
+ copiedSplitDirs,
+ copiedResourceDirs,
+ key.mLibDirs,
+ key.mDisplayId,
+ key.mOverrideConfiguration,
+ key.mCompatInfo
+ ));
+ }
+ }
+
redirectResourcesToNewImplLocked(updatedResourceKeys);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
@@ -1250,5 +1532,25 @@
}
}
}
+
+ // Update any references that need to be re-built with loaders. These are intentionally not
+ // part of either of the above lists.
+ final int loaderCount = mResourcesWithLoaders.size();
+ for (int index = loaderCount - 1; index >= 0; index--) {
+ ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
+ Resources resources = resourcesWithLoaders.resources();
+ if (resources == null) {
+ continue;
+ }
+
+ ResourcesKey newKey = updatedResourceKeys.get(resources.getImpl());
+ if (newKey == null) {
+ continue;
+ }
+
+ resourcesWithLoaders.updateKey(newKey);
+ resourcesWithLoaders.resources()
+ .setImpl(createResourcesImpl(newKey));
+ }
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index c3c383c..8ea1ff5 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2330,6 +2330,12 @@
"android.app.action.ADMIN_POLICY_COMPLIANCE";
/**
+ * Maximum supported password length. Kind-of arbitrary.
+ * @hide
+ */
+ public static final int MAX_PASSWORD_LENGTH = 16;
+
+ /**
* Return true if the given administrator component is currently active (enabled) in the system.
*
* @param admin The administrator component to check for.
@@ -3233,6 +3239,22 @@
}
/**
+ * Returns minimum PasswordMetrics that satisfies all admin policies.
+ *
+ * @hide
+ */
+ public PasswordMetrics getPasswordMinimumMetrics(@UserIdInt int userHandle) {
+ if (mService != null) {
+ try {
+ return mService.getPasswordMinimumMetrics(userHandle);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* Called by an application that is administering the device to set the length of the password
* history. After setting this, the user will not be able to enter a new password that is the
* same as any password in the history. Note that the current password will remain until the
@@ -3415,8 +3437,7 @@
if (!pm.hasSystemFeature(PackageManager.FEATURE_SECURE_LOCK_SCREEN)) {
return 0;
}
- // Kind-of arbitrary.
- return 16;
+ return MAX_PASSWORD_LENGTH;
}
/**
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 0da5b7a..7d2c54e 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -72,6 +72,8 @@
void setPasswordMinimumNonLetter(in ComponentName who, int length, boolean parent);
int getPasswordMinimumNonLetter(in ComponentName who, int userHandle, boolean parent);
+ PasswordMetrics getPasswordMinimumMetrics(int userHandle);
+
void setPasswordHistoryLength(in ComponentName who, int length, boolean parent);
int getPasswordHistoryLength(in ComponentName who, int userHandle, boolean parent);
diff --git a/core/java/android/app/admin/PasswordMetrics.java b/core/java/android/app/admin/PasswordMetrics.java
index 9929855..d9bfde5 100644
--- a/core/java/android/app/admin/PasswordMetrics.java
+++ b/core/java/android/app/admin/PasswordMetrics.java
@@ -16,45 +16,65 @@
package android.app.admin;
+import static android.app.admin.DevicePolicyManager.MAX_PASSWORD_LENGTH;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+import static com.android.internal.widget.LockPatternUtils.MIN_LOCK_PASSWORD_SIZE;
+import static com.android.internal.widget.PasswordValidationError.CONTAINS_INVALID_CHARACTERS;
+import static com.android.internal.widget.PasswordValidationError.CONTAINS_SEQUENCE;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_DIGITS;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_LETTERS;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_LOWER_CASE;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_NON_DIGITS;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_NON_LETTER;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_SYMBOLS;
+import static com.android.internal.widget.PasswordValidationError.NOT_ENOUGH_UPPER_CASE;
+import static com.android.internal.widget.PasswordValidationError.TOO_LONG;
+import static com.android.internal.widget.PasswordValidationError.TOO_SHORT;
+import static com.android.internal.widget.PasswordValidationError.WEAK_CREDENTIAL_TYPE;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils.CredentialType;
+import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.PasswordValidationError;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
/**
* A class that represents the metrics of a credential that are used to decide whether or not a
- * credential meets the requirements. If the credential is a pattern, only quality matters.
+ * credential meets the requirements.
*
* {@hide}
*/
-public class PasswordMetrics implements Parcelable {
+public final class PasswordMetrics implements Parcelable {
+ private static final String TAG = "PasswordMetrics";
+
// Maximum allowed number of repeated or ordered characters in a sequence before we'll
// consider it a complex PIN/password.
public static final int MAX_ALLOWED_SEQUENCE = 3;
- public int quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+ public @CredentialType int credType;
+ // Fields below only make sense when credType is PASSWORD.
public int length = 0;
public int letters = 0;
public int upperCase = 0;
@@ -62,139 +82,62 @@
public int numeric = 0;
public int symbols = 0;
public int nonLetter = 0;
+ public int nonNumeric = 0;
+ // MAX_VALUE is the most relaxed value, any sequence is ok, e.g. 123456789. 4 would forbid it.
+ public int seqLength = Integer.MAX_VALUE;
- public PasswordMetrics() {}
-
- public PasswordMetrics(int quality) {
- this.quality = quality;
+ public PasswordMetrics(int credType) {
+ this.credType = credType;
}
- public PasswordMetrics(int quality, int length) {
- this.quality = quality;
+ public PasswordMetrics(int credType , int length, int letters, int upperCase, int lowerCase,
+ int numeric, int symbols, int nonLetter, int nonNumeric, int seqLength) {
+ this.credType = credType;
this.length = length;
- }
-
- public PasswordMetrics(int quality, int length, int letters, int upperCase, int lowerCase,
- int numeric, int symbols, int nonLetter) {
- this(quality, length);
this.letters = letters;
this.upperCase = upperCase;
this.lowerCase = lowerCase;
this.numeric = numeric;
this.symbols = symbols;
this.nonLetter = nonLetter;
+ this.nonNumeric = nonNumeric;
+ this.seqLength = seqLength;
}
- private PasswordMetrics(Parcel in) {
- quality = in.readInt();
- length = in.readInt();
- letters = in.readInt();
- upperCase = in.readInt();
- lowerCase = in.readInt();
- numeric = in.readInt();
- symbols = in.readInt();
- nonLetter = in.readInt();
- }
-
- /** Returns the min quality allowed by {@code complexityLevel}. */
- public static int complexityLevelToMinQuality(@PasswordComplexity int complexityLevel) {
- // this would be the quality of the first metrics since mMetrics is sorted in ascending
- // order of quality
- return PasswordComplexityBucket
- .complexityLevelToBucket(complexityLevel).mMetrics[0].quality;
- }
-
- /**
- * Returns a merged minimum {@link PasswordMetrics} requirements that a new password must meet
- * to fulfil {@code requestedQuality}, {@code requiresNumeric} and {@code
- * requiresLettersOrSymbols}, which are derived from {@link DevicePolicyManager} requirements,
- * and {@code complexityLevel}.
- *
- * <p>Note that we are taking {@code userEnteredPasswordQuality} into account because there are
- * more than one set of metrics to meet the minimum complexity requirement and inspecting what
- * the user has entered can help determine whether the alphabetic or alphanumeric set of metrics
- * should be used. For example, suppose minimum complexity requires either ALPHABETIC(8+), or
- * ALPHANUMERIC(6+). If the user has entered "a", the length requirement displayed on the UI
- * would be 8. Then the user appends "1" to make it "a1". We now know the user is entering
- * an alphanumeric password so we would update the min complexity required min length to 6.
- */
- public static PasswordMetrics getMinimumMetrics(@PasswordComplexity int complexityLevel,
- int userEnteredPasswordQuality, int requestedQuality, boolean requiresNumeric,
- boolean requiresLettersOrSymbols) {
- int targetQuality = Math.max(
- userEnteredPasswordQuality,
- getActualRequiredQuality(
- requestedQuality, requiresNumeric, requiresLettersOrSymbols));
- return getTargetQualityMetrics(complexityLevel, targetQuality);
- }
-
- /**
- * Returns the {@link PasswordMetrics} at {@code complexityLevel} which the metrics quality
- * is the same as {@code targetQuality}.
- *
- * <p>If {@code complexityLevel} does not allow {@code targetQuality}, returns the metrics
- * with the min quality at {@code complexityLevel}.
- */
- // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private
- @VisibleForTesting
- public static PasswordMetrics getTargetQualityMetrics(
- @PasswordComplexity int complexityLevel, int targetQuality) {
- PasswordComplexityBucket targetBucket =
- PasswordComplexityBucket.complexityLevelToBucket(complexityLevel);
- for (PasswordMetrics metrics : targetBucket.mMetrics) {
- if (targetQuality == metrics.quality) {
- return metrics;
- }
- }
- // none of the metrics at complexityLevel has targetQuality, return metrics with min quality
- // see test case testGetMinimumMetrics_actualRequiredQualityStricter for an example, where
- // min complexity allows at least NUMERIC_COMPLEX, user has not entered anything yet, and
- // requested quality is NUMERIC
- return targetBucket.mMetrics[0];
- }
-
- /**
- * Finds out the actual quality requirement based on whether quality is {@link
- * DevicePolicyManager#PASSWORD_QUALITY_COMPLEX} and whether digits, letters or symbols are
- * required.
- */
- @VisibleForTesting
- // TODO(bernardchau): update tests to test getMinimumMetrics and change this to be private
- public static int getActualRequiredQuality(
- int requestedQuality, boolean requiresNumeric, boolean requiresLettersOrSymbols) {
- if (requestedQuality != PASSWORD_QUALITY_COMPLEX) {
- return requestedQuality;
- }
-
- // find out actual password quality from complex requirements
- if (requiresNumeric && requiresLettersOrSymbols) {
- return PASSWORD_QUALITY_ALPHANUMERIC;
- }
- if (requiresLettersOrSymbols) {
- return PASSWORD_QUALITY_ALPHABETIC;
- }
- if (requiresNumeric) {
- // cannot specify numeric complex using complex quality so this must be numeric
- return PASSWORD_QUALITY_NUMERIC;
- }
-
- // reaching here means dpm sets quality to complex without specifying any requirements
- return PASSWORD_QUALITY_UNSPECIFIED;
+ private PasswordMetrics(PasswordMetrics other) {
+ this(other.credType, other.length, other.letters, other.upperCase, other.lowerCase,
+ other.numeric, other.symbols, other.nonLetter, other.nonNumeric, other.seqLength);
}
/**
* Returns {@code complexityLevel} or {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}
* if {@code complexityLevel} is not valid.
+ *
+ * TODO: move to PasswordPolicy
*/
@PasswordComplexity
public static int sanitizeComplexityLevel(@PasswordComplexity int complexityLevel) {
- return PasswordComplexityBucket.complexityLevelToBucket(complexityLevel).mComplexityLevel;
+ switch (complexityLevel) {
+ case PASSWORD_COMPLEXITY_HIGH:
+ case PASSWORD_COMPLEXITY_MEDIUM:
+ case PASSWORD_COMPLEXITY_LOW:
+ case PASSWORD_COMPLEXITY_NONE:
+ return complexityLevel;
+ default:
+ Log.w(TAG, "Invalid password complexity used: " + complexityLevel);
+ return PASSWORD_COMPLEXITY_NONE;
+ }
}
- public boolean isDefault() {
- return quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED
- && length == 0 && letters == 0 && upperCase == 0 && lowerCase == 0
- && numeric == 0 && symbols == 0 && nonLetter == 0;
+ private static boolean hasInvalidCharacters(byte[] password) {
+ // Allow non-control Latin-1 characters only.
+ for (byte b : password) {
+ char c = (char) b;
+ if (c < 32 || c > 127) {
+ return true;
+ }
+ }
+ return false;
}
@Override
@@ -204,7 +147,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(quality);
+ dest.writeInt(credType);
dest.writeInt(length);
dest.writeInt(letters);
dest.writeInt(upperCase);
@@ -212,12 +155,25 @@
dest.writeInt(numeric);
dest.writeInt(symbols);
dest.writeInt(nonLetter);
+ dest.writeInt(nonNumeric);
+ dest.writeInt(seqLength);
}
- public static final @android.annotation.NonNull Parcelable.Creator<PasswordMetrics> CREATOR
+ public static final @NonNull Parcelable.Creator<PasswordMetrics> CREATOR
= new Parcelable.Creator<PasswordMetrics>() {
public PasswordMetrics createFromParcel(Parcel in) {
- return new PasswordMetrics(in);
+ int credType = in.readInt();
+ int length = in.readInt();
+ int letters = in.readInt();
+ int upperCase = in.readInt();
+ int lowerCase = in.readInt();
+ int numeric = in.readInt();
+ int symbols = in.readInt();
+ int nonLetter = in.readInt();
+ int nonNumeric = in.readInt();
+ int seqLength = in.readInt();
+ return new PasswordMetrics(credType, length, letters, upperCase, lowerCase, numeric,
+ symbols, nonLetter, nonNumeric, seqLength);
}
public PasswordMetrics[] newArray(int size) {
@@ -226,21 +182,21 @@
};
/**
- * Returnsthe {@code PasswordMetrics} for a given credential.
+ * Returns the {@code PasswordMetrics} for a given credential.
*
* If the credential is a pin or a password, equivalent to {@link #computeForPassword(byte[])}.
* {@code credential} cannot be null when {@code type} is
* {@link com.android.internal.widget.LockPatternUtils#CREDENTIAL_TYPE_PASSWORD}.
*/
- public static PasswordMetrics computeForCredential(
- @CredentialType int type, byte[] credential) {
- if (type == CREDENTIAL_TYPE_PASSWORD) {
- Preconditions.checkNotNull(credential, "credential cannot be null");
- return PasswordMetrics.computeForPassword(credential);
- } else if (type == CREDENTIAL_TYPE_PATTERN) {
- return new PasswordMetrics(PASSWORD_QUALITY_SOMETHING);
- } else /* if (type == CREDENTIAL_TYPE_NONE) */ {
- return new PasswordMetrics(PASSWORD_QUALITY_UNSPECIFIED);
+ public static PasswordMetrics computeForCredential(LockscreenCredential credential) {
+ if (credential.isPassword()) {
+ return PasswordMetrics.computeForPassword(credential.getCredential());
+ } else if (credential.isPattern()) {
+ return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
+ } else if (credential.isNone()) {
+ return new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+ } else {
+ throw new IllegalArgumentException("Unknown credential type " + credential.getType());
}
}
@@ -255,16 +211,19 @@
int numeric = 0;
int symbols = 0;
int nonLetter = 0;
+ int nonNumeric = 0;
final int length = password.length;
for (byte b : password) {
switch (categoryChar((char) b)) {
case CHAR_LOWER_CASE:
letters++;
lowerCase++;
+ nonNumeric++;
break;
case CHAR_UPPER_CASE:
letters++;
upperCase++;
+ nonNumeric++;
break;
case CHAR_DIGIT:
numeric++;
@@ -273,53 +232,14 @@
case CHAR_SYMBOL:
symbols++;
nonLetter++;
+ nonNumeric++;
break;
}
}
- // Determine the quality of the password
- final boolean hasNumeric = numeric > 0;
- final boolean hasNonNumeric = (letters + symbols) > 0;
- final int quality;
- if (hasNonNumeric && hasNumeric) {
- quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
- } else if (hasNonNumeric) {
- quality = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
- } else if (hasNumeric) {
- quality = maxLengthSequence(password) > MAX_ALLOWED_SEQUENCE
- ? DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
- : DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
- } else {
- quality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
- }
-
- return new PasswordMetrics(
- quality, length, letters, upperCase, lowerCase, numeric, symbols, nonLetter);
- }
-
- @Override
- public boolean equals(Object other) {
- if (!(other instanceof PasswordMetrics)) {
- return false;
- }
- PasswordMetrics o = (PasswordMetrics) other;
- return this.quality == o.quality
- && this.length == o.length
- && this.letters == o.letters
- && this.upperCase == o.upperCase
- && this.lowerCase == o.lowerCase
- && this.numeric == o.numeric
- && this.symbols == o.symbols
- && this.nonLetter == o.nonLetter;
- }
-
- private boolean satisfiesBucket(PasswordMetrics... bucket) {
- for (PasswordMetrics metrics : bucket) {
- if (this.quality == metrics.quality) {
- return this.length >= metrics.length;
- }
- }
- return false;
+ final int seqLength = maxLengthSequence(password);
+ return new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD, length, letters, upperCase, lowerCase,
+ numeric, symbols, nonLetter, nonNumeric, seqLength);
}
/**
@@ -404,108 +324,394 @@
}
}
- /** Determines the {@link PasswordComplexity} of this {@link PasswordMetrics}. */
- @PasswordComplexity
- public int determineComplexity() {
- for (PasswordComplexityBucket bucket : PasswordComplexityBucket.BUCKETS) {
- if (satisfiesBucket(bucket.mMetrics)) {
- return bucket.mComplexityLevel;
- }
+ /**
+ * Returns the weakest metrics that is stricter or equal to all given metrics.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ public static PasswordMetrics merge(List<PasswordMetrics> metrics) {
+ PasswordMetrics result = new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+ for (PasswordMetrics m : metrics) {
+ result.maxWith(m);
}
- return PASSWORD_COMPLEXITY_NONE;
+
+ return result;
}
/**
- * Requirements in terms of {@link PasswordMetrics} for each {@link PasswordComplexity}.
+ * Makes current metric at least as strong as {@code other} in every criterion.
+ *
+ * TODO: move to PasswordPolicy
*/
- private static class PasswordComplexityBucket {
- /**
- * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_HIGH} in terms of
- * {@link PasswordMetrics}.
- */
- private static final PasswordComplexityBucket HIGH =
- new PasswordComplexityBucket(
- PASSWORD_COMPLEXITY_HIGH,
- new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
- 8),
- new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 6),
- new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */
- 6));
+ private void maxWith(PasswordMetrics other) {
+ credType = Math.max(credType, other.credType);
+ if (credType != CREDENTIAL_TYPE_PASSWORD) {
+ return;
+ }
+ length = Math.max(length, other.length);
+ letters = Math.max(letters, other.letters);
+ upperCase = Math.max(upperCase, other.upperCase);
+ lowerCase = Math.max(lowerCase, other.lowerCase);
+ numeric = Math.max(numeric, other.numeric);
+ symbols = Math.max(symbols, other.symbols);
+ nonLetter = Math.max(nonLetter, other.nonLetter);
+ nonNumeric = Math.max(nonNumeric, other.nonNumeric);
+ seqLength = Math.min(seqLength, other.seqLength);
+ }
- /**
- * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_MEDIUM} in terms of
- * {@link PasswordMetrics}.
- */
- private static final PasswordComplexityBucket MEDIUM =
- new PasswordComplexityBucket(
- PASSWORD_COMPLEXITY_MEDIUM,
- new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX, /* length= */
- 4),
- new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, /* length= */ 4),
- new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, /* length= */
- 4));
+ /**
+ * Returns minimum password quality for a given complexity level.
+ *
+ * TODO: this function is used for determining allowed credential types, so it should return
+ * credential type rather than 'quality'.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ public static int complexityLevelToMinQuality(int complexity) {
+ switch (complexity) {
+ case PASSWORD_COMPLEXITY_HIGH:
+ case PASSWORD_COMPLEXITY_MEDIUM:
+ return PASSWORD_QUALITY_NUMERIC_COMPLEX;
+ case PASSWORD_COMPLEXITY_LOW:
+ return PASSWORD_QUALITY_SOMETHING;
+ case PASSWORD_COMPLEXITY_NONE:
+ default:
+ return PASSWORD_QUALITY_UNSPECIFIED;
+ }
+ }
- /**
- * Definition of {@link DevicePolicyManager#PASSWORD_COMPLEXITY_LOW} in terms of
- * {@link PasswordMetrics}.
- */
- private static final PasswordComplexityBucket LOW =
- new PasswordComplexityBucket(
- PASSWORD_COMPLEXITY_LOW,
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC));
-
- /**
- * A special bucket to represent {@link DevicePolicyManager#PASSWORD_COMPLEXITY_NONE}.
- */
- private static final PasswordComplexityBucket NONE =
- new PasswordComplexityBucket(PASSWORD_COMPLEXITY_NONE, new PasswordMetrics());
-
- /** Array containing all buckets from high to low. */
- private static final PasswordComplexityBucket[] BUCKETS =
- new PasswordComplexityBucket[] {HIGH, MEDIUM, LOW};
-
- @PasswordComplexity
- private final int mComplexityLevel;
- private final PasswordMetrics[] mMetrics;
-
- /**
- * @param metricsArray must be sorted in ascending order of {@link #quality}.
- */
- private PasswordComplexityBucket(@PasswordComplexity int complexityLevel,
- PasswordMetrics... metricsArray) {
- int previousQuality = PASSWORD_QUALITY_UNSPECIFIED;
- for (PasswordMetrics metrics : metricsArray) {
- if (metrics.quality < previousQuality) {
- throw new IllegalArgumentException("metricsArray must be sorted in ascending"
- + " order of quality");
- }
- previousQuality = metrics.quality;
+ /**
+ * Enum representing requirements for each complexity level.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ private enum ComplexityBucket {
+ // Keep ordered high -> low.
+ BUCKET_HIGH(PASSWORD_COMPLEXITY_HIGH) {
+ @Override
+ boolean canHaveSequence() {
+ return false;
}
- this.mMetrics = metricsArray;
- this.mComplexityLevel = complexityLevel;
+ @Override
+ int getMinimumLength(boolean containsNonNumeric) {
+ return containsNonNumeric ? 6 : 8;
+ }
+ @Override
+ boolean allowsNumericPassword() {
+ return false;
+ }
+
+ @Override
+ boolean allowsCredType(int credType) {
+ return credType == CREDENTIAL_TYPE_PASSWORD;
+ }
+ },
+ BUCKET_MEDIUM(PASSWORD_COMPLEXITY_MEDIUM) {
+ @Override
+ boolean canHaveSequence() {
+ return false;
+ }
+
+ @Override
+ int getMinimumLength(boolean containsNonNumeric) {
+ return 4;
+ }
+
+ @Override
+ boolean allowsNumericPassword() {
+ return false;
+ }
+
+ @Override
+ boolean allowsCredType(int credType) {
+ return credType == CREDENTIAL_TYPE_PASSWORD;
+ }
+ },
+ BUCKET_LOW(PASSWORD_COMPLEXITY_LOW) {
+ @Override
+ boolean canHaveSequence() {
+ return true;
+ }
+
+ @Override
+ int getMinimumLength(boolean containsNonNumeric) {
+ return 0;
+ }
+
+ @Override
+ boolean allowsNumericPassword() {
+ return true;
+ }
+
+ @Override
+ boolean allowsCredType(int credType) {
+ return credType != CREDENTIAL_TYPE_NONE;
+ }
+ },
+ BUCKET_NONE(PASSWORD_COMPLEXITY_NONE) {
+ @Override
+ boolean canHaveSequence() {
+ return true;
+ }
+
+ @Override
+ int getMinimumLength(boolean containsNonNumeric) {
+ return 0;
+ }
+
+ @Override
+ boolean allowsNumericPassword() {
+ return true;
+ }
+
+ @Override
+ boolean allowsCredType(int credType) {
+ return true;
+ }
+ };
+
+ int mComplexityLevel;
+
+ abstract boolean canHaveSequence();
+ abstract int getMinimumLength(boolean containsNonNumeric);
+ abstract boolean allowsNumericPassword();
+ abstract boolean allowsCredType(int credType);
+
+ ComplexityBucket(int complexityLevel) {
+ this.mComplexityLevel = complexityLevel;
}
- /** Returns the bucket that {@code complexityLevel} represents. */
- private static PasswordComplexityBucket complexityLevelToBucket(
- @PasswordComplexity int complexityLevel) {
- for (PasswordComplexityBucket bucket : BUCKETS) {
+ static ComplexityBucket forComplexity(int complexityLevel) {
+ for (ComplexityBucket bucket : values()) {
if (bucket.mComplexityLevel == complexityLevel) {
return bucket;
}
}
- return NONE;
+ throw new IllegalArgumentException("Invalid complexity level: " + complexityLevel);
}
}
+
+ /**
+ * Returns whether current metrics satisfies a given complexity bucket.
+ *
+ * TODO: move inside ComplexityBucket.
+ */
+ private boolean satisfiesBucket(ComplexityBucket bucket) {
+ if (!bucket.allowsCredType(credType)) {
+ return false;
+ }
+ if (credType != CREDENTIAL_TYPE_PASSWORD) {
+ return true;
+ }
+ return (bucket.canHaveSequence() || seqLength <= MAX_ALLOWED_SEQUENCE)
+ && length >= bucket.getMinimumLength(nonNumeric > 0 /* hasNonNumeric */);
+ }
+
+ /**
+ * Returns the maximum complexity level satisfied by password with this metrics.
+ *
+ * TODO: move inside ComplexityBucket.
+ */
+ public int determineComplexity() {
+ for (ComplexityBucket bucket : ComplexityBucket.values()) {
+ if (satisfiesBucket(bucket)) {
+ return bucket.mComplexityLevel;
+ }
+ }
+ throw new IllegalStateException("Failed to figure out complexity for a given metrics");
+ }
+
+ /**
+ * Validates password against minimum metrics and complexity.
+ *
+ * @param adminMetrics - minimum metrics to satisfy admin requirements.
+ * @param minComplexity - minimum complexity imposed by the requester.
+ * @param isPin - whether it is PIN that should be only digits
+ * @param password - password to validate.
+ * @return a list of password validation errors. An empty list means the password is OK.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ public static List<PasswordValidationError> validatePassword(
+ PasswordMetrics adminMetrics, int minComplexity, boolean isPin, byte[] password) {
+
+ if (hasInvalidCharacters(password)) {
+ return Collections.singletonList(
+ new PasswordValidationError(CONTAINS_INVALID_CHARACTERS, 0));
+ }
+
+ final PasswordMetrics enteredMetrics = computeForPassword(password);
+ return validatePasswordMetrics(adminMetrics, minComplexity, isPin, enteredMetrics);
+ }
+
+ /**
+ * Validates password metrics against minimum metrics and complexity
+ *
+ * @param adminMetrics - minimum metrics to satisfy admin requirements.
+ * @param minComplexity - minimum complexity imposed by the requester.
+ * @param isPin - whether it is PIN that should be only digits
+ * @param actualMetrics - metrics for password to validate.
+ * @return a list of password validation errors. An empty list means the password is OK.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ public static List<PasswordValidationError> validatePasswordMetrics(
+ PasswordMetrics adminMetrics, int minComplexity, boolean isPin,
+ PasswordMetrics actualMetrics) {
+ final ComplexityBucket bucket = ComplexityBucket.forComplexity(minComplexity);
+
+ // Make sure credential type is satisfactory.
+ // TODO: stop relying on credential type ordering.
+ if (actualMetrics.credType < adminMetrics.credType
+ || !bucket.allowsCredType(actualMetrics.credType)) {
+ return Collections.singletonList(new PasswordValidationError(WEAK_CREDENTIAL_TYPE, 0));
+ }
+ // TODO: this needs to be modified if CREDENTIAL_TYPE_PIN is added.
+ if (actualMetrics.credType != CREDENTIAL_TYPE_PASSWORD) {
+ return Collections.emptyList(); // Nothing to check for pattern or none.
+ }
+
+ if (isPin && actualMetrics.nonNumeric > 0) {
+ return Collections.singletonList(
+ new PasswordValidationError(CONTAINS_INVALID_CHARACTERS, 0));
+ }
+
+ final ArrayList<PasswordValidationError> result = new ArrayList<>();
+ if (actualMetrics.length > MAX_PASSWORD_LENGTH) {
+ result.add(new PasswordValidationError(TOO_LONG, MAX_PASSWORD_LENGTH));
+ }
+
+ final PasswordMetrics minMetrics = applyComplexity(adminMetrics, isPin, bucket);
+
+ // Clamp required length between maximum and minimum valid values.
+ minMetrics.length = Math.min(MAX_PASSWORD_LENGTH,
+ Math.max(minMetrics.length, MIN_LOCK_PASSWORD_SIZE));
+ minMetrics.removeOverlapping();
+
+ comparePasswordMetrics(minMetrics, actualMetrics, result);
+
+ return result;
+ }
+
+ /**
+ * TODO: move to PasswordPolicy
+ */
+ private static void comparePasswordMetrics(PasswordMetrics minMetrics,
+ PasswordMetrics actualMetrics, ArrayList<PasswordValidationError> result) {
+ if (actualMetrics.length < minMetrics.length) {
+ result.add(new PasswordValidationError(TOO_SHORT, minMetrics.length));
+ }
+ if (actualMetrics.letters < minMetrics.letters) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_LETTERS, minMetrics.letters));
+ }
+ if (actualMetrics.upperCase < minMetrics.upperCase) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_UPPER_CASE, minMetrics.upperCase));
+ }
+ if (actualMetrics.lowerCase < minMetrics.lowerCase) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_LOWER_CASE, minMetrics.lowerCase));
+ }
+ if (actualMetrics.numeric < minMetrics.numeric) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_DIGITS, minMetrics.numeric));
+ }
+ if (actualMetrics.symbols < minMetrics.symbols) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_SYMBOLS, minMetrics.symbols));
+ }
+ if (actualMetrics.nonLetter < minMetrics.nonLetter) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_NON_LETTER, minMetrics.nonLetter));
+ }
+ if (actualMetrics.nonNumeric < minMetrics.nonNumeric) {
+ result.add(new PasswordValidationError(NOT_ENOUGH_NON_DIGITS, minMetrics.nonNumeric));
+ }
+ if (actualMetrics.seqLength > minMetrics.seqLength) {
+ result.add(new PasswordValidationError(CONTAINS_SEQUENCE, 0));
+ }
+ }
+
+ /**
+ * Drop requirements that are superseded by others, e.g. if it is required to have 5 upper case
+ * letters and 5 lower case letters, there is no need to require minimum number of letters to
+ * be 10 since it will be fulfilled once upper and lower case requirements are fulfilled.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ private void removeOverlapping() {
+ // upperCase + lowerCase can override letters
+ final int indirectLetters = upperCase + lowerCase;
+
+ // numeric + symbols can override nonLetter
+ final int indirectNonLetter = numeric + symbols;
+
+ // letters + symbols can override nonNumeric
+ final int effectiveLetters = Math.max(letters, indirectLetters);
+ final int indirectNonNumeric = effectiveLetters + symbols;
+
+ // letters + nonLetters can override length
+ // numeric + nonNumeric can also override length, so max it with previous.
+ final int effectiveNonLetter = Math.max(nonLetter, indirectNonLetter);
+ final int effectiveNonNumeric = Math.max(nonNumeric, indirectNonNumeric);
+ final int indirectLength = Math.max(effectiveLetters + effectiveNonLetter,
+ numeric + effectiveNonNumeric);
+
+ if (indirectLetters >= letters) {
+ letters = 0;
+ }
+ if (indirectNonLetter >= nonLetter) {
+ nonLetter = 0;
+ }
+ if (indirectNonNumeric >= nonNumeric) {
+ nonNumeric = 0;
+ }
+ if (indirectLength >= length) {
+ length = 0;
+ }
+ }
+
+ /**
+ * Combine minimum metrics, set by admin, complexity set by the requester and actual entered
+ * password metrics to get resulting minimum metrics that the password has to satisfy. Always
+ * returns a new PasswordMetrics object.
+ *
+ * TODO: move to PasswordPolicy
+ */
+ private static PasswordMetrics applyComplexity(
+ PasswordMetrics adminMetrics, boolean isPin, ComplexityBucket bucket) {
+ final PasswordMetrics minMetrics = new PasswordMetrics(adminMetrics);
+
+ if (!bucket.canHaveSequence()) {
+ minMetrics.seqLength = Math.min(minMetrics.seqLength, MAX_ALLOWED_SEQUENCE);
+ }
+
+ minMetrics.length = Math.max(minMetrics.length, bucket.getMinimumLength(!isPin));
+
+ if (!isPin && !bucket.allowsNumericPassword()) {
+ minMetrics.nonNumeric = Math.max(minMetrics.nonNumeric, 1);
+ }
+
+ return minMetrics;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ final PasswordMetrics that = (PasswordMetrics) o;
+ return credType == that.credType
+ && length == that.length
+ && letters == that.letters
+ && upperCase == that.upperCase
+ && lowerCase == that.lowerCase
+ && numeric == that.numeric
+ && symbols == that.symbols
+ && nonLetter == that.nonLetter
+ && nonNumeric == that.nonNumeric
+ && seqLength == that.seqLength;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(credType, length, letters, upperCase, lowerCase, numeric, symbols,
+ nonLetter, nonNumeric, seqLength);
+ }
}
diff --git a/core/java/android/app/admin/PasswordPolicy.java b/core/java/android/app/admin/PasswordPolicy.java
new file mode 100644
index 0000000..13f11ad
--- /dev/null
+++ b/core/java/android/app/admin/PasswordPolicy.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+
+/**
+ * {@hide}
+ */
+public class PasswordPolicy {
+ public static final int DEF_MINIMUM_LENGTH = 0;
+ public static final int DEF_MINIMUM_LETTERS = 1;
+ public static final int DEF_MINIMUM_UPPER_CASE = 0;
+ public static final int DEF_MINIMUM_LOWER_CASE = 0;
+ public static final int DEF_MINIMUM_NUMERIC = 1;
+ public static final int DEF_MINIMUM_SYMBOLS = 1;
+ public static final int DEF_MINIMUM_NON_LETTER = 0;
+
+ public int quality = PASSWORD_QUALITY_UNSPECIFIED;
+ public int length = DEF_MINIMUM_LENGTH;
+ public int letters = DEF_MINIMUM_LETTERS;
+ public int upperCase = DEF_MINIMUM_UPPER_CASE;
+ public int lowerCase = DEF_MINIMUM_LOWER_CASE;
+ public int numeric = DEF_MINIMUM_NUMERIC;
+ public int symbols = DEF_MINIMUM_SYMBOLS;
+ public int nonLetter = DEF_MINIMUM_NON_LETTER;
+
+ /**
+ * Returns a minimum password metrics that the password should have to satisfy current policy.
+ */
+ public PasswordMetrics getMinMetrics() {
+ if (quality == PASSWORD_QUALITY_UNSPECIFIED) {
+ return new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+ } else if (quality == PASSWORD_QUALITY_BIOMETRIC_WEAK
+ || quality == PASSWORD_QUALITY_SOMETHING) {
+ return new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
+ } // quality is NUMERIC or stronger.
+
+ PasswordMetrics result = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
+ result.length = length;
+
+ if (quality == PASSWORD_QUALITY_NUMERIC_COMPLEX) {
+ result.seqLength = PasswordMetrics.MAX_ALLOWED_SEQUENCE;
+ } else if (quality == PASSWORD_QUALITY_ALPHABETIC) {
+ result.nonNumeric = 1;
+ } else if (quality == PASSWORD_QUALITY_ALPHANUMERIC) {
+ result.numeric = 1;
+ result.nonNumeric = 1;
+ } else if (quality == PASSWORD_QUALITY_COMPLEX) {
+ result.numeric = numeric;
+ result.letters = letters;
+ result.upperCase = upperCase;
+ result.lowerCase = lowerCase;
+ result.nonLetter = nonLetter;
+ result.symbols = symbols;
+ }
+ return result;
+ }
+}
diff --git a/core/java/android/app/slice/SliceManager.java b/core/java/android/app/slice/SliceManager.java
index 955093d..90ecce2 100644
--- a/core/java/android/app/slice/SliceManager.java
+++ b/core/java/android/app/slice/SliceManager.java
@@ -390,6 +390,8 @@
}
Bundle extras = new Bundle();
extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
+ extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
+ new ArrayList<>(supportedSpecs));
final Bundle res = provider.call(SliceProvider.METHOD_MAP_INTENT, null, extras);
if (res == null) {
return null;
diff --git a/core/java/android/app/timedetector/TimeSignal.java b/core/java/android/app/timedetector/TimeSignal.java
index da21794..b494260 100644
--- a/core/java/android/app/timedetector/TimeSignal.java
+++ b/core/java/android/app/timedetector/TimeSignal.java
@@ -56,8 +56,7 @@
private static TimeSignal createFromParcel(Parcel in) {
String sourceId = in.readString();
- TimestampedValue<Long> utcTime =
- TimestampedValue.readFromParcel(in, null /* classLoader */, Long.class);
+ TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
return new TimeSignal(sourceId, utcTime);
}
@@ -69,7 +68,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mSourceId);
- TimestampedValue.writeToParcel(dest, mUtcTime);
+ dest.writeParcelable(mUtcTime, 0);
}
@NonNull
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index b3260c4..024afe2 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -154,12 +154,6 @@
public abstract int[] getIdleUidsForUser(@UserIdInt int userId);
/**
- * @return True if currently app idle parole mode is on. This means all idle apps are allow to
- * run for a short period of time.
- */
- public abstract boolean isAppIdleParoleOn();
-
- /**
* Sets up a listener for changes to packages being accessed.
* @param listener A listener within the system process.
*/
@@ -180,12 +174,6 @@
boolean idle, int bucket, int reason);
/**
- * Callback to inform listeners that the parole state has changed. This means apps are
- * allowed to do work even if they're idle or in a low bucket.
- */
- public abstract void onParoleStateChanged(boolean isParoleOn);
-
- /**
* Optional callback to inform the listener that the app has transitioned into
* an active state due to user interaction.
*/
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 08817e0..e7e278f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1559,7 +1559,14 @@
* @see Environment#getExternalStorageState(File)
* @see Environment#isExternalStorageEmulated(File)
* @see Environment#isExternalStorageRemovable(File)
+ * @deprecated These directories still exist and are scanned, but developers
+ * are encouraged to migrate to inserting content into a
+ * {@link MediaStore} collection directly, as any app can
+ * contribute new media to {@link MediaStore} with no
+ * permissions required, starting in
+ * {@link android.os.Build.VERSION_CODES#Q}.
*/
+ @Deprecated
public abstract File[] getExternalMediaDirs();
/**
@@ -5217,8 +5224,9 @@
*/
@SystemApi
@TestApi
+ @NonNull
public Context createPackageContextAsUser(
- String packageName, @CreatePackageOptions int flags, UserHandle user)
+ @NonNull String packageName, @CreatePackageOptions int flags, @NonNull UserHandle user)
throws PackageManager.NameNotFoundException {
if (Build.IS_ENG) {
throw new IllegalStateException("createPackageContextAsUser not overridden!");
@@ -5227,6 +5235,23 @@
}
/**
+ * Similar to {@link #createPackageContext(String, int)}, but for the own package with a
+ * different {@link UserHandle}. For example, {@link #getContentResolver()}
+ * will open any {@link Uri} as the given user.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @NonNull
+ public Context createContextAsUser(@NonNull UserHandle user, @CreatePackageOptions int flags) {
+ if (Build.IS_ENG) {
+ throw new IllegalStateException("createContextAsUser not overridden!");
+ }
+ return this;
+ }
+
+ /**
* Creates a context given an {@link android.content.pm.ApplicationInfo}.
*
* @hide
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 0859f97..7993ea1 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -885,6 +885,12 @@
/** @hide */
@Override
+ public Context createContextAsUser(UserHandle user, @CreatePackageOptions int flags) {
+ return mBase.createContextAsUser(user, flags);
+ }
+
+ /** @hide */
+ @Override
@UnsupportedAppUsage
public Context createApplicationContext(ApplicationInfo application,
int flags) throws PackageManager.NameNotFoundException {
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 853e818..a2f8886 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -166,9 +166,8 @@
}
/**
- * Returns information about all overlays for the given target package for
- * the specified user. The returned list is ordered according to the
- * overlay priority with the highest priority at the end of the list.
+ * Clear part of the overlay manager's internal cache of PackageInfo
+ * objects. Only intended for testing.
*
* @param targetPackageName The name of the target package.
* @param user The user to get the OverlayInfos for.
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 4d7c43a..1d78e2c 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -682,10 +682,14 @@
String getWellbeingPackageName();
+ String[] getTelephonyPackageNames();
+
String getAppPredictionServicePackageName();
String getSystemCaptionsServicePackageName();
+ String getSetupWizardPackageName();
+
String getIncidentReportApproverPackageName();
boolean isPackageStateProtected(String packageName, int userId);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 69ce3bd..edc66c5 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -349,14 +349,7 @@
*/
public int createSession(@NonNull SessionParams params) throws IOException {
try {
- final String installerPackage;
- if (params.installerPackageName == null) {
- installerPackage = mInstallerPackageName;
- } else {
- installerPackage = params.installerPackageName;
- }
-
- return mInstaller.createSession(params, installerPackage, mUserId);
+ return mInstaller.createSession(params, mInstallerPackageName, mUserId);
} catch (RuntimeException e) {
ExceptionUtils.maybeUnwrapIOException(e);
throw e;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index fd14d23..7509065 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2858,6 +2858,14 @@
/**
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+ * The device does not have slices implementation.
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_SLICES_DISABLED = "android.software.slices_disabled";
+
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
* The device supports device-unique Keystore attestations. Only available on devices that
* also support {@link #FEATURE_STRONGBOX_KEYSTORE}, and can only be used by device owner
* apps (see {@link android.app.admin.DevicePolicyManager#generateKeyPair}).
@@ -7416,6 +7424,18 @@
}
/**
+ * @return the system defined telephony package names, or null if there's none.
+ *
+ * @hide
+ */
+ @Nullable
+ @TestApi
+ public String[] getTelephonyPackageNames() {
+ throw new UnsupportedOperationException(
+ "getTelephonyPackageNames not implemented in subclass");
+ }
+
+ /**
* @return the system defined content capture service package name, or null if there's none.
*
* @hide
@@ -7427,6 +7447,17 @@
}
/**
+ * @return the system defined setup wizard package name, or null if there's none.
+ *
+ * @hide
+ */
+ @Nullable
+ public String getSetupWizardPackageName() {
+ throw new UnsupportedOperationException(
+ "getSetupWizardPackageName not implemented in subclass");
+ }
+
+ /**
* @return the incident report approver app package name, or null if it's not defined
* by the OEM.
*
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 0b157fa..cf21e96 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -577,8 +577,6 @@
*/
public interface Callback {
boolean hasFeature(String feature);
- String[] getOverlayPaths(String targetPackageName, String targetPath);
- String[] getOverlayApks(String targetPackageName);
}
/**
@@ -595,14 +593,6 @@
@Override public boolean hasFeature(String feature) {
return mPm.hasSystemFeature(feature);
}
-
- @Override public String[] getOverlayPaths(String targetPackageName, String targetPath) {
- return null;
- }
-
- @Override public String[] getOverlayApks(String targetPackageName) {
- return null;
- }
}
/**
@@ -1195,19 +1185,7 @@
}
final byte[] bytes = IoUtils.readFileAsByteArray(cacheFile.getAbsolutePath());
- Package p = fromCacheEntry(bytes);
- if (mCallback != null) {
- String[] overlayApks = mCallback.getOverlayApks(p.packageName);
- if (overlayApks != null && overlayApks.length > 0) {
- for (String overlayApk : overlayApks) {
- // If a static RRO is updated, return null.
- if (!isCacheUpToDate(new File(overlayApk), cacheFile)) {
- return null;
- }
- }
- }
- }
- return p;
+ return fromCacheEntry(bytes);
} catch (Throwable e) {
Slog.w(TAG, "Error reading package cache: ", e);
@@ -1381,7 +1359,7 @@
final Resources res = new Resources(assets, mMetrics, null);
final String[] outError = new String[1];
- final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
+ final Package pkg = parseBaseApk(res, parser, flags, outError);
if (pkg == null) {
throw new PackageParserException(mParseError,
apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
@@ -1944,7 +1922,6 @@
* need to consider whether they should be supported by split APKs and child
* packages.
*
- * @param apkPath The package apk file path
* @param res The resources from which to resolve values
* @param parser The manifest parser
* @param flags Flags how to parse
@@ -1954,8 +1931,7 @@
* @throws XmlPullParserException
* @throws IOException
*/
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,
+ private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
String[] outError) throws XmlPullParserException, IOException {
final String splitName;
final String pkgName;
@@ -1975,15 +1951,6 @@
return null;
}
- if (mCallback != null) {
- String[] overlayPaths = mCallback.getOverlayPaths(pkgName, apkPath);
- if (overlayPaths != null && overlayPaths.length > 0) {
- for (String overlayPath : overlayPaths) {
- res.getAssets().addOverlayPath(overlayPath);
- }
- }
- }
-
final Package pkg = new Package(pkgName);
TypedArray sa = res.obtainAttributes(parser,
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index dd5c6a5..aa6f58e 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -237,6 +237,28 @@
@TestApi
public static final int PROTECTION_FLAG_APP_PREDICTOR = 0x200000;
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>telephony</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int PROTECTION_FLAG_TELEPHONY = 0x400000;
+
+ /**
+ * Additional flag for {@link #protectionLevel}, corresponding
+ * to the <code>wifi</code> value of
+ * {@link android.R.attr#protectionLevel}.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int PROTECTION_FLAG_WIFI = 0x800000;
+
/** @hide */
@IntDef(flag = true, prefix = { "PROTECTION_FLAG_" }, value = {
PROTECTION_FLAG_PRIVILEGED,
@@ -258,6 +280,8 @@
PROTECTION_FLAG_CONFIGURATOR,
PROTECTION_FLAG_INCIDENT_REPORT_APPROVER,
PROTECTION_FLAG_APP_PREDICTOR,
+ PROTECTION_FLAG_TELEPHONY,
+ PROTECTION_FLAG_WIFI,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProtectionFlags {}
@@ -501,6 +525,12 @@
if ((level & PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR) != 0) {
protLevel += "|appPredictor";
}
+ if ((level & PermissionInfo.PROTECTION_FLAG_TELEPHONY) != 0) {
+ protLevel += "|telephony";
+ }
+ if ((level & PermissionInfo.PROTECTION_FLAG_WIFI) != 0) {
+ protLevel += "|wifi";
+ }
return protLevel;
}
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 9f0bade..ccfa184 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -350,7 +350,7 @@
@UnsupportedAppUsage
public UserHandle getUserHandle() {
- return new UserHandle(id);
+ return UserHandle.of(id);
}
@Override
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index a35ad56..de1d514 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -16,7 +16,10 @@
package android.content.res;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
+import android.content.res.loader.ResourcesProvider;
+import android.text.TextUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
@@ -36,10 +39,14 @@
*/
public final class ApkAssets {
@GuardedBy("this") private final long mNativePtr;
+
+ @Nullable
@GuardedBy("this") private final StringBlock mStringBlock;
@GuardedBy("this") private boolean mOpen = true;
+ private final boolean mForLoader;
+
/**
* Creates a new ApkAssets instance from the given path on disk.
*
@@ -48,7 +55,8 @@
* @throws IOException if a disk I/O error or parsing error occurred.
*/
public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException {
- return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/);
+ return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/,
+ false /*arscOnly*/, false /*forLoader*/);
}
/**
@@ -61,7 +69,8 @@
*/
public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system)
throws IOException {
- return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/);
+ return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/,
+ false /*arscOnly*/, false /*forLoader*/);
}
/**
@@ -76,7 +85,8 @@
*/
public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system,
boolean forceSharedLibrary) throws IOException {
- return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/);
+ return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/,
+ false /*arscOnly*/, false /*forLoader*/);
}
/**
@@ -96,7 +106,8 @@
public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd,
@NonNull String friendlyName, boolean system, boolean forceSharedLibrary)
throws IOException {
- return new ApkAssets(fd, friendlyName, system, forceSharedLibrary);
+ return new ApkAssets(fd, friendlyName, system, forceSharedLibrary, false /*arscOnly*/,
+ false /*forLoader*/);
}
/**
@@ -110,21 +121,90 @@
*/
public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system)
throws IOException {
- return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/);
+ return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/,
+ false /*arscOnly*/, false /*forLoader*/);
}
- private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
+ /**
+ * Creates a new ApkAssets instance from the given path on disk for use with a
+ * {@link ResourcesProvider}.
+ *
+ * @param path The path to an APK on disk.
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadApkForLoader(@NonNull String path)
throws IOException {
+ return new ApkAssets(path, false /*system*/, false /*forceSharedLibrary*/,
+ false /*overlay*/, false /*arscOnly*/, true /*forLoader*/);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given file descriptor for use with a
+ * {@link ResourcesProvider}.
+ *
+ * Performs a dup of the underlying fd, so you must take care of still closing
+ * the FileDescriptor yourself (and can do that whenever you want).
+ *
+ * @param fd The FileDescriptor of an open, readable APK.
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ @NonNull
+ public static ApkAssets loadApkForLoader(@NonNull FileDescriptor fd) throws IOException {
+ return new ApkAssets(fd, TextUtils.emptyIfNull(fd.toString()),
+ false /*system*/, false /*forceSharedLib*/, false /*arscOnly*/, true /*forLoader*/);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given file descriptor representing an ARSC
+ * for use with a {@link ResourcesProvider}.
+ *
+ * Performs a dup of the underlying fd, so you must take care of still closing
+ * the FileDescriptor yourself (and can do that whenever you want).
+ *
+ * @param fd The FileDescriptor of an open, readable .arsc.
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadArscForLoader(@NonNull FileDescriptor fd)
+ throws IOException {
+ return new ApkAssets(fd, TextUtils.emptyIfNull(fd.toString()),
+ false /*system*/, false /*forceSharedLib*/, true /*arscOnly*/, true /*forLoader*/);
+ }
+
+ /**
+ * Generates an entirely empty ApkAssets. Needed because the ApkAssets instance and presence
+ * is required for a lot of APIs, and it's easier to have a non-null reference rather than
+ * tracking a separate identifier.
+ */
+ @NonNull
+ public static ApkAssets loadEmptyForLoader() {
+ return new ApkAssets(true);
+ }
+
+ private ApkAssets(boolean forLoader) {
+ mForLoader = forLoader;
+ mNativePtr = nativeLoadEmpty(forLoader);
+ mStringBlock = null;
+ }
+
+ private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay,
+ boolean arscOnly, boolean forLoader) throws IOException {
+ mForLoader = forLoader;
Preconditions.checkNotNull(path, "path");
- mNativePtr = nativeLoad(path, system, forceSharedLib, overlay);
+ mNativePtr = arscOnly ? nativeLoadArsc(path, forLoader)
+ : nativeLoad(path, system, forceSharedLib, overlay, forLoader);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
}
private ApkAssets(@NonNull FileDescriptor fd, @NonNull String friendlyName, boolean system,
- boolean forceSharedLib) throws IOException {
+ boolean forceSharedLib, boolean arscOnly, boolean forLoader) throws IOException {
+ mForLoader = forLoader;
Preconditions.checkNotNull(fd, "fd");
Preconditions.checkNotNull(friendlyName, "friendlyName");
- mNativePtr = nativeLoadFromFd(fd, friendlyName, system, forceSharedLib);
+ mNativePtr = arscOnly ? nativeLoadArscFromFd(fd, friendlyName, forLoader)
+ : nativeLoadFromFd(fd, friendlyName, system, forceSharedLib, forLoader);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
}
@@ -136,11 +216,19 @@
}
CharSequence getStringFromPool(int idx) {
+ if (mStringBlock == null) {
+ return null;
+ }
+
synchronized (this) {
return mStringBlock.get(idx);
}
}
+ public boolean isForLoader() {
+ return mForLoader;
+ }
+
/**
* Retrieve a parser for a compiled XML file. This is associated with a single APK and
* <em>NOT</em> a full AssetManager. This means that shared-library references will not be
@@ -192,18 +280,26 @@
synchronized (this) {
if (mOpen) {
mOpen = false;
- mStringBlock.close();
+ if (mStringBlock != null) {
+ mStringBlock.close();
+ }
nativeDestroy(mNativePtr);
}
}
}
- private static native long nativeLoad(
- @NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
+ private static native long nativeLoad(@NonNull String path, boolean system,
+ boolean forceSharedLib, boolean overlay, boolean forLoader)
throws IOException;
private static native long nativeLoadFromFd(@NonNull FileDescriptor fd,
- @NonNull String friendlyName, boolean system, boolean forceSharedLib)
+ @NonNull String friendlyName, boolean system, boolean forceSharedLib,
+ boolean forLoader)
throws IOException;
+ private static native long nativeLoadArsc(@NonNull String path, boolean forLoader)
+ throws IOException;
+ private static native long nativeLoadArscFromFd(@NonNull FileDescriptor fd,
+ @NonNull String friendlyName, boolean forLoader) throws IOException;
+ private static native long nativeLoadEmpty(boolean forLoader);
private static native void nativeDestroy(long ptr);
private static native @NonNull String nativeGetAssetPath(long ptr);
private static native long nativeGetStringBlock(long ptr);
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 7d6dc97..23e7720 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -27,9 +27,13 @@
import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration.NativeConfig;
+import android.content.res.loader.ResourceLoader;
+import android.content.res.loader.ResourceLoaderManager;
+import android.content.res.loader.ResourcesProvider;
import android.os.ParcelFileDescriptor;
import android.util.ArraySet;
import android.util.Log;
+import android.util.Pair;
import android.util.SparseArray;
import android.util.TypedValue;
@@ -39,15 +43,19 @@
import libcore.io.IoUtils;
import java.io.BufferedReader;
+import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.channels.FileLock;
+import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
import java.util.Map;
/**
@@ -110,6 +118,13 @@
@GuardedBy("this") private int mNumRefs = 1;
@GuardedBy("this") private HashMap<Long, RuntimeException> mRefStacks;
+ private ResourceLoaderManager mResourceLoaderManager;
+
+ /** @hide */
+ public void setResourceLoaderManager(ResourceLoaderManager resourceLoaderManager) {
+ mResourceLoaderManager = resourceLoaderManager;
+ }
+
/**
* A Builder class that helps create an AssetManager with only a single invocation of
* {@link AssetManager#setApkAssets(ApkAssets[], boolean)}. Without using this builder,
@@ -507,7 +522,7 @@
outValue.changingConfigurations);
if (outValue.type == TypedValue.TYPE_STRING) {
- outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
+ outValue.string = getPooledStringForCookie(cookie, outValue.data);
}
return true;
}
@@ -554,7 +569,7 @@
outValue.changingConfigurations);
if (outValue.type == TypedValue.TYPE_STRING) {
- return mApkAssets[cookie - 1].getStringFromPool(outValue.data);
+ return getPooledStringForCookie(cookie, outValue.data);
}
return outValue.coerceToString();
}
@@ -632,7 +647,7 @@
int cookie = rawInfoArray[i];
int index = rawInfoArray[i + 1];
retArray[j] = (index >= 0 && cookie > 0)
- ? mApkAssets[cookie - 1].getStringFromPool(index) : null;
+ ? getPooledStringForCookie(cookie, index) : null;
}
return retArray;
}
@@ -688,7 +703,7 @@
outValue.changingConfigurations);
if (outValue.type == TypedValue.TYPE_STRING) {
- outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
+ outValue.string = getPooledStringForCookie(cookie, outValue.data);
}
return true;
}
@@ -753,6 +768,7 @@
*
* @hide
*/
+ @TestApi
public void setResourceResolutionLoggingEnabled(boolean enabled) {
synchronized (this) {
ensureValidLocked();
@@ -768,6 +784,7 @@
*
* @hide
*/
+ @TestApi
public @Nullable String getLastResourceResolution() {
synchronized (this) {
ensureValidLocked();
@@ -814,6 +831,13 @@
Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
+
+ String path = Paths.get("assets", fileName).toString();
+ InputStream inputStream = searchLoaders(0, path, accessMode);
+ if (inputStream != null) {
+ return inputStream;
+ }
+
final long asset = nativeOpenAsset(mObject, fileName, accessMode);
if (asset == 0) {
throw new FileNotFoundException("Asset file: " + fileName);
@@ -838,6 +862,13 @@
Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
+
+ String path = Paths.get("assets", fileName).toString();
+ AssetFileDescriptor fileDescriptor = searchLoadersFd(0, path);
+ if (fileDescriptor != null) {
+ return fileDescriptor;
+ }
+
final ParcelFileDescriptor pfd = nativeOpenAssetFd(mObject, fileName, mOffsets);
if (pfd == null) {
throw new FileNotFoundException("Asset file: " + fileName);
@@ -931,6 +962,12 @@
Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
+
+ InputStream inputStream = searchLoaders(cookie, fileName, accessMode);
+ if (inputStream != null) {
+ return inputStream;
+ }
+
final long asset = nativeOpenNonAsset(mObject, cookie, fileName, accessMode);
if (asset == 0) {
throw new FileNotFoundException("Asset absolute file: " + fileName);
@@ -970,6 +1007,12 @@
Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
+
+ AssetFileDescriptor fileDescriptor = searchLoadersFd(cookie, fileName);
+ if (fileDescriptor != null) {
+ return fileDescriptor;
+ }
+
final ParcelFileDescriptor pfd =
nativeOpenNonAssetFd(mObject, cookie, fileName, mOffsets);
if (pfd == null) {
@@ -1031,7 +1074,16 @@
Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
- final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);
+
+ final long xmlBlock;
+ AssetFileDescriptor fileDescriptor = searchLoadersFd(cookie, fileName);
+ if (fileDescriptor != null) {
+ xmlBlock = nativeOpenXmlAssetFd(mObject, cookie,
+ fileDescriptor.getFileDescriptor());
+ } else {
+ xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);
+ }
+
if (xmlBlock == 0) {
throw new FileNotFoundException("Asset XML file: " + fileName);
}
@@ -1041,6 +1093,85 @@
}
}
+ private InputStream searchLoaders(int cookie, @NonNull String fileName, int accessMode)
+ throws IOException {
+ if (mResourceLoaderManager == null) {
+ return null;
+ }
+
+ List<Pair<ResourceLoader, ResourcesProvider>> loaders =
+ mResourceLoaderManager.getInternalList();
+
+ // A cookie of 0 means no specific ApkAssets, so search everything
+ if (cookie == 0) {
+ for (int index = loaders.size() - 1; index >= 0; index--) {
+ Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+ try {
+ InputStream inputStream = pair.first.loadAsset(fileName, accessMode);
+ if (inputStream != null) {
+ return inputStream;
+ }
+ } catch (IOException ignored) {
+ // When searching, ignore read failures
+ }
+ }
+
+ return null;
+ }
+
+ ApkAssets apkAssets = mApkAssets[cookie - 1];
+ for (int index = loaders.size() - 1; index >= 0; index--) {
+ Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+ if (pair.second.getApkAssets() == apkAssets) {
+ return pair.first.loadAsset(fileName, accessMode);
+ }
+ }
+
+ return null;
+ }
+
+ private AssetFileDescriptor searchLoadersFd(int cookie, @NonNull String fileName)
+ throws IOException {
+ if (mResourceLoaderManager == null) {
+ return null;
+ }
+
+ List<Pair<ResourceLoader, ResourcesProvider>> loaders =
+ mResourceLoaderManager.getInternalList();
+
+ // A cookie of 0 means no specific ApkAssets, so search everything
+ if (cookie == 0) {
+ for (int index = loaders.size() - 1; index >= 0; index--) {
+ Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+ try {
+ ParcelFileDescriptor fileDescriptor = pair.first.loadAssetFd(fileName);
+ if (fileDescriptor != null) {
+ return new AssetFileDescriptor(fileDescriptor, 0,
+ AssetFileDescriptor.UNKNOWN_LENGTH);
+ }
+ } catch (IOException ignored) {
+ // When searching, ignore read failures
+ }
+ }
+
+ return null;
+ }
+
+ ApkAssets apkAssets = mApkAssets[cookie - 1];
+ for (int index = loaders.size() - 1; index >= 0; index--) {
+ Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+ if (pair.second.getApkAssets() == apkAssets) {
+ ParcelFileDescriptor fileDescriptor = pair.first.loadAssetFd(fileName);
+ if (fileDescriptor != null) {
+ return new AssetFileDescriptor(fileDescriptor, 0,
+ AssetFileDescriptor.UNKNOWN_LENGTH);
+ }
+ return null;
+ }
+ }
+ return null;
+ }
+
void xmlBlockGone(int id) {
synchronized (this) {
decRefsLocked(id);
@@ -1296,7 +1427,7 @@
*
* <p>On SDK 21 (Android 5.0: Lollipop) and above, Locale strings are valid
* <a href="https://tools.ietf.org/html/bcp47">BCP-47</a> language tags and can be
- * parsed using {@link java.util.Locale#forLanguageTag(String)}.
+ * parsed using {@link Locale#forLanguageTag(String)}.
*
* <p>On SDK 20 (Android 4.4W: Kitkat for watches) and below, locale strings
* are of the form {@code ll_CC} where {@code ll} is a two letter language code,
@@ -1439,6 +1570,8 @@
private static native @Nullable ParcelFileDescriptor nativeOpenNonAssetFd(long ptr, int cookie,
@NonNull String fileName, @NonNull long[] outOffsets) throws IOException;
private static native long nativeOpenXmlAsset(long ptr, int cookie, @NonNull String fileName);
+ private static native long nativeOpenXmlAssetFd(long ptr, int cookie,
+ @NonNull FileDescriptor fileDescriptor);
// Primitive resource native methods.
private static native int nativeGetResourceValue(long ptr, @AnyRes int resId, short density,
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index d7e4e14..2698c2d 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -30,6 +30,7 @@
import android.annotation.DrawableRes;
import android.annotation.FontRes;
import android.annotation.FractionRes;
+import android.annotation.IntRange;
import android.annotation.IntegerRes;
import android.annotation.LayoutRes;
import android.annotation.NonNull;
@@ -41,8 +42,12 @@
import android.annotation.StyleableRes;
import android.annotation.UnsupportedAppUsage;
import android.annotation.XmlRes;
+import android.app.ResourcesManager;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
+import android.content.res.loader.ResourceLoader;
+import android.content.res.loader.ResourceLoaderManager;
+import android.content.res.loader.ResourcesProvider;
import android.graphics.Movie;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
@@ -54,13 +59,16 @@
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.LongSparseArray;
+import android.util.Pair;
import android.util.Pools.SynchronizedPool;
import android.util.TypedValue;
import android.view.DisplayAdjustments;
import android.view.ViewDebug;
import android.view.ViewHierarchyEncoder;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
import com.android.internal.util.XmlUtils;
@@ -71,6 +79,8 @@
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/**
* Class for accessing an application's resources. This sits on top of the
@@ -133,6 +143,11 @@
@UnsupportedAppUsage
final ClassLoader mClassLoader;
+ private final Object mResourceLoaderLock = new Object();
+
+ @GuardedBy("mResourceLoaderLock")
+ private ResourceLoaderManager mResourceLoaderManager;
+
/**
* WeakReferences to Themes that were constructed from this Resources object.
* We keep track of these in case our underlying implementation is changed, in which case
@@ -148,6 +163,8 @@
private static final int MIN_THEME_REFS_FLUSH_SIZE = 32;
private int mThemeRefsNextFlushSize = MIN_THEME_REFS_FLUSH_SIZE;
+ private int mBaseApkAssetsSize;
+
/**
* Returns the most appropriate default theme for the specified target SDK version.
* <ul>
@@ -283,8 +300,15 @@
return;
}
+ mBaseApkAssetsSize = ArrayUtils.size(impl.getAssets().getApkAssets());
mResourcesImpl = impl;
+ synchronized (mResourceLoaderLock) {
+ if (mResourceLoaderManager != null) {
+ mResourceLoaderManager.onImplUpdate(mResourcesImpl);
+ }
+ }
+
// Create new ThemeImpls that are identical to the ones we have.
synchronized (mThemeRefs) {
final int count = mThemeRefs.size();
@@ -903,7 +927,7 @@
try {
final ResourcesImpl impl = mResourcesImpl;
impl.getValueForDensity(id, density, value, true);
- return impl.loadDrawable(this, value, id, density, theme);
+ return loadDrawable(value, id, density, theme);
} finally {
releaseTempTypedValue(value);
}
@@ -913,6 +937,14 @@
@UnsupportedAppUsage
Drawable loadDrawable(@NonNull TypedValue value, int id, int density, @Nullable Theme theme)
throws NotFoundException {
+ ResourceLoader loader = findLoader(value.assetCookie);
+ if (loader != null) {
+ Drawable drawable = loader.loadDrawable(value, id, density, theme);
+ if (drawable != null) {
+ return drawable;
+ }
+ }
+
return mResourcesImpl.loadDrawable(this, value, id, density, theme);
}
@@ -2280,7 +2312,7 @@
final ResourcesImpl impl = mResourcesImpl;
impl.getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
- return impl.loadXmlResourceParser(value.string.toString(), id,
+ return loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
}
throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
@@ -2304,6 +2336,14 @@
@UnsupportedAppUsage
XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie,
String type) throws NotFoundException {
+ ResourceLoader loader = findLoader(assetCookie);
+ if (loader != null) {
+ XmlResourceParser xml = loader.loadXmlResourceParser(file, id);
+ if (xml != null) {
+ return xml;
+ }
+ }
+
return mResourcesImpl.loadXmlResourceParser(file, id, assetCookie, type);
}
@@ -2329,4 +2369,137 @@
}
return theme.obtainStyledAttributes(set, attrs, 0, 0);
}
+
+ private ResourceLoader findLoader(int assetCookie) {
+ ApkAssets[] apkAssetsArray = mResourcesImpl.getAssets().getApkAssets();
+ int apkAssetsIndex = assetCookie - 1;
+ if (apkAssetsIndex < apkAssetsArray.length && apkAssetsIndex >= 0) {
+ ApkAssets apkAssets = apkAssetsArray[apkAssetsIndex];
+ if (apkAssets.isForLoader()) {
+ List<Pair<ResourceLoader, ResourcesProvider>> loaders;
+ // Since we don't lock the entire resolution path anyways,
+ // only lock here instead of entire method. The list is copied
+ // and effectively a snapshot is used.
+ synchronized (mResourceLoaderLock) {
+ loaders = mResourceLoaderManager.getInternalList();
+ }
+
+ if (!ArrayUtils.isEmpty(loaders)) {
+ int size = loaders.size();
+ for (int index = 0; index < size; index++) {
+ Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
+ if (pair.second.getApkAssets() == apkAssets) {
+ return pair.first;
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return copied list of loaders and providers previously added
+ */
+ @NonNull
+ public List<Pair<ResourceLoader, ResourcesProvider>> getLoaders() {
+ synchronized (mResourceLoaderLock) {
+ return mResourceLoaderManager == null
+ ? Collections.emptyList()
+ : mResourceLoaderManager.getLoaders();
+ }
+ }
+
+ /**
+ * Add a custom {@link ResourceLoader} which is added to the paths searched by
+ * {@link AssetManager} when resolving a resource.
+ *
+ * Resources are resolved as if the loader was a resource overlay, meaning the latest
+ * in the list, of equal or better config, is returned.
+ *
+ * {@link ResourcesProvider}s passed in here are not managed and a reference should be held
+ * to remove, re-use, or close them when necessary.
+ *
+ * @param resourceLoader an interface used to resolve file paths for drawables/XML files;
+ * a reference should be kept to remove the loader if necessary
+ * @param resourcesProvider an .apk or .arsc file representation
+ * @param index where to add the loader in the list
+ * @throws IllegalArgumentException if the resourceLoader is already added
+ * @throws IndexOutOfBoundsException if the index is invalid
+ */
+ public void addLoader(@NonNull ResourceLoader resourceLoader,
+ @NonNull ResourcesProvider resourcesProvider, @IntRange(from = 0) int index) {
+ synchronized (mResourceLoaderLock) {
+ if (mResourceLoaderManager == null) {
+ ResourcesManager.getInstance().registerForLoaders(this);
+ mResourceLoaderManager = new ResourceLoaderManager(mResourcesImpl);
+ }
+
+ mResourceLoaderManager.addLoader(resourceLoader, resourcesProvider, index);
+ }
+ }
+
+ /**
+ * @see #addLoader(ResourceLoader, ResourcesProvider, int).
+ *
+ * Adds to the end of the list.
+ *
+ * @return index the loader was added at
+ */
+ public int addLoader(@NonNull ResourceLoader resourceLoader,
+ @NonNull ResourcesProvider resourcesProvider) {
+ synchronized (mResourceLoaderLock) {
+ int index = getLoaders().size();
+ addLoader(resourceLoader, resourcesProvider, index);
+ return index;
+ }
+ }
+
+ /**
+ * Remove a loader previously added by
+ * {@link #addLoader(ResourceLoader, ResourcesProvider, int)}
+ *
+ * The caller maintains responsibility for holding a reference to the matching
+ * {@link ResourcesProvider} and closing it after this method has been called.
+ *
+ * @param resourceLoader the same reference passed into [addLoader
+ * @return the index the loader was at in the list, or -1 if the loader was not found
+ */
+ public int removeLoader(@NonNull ResourceLoader resourceLoader) {
+ synchronized (mResourceLoaderLock) {
+ if (mResourceLoaderManager == null) {
+ return -1;
+ }
+
+ return mResourceLoaderManager.removeLoader(resourceLoader);
+ }
+ }
+
+ /**
+ * Swap the current set of loaders. Preferred to multiple remove/add calls as this doesn't
+ * update the resource data structures after each modification.
+ *
+ * Set to null or an empty list to clear the set of loaders.
+ *
+ * The caller maintains responsibility for holding references to the added
+ * {@link ResourcesProvider}s and closing them after this method has been called.
+ *
+ * @param resourceLoadersAndProviders a list of pairs to add
+ */
+ public void setLoaders(
+ @Nullable List<Pair<ResourceLoader, ResourcesProvider>> resourceLoadersAndProviders) {
+ synchronized (mResourceLoaderLock) {
+ if (mResourceLoaderManager == null) {
+ if (ArrayUtils.isEmpty(resourceLoadersAndProviders)) {
+ return;
+ }
+
+ ResourcesManager.getInstance().registerForLoaders(this);
+ mResourceLoaderManager = new ResourceLoaderManager(mResourcesImpl);
+ }
+
+ mResourceLoaderManager.setLoaders(resourceLoadersAndProviders);
+ }
+ }
}
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index b72544c..84489cf 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -57,6 +57,8 @@
import com.android.internal.util.GrowingArrayUtils;
+import libcore.io.IoUtils;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -376,7 +378,7 @@
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesImpl#updateConfiguration");
try {
synchronized (mAccessLock) {
- if (false) {
+ if (DEBUG_CONFIG) {
Slog.i(TAG, "**** Updating config of " + this + ": old config is "
+ mConfiguration + " old compat is "
+ mDisplayAdjustments.getCompatibilityInfo());
@@ -572,6 +574,20 @@
}
}
+ /**
+ * Wipe all caches that might be read and return an outdated object when resolving a resource.
+ */
+ public void clearAllCaches() {
+ synchronized (mAccessLock) {
+ mDrawableCache.clear();
+ mColorDrawableCache.clear();
+ mComplexColorCache.clear();
+ mAnimatorCache.clear();
+ mStateListAnimatorCache.clear();
+ flushLayoutCache();
+ }
+ }
+
@Nullable
Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
int density, @Nullable Resources.Theme theme)
@@ -802,6 +818,27 @@
}
/**
+ * Loads a Drawable from an encoded image stream, or null.
+ *
+ * This call will handle closing the {@link InputStream}.
+ */
+ @Nullable
+ private Drawable decodeImageDrawable(@NonNull InputStream inputStream,
+ @NonNull Resources wrapper, @NonNull TypedValue value) {
+ ImageDecoder.Source src = ImageDecoder.createSource(wrapper, inputStream, value.density);
+ try {
+ return ImageDecoder.decodeDrawable(src, (decoder, info, s) ->
+ decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE));
+ } catch (IOException ignored) {
+ // This is okay. This may be something that ImageDecoder does not
+ // support, like SVG.
+ return null;
+ } finally {
+ IoUtils.closeQuietly(inputStream);
+ }
+ }
+
+ /**
* Loads a drawable from XML or resources stream.
*
* @return Drawable, or null if Drawable cannot be decoded.
@@ -865,8 +902,12 @@
} else {
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
- AssetInputStream ais = (AssetInputStream) is;
- dr = decodeImageDrawable(ais, wrapper, value);
+ if (is instanceof AssetInputStream) {
+ AssetInputStream ais = (AssetInputStream) is;
+ dr = decodeImageDrawable(ais, wrapper, value);
+ } else {
+ dr = decodeImageDrawable(is, wrapper, value);
+ }
}
} finally {
stack.pop();
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index 2ae1932..d43bd36 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -47,6 +47,7 @@
import com.android.internal.annotations.GuardedBy;
+import java.io.Closeable;
import java.util.Arrays;
/**
@@ -54,7 +55,7 @@
*
* {@hide}
*/
-final class StringBlock {
+public final class StringBlock implements Closeable {
private static final String TAG = "AssetManager";
private static final boolean localLOGV = false;
@@ -175,6 +176,7 @@
}
}
+ @Override
public void close() {
synchronized (this) {
if (mOpen) {
@@ -517,7 +519,7 @@
* of this newly creating StringBlock.
*/
@UnsupportedAppUsage
- StringBlock(long obj, boolean useSparse) {
+ public StringBlock(long obj, boolean useSparse) {
mNative = obj;
mUseSparse = useSparse;
mOwnsNative = false;
diff --git a/core/java/android/content/res/ThemedResourceCache.java b/core/java/android/content/res/ThemedResourceCache.java
index 06cafdb..968ab40 100644
--- a/core/java/android/content/res/ThemedResourceCache.java
+++ b/core/java/android/content/res/ThemedResourceCache.java
@@ -22,8 +22,8 @@
import android.content.pm.ActivityInfo.Config;
import android.content.res.Resources.Theme;
import android.content.res.Resources.ThemeKey;
-import android.util.LongSparseArray;
import android.util.ArrayMap;
+import android.util.LongSparseArray;
import java.lang.ref.WeakReference;
@@ -234,4 +234,18 @@
return entry == null || (configChanges != 0
&& shouldInvalidateEntry(entry, configChanges));
}
+
+ public synchronized void clear() {
+ if (mThemedEntries != null) {
+ mThemedEntries.clear();
+ }
+
+ if (mUnthemedEntries != null) {
+ mUnthemedEntries.clear();
+ }
+
+ if (mNullThemedEntries != null) {
+ mNullThemedEntries.clear();
+ }
+ }
}
diff --git a/core/java/android/content/res/loader/DirectoryResourceLoader.java b/core/java/android/content/res/loader/DirectoryResourceLoader.java
new file mode 100644
index 0000000..7d90e72
--- /dev/null
+++ b/core/java/android/content/res/loader/DirectoryResourceLoader.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A {@link ResourceLoader} that searches a directory for assets.
+ *
+ * Assumes that resource paths are resolvable child paths of the directory passed in.
+ */
+public class DirectoryResourceLoader implements ResourceLoader {
+
+ @NonNull
+ private final File mDirectory;
+
+ public DirectoryResourceLoader(@NonNull File directory) {
+ this.mDirectory = directory;
+ }
+
+ @Nullable
+ @Override
+ public InputStream loadAsset(@NonNull String path, int accessMode) throws IOException {
+ File file = findFile(path);
+ if (file == null || !file.exists()) {
+ return null;
+ }
+ return new FileInputStream(file);
+ }
+
+ @Nullable
+ @Override
+ public ParcelFileDescriptor loadAssetFd(@NonNull String path) throws IOException {
+ File file = findFile(path);
+ if (file == null || !file.exists()) {
+ return null;
+ }
+ return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ }
+
+ /**
+ * Find the file for the given path encoded into the resource table.
+ */
+ @Nullable
+ public File findFile(@NonNull String path) {
+ return mDirectory.toPath().resolve(path).toFile();
+ }
+
+ @NonNull
+ public File getDirectory() {
+ return mDirectory;
+ }
+}
diff --git a/core/java/android/content/res/loader/ResourceLoader.java b/core/java/android/content/res/loader/ResourceLoader.java
new file mode 100644
index 0000000..af32aa2
--- /dev/null
+++ b/core/java/android/content/res/loader/ResourceLoader.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader;
+
+import android.annotation.AnyRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.os.ParcelFileDescriptor;
+import android.util.TypedValue;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Exposes methods for overriding file-based resource loading from a {@link Resources}.
+ *
+ * To be used with {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)} and related
+ * methods to override resource loading.
+ *
+ * Note that this class doesn't actually contain any resource data. Non-file-based resources are
+ * loaded directly from the {@link ResourcesProvider}'s .arsc representation.
+ *
+ * An instance's methods will only be called if its corresponding {@link ResourcesProvider}'s
+ * resources table contains an entry for the resource ID being resolved,
+ * with the exception of the non-cookie variants of {@link AssetManager}'s openAsset and
+ * openNonAsset.
+ *
+ * Those methods search backwards through all {@link ResourceLoader}s and then any paths provided
+ * by the application or system.
+ *
+ * Otherwise, an ARSC that defines R.drawable.some_id must be provided if a {@link ResourceLoader}
+ * wants to point R.drawable.some_id to a different file on disk.
+ */
+public interface ResourceLoader {
+
+ /**
+ * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to
+ * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return a
+ * {@link Drawable} which should be returned by the parent
+ * {@link Resources#getDrawable(int, Resources.Theme)}.
+ *
+ * @param value the resolved {@link TypedValue} before it has been converted to a Drawable
+ * object
+ * @param id the R.drawable ID this resolution is for
+ * @param density the requested density
+ * @param theme the {@link Resources.Theme} resolved under
+ * @return null if resolution should try to find an entry inside the {@link ResourcesProvider},
+ * including calling through to {@link #loadAsset(String, int)} or {@link #loadAssetFd(String)}
+ */
+ @Nullable
+ default Drawable loadDrawable(@NonNull TypedValue value, int id, int density,
+ @Nullable Resources.Theme theme) {
+ return null;
+ }
+
+ /**
+ * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to
+ * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return an
+ * {@link XmlResourceParser} which should be returned by the parent
+ * {@link Resources#getDrawable(int, Resources.Theme)}.
+ *
+ * @param path the string that was found in the string pool
+ * @param id the XML ID this resolution is for, can be R.anim, R.layout, or R.xml
+ * @return null if resolution should try to find an entry inside the {@link ResourcesProvider},
+ * including calling through to {@link #loadAssetFd(String)} (String, int)}
+ */
+ @Nullable
+ default XmlResourceParser loadXmlResourceParser(@NonNull String path, @AnyRes int id) {
+ return null;
+ }
+
+ /**
+ * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to
+ * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return an
+ * {@link InputStream} which should be returned when an asset is loaded by {@link AssetManager}.
+ * Assets will be loaded from a provider's root, with anything in its assets subpath prefixed
+ * with "assets/".
+ *
+ * @param path the asset path to load
+ * @param accessMode {@link AssetManager} access mode; does not have to be respected
+ * @return null if resolution should try to find an entry inside the {@link ResourcesProvider}
+ */
+ @Nullable
+ default InputStream loadAsset(@NonNull String path, int accessMode) throws IOException {
+ return null;
+ }
+
+ /**
+ * {@link ParcelFileDescriptor} variant of {@link #loadAsset(String, int)}.
+ *
+ * @param path the asset path to load
+ * @return null if resolution should try to find an entry inside the {@link ResourcesProvider}
+ */
+ @Nullable
+ default ParcelFileDescriptor loadAssetFd(@NonNull String path) throws IOException {
+ return null;
+ }
+}
diff --git a/core/java/android/content/res/loader/ResourceLoaderManager.java b/core/java/android/content/res/loader/ResourceLoaderManager.java
new file mode 100644
index 0000000..ddbfa81
--- /dev/null
+++ b/core/java/android/content/res/loader/ResourceLoaderManager.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader;
+
+import android.annotation.Nullable;
+import android.content.res.ApkAssets;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.content.res.ResourcesImpl;
+import android.util.Pair;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public class ResourceLoaderManager {
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final List<Pair<ResourceLoader, ResourcesProvider>> mResourceLoaders =
+ new ArrayList<>();
+
+ @GuardedBy("mLock")
+ private ResourcesImpl mResourcesImpl;
+
+ public ResourceLoaderManager(ResourcesImpl resourcesImpl) {
+ this.mResourcesImpl = resourcesImpl;
+ this.mResourcesImpl.getAssets().setResourceLoaderManager(this);
+ }
+
+ /**
+ * Copies the list to ensure that ongoing mutations don't affect the list if it's being used
+ * as a search set.
+ *
+ * @see Resources#getLoaders()
+ */
+ public List<Pair<ResourceLoader, ResourcesProvider>> getLoaders() {
+ synchronized (mLock) {
+ return new ArrayList<>(mResourceLoaders);
+ }
+ }
+
+ /**
+ * Returns a list for searching for a loader. Locks and copies the list to ensure that
+ * ongoing mutations don't affect the search set.
+ */
+ public List<Pair<ResourceLoader, ResourcesProvider>> getInternalList() {
+ synchronized (mLock) {
+ return new ArrayList<>(mResourceLoaders);
+ }
+ }
+
+ /**
+ * TODO(b/136251855): Consider optional boolean ignoreConfigurations to allow ResourceLoader
+ * to override every configuration in the target package
+ *
+ * @see Resources#addLoader(ResourceLoader, ResourcesProvider)
+ */
+ public void addLoader(ResourceLoader resourceLoader, ResourcesProvider resourcesProvider,
+ int index) {
+ synchronized (mLock) {
+ for (int listIndex = 0; listIndex < mResourceLoaders.size(); listIndex++) {
+ if (Objects.equals(mResourceLoaders.get(listIndex).first, resourceLoader)) {
+ throw new IllegalArgumentException("Cannot add the same ResourceLoader twice");
+ }
+ }
+
+ mResourceLoaders.add(index, Pair.create(resourceLoader, resourcesProvider));
+ updateLoaders();
+ }
+ }
+
+ /**
+ * @see Resources#removeLoader(ResourceLoader)
+ */
+ public int removeLoader(ResourceLoader resourceLoader) {
+ synchronized (mLock) {
+ int indexOfLoader = -1;
+
+ for (int index = 0; index < mResourceLoaders.size(); index++) {
+ if (mResourceLoaders.get(index).first == resourceLoader) {
+ indexOfLoader = index;
+ break;
+ }
+ }
+
+ if (indexOfLoader < 0) {
+ return indexOfLoader;
+ }
+
+ mResourceLoaders.remove(indexOfLoader);
+ updateLoaders();
+ return indexOfLoader;
+ }
+ }
+
+ /**
+ * @see Resources#setLoaders(List)
+ */
+ public void setLoaders(
+ @Nullable List<Pair<ResourceLoader, ResourcesProvider>> newLoadersAndProviders) {
+ synchronized (mLock) {
+ if (ArrayUtils.isEmpty(newLoadersAndProviders)) {
+ mResourceLoaders.clear();
+ updateLoaders();
+ return;
+ }
+
+ int size = newLoadersAndProviders.size();
+ for (int newIndex = 0; newIndex < size; newIndex++) {
+ ResourceLoader resourceLoader = newLoadersAndProviders.get(newIndex).first;
+ for (int oldIndex = 0; oldIndex < mResourceLoaders.size(); oldIndex++) {
+ if (Objects.equals(mResourceLoaders.get(oldIndex).first, resourceLoader)) {
+ throw new IllegalArgumentException(
+ "Cannot add the same ResourceLoader twice");
+ }
+ }
+ }
+
+ mResourceLoaders.clear();
+ mResourceLoaders.addAll(newLoadersAndProviders);
+
+ updateLoaders();
+ }
+ }
+
+ /**
+ * Swap the tracked {@link ResourcesImpl} and reattach any loaders to it.
+ */
+ public void onImplUpdate(ResourcesImpl resourcesImpl) {
+ synchronized (mLock) {
+ this.mResourcesImpl = resourcesImpl;
+ updateLoaders();
+ }
+ }
+
+ private void updateLoaders() {
+ synchronized (mLock) {
+ AssetManager assetManager = mResourcesImpl.getAssets();
+ ApkAssets[] existingApkAssets = assetManager.getApkAssets();
+ int baseApkAssetsSize = 0;
+ for (int index = existingApkAssets.length - 1; index >= 0; index--) {
+ // Loaders are always last, so the first non-loader is the end of the base assets
+ if (!existingApkAssets[index].isForLoader()) {
+ baseApkAssetsSize = index + 1;
+ break;
+ }
+ }
+
+ List<ApkAssets> newAssets = new ArrayList<>();
+ for (int index = 0; index < baseApkAssetsSize; index++) {
+ newAssets.add(existingApkAssets[index]);
+ }
+
+ int size = mResourceLoaders.size();
+ for (int index = 0; index < size; index++) {
+ ApkAssets apkAssets = mResourceLoaders.get(index).second.getApkAssets();
+ newAssets.add(apkAssets);
+ }
+
+ assetManager.setApkAssets(newAssets.toArray(new ApkAssets[0]), true);
+
+ // Short of resolving every resource, it's too difficult to determine what has changed
+ // when a resource loader is changed, so just clear everything.
+ mResourcesImpl.clearAllCaches();
+ }
+ }
+}
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
new file mode 100644
index 0000000..050aeb7
--- /dev/null
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.res.ApkAssets;
+import android.content.res.Resources;
+import android.os.ParcelFileDescriptor;
+import android.os.SharedMemory;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * Provides methods to load resources from an .apk or .arsc file to pass to
+ * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}.
+ *
+ * It is the responsibility of the app to close any instances.
+ */
+public final class ResourcesProvider implements AutoCloseable, Closeable {
+
+ /**
+ * Contains no data, assuming that any resource loading behavior will be handled in the
+ * corresponding {@link ResourceLoader}.
+ */
+ @NonNull
+ public static ResourcesProvider empty() {
+ return new ResourcesProvider(ApkAssets.loadEmptyForLoader());
+ }
+
+ /**
+ * Read from an .apk file descriptor.
+ *
+ * The file descriptor is duplicated and the one passed in may be closed by the application
+ * at any time.
+ */
+ @NonNull
+ public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor)
+ throws IOException {
+ return new ResourcesProvider(
+ ApkAssets.loadApkForLoader(fileDescriptor.getFileDescriptor()));
+ }
+
+ /**
+ * Read from an .apk file representation in memory.
+ */
+ @NonNull
+ public static ResourcesProvider loadFromApk(@NonNull SharedMemory sharedMemory)
+ throws IOException {
+ return new ResourcesProvider(
+ ApkAssets.loadApkForLoader(sharedMemory.getFileDescriptor()));
+ }
+
+ /**
+ * Read from an .arsc file descriptor.
+ *
+ * The file descriptor is duplicated and the one passed in may be closed by the application
+ * at any time.
+ */
+ @NonNull
+ public static ResourcesProvider loadFromArsc(@NonNull ParcelFileDescriptor fileDescriptor)
+ throws IOException {
+ return new ResourcesProvider(
+ ApkAssets.loadArscForLoader(fileDescriptor.getFileDescriptor()));
+ }
+
+ /**
+ * Read from an .arsc file representation in memory.
+ */
+ @NonNull
+ public static ResourcesProvider loadFromArsc(@NonNull SharedMemory sharedMemory)
+ throws IOException {
+ return new ResourcesProvider(
+ ApkAssets.loadArscForLoader(sharedMemory.getFileDescriptor()));
+ }
+
+ /**
+ * Read from a split installed alongside the application, which may not have been
+ * loaded initially because the application requested isolated split loading.
+ */
+ @NonNull
+ public static ResourcesProvider loadFromSplit(@NonNull Context context,
+ @NonNull String splitName) throws IOException {
+ ApplicationInfo appInfo = context.getApplicationInfo();
+ int splitIndex = ArrayUtils.indexOf(appInfo.splitNames, splitName);
+ if (splitIndex < 0) {
+ throw new IllegalArgumentException("Split " + splitName + " not found");
+ }
+
+ String splitPath = appInfo.getSplitCodePaths()[splitIndex];
+ return new ResourcesProvider(ApkAssets.loadApkForLoader(splitPath));
+ }
+
+
+ @NonNull
+ private final ApkAssets mApkAssets;
+
+ private ResourcesProvider(@NonNull ApkAssets apkAssets) {
+ this.mApkAssets = apkAssets;
+ }
+
+ /** @hide */
+ @NonNull
+ public ApkAssets getApkAssets() {
+ return mApkAssets;
+ }
+
+ @Override
+ public void close() {
+ try {
+ mApkAssets.close();
+ } catch (Throwable ignored) {
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ close();
+ super.finalize();
+ }
+}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 111a8c4..5c65238 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -3265,42 +3265,77 @@
/**
* Called when the framework connects and has declared a new network ready for use.
- * This callback may be called more than once if the {@link Network} that is
- * satisfying the request changes. This will always immediately be followed by a
- * call to {@link #onCapabilitiesChanged(Network, NetworkCapabilities)} then by a
- * call to {@link #onLinkPropertiesChanged(Network, LinkProperties)}, and a call to
- * {@link #onBlockedStatusChanged(Network, boolean)}.
+ *
+ * <p>For callbacks registered with {@link #registerNetworkCallback}, multiple networks may
+ * be available at the same time, and onAvailable will be called for each of these as they
+ * appear.
+ *
+ * <p>For callbacks registered with {@link #requestNetwork} and
+ * {@link #registerDefaultNetworkCallback}, this means the network passed as an argument
+ * is the new best network for this request and is now tracked by this callback ; this
+ * callback will no longer receive method calls about other networks that may have been
+ * passed to this method previously. The previously-best network may have disconnected, or
+ * it may still be around and the newly-best network may simply be better.
+ *
+ * <p>Starting with {@link android.os.Build.VERSION_CODES#O}, this will always immediately
+ * be followed by a call to {@link #onCapabilitiesChanged(Network, NetworkCapabilities)}
+ * then by a call to {@link #onLinkPropertiesChanged(Network, LinkProperties)}, and a call
+ * to {@link #onBlockedStatusChanged(Network, boolean)}.
+ *
+ * <p>Do NOT call {@link #getNetworkCapabilities(Network)} or
+ * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in
+ * this callback as this is prone to race conditions (there is no guarantee the objects
+ * returned by these methods will be current). Instead, wait for a call to
+ * {@link #onCapabilitiesChanged(Network, NetworkCapabilities)} and
+ * {@link #onLinkPropertiesChanged(Network, LinkProperties)} whose arguments are guaranteed
+ * to be well-ordered with respect to other callbacks.
*
* @param network The {@link Network} of the satisfying network.
*/
public void onAvailable(@NonNull Network network) {}
/**
- * Called when the network is about to be disconnected. Often paired with an
- * {@link NetworkCallback#onAvailable} call with the new replacement network
- * for graceful handover. This may not be called if we have a hard loss
- * (loss without warning). This may be followed by either a
- * {@link NetworkCallback#onLost} call or a
- * {@link NetworkCallback#onAvailable} call for this network depending
- * on whether we lose or regain it.
+ * Called when the network is about to be lost, typically because there are no outstanding
+ * requests left for it. This may be paired with a {@link NetworkCallback#onAvailable} call
+ * with the new replacement network for graceful handover. This method is not guaranteed
+ * to be called before {@link NetworkCallback#onLost} is called, for example in case a
+ * network is suddenly disconnected.
*
- * @param network The {@link Network} that is about to be disconnected.
- * @param maxMsToLive The time in ms the framework will attempt to keep the
- * network connected. Note that the network may suffer a
- * hard loss at any time.
+ * <p>Do NOT call {@link #getNetworkCapabilities(Network)} or
+ * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in
+ * this callback as this is prone to race conditions ; calling these methods while in a
+ * callback may return an outdated or even a null object.
+ *
+ * @param network The {@link Network} that is about to be lost.
+ * @param maxMsToLive The time in milliseconds the system intends to keep the network
+ * connected for graceful handover; note that the network may still
+ * suffer a hard loss at any time.
*/
public void onLosing(@NonNull Network network, int maxMsToLive) {}
/**
- * Called when the framework has a hard loss of the network or when the
- * graceful failure ends.
+ * Called when a network disconnects or otherwise no longer satisfies this request or
+ * callback.
+ *
+ * <p>If the callback was registered with requestNetwork() or
+ * registerDefaultNetworkCallback(), it will only be invoked against the last network
+ * returned by onAvailable() when that network is lost and no other network satisfies
+ * the criteria of the request.
+ *
+ * <p>If the callback was registered with registerNetworkCallback() it will be called for
+ * each network which no longer satisfies the criteria of the callback.
+ *
+ * <p>Do NOT call {@link #getNetworkCapabilities(Network)} or
+ * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in
+ * this callback as this is prone to race conditions ; calling these methods while in a
+ * callback may return an outdated or even a null object.
*
* @param network The {@link Network} lost.
*/
public void onLost(@NonNull Network network) {}
/**
- * Called if no network is found in the timeout time specified in
+ * Called if no network is found within the timeout time specified in
* {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} call or if the
* requested network request cannot be fulfilled (whether or not a timeout was
* specified). When this callback is invoked the associated
@@ -3310,8 +3345,15 @@
public void onUnavailable() {}
/**
- * Called when the network the framework connected to for this request
- * changes capabilities but still satisfies the stated need.
+ * Called when the network corresponding to this request changes capabilities but still
+ * satisfies the requested criteria.
+ *
+ * <p>Starting with {@link android.os.Build.VERSION_CODES#O} this method is guaranteed
+ * to be called immediately after {@link #onAvailable}.
+ *
+ * <p>Do NOT call {@link #getLinkProperties(Network)} or other synchronous
+ * ConnectivityManager methods in this callback as this is prone to race conditions :
+ * calling these methods while in a callback may return an outdated or even a null object.
*
* @param network The {@link Network} whose capabilities have changed.
* @param networkCapabilities The new {@link android.net.NetworkCapabilities} for this
@@ -3321,8 +3363,14 @@
@NonNull NetworkCapabilities networkCapabilities) {}
/**
- * Called when the network the framework connected to for this request
- * changes {@link LinkProperties}.
+ * Called when the network corresponding to this request changes {@link LinkProperties}.
+ *
+ * <p>Starting with {@link android.os.Build.VERSION_CODES#O} this method is guaranteed
+ * to be called immediately after {@link #onAvailable}.
+ *
+ * <p>Do NOT call {@link #getNetworkCapabilities(Network)} or other synchronous
+ * ConnectivityManager methods in this callback as this is prone to race conditions :
+ * calling these methods while in a callback may return an outdated or even a null object.
*
* @param network The {@link Network} whose link properties have changed.
* @param linkProperties The new {@link LinkProperties} for this network.
@@ -3331,12 +3379,20 @@
@NonNull LinkProperties linkProperties) {}
/**
- * Called when the network the framework connected to for this request
- * goes into {@link NetworkInfo.State#SUSPENDED}.
- * This generally means that while the TCP connections are still live,
- * temporarily network data fails to transfer. Specifically this is used
- * on cellular networks to mask temporary outages when driving through
- * a tunnel, etc.
+ * Called when the network the framework connected to for this request suspends data
+ * transmission temporarily.
+ *
+ * <p>This generally means that while the TCP connections are still live temporarily
+ * network data fails to transfer. To give a specific example, this is used on cellular
+ * networks to mask temporary outages when driving through a tunnel, etc. In general this
+ * means read operations on sockets on this network will block once the buffers are
+ * drained, and write operations will block once the buffers are full.
+ *
+ * <p>Do NOT call {@link #getNetworkCapabilities(Network)} or
+ * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in
+ * this callback as this is prone to race conditions (there is no guarantee the objects
+ * returned by these methods will be current).
+ *
* @hide
*/
public void onNetworkSuspended(@NonNull Network network) {}
@@ -3345,6 +3401,12 @@
* Called when the network the framework connected to for this request
* returns from a {@link NetworkInfo.State#SUSPENDED} state. This should always be
* preceded by a matching {@link NetworkCallback#onNetworkSuspended} call.
+
+ * <p>Do NOT call {@link #getNetworkCapabilities(Network)} or
+ * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in
+ * this callback as this is prone to race conditions : calling these methods while in a
+ * callback may return an outdated or even a null object.
+ *
* @hide
*/
public void onNetworkResumed(@NonNull Network network) {}
@@ -3352,6 +3414,11 @@
/**
* Called when access to the specified network is blocked or unblocked.
*
+ * <p>Do NOT call {@link #getNetworkCapabilities(Network)} or
+ * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in
+ * this callback as this is prone to race conditions : calling these methods while in a
+ * callback may return an outdated or even a null object.
+ *
* @param network The {@link Network} whose blocked status has changed.
* @param blocked The blocked status of this {@link Network}.
*/
@@ -3588,13 +3655,51 @@
/**
* Request a network to satisfy a set of {@link android.net.NetworkCapabilities}.
*
- * This {@link NetworkRequest} will live until released via
- * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. A
- * version of the method which takes a timeout is
- * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)}.
- * Status of the request can be followed by listening to the various
- * callbacks described in {@link NetworkCallback}. The {@link Network}
- * can be used to direct traffic to the network.
+ * <p>This method will attempt to find the best network that matches the passed
+ * {@link NetworkRequest}, and to bring up one that does if none currently satisfies the
+ * criteria. The platform will evaluate which network is the best at its own discretion.
+ * Throughput, latency, cost per byte, policy, user preference and other considerations
+ * may be factored in the decision of what is considered the best network.
+ *
+ * <p>As long as this request is outstanding, the platform will try to maintain the best network
+ * matching this request, while always attempting to match the request to a better network if
+ * possible. If a better match is found, the platform will switch this request to the now-best
+ * network and inform the app of the newly best network by invoking
+ * {@link NetworkCallback#onAvailable(Network)} on the provided callback. Note that the platform
+ * will not try to maintain any other network than the best one currently matching the request:
+ * a network not matching any network request may be disconnected at any time.
+ *
+ * <p>For example, an application could use this method to obtain a connected cellular network
+ * even if the device currently has a data connection over Ethernet. This may cause the cellular
+ * radio to consume additional power. Or, an application could inform the system that it wants
+ * a network supporting sending MMSes and have the system let it know about the currently best
+ * MMS-supporting network through the provided {@link NetworkCallback}.
+ *
+ * <p>The status of the request can be followed by listening to the various callbacks described
+ * in {@link NetworkCallback}. The {@link Network} object passed to the callback methods can be
+ * used to direct traffic to the network (although accessing some networks may be subject to
+ * holding specific permissions). Callers will learn about the specific characteristics of the
+ * network through
+ * {@link NetworkCallback#onCapabilitiesChanged(Network, NetworkCapabilities)} and
+ * {@link NetworkCallback#onLinkPropertiesChanged(Network, LinkProperties)}. The methods of the
+ * provided {@link NetworkCallback} will only be invoked due to changes in the best network
+ * matching the request at any given time; therefore when a better network matching the request
+ * becomes available, the {@link NetworkCallback#onAvailable(Network)} method is called
+ * with the new network after which no further updates are given about the previously-best
+ * network, unless it becomes the best again at some later time. All callbacks are invoked
+ * in order on the same thread, which by default is a thread created by the framework running
+ * in the app.
+ * {@see #requestNetwork(NetworkRequest, NetworkCallback, Handler)} to change where the
+ * callbacks are invoked.
+ *
+ * <p>This{@link NetworkRequest} will live until released via
+ * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits, at
+ * which point the system may let go of the network at any time.
+ *
+ * <p>A version of this method which takes a timeout is
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)}, that an app can use to only
+ * wait for a limited amount of time for the network to become unavailable.
+ *
* <p>It is presently unsupported to request a network with mutable
* {@link NetworkCapabilities} such as
* {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or
@@ -3602,7 +3707,7 @@
* as these {@code NetworkCapabilities} represent states that a particular
* network may never attain, and whether a network will attain these states
* is unknown prior to bringing up the network so the framework does not
- * know how to go about satisfing a request with these capabilities.
+ * know how to go about satisfying a request with these capabilities.
*
* <p>This method requires the caller to hold either the
* {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
@@ -3625,34 +3730,17 @@
/**
* Request a network to satisfy a set of {@link android.net.NetworkCapabilities}.
*
- * This {@link NetworkRequest} will live until released via
- * {@link #unregisterNetworkCallback(NetworkCallback)} or the calling application exits. A
- * version of the method which takes a timeout is
- * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)}.
- * Status of the request can be followed by listening to the various
- * callbacks described in {@link NetworkCallback}. The {@link Network}
- * can be used to direct traffic to the network.
- * <p>It is presently unsupported to request a network with mutable
- * {@link NetworkCapabilities} such as
- * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or
- * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}
- * as these {@code NetworkCapabilities} represent states that a particular
- * network may never attain, and whether a network will attain these states
- * is unknown prior to bringing up the network so the framework does not
- * know how to go about satisfying a request with these capabilities.
+ * This method behaves identically to {@link #requestNetwork(NetworkRequest, NetworkCallback)}
+ * but runs all the callbacks on the passed Handler.
*
- * <p>This method requires the caller to hold either the
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
- * or the ability to modify system settings as determined by
- * {@link android.provider.Settings.System#canWrite}.</p>
+ * <p>This method has the same permission requirements as
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)} and throws the same exceptions in
+ * the same conditions.
*
* @param request {@link NetworkRequest} describing this request.
* @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
* the callback must not be shared - it uniquely specifies this request.
* @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
- * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
- * @throws SecurityException if missing the appropriate permissions.
- * @throws RuntimeException if request limit per UID is exceeded.
*/
public void requestNetwork(@NonNull NetworkRequest request,
@NonNull NetworkCallback networkCallback, @NonNull Handler handler) {
@@ -3677,10 +3765,9 @@
* timeout) - {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} is provided
* for that purpose. Calling this method will attempt to bring up the requested network.
*
- * <p>This method requires the caller to hold either the
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
- * or the ability to modify system settings as determined by
- * {@link android.provider.Settings.System#canWrite}.</p>
+ * <p>This method has the same permission requirements as
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)} and throws the same exceptions in
+ * the same conditions.
*
* @param request {@link NetworkRequest} describing this request.
* @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
@@ -3688,9 +3775,6 @@
* @param timeoutMs The time in milliseconds to attempt looking for a suitable network
* before {@link NetworkCallback#onUnavailable()} is called. The timeout must
* be a positive value (i.e. >0).
- * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
- * @throws SecurityException if missing the appropriate permissions.
- * @throws RuntimeException if request limit per UID is exceeded.
*/
public void requestNetwork(@NonNull NetworkRequest request,
@NonNull NetworkCallback networkCallback, int timeoutMs) {
@@ -3703,21 +3787,13 @@
* Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited
* by a timeout.
*
- * This function behaves identically to the version without timeout, but if a suitable
- * network is not found within the given time (in milliseconds) the
- * {@link NetworkCallback#onUnavailable} callback is called. The request can still be
- * released normally by calling {@link #unregisterNetworkCallback(NetworkCallback)} but does
- * not have to be released if timed-out (it is automatically released). Unregistering a
- * request that timed out is not an error.
+ * This method behaves identically to
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} but runs all the callbacks
+ * on the passed Handler.
*
- * <p>Do not use this method to poll for the existence of specific networks (e.g. with a small
- * timeout) - {@link #registerNetworkCallback(NetworkRequest, NetworkCallback)} is provided
- * for that purpose. Calling this method will attempt to bring up the requested network.
- *
- * <p>This method requires the caller to hold either the
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE} permission
- * or the ability to modify system settings as determined by
- * {@link android.provider.Settings.System#canWrite}.</p>
+ * <p>This method has the same permission requirements as
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback, int)} and throws the same exceptions
+ * in the same conditions.
*
* @param request {@link NetworkRequest} describing this request.
* @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
@@ -3725,9 +3801,6 @@
* @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
* @param timeoutMs The time in milliseconds to attempt looking for a suitable network
* before {@link NetworkCallback#onUnavailable} is called.
- * @throws IllegalArgumentException if {@code request} contains invalid network capabilities.
- * @throws SecurityException if missing the appropriate permissions.
- * @throws RuntimeException if request limit per UID is exceeded.
*/
public void requestNetwork(@NonNull NetworkRequest request,
@NonNull NetworkCallback networkCallback, @NonNull Handler handler, int timeoutMs) {
diff --git a/core/java/android/net/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl
index 106b7be..fe9141c 100644
--- a/core/java/android/net/INetworkPolicyListener.aidl
+++ b/core/java/android/net/INetworkPolicyListener.aidl
@@ -15,6 +15,7 @@
*/
package android.net;
+import android.telephony.SubscriptionPlan;
/** {@hide} */
oneway interface INetworkPolicyListener {
@@ -22,5 +23,6 @@
void onMeteredIfacesChanged(in String[] meteredIfaces);
void onRestrictBackgroundChanged(boolean restrictBackground);
void onUidPoliciesChanged(int uid, int uidPolicies);
- void onSubscriptionOverride(int subId, int overrideMask, int overrideValue, long networkTypeMask);
+ void onSubscriptionOverride(int subId, int overrideMask, int overrideValue);
+ void onSubscriptionPlansChanged(int subId, in SubscriptionPlan[] plans);
}
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 90327663..385cb1d 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -76,7 +76,7 @@
SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage);
void setSubscriptionPlans(int subId, in SubscriptionPlan[] plans, String callingPackage);
String getSubscriptionPlansOwner(int subId);
- void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, long networkTypeMask, long timeoutMillis, String callingPackage);
+ void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, long timeoutMillis, String callingPackage);
void factoryReset(String subscriber);
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 628dcd2..9150aae 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -31,6 +31,7 @@
import android.os.Build;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.telephony.SubscriptionPlan;
import android.util.DebugUtils;
import android.util.Pair;
import android.util.Range;
@@ -380,7 +381,8 @@
@Override public void onMeteredIfacesChanged(String[] meteredIfaces) { }
@Override public void onRestrictBackgroundChanged(boolean restrictBackground) { }
@Override public void onUidPoliciesChanged(int uid, int uidPolicies) { }
- @Override public void onSubscriptionOverride(int subId, int overrideMask, int overrideValue,
- long networkTypeMask) { }
+ @Override public void onSubscriptionOverride(int subId, int overrideMask,
+ int overrideValue) { }
+ @Override public void onSubscriptionPlansChanged(int subId, SubscriptionPlan[] plans) { }
}
}
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 2c9333b..ef3afab 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -1085,4 +1085,13 @@
StrictMode.clearGatheredViolations();
return res;
}
+
+ /**
+ * Returns the specified service from servicemanager. If the service is not running,
+ * servicemanager will attempt to start it, and this function will wait for it to be ready.
+ * Returns nullptr only if there are permission problems or fatal errors.
+ * @hide
+ */
+ public static final native @Nullable IBinder waitForService(@NonNull String serviceName)
+ throws RemoteException;
}
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 7a70e93..947b0a1 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -69,6 +69,8 @@
private static final String METADATA_DRIVER_BUILD_TIME = "com.android.gamedriver.build_time";
private static final String METADATA_DEVELOPER_DRIVER_ENABLE =
"com.android.graphics.developerdriver.enable";
+ private static final String METADATA_INJECT_LAYERS_ENABLE =
+ "com.android.graphics.injectLayers.enable";
private static final String ANGLE_RULES_FILE = "a4a_rules.json";
private static final String ANGLE_TEMP_RULES = "debug.angle.rules";
private static final String ACTION_ANGLE_FOR_ANDROID = "android.app.action.ANGLE_FOR_ANDROID";
@@ -100,14 +102,16 @@
public void setup(Context context, Bundle coreSettings) {
final PackageManager pm = context.getPackageManager();
final String packageName = context.getPackageName();
+ final ApplicationInfo appInfoWithMetaData =
+ getAppInfoWithMetadata(context, pm, packageName);
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupGpuLayers");
- setupGpuLayers(context, coreSettings, pm, packageName);
+ setupGpuLayers(context, coreSettings, pm, packageName, appInfoWithMetaData);
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setupAngle");
setupAngle(context, coreSettings, pm, packageName);
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "chooseDriver");
- if (!chooseDriver(context, coreSettings, pm, packageName)) {
+ if (!chooseDriver(context, coreSettings, pm, packageName, appInfoWithMetaData)) {
setGpuStats(SYSTEM_DRIVER_NAME, SYSTEM_DRIVER_VERSION_NAME, SYSTEM_DRIVER_VERSION_CODE,
SystemProperties.getLong(PROPERTY_GFX_DRIVER_BUILD_TIME, 0), packageName,
getVulkanVersion(pm));
@@ -180,6 +184,14 @@
}
/**
+ * Check whether application is has set the manifest metadata for layer injection.
+ */
+ private static boolean canInjectLayers(ApplicationInfo ai) {
+ return (ai.metaData != null && ai.metaData.getBoolean(METADATA_INJECT_LAYERS_ENABLE)
+ && setInjectLayersPrSetDumpable());
+ }
+
+ /**
* Store the layer paths available to the loader.
*/
public void setLayerPaths(ClassLoader classLoader,
@@ -225,15 +237,16 @@
* If debuggable, check for additional debug settings
*/
private void setupGpuLayers(
- Context context, Bundle coreSettings, PackageManager pm, String packageName) {
+ Context context, Bundle coreSettings, PackageManager pm, String packageName,
+ ApplicationInfo ai) {
String layerPaths = "";
// Only enable additional debug functionality if the following conditions are met:
- // 1. App is debuggable or device is rooted
+ // 1. App is debuggable or device is rooted or layer injection metadata flag is true
// 2. ENABLE_GPU_DEBUG_LAYERS is true
// 3. Package name is equal to GPU_DEBUG_APP
- if (isDebuggable(context) || (getCanLoadSystemLibraries() == 1)) {
+ if (isDebuggable(context) || (getCanLoadSystemLibraries() == 1) || canInjectLayers(ai)) {
final int enable = coreSettings.getInt(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, 0);
@@ -343,6 +356,20 @@
return -1;
}
+ private static ApplicationInfo getAppInfoWithMetadata(Context context,
+ PackageManager pm, String packageName) {
+ ApplicationInfo ai;
+ try {
+ // Get the ApplicationInfo from PackageManager so that metadata fields present.
+ ai = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Unlikely to fail for applications, but in case of failure, fall back to use the
+ // ApplicationInfo from context directly.
+ ai = context.getApplicationInfo();
+ }
+ return ai;
+ }
+
private static String getDriverForPkg(Context context, Bundle bundle, String packageName) {
final String allUseAngle;
if (bundle != null) {
@@ -693,8 +720,7 @@
/**
* Return the driver package name to use. Return null for system driver.
*/
- private static String chooseDriverInternal(
- Context context, Bundle coreSettings, PackageManager pm, String packageName) {
+ private static String chooseDriverInternal(Bundle coreSettings, ApplicationInfo ai) {
final String gameDriver = SystemProperties.get(PROPERTY_GFX_DRIVER);
final boolean hasGameDriver = gameDriver != null && !gameDriver.isEmpty();
@@ -709,15 +735,6 @@
// To minimize risk of driver updates crippling the device beyond user repair, never use an
// updated driver for privileged or non-updated system apps. Presumably pre-installed apps
// were tested thoroughly with the pre-installed driver.
- ApplicationInfo ai;
- try {
- // Get the ApplicationInfo from PackageManager so that metadata fields present.
- ai = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA);
- } catch (PackageManager.NameNotFoundException e) {
- // Unlikely to fail for applications, but in case of failure, fall back to use the
- // ApplicationInfo from context directly.
- ai = context.getApplicationInfo();
- }
if (ai.isPrivilegedApp() || (ai.isSystemApp() && !ai.isUpdatedSystemApp())) {
if (DEBUG) Log.v(TAG, "Ignoring driver package for privileged/non-updated system app.");
return null;
@@ -797,9 +814,9 @@
* Choose whether the current process should use the builtin or an updated driver.
*/
private static boolean chooseDriver(
- Context context, Bundle coreSettings, PackageManager pm, String packageName) {
- final String driverPackageName = chooseDriverInternal(context, coreSettings, pm,
- packageName);
+ Context context, Bundle coreSettings, PackageManager pm, String packageName,
+ ApplicationInfo ai) {
+ final String driverPackageName = chooseDriverInternal(coreSettings, ai);
if (driverPackageName == null) {
return false;
}
@@ -911,4 +928,5 @@
private static native void setAngleInfo(String path, String appPackage, String devOptIn,
FileDescriptor rulesFd, long rulesOffset, long rulesLength);
private static native boolean getShouldUseAngle(String packageName);
+ private static native boolean setInjectLayersPrSetDumpable();
}
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index bcb94ce..fdb44e7 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -35,12 +35,15 @@
import android.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.net.Uri;
import android.os.MessageQueue.OnFileDescriptorEventListener;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.system.StructStat;
import android.util.Log;
+import android.util.Size;
import dalvik.system.CloseGuard;
import dalvik.system.VMRuntime;
@@ -204,6 +207,10 @@
/**
* Create a new ParcelFileDescriptor accessing a given file.
+ * <p>
+ * This method should only be used for files that you have direct access to;
+ * if you'd like to work with files hosted outside your app, use an API like
+ * {@link ContentResolver#openFile(Uri, String, CancellationSignal)}.
*
* @param file The file to be opened.
* @param mode The desired access mode, must be one of
@@ -226,6 +233,10 @@
/**
* Create a new ParcelFileDescriptor accessing a given file.
+ * <p>
+ * This method should only be used for files that you have direct access to;
+ * if you'd like to work with files hosted outside your app, use an API like
+ * {@link ContentResolver#openFile(Uri, String, CancellationSignal)}.
*
* @param file The file to be opened.
* @param mode The desired access mode, must be one of
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index 654b0f7..26c1ec1 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -31,8 +31,8 @@
void revokeRuntimePermissions(in Bundle request, boolean doDryRun, int reason,
String callerPackageName, in AndroidFuture callback);
void getRuntimePermissionBackup(in UserHandle user, in ParcelFileDescriptor pipe);
- void restoreRuntimePermissionBackup(in UserHandle user, in ParcelFileDescriptor pipe);
- void restoreDelayedRuntimePermissionBackup(String packageName, in UserHandle user,
+ void stageAndApplyRuntimePermissionsBackup(in UserHandle user, in ParcelFileDescriptor pipe);
+ void applyStagedRuntimePermissionBackup(String packageName, in UserHandle user,
in AndroidFuture callback);
void getAppPermissions(String packageName, in AndroidFuture callback);
void revokeRuntimePermission(String packageName, String permissionName);
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index 923d9f8..421e29e 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -62,6 +62,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -139,20 +140,6 @@
}
/**
- * Callback for delivering the result of {@link #getRuntimePermissionBackup}.
- *
- * @hide
- */
- public interface OnGetRuntimePermissionBackupCallback {
- /**
- * The result for {@link #getRuntimePermissionBackup}.
- *
- * @param backup The backup file
- */
- void onGetRuntimePermissionsBackup(@NonNull byte[] backup);
- }
-
- /**
* Callback for delivering the result of {@link #getAppPermissions}.
*
* @hide
@@ -246,6 +233,24 @@
}
/**
+ * Throw a {@link SecurityException} if not at least one of the permissions is granted.
+ *
+ * @param requiredPermissions A list of permissions. Any of of them if sufficient to pass the
+ * check
+ */
+ private void enforceSomePermissionsGrantedToSelf(@NonNull String... requiredPermissions) {
+ for (String requiredPermission : requiredPermissions) {
+ if (mContext.checkSelfPermission(requiredPermission)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ }
+
+ throw new SecurityException("At lest one of the following permissions is required: "
+ + Arrays.toString(requiredPermissions));
+ }
+
+ /**
* Revoke a set of runtime permissions for various apps.
*
* @param request The permissions to revoke as {@code Map<packageName, List<permission>>}
@@ -268,11 +273,7 @@
}
// Check required permission to fail immediately instead of inside the oneway binder call
- if (mContext.checkSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
- != PackageManager.PERMISSION_GRANTED) {
- throw new SecurityException(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS
- + " required");
- }
+ enforceSomePermissionsGrantedToSelf(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
mRemoteService.postAsync(service -> {
Bundle bundledizedRequest = new Bundle();
@@ -358,46 +359,61 @@
*
* @param user The user to be backed up
* @param executor Executor on which to invoke the callback
- * @param callback Callback to receive the result
- *
- * @hide
+ * @param callback Callback to receive the result. The resulting backup-file is opaque and no
+ * guarantees are made other than that the file can be send to
+ * {@link #restoreRuntimePermissionBackup} in this and future versions of
+ * Android.
*/
@RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
public void getRuntimePermissionBackup(@NonNull UserHandle user,
@NonNull @CallbackExecutor Executor executor,
- @NonNull OnGetRuntimePermissionBackupCallback callback) {
+ @NonNull Consumer<byte[]> callback) {
checkNotNull(user);
checkNotNull(executor);
checkNotNull(callback);
+ // Check required permission to fail immediately instead of inside the oneway binder call
+ enforceSomePermissionsGrantedToSelf(Manifest.permission.GET_RUNTIME_PERMISSIONS);
+
mRemoteService.postAsync(service -> RemoteStream.receiveBytes(remotePipe -> {
service.getRuntimePermissionBackup(user, remotePipe);
})).whenCompleteAsync((bytes, err) -> {
if (err != null) {
Log.e(TAG, "Error getting permission backup", err);
- callback.onGetRuntimePermissionsBackup(EmptyArray.BYTE);
+ callback.accept(EmptyArray.BYTE);
} else {
- callback.onGetRuntimePermissionsBackup(bytes);
+ callback.accept(bytes);
}
}, executor);
}
/**
- * Restore a backup of the runtime permissions.
+ * Restore a {@link #getRuntimePermissionBackup backup-file} of the runtime permissions.
*
- * @param backup the backup to restore. The backup is sent asynchronously, hence it should not
- * be modified after calling this method.
+ * <p>This might leave some part of the backup-file unapplied if an package mentioned in the
+ * backup-file is not yet installed. It is required that
+ * {@link #applyStagedRuntimePermissionBackup} is called after any package is installed to
+ * apply the rest of the backup-file.
+ *
+ * @param backup the backup-file to restore. The backup is sent asynchronously, hence it should
+ * not be modified after calling this method.
* @param user The user to be restore
- *
- * @hide
*/
- @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
- public void restoreRuntimePermissionBackup(@NonNull byte[] backup, @NonNull UserHandle user) {
+ @RequiresPermission(anyOf = {
+ Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.RESTORE_RUNTIME_PERMISSIONS
+ })
+ public void stageAndApplyRuntimePermissionsBackup(@NonNull byte[] backup,
+ @NonNull UserHandle user) {
checkNotNull(backup);
checkNotNull(user);
+ // Check required permission to fail immediately instead of inside the oneway binder call
+ enforceSomePermissionsGrantedToSelf(Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.RESTORE_RUNTIME_PERMISSIONS);
+
mRemoteService.postAsync(service -> RemoteStream.sendBytes(remotePipe -> {
- service.restoreRuntimePermissionBackup(user, remotePipe);
+ service.stageAndApplyRuntimePermissionsBackup(user, remotePipe);
}, backup))
.whenComplete((nullResult, err) -> {
if (err != null) {
@@ -407,17 +423,22 @@
}
/**
- * Restore a backup of the runtime permissions that has been delayed.
+ * Restore unapplied parts of a {@link #stageAndApplyRuntimePermissionsBackup previously staged}
+ * backup-file of the runtime permissions.
+ *
+ * <p>This should be called every time after a package is installed until the callback
+ * reports that there is no more unapplied backup left.
*
* @param packageName The package that is ready to have it's permissions restored.
- * @param user The user to restore
+ * @param user The user the package belongs to
* @param executor Executor to execute the callback on
- * @param callback Is called with {@code true} iff there is still more delayed backup left
- *
- * @hide
+ * @param callback Is called with {@code true} iff there is still more unapplied backup left
*/
- @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
- public void restoreDelayedRuntimePermissionBackup(@NonNull String packageName,
+ @RequiresPermission(anyOf = {
+ Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.RESTORE_RUNTIME_PERMISSIONS
+ })
+ public void applyStagedRuntimePermissionBackup(@NonNull String packageName,
@NonNull UserHandle user,
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<Boolean> callback) {
@@ -426,13 +447,17 @@
checkNotNull(executor);
checkNotNull(callback);
+ // Check required permission to fail immediately instead of inside the oneway binder call
+ enforceSomePermissionsGrantedToSelf(Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.RESTORE_RUNTIME_PERMISSIONS);
+
mRemoteService.postAsync(service -> {
- AndroidFuture<Boolean> restoreDelayedRuntimePermissionBackupResult =
+ AndroidFuture<Boolean> applyStagedRuntimePermissionBackupResult =
new AndroidFuture<>();
- service.restoreDelayedRuntimePermissionBackup(packageName, user,
- restoreDelayedRuntimePermissionBackupResult);
- return restoreDelayedRuntimePermissionBackupResult;
- }).whenCompleteAsync((restoreDelayedRuntimePermissionBackupResult, err) -> {
+ service.applyStagedRuntimePermissionBackup(packageName, user,
+ applyStagedRuntimePermissionBackupResult);
+ return applyStagedRuntimePermissionBackupResult;
+ }).whenCompleteAsync((applyStagedRuntimePermissionBackupResult, err) -> {
long token = Binder.clearCallingIdentity();
try {
if (err != null) {
@@ -440,7 +465,7 @@
callback.accept(true);
} else {
callback.accept(
- Boolean.TRUE.equals(restoreDelayedRuntimePermissionBackupResult));
+ Boolean.TRUE.equals(applyStagedRuntimePermissionBackupResult));
}
} finally {
Binder.restoreCallingIdentity(token);
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index 7363d77..8f765fa 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -54,6 +54,7 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
@@ -105,31 +106,54 @@
public abstract void onGetRuntimePermissionsBackup(@NonNull UserHandle user,
@NonNull OutputStream backup, @NonNull Runnable callback);
+
+ /**
+ * @deprecated Implement {@link #onStageAndApplyRuntimePermissionsBackup} instead
+ */
+ @Deprecated
+ @BinderThread
+ public void onRestoreRuntimePermissionsBackup(@NonNull UserHandle user,
+ @NonNull InputStream backup, @NonNull Runnable callback) {
+ }
+
/**
* Restore a backup of the runtime permissions.
*
* <p>If an app mentioned in the backup is not installed the state should be saved to later
- * be restored via {@link #onRestoreDelayedRuntimePermissionsBackup}.
+ * be restored via {@link #onApplyStagedRuntimePermissionBackup}.
*
* @param user The user to restore
* @param backup The stream to read the backup from
* @param callback Callback waiting for operation to be complete
*/
@BinderThread
- public abstract void onRestoreRuntimePermissionsBackup(@NonNull UserHandle user,
- @NonNull InputStream backup, @NonNull Runnable callback);
+ public void onStageAndApplyRuntimePermissionsBackup(@NonNull UserHandle user,
+ @NonNull InputStream backup, @NonNull Runnable callback) {
+ onRestoreRuntimePermissionsBackup(user, backup, callback);
+ }
+
+ /**
+ * @deprecated Implement {@link #onApplyStagedRuntimePermissionBackup} instead
+ */
+ @Deprecated
+ @BinderThread
+ public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String packageName,
+ @NonNull UserHandle user, @NonNull Consumer<Boolean> callback) {
+ }
/**
* Restore the permission state of an app that was provided in
- * {@link #onRestoreRuntimePermissionsBackup} but could not be restored back then.
+ * {@link #onStageAndApplyRuntimePermissionsBackup} but could not be restored back then.
*
* @param packageName The app to restore
* @param user The user to restore
* @param callback Callback waiting for whether there is still delayed backup left
*/
@BinderThread
- public abstract void onRestoreDelayedRuntimePermissionsBackup(@NonNull String packageName,
- @NonNull UserHandle user, @NonNull Consumer<Boolean> callback);
+ public void onApplyStagedRuntimePermissionBackup(@NonNull String packageName,
+ @NonNull UserHandle user, @NonNull Consumer<Boolean> callback) {
+ onRestoreDelayedRuntimePermissionsBackup(packageName, user, callback);
+ }
/**
* Gets the runtime permissions for an app.
@@ -238,7 +262,8 @@
request.put(packageName, permissions);
}
- enforceCallingPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
// Verify callerPackageName
try {
@@ -258,12 +283,33 @@
});
}
+ /**
+ * Throw a {@link SecurityException} if not at least one of the permissions is granted.
+ *
+ * @param requiredPermissions A list of permissions. Any of of them if sufficient to
+ * pass the check
+ */
+ private void enforceSomePermissionsGrantedToCaller(
+ @NonNull String... requiredPermissions) {
+ for (String requiredPermission : requiredPermissions) {
+ if (checkCallingPermission(requiredPermission)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ }
+
+ throw new SecurityException(
+ "At lest one of the following permissions is required: " + Arrays.toString(
+ requiredPermissions));
+ }
+
+
@Override
public void getRuntimePermissionBackup(UserHandle user, ParcelFileDescriptor pipe) {
checkNotNull(user);
checkNotNull(pipe);
- enforceCallingPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(Manifest.permission.GET_RUNTIME_PERMISSIONS);
try (OutputStream backup = new ParcelFileDescriptor.AutoCloseOutputStream(pipe)) {
CountDownLatch latch = new CountDownLatch(1);
@@ -277,15 +323,17 @@
}
@Override
- public void restoreRuntimePermissionBackup(UserHandle user, ParcelFileDescriptor pipe) {
+ public void stageAndApplyRuntimePermissionsBackup(UserHandle user,
+ ParcelFileDescriptor pipe) {
checkNotNull(user);
checkNotNull(pipe);
- enforceCallingPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.RESTORE_RUNTIME_PERMISSIONS);
try (InputStream backup = new ParcelFileDescriptor.AutoCloseInputStream(pipe)) {
CountDownLatch latch = new CountDownLatch(1);
- onRestoreRuntimePermissionsBackup(user, backup, latch::countDown);
+ onStageAndApplyRuntimePermissionsBackup(user, backup, latch::countDown);
latch.await();
} catch (IOException e) {
Log.e(LOG_TAG, "Could not open pipe to read backup from", e);
@@ -295,15 +343,16 @@
}
@Override
- public void restoreDelayedRuntimePermissionBackup(String packageName, UserHandle user,
+ public void applyStagedRuntimePermissionBackup(String packageName, UserHandle user,
AndroidFuture callback) {
checkNotNull(packageName);
checkNotNull(user);
checkNotNull(callback);
- enforceCallingPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.RESTORE_RUNTIME_PERMISSIONS);
- onRestoreDelayedRuntimePermissionsBackup(packageName, user, callback::complete);
+ onApplyStagedRuntimePermissionBackup(packageName, user, callback::complete);
}
@Override
@@ -311,7 +360,7 @@
checkNotNull(packageName, "packageName");
checkNotNull(callback, "callback");
- enforceCallingPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(Manifest.permission.GET_RUNTIME_PERMISSIONS);
onGetAppPermissions(packageName, callback::complete);
}
@@ -321,7 +370,8 @@
checkNotNull(packageName, "packageName");
checkNotNull(permissionName, "permissionName");
- enforceCallingPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
CountDownLatch latch = new CountDownLatch(1);
PermissionControllerService.this.onRevokeRuntimePermission(packageName,
@@ -340,7 +390,7 @@
checkFlagsArgument(flags, COUNT_WHEN_SYSTEM | COUNT_ONLY_WHEN_GRANTED);
checkNotNull(callback, "callback");
- enforceCallingPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(Manifest.permission.GET_RUNTIME_PERMISSIONS);
onCountPermissionApps(permissionNames, flags, callback::complete);
}
@@ -351,7 +401,7 @@
checkArgumentNonnegative(numMillis);
checkNotNull(callback, "callback");
- enforceCallingPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(Manifest.permission.GET_RUNTIME_PERMISSIONS);
onGetPermissionUsages(countSystem, numMillis, callback::complete);
}
@@ -369,15 +419,17 @@
checkNotNull(callback);
if (grantState == PERMISSION_GRANT_STATE_DENIED) {
- enforceCallingPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(
+ Manifest.permission.GRANT_RUNTIME_PERMISSIONS);
}
if (grantState == PERMISSION_GRANT_STATE_DENIED) {
- enforceCallingPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, null);
+ enforceSomePermissionsGrantedToCaller(
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
}
- enforceCallingPermission(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY,
- null);
+ enforceSomePermissionsGrantedToCaller(
+ Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY);
onSetRuntimePermissionGrantStateByDeviceAdmin(callerPackageName,
packageName, permission, grantState, callback::complete);
@@ -387,8 +439,8 @@
public void grantOrUpgradeDefaultRuntimePermissions(@NonNull AndroidFuture callback) {
checkNotNull(callback, "callback");
- enforceCallingPermission(Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY,
- null);
+ enforceSomePermissionsGrantedToCaller(
+ Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY);
onGrantOrUpgradeDefaultRuntimePermissions(() -> callback.complete(true));
}
diff --git a/core/java/android/print/TEST_MAPPING b/core/java/android/print/TEST_MAPPING
new file mode 100644
index 0000000..4fa8822
--- /dev/null
+++ b/core/java/android/print/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsPrintTestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ }
+ ]
+ }
+ ]
+}
diff --git a/core/java/android/print/pdf/TEST_MAPPING b/core/java/android/print/pdf/TEST_MAPPING
new file mode 100644
index 0000000..d763598
--- /dev/null
+++ b/core/java/android/print/pdf/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsPdfTestCases"
+ }
+ ]
+}
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index fd1381a..e456c8a 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -403,9 +403,37 @@
@TestApi
@RequiresPermission(READ_DEVICE_CONFIG)
public static String getProperty(@NonNull String namespace, @NonNull String name) {
+ // Fetch all properties for the namespace at once and cache them in the local process, so we
+ // incur the cost of the IPC less often. Lookups happen much more frequently than updates,
+ // and we want to optimize the former.
+ return getProperties(namespace, name).getString(name, null);
+ }
+
+ /**
+ * Look up the values of multiple properties for a particular namespace. The lookup is atomic,
+ * such that the values of these properties cannot change between the time when the first is
+ * fetched and the time when the last is fetched.
+ *
+ * TODO: reference setProperties when it is added.
+ *
+ * @param namespace The namespace containing the properties to look up.
+ * @param names The names of properties to look up, or empty to fetch all properties for the
+ * given namespace.
+ * @return {@link Properties} object containing the requested properties. This reflects the
+ * state of these properties at the time of the lookup, and is not updated to reflect any
+ * future changes. The keyset of this Properties object will contain only the intersection
+ * of properties already set and properties requested via the names parameter. Properties
+ * that are already set but were not requested will not be contained here. Properties that
+ * are not set, but were requested will not be contained here either.
+ * @hide
+ */
+ @SystemApi
+ @NonNull
+ @RequiresPermission(READ_DEVICE_CONFIG)
+ public static Properties getProperties(@NonNull String namespace, @NonNull String ... names) {
ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
- String compositeName = createCompositeName(namespace, name);
- return Settings.Config.getString(contentResolver, compositeName);
+ return new Properties(namespace,
+ Settings.Config.getStrings(contentResolver, namespace, Arrays.asList(names)));
}
/**
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index fd81178..eb09930 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -363,15 +363,22 @@
* <p>
* Type: INTEGER (int)
*
- * @see #FLAG_SUPPORTS_WRITE
- * @see #FLAG_SUPPORTS_DELETE
- * @see #FLAG_SUPPORTS_THUMBNAIL
+ * @see #FLAG_DIR_BLOCKS_TREE
* @see #FLAG_DIR_PREFERS_GRID
* @see #FLAG_DIR_PREFERS_LAST_MODIFIED
- * @see #FLAG_VIRTUAL_DOCUMENT
+ * @see #FLAG_DIR_SUPPORTS_CREATE
+ * @see #FLAG_PARTIAL
* @see #FLAG_SUPPORTS_COPY
+ * @see #FLAG_SUPPORTS_DELETE
+ * @see #FLAG_SUPPORTS_METADATA
* @see #FLAG_SUPPORTS_MOVE
* @see #FLAG_SUPPORTS_REMOVE
+ * @see #FLAG_SUPPORTS_RENAME
+ * @see #FLAG_SUPPORTS_SETTINGS
+ * @see #FLAG_SUPPORTS_THUMBNAIL
+ * @see #FLAG_SUPPORTS_WRITE
+ * @see #FLAG_VIRTUAL_DOCUMENT
+ * @see #FLAG_WEB_LINKABLE
*/
public static final String COLUMN_FLAGS = "flags";
@@ -542,6 +549,23 @@
* @see DocumentsContract#getDocumentMetadata(ContentInterface, Uri)
*/
public static final int FLAG_SUPPORTS_METADATA = 1 << 14;
+
+ /**
+ * Flag indicating that a document is a directory that wants to block itself
+ * from being selected when the user launches an {@link Intent#ACTION_OPEN_DOCUMENT_TREE}
+ * intent. Only valid when {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
+ * <p>
+ * Note that this flag <em>only</em> applies to the single directory to which it is
+ * applied. It does <em>not</em> block the user from selecting either a parent or
+ * child directory during an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} request.
+ * In particular, the only way to guarantee that a specific directory can never
+ * be granted via an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} request is to ensure
+ * that both it and <em>all of its parent directories</em> have set this flag.
+ *
+ * @see Intent#ACTION_OPEN_DOCUMENT_TREE
+ * @see #COLUMN_FLAGS
+ */
+ public static final int FLAG_DIR_BLOCKS_TREE = 1 << 15;
}
/**
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 079a42d..a1333df 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -39,7 +39,6 @@
import android.content.Intent;
import android.content.UriPermission;
import android.database.Cursor;
-import android.database.DatabaseUtils;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageDecoder;
@@ -47,6 +46,7 @@
import android.graphics.PostProcessor;
import android.media.ExifInterface;
import android.media.MediaFile;
+import android.media.MediaFormat;
import android.net.Uri;
import android.os.Bundle;
import android.os.CancellationSignal;
@@ -69,15 +69,19 @@
import com.android.internal.annotations.GuardedBy;
+import libcore.util.HexEncoding;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.text.Collator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
@@ -2058,7 +2062,17 @@
/**
* A non human readable key calculated from the TITLE, used for
* searching, sorting and grouping
+ *
+ * @see Audio#keyFor(String)
+ * @deprecated These keys are generated using
+ * {@link java.util.Locale#ROOT}, which means they don't
+ * reflect locale-specific sorting preferences. To apply
+ * locale-specific sorting preferences, use
+ * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+ * {@code COLLATE LOCALIZED}, or
+ * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
*/
+ @Deprecated
@Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
public static final String TITLE_KEY = "title_key";
@@ -2103,7 +2117,17 @@
/**
* A non human readable key calculated from the ARTIST, used for
* searching, sorting and grouping
+ *
+ * @see Audio#keyFor(String)
+ * @deprecated These keys are generated using
+ * {@link java.util.Locale#ROOT}, which means they don't
+ * reflect locale-specific sorting preferences. To apply
+ * locale-specific sorting preferences, use
+ * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+ * {@code COLLATE LOCALIZED}, or
+ * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
*/
+ @Deprecated
@Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
public static final String ARTIST_KEY = "artist_key";
@@ -2128,7 +2152,17 @@
/**
* A non human readable key calculated from the ALBUM, used for
* searching, sorting and grouping
+ *
+ * @see Audio#keyFor(String)
+ * @deprecated These keys are generated using
+ * {@link java.util.Locale#ROOT}, which means they don't
+ * reflect locale-specific sorting preferences. To apply
+ * locale-specific sorting preferences, use
+ * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+ * {@code COLLATE LOCALIZED}, or
+ * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
*/
+ @Deprecated
@Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
public static final String ALBUM_KEY = "album_key";
@@ -2185,91 +2219,89 @@
public static final String IS_AUDIOBOOK = "is_audiobook";
/**
- * The genre of the audio file, if any
- * Does not exist in the database - only used by the media scanner for inserts.
- * @hide
+ * The id of the genre the audio file is from, if any
*/
- @Deprecated
- // @Column(Cursor.FIELD_TYPE_STRING)
+ @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
+ public static final String GENRE_ID = "genre_id";
+
+ /**
+ * The genre of the audio file, if any.
+ */
+ @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
public static final String GENRE = "genre";
/**
- * The resource URI of a localized title, if any
+ * A non human readable key calculated from the GENRE, used for
+ * searching, sorting and grouping
+ *
+ * @see Audio#keyFor(String)
+ * @deprecated These keys are generated using
+ * {@link java.util.Locale#ROOT}, which means they don't
+ * reflect locale-specific sorting preferences. To apply
+ * locale-specific sorting preferences, use
+ * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+ * {@code COLLATE LOCALIZED}, or
+ * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
+ */
+ @Deprecated
+ @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
+ public static final String GENRE_KEY = "genre_key";
+
+ /**
+ * The resource URI of a localized title, if any.
+ * <p>
* Conforms to this pattern:
- * Scheme: {@link ContentResolver.SCHEME_ANDROID_RESOURCE}
- * Authority: Package Name of ringtone title provider
- * First Path Segment: Type of resource (must be "string")
- * Second Path Segment: Resource ID of title
- * @hide
+ * <ul>
+ * <li>Scheme: {@link ContentResolver#SCHEME_ANDROID_RESOURCE}
+ * <li>Authority: Package Name of ringtone title provider
+ * <li>First Path Segment: Type of resource (must be "string")
+ * <li>Second Path Segment: Resource ID of title
+ * </ul>
*/
@Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
public static final String TITLE_RESOURCE_URI = "title_resource_uri";
}
+ private static final Pattern PATTERN_TRIM_BEFORE = Pattern.compile(
+ "(?i)(^(the|an|a) |,\\s*(the|an|a)$|[^\\w\\s]|^\\s+|\\s+$)");
+ private static final Pattern PATTERN_TRIM_AFTER = Pattern.compile(
+ "(^(00)+|(00)+$)");
+
/**
- * Converts a name to a "key" that can be used for grouping, sorting
- * and searching.
- * The rules that govern this conversion are:
- * - remove 'special' characters like ()[]'!?.,
- * - remove leading/trailing spaces
- * - convert everything to lowercase
- * - remove leading "the ", "an " and "a "
- * - remove trailing ", the|an|a"
- * - remove accents. This step leaves us with CollationKey data,
- * which is not human readable
+ * Converts a user-visible string into a "key" that can be used for
+ * grouping, sorting, and searching.
*
- * @param name The artist or album name to convert
- * @return The "key" for the given name.
+ * @return Opaque token that should not be parsed or displayed to users.
+ * @deprecated These keys are generated using
+ * {@link java.util.Locale#ROOT}, which means they don't
+ * reflect locale-specific sorting preferences. To apply
+ * locale-specific sorting preferences, use
+ * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+ * {@code COLLATE LOCALIZED}, or
+ * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
*/
- public static String keyFor(String name) {
- if (name != null) {
- boolean sortfirst = false;
- if (name.equals(UNKNOWN_STRING)) {
- return "\001";
- }
- // Check if the first character is \001. We use this to
- // force sorting of certain special files, like the silent ringtone.
- if (name.startsWith("\001")) {
- sortfirst = true;
- }
- name = name.trim().toLowerCase();
- if (name.startsWith("the ")) {
- name = name.substring(4);
- }
- if (name.startsWith("an ")) {
- name = name.substring(3);
- }
- if (name.startsWith("a ")) {
- name = name.substring(2);
- }
- if (name.endsWith(", the") || name.endsWith(",the") ||
- name.endsWith(", an") || name.endsWith(",an") ||
- name.endsWith(", a") || name.endsWith(",a")) {
- name = name.substring(0, name.lastIndexOf(','));
- }
- name = name.replaceAll("[\\[\\]\\(\\)\"'.,?!]", "").trim();
- if (name.length() > 0) {
- // Insert a separator between the characters to avoid
- // matches on a partial character. If we ever change
- // to start-of-word-only matches, this can be removed.
- StringBuilder b = new StringBuilder();
- b.append('.');
- int nl = name.length();
- for (int i = 0; i < nl; i++) {
- b.append(name.charAt(i));
- b.append('.');
- }
- name = b.toString();
- String key = DatabaseUtils.getCollationKey(name);
- if (sortfirst) {
- key = "\001" + key;
- }
- return key;
- } else {
- return "";
- }
+ @Deprecated
+ public static @Nullable String keyFor(@Nullable String name) {
+ if (TextUtils.isEmpty(name)) return null;
+
+ if (UNKNOWN_STRING.equals(name)) {
+ return "01";
}
- return null;
+
+ final boolean sortFirst = name.startsWith("\001");
+
+ name = PATTERN_TRIM_BEFORE.matcher(name).replaceAll("");
+ if (TextUtils.isEmpty(name)) return null;
+
+ final Collator c = Collator.getInstance(Locale.ROOT);
+ c.setStrength(Collator.PRIMARY);
+ name = HexEncoding.encodeToString(c.getCollationKey(name).toByteArray(), false);
+
+ name = PATTERN_TRIM_AFTER.matcher(name).replaceAll("");
+ if (sortFirst) {
+ name = "01" + name;
+ }
+ return name;
}
public static final class Media implements AudioColumns {
@@ -2631,7 +2663,17 @@
/**
* A non human readable key calculated from the ARTIST, used for
* searching, sorting and grouping
+ *
+ * @see Audio#keyFor(String)
+ * @deprecated These keys are generated using
+ * {@link java.util.Locale#ROOT}, which means they don't
+ * reflect locale-specific sorting preferences. To apply
+ * locale-specific sorting preferences, use
+ * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+ * {@code COLLATE LOCALIZED}, or
+ * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
*/
+ @Deprecated
@Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
public static final String ARTIST_KEY = "artist_key";
@@ -2735,6 +2777,23 @@
public static final String ARTIST = "artist";
/**
+ * A non human readable key calculated from the ARTIST, used for
+ * searching, sorting and grouping
+ *
+ * @see Audio#keyFor(String)
+ * @deprecated These keys are generated using
+ * {@link java.util.Locale#ROOT}, which means they don't
+ * reflect locale-specific sorting preferences. To apply
+ * locale-specific sorting preferences, use
+ * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+ * {@code COLLATE LOCALIZED}, or
+ * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
+ */
+ @Deprecated
+ @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
+ public static final String ARTIST_KEY = "artist_key";
+
+ /**
* The number of songs on this album
*/
@Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
@@ -2769,7 +2828,17 @@
/**
* A non human readable key calculated from the ALBUM, used for
* searching, sorting and grouping
+ *
+ * @see Audio#keyFor(String)
+ * @deprecated These keys are generated using
+ * {@link java.util.Locale#ROOT}, which means they don't
+ * reflect locale-specific sorting preferences. To apply
+ * locale-specific sorting preferences, use
+ * {@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER} with
+ * {@code COLLATE LOCALIZED}, or
+ * {@link ContentResolver#QUERY_ARG_SORT_LOCALE}.
*/
+ @Deprecated
@Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
public static final String ALBUM_KEY = "album_key";
@@ -3007,22 +3076,32 @@
public static final String BOOKMARK = "bookmark";
/**
- * The standard of color aspects
- * @hide
+ * The color standard of this media file, if available.
+ *
+ * @see MediaFormat#COLOR_STANDARD_BT709
+ * @see MediaFormat#COLOR_STANDARD_BT601_PAL
+ * @see MediaFormat#COLOR_STANDARD_BT601_NTSC
+ * @see MediaFormat#COLOR_STANDARD_BT2020
*/
@Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
public static final String COLOR_STANDARD = "color_standard";
/**
- * The transfer of color aspects
- * @hide
+ * The color transfer of this media file, if available.
+ *
+ * @see MediaFormat#COLOR_TRANSFER_LINEAR
+ * @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO
+ * @see MediaFormat#COLOR_TRANSFER_ST2084
+ * @see MediaFormat#COLOR_TRANSFER_HLG
*/
@Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
public static final String COLOR_TRANSFER = "color_transfer";
/**
- * The range of color aspects
- * @hide
+ * The color range of this media file, if available.
+ *
+ * @see MediaFormat#COLOR_RANGE_LIMITED
+ * @see MediaFormat#COLOR_RANGE_FULL
*/
@Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
public static final String COLOR_RANGE = "color_range";
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e4e8bf7..800c15c 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -84,8 +84,10 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.URISyntaxException;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -2248,7 +2250,7 @@
private static final String NAME_EQ_PLACEHOLDER = "name=?";
// Must synchronize on 'this' to access mValues and mValuesVersion.
- private final HashMap<String, String> mValues = new HashMap<>();
+ private final ArrayMap<String, String> mValues = new ArrayMap<>();
private final Uri mUri;
@UnsupportedAppUsage
@@ -2258,15 +2260,22 @@
// for the fast path of retrieving settings.
private final String mCallGetCommand;
private final String mCallSetCommand;
+ private final String mCallListCommand;
@GuardedBy("this")
private GenerationTracker mGenerationTracker;
public NameValueCache(Uri uri, String getCommand, String setCommand,
ContentProviderHolder providerHolder) {
+ this(uri, getCommand, setCommand, null, providerHolder);
+ }
+
+ NameValueCache(Uri uri, String getCommand, String setCommand, String listCommand,
+ ContentProviderHolder providerHolder) {
mUri = uri;
mCallGetCommand = getCommand;
mCallSetCommand = setCommand;
+ mCallListCommand = listCommand;
mProviderHolder = providerHolder;
}
@@ -2448,8 +2457,8 @@
String value = c.moveToNext() ? c.getString(0) : null;
synchronized (NameValueCache.this) {
- if(mGenerationTracker != null &&
- currentGeneration == mGenerationTracker.getCurrentGeneration()) {
+ if (mGenerationTracker != null
+ && currentGeneration == mGenerationTracker.getCurrentGeneration()) {
mValues.put(name, value);
}
}
@@ -2466,6 +2475,141 @@
}
}
+ public ArrayMap<String, String> getStringsForPrefix(ContentResolver cr, String prefix,
+ List<String> names) {
+ ArrayMap<String, String> keyValues = new ArrayMap<>();
+ int currentGeneration = -1;
+
+ synchronized (NameValueCache.this) {
+ if (mGenerationTracker != null) {
+ if (mGenerationTracker.isGenerationChanged()) {
+ if (DEBUG) {
+ Log.i(TAG, "Generation changed for type:" + mUri.getPath()
+ + " in package:" + cr.getPackageName());
+ }
+ mValues.clear();
+ } else {
+ boolean prefixCached = false;
+ int size = mValues.size();
+ for (int i = 0; i < size; ++i) {
+ if (mValues.keyAt(i).startsWith(prefix + "/")) {
+ prefixCached = true;
+ break;
+ }
+ }
+ if (prefixCached) {
+ if (!names.isEmpty()) {
+ for (String name : names) {
+ if (mValues.containsKey(name)) {
+ keyValues.put(name, mValues.get(name));
+ }
+ }
+ } else {
+ for (int i = 0; i < size; ++i) {
+ String key = mValues.keyAt(i);
+ if (key.startsWith(prefix + "/")) {
+ keyValues.put(key, mValues.get(key));
+ }
+ }
+ }
+ return keyValues;
+ }
+ }
+ if (mGenerationTracker != null) {
+ currentGeneration = mGenerationTracker.getCurrentGeneration();
+ }
+ }
+ }
+
+ if (mCallListCommand == null) {
+ // No list command specified, return empty map
+ return keyValues;
+ }
+ IContentProvider cp = mProviderHolder.getProvider(cr);
+
+ try {
+ Bundle args = new Bundle();
+ args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
+ boolean needsGenerationTracker = false;
+ synchronized (NameValueCache.this) {
+ if (mGenerationTracker == null) {
+ needsGenerationTracker = true;
+ args.putString(CALL_METHOD_TRACK_GENERATION_KEY, null);
+ if (DEBUG) {
+ Log.i(TAG, "Requested generation tracker for type: "
+ + mUri.getPath() + " in package:" + cr.getPackageName());
+ }
+ }
+ }
+
+ // Fetch all flags for the namespace at once for caching purposes
+ Bundle b = cp.call(cr.getPackageName(), mProviderHolder.mUri.getAuthority(),
+ mCallListCommand, null, args);
+ if (b == null) {
+ // Invalid response, return an empty map
+ return keyValues;
+ }
+
+ // All flags for the namespace
+ Map<String, String> flagsToValues =
+ (HashMap) b.getSerializable(Settings.NameValueTable.VALUE);
+ // Only the flags requested by the caller
+ if (!names.isEmpty()) {
+ for (Map.Entry<String, String> flag : flagsToValues.entrySet()) {
+ if (names.contains(flag.getKey())) {
+ keyValues.put(flag.getKey(), flag.getValue());
+ }
+ }
+ } else {
+ keyValues.putAll(flagsToValues);
+ }
+
+ synchronized (NameValueCache.this) {
+ if (needsGenerationTracker) {
+ MemoryIntArray array = b.getParcelable(
+ CALL_METHOD_TRACK_GENERATION_KEY);
+ final int index = b.getInt(
+ CALL_METHOD_GENERATION_INDEX_KEY, -1);
+ if (array != null && index >= 0) {
+ final int generation = b.getInt(
+ CALL_METHOD_GENERATION_KEY, 0);
+ if (DEBUG) {
+ Log.i(TAG, "Received generation tracker for type:"
+ + mUri.getPath() + " in package:"
+ + cr.getPackageName() + " with index:" + index);
+ }
+ if (mGenerationTracker != null) {
+ mGenerationTracker.destroy();
+ }
+ mGenerationTracker = new GenerationTracker(array, index,
+ generation, () -> {
+ synchronized (NameValueCache.this) {
+ Log.e(TAG, "Error accessing generation tracker"
+ + " - removing");
+ if (mGenerationTracker != null) {
+ GenerationTracker generationTracker =
+ mGenerationTracker;
+ mGenerationTracker = null;
+ generationTracker.destroy();
+ mValues.clear();
+ }
+ }
+ });
+ }
+ }
+ if (mGenerationTracker != null && currentGeneration
+ == mGenerationTracker.getCurrentGeneration()) {
+ // cache the complete list of flags for the namespace
+ mValues.putAll(flagsToValues);
+ }
+ }
+ return keyValues;
+ } catch (RemoteException e) {
+ // Not supported by the remote side, return an empty map
+ return keyValues;
+ }
+ }
+
public void clearGenerationTrackerForTest() {
synchronized (NameValueCache.this) {
if (mGenerationTracker != null) {
@@ -8097,20 +8241,6 @@
public static final String AWARE_LOCK_ENABLED = "aware_lock_enabled";
/**
- * The settings values which should only be restored if the target device is the
- * same as the source device
- *
- * NOTE: Settings are backed up and restored in the order they appear
- * in this array. If you have one setting depending on another,
- * make sure that they are ordered appropriately.
- *
- * @hide
- */
- public static final String[] DEVICE_SPECIFIC_SETTINGS_TO_BACKUP = {
- DISPLAY_DENSITY_FORCED,
- };
-
- /**
* Keys we no longer back up under the current schema, but want to continue to
* process when restoring historical backup datasets.
*
@@ -10733,16 +10863,13 @@
* App standby (app idle) specific settings.
* This is encoded as a key=value list, separated by commas. Ex:
* <p>
- * "idle_duration=5000,parole_interval=4500,screen_thresholds=0/0/60000/120000"
+ * "idle_duration=5000,prediction_timeout=4500,screen_thresholds=0/0/60000/120000"
* <p>
* All durations are in millis.
* Array values are separated by forward slashes
* The following keys are supported:
*
* <pre>
- * parole_interval (long)
- * parole_window (long)
- * parole_duration (long)
* screen_thresholds (long[4])
* elapsed_thresholds (long[4])
* strong_usage_duration (long)
@@ -10753,17 +10880,12 @@
* exempted_sync_duration (long)
* system_interaction_duration (long)
* initial_foreground_service_start_duration (long)
- * stable_charging_threshold (long)
- *
- * idle_duration (long) // This is deprecated and used to circumvent b/26355386.
- * idle_duration2 (long) // deprecated
- * wallclock_threshold (long) // deprecated
* </pre>
*
* <p>
* Type: string
* @hide
- * @see com.android.server.usage.UsageStatsService.SettingsObserver
+ * @see com.android.server.usage.AppStandbyController
*/
public static final String APP_IDLE_CONSTANTS = "app_idle_constants";
@@ -13499,6 +13621,7 @@
DeviceConfig.CONTENT_URI,
CALL_METHOD_GET_CONFIG,
CALL_METHOD_PUT_CONFIG,
+ CALL_METHOD_LIST_CONFIG,
sProviderHolder);
/**
@@ -13515,6 +13638,37 @@
}
/**
+ * Look up a list of names in the database, based on a common prefix.
+ *
+ * @param resolver to access the database with
+ * @param prefix to apply to all of the names which will be fetched
+ * @param names to look up in the table
+ * @return a non null, but possibly empty, map from name to value for any of the names that
+ * were found during lookup.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+ static Map<String, String> getStrings(@NonNull ContentResolver resolver,
+ @NonNull String prefix, @NonNull List<String> names) {
+ List<String> concatenatedNames = new ArrayList<>(names.size());
+ for (String name : names) {
+ concatenatedNames.add(prefix + "/" + name);
+ }
+
+ ArrayMap<String, String> rawKeyValues = sNameValueCache.getStringsForPrefix(
+ resolver, prefix, concatenatedNames);
+ int size = rawKeyValues.size();
+ int substringLength = prefix.length() + 1;
+ ArrayMap<String, String> keyValues = new ArrayMap<>(size);
+ for (int i = 0; i < size; ++i) {
+ keyValues.put(rawKeyValues.keyAt(i).substring(substringLength),
+ rawKeyValues.valueAt(i));
+ }
+ return keyValues;
+ }
+
+ /**
* Store a name/value pair into the database.
* <p>
* Also the method takes an argument whether to make the value the default for this setting.
diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index ff8b135..8a9f689 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -15,6 +15,8 @@
*/
package android.service.euicc;
+import static android.telephony.euicc.EuiccCardManager.ResetOption;
+
import android.annotation.CallSuper;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -503,7 +505,7 @@
String nickname);
/**
- * Erase all of the subscriptions on the device.
+ * Erase all operational subscriptions on the device.
*
* <p>This is intended to be used for device resets. As such, the reset should be performed even
* if an active SIM must be deactivated in order to access the eUICC.
@@ -512,10 +514,31 @@
* @return the result of the erase operation. May be one of the predefined {@code RESULT_}
* constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}.
* @see android.telephony.euicc.EuiccManager#eraseSubscriptions
+ *
+ * @deprecated From R, callers should specify a flag for specific set of subscriptions to erase
+ * and use @link{onEraseSubscriptionsWithOptions} instead
*/
+ @Deprecated
public abstract int onEraseSubscriptions(int slotId);
/**
+ * Erase specific subscriptions on the device.
+ *
+ * <p>This is intended to be used for device resets. As such, the reset should be performed even
+ * if an active SIM must be deactivated in order to access the eUICC.
+ *
+ * @param slotIndex index of the SIM slot to use for the operation.
+ * @param options flag for specific group of subscriptions to erase
+ * @return the result of the erase operation. May be one of the predefined {@code RESULT_}
+ * constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}.
+ * @see android.telephony.euicc.EuiccManager#eraseSubscriptionsWithOptions
+ */
+ public int onEraseSubscriptionsWithOptions(int slotIndex, @ResetOption int options) {
+ throw new UnsupportedOperationException(
+ "This method must be overridden to enable the ResetOption parameter");
+ }
+
+ /**
* Ensure that subscriptions will be retained on the next factory reset.
*
* <p>Called directly before a factory reset. Assumes that a normal factory reset will lead to
@@ -751,6 +774,23 @@
}
@Override
+ public void eraseSubscriptionsWithOptions(
+ int slotIndex, @ResetOption int options, IEraseSubscriptionsCallback callback) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ int result = EuiccService.this.onEraseSubscriptionsWithOptions(
+ slotIndex, options);
+ try {
+ callback.onComplete(result);
+ } catch (RemoteException e) {
+ // Can't communicate with the phone process; ignore.
+ }
+ }
+ });
+ }
+
+ @Override
public void retainSubscriptionsForFactoryReset(int slotId,
IRetainSubscriptionsForFactoryResetCallback callback) {
mExecutor.execute(new Runnable() {
diff --git a/core/java/android/service/euicc/IEuiccService.aidl b/core/java/android/service/euicc/IEuiccService.aidl
index c2cdf09..2acc47a 100644
--- a/core/java/android/service/euicc/IEuiccService.aidl
+++ b/core/java/android/service/euicc/IEuiccService.aidl
@@ -52,6 +52,8 @@
void updateSubscriptionNickname(int slotId, String iccid, String nickname,
in IUpdateSubscriptionNicknameCallback callback);
void eraseSubscriptions(int slotId, in IEraseSubscriptionsCallback callback);
+ void eraseSubscriptionsWithOptions(
+ int slotIndex, int options, in IEraseSubscriptionsCallback callback);
void retainSubscriptionsForFactoryReset(
int slotId, in IRetainSubscriptionsForFactoryResetCallback callback);
}
\ No newline at end of file
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index f1c870d..dd2586c 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -126,11 +126,29 @@
= "android.service.quicksettings.ACTIVE_TILE";
/**
+ * Meta-data for a tile to support {@code BooleanState}.
+ * <p>
+ * BooleanState is for tiles that should support switch tile behavior in accessibility. This is
+ * the behavior of most of the framework tiles.
+ *
+ * To make a TileService support BooleanState, set this meta-data to true on the TileService's
+ * manifest declaration.
+ * <pre class="prettyprint">
+ * {@literal
+ * <meta-data android:name="android.service.quicksettings.BOOLEAN_TILE"
+ * android:value="true" />
+ * }
+ * </pre>
+ */
+ public static final String META_DATA_BOOLEAN_TILE =
+ "android.service.quicksettings.BOOLEAN_TILE";
+
+ /**
* Used to notify SysUI that Listening has be requested.
* @hide
*/
- public static final String ACTION_REQUEST_LISTENING
- = "android.service.quicksettings.action.REQUEST_LISTENING";
+ public static final String ACTION_REQUEST_LISTENING =
+ "android.service.quicksettings.action.REQUEST_LISTENING";
/**
* @hide
diff --git a/core/java/android/util/DebugUtils.java b/core/java/android/util/DebugUtils.java
index 20e0d14..bc5edf8 100644
--- a/core/java/android/util/DebugUtils.java
+++ b/core/java/android/util/DebugUtils.java
@@ -255,7 +255,7 @@
if (value == 0 && flagsWasZero) {
return constNameWithoutPrefix(prefix, field);
}
- if ((flags & value) == value) {
+ if (value != 0 && (flags & value) == value) {
flags &= ~value;
res.append(constNameWithoutPrefix(prefix, field)).append('|');
}
diff --git a/core/java/android/util/TimestampedValue.java b/core/java/android/util/TimestampedValue.java
index 1289e4d..4505673 100644
--- a/core/java/android/util/TimestampedValue.java
+++ b/core/java/android/util/TimestampedValue.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
+import android.os.Parcelable;
import android.os.SystemClock;
import java.util.Objects;
@@ -30,14 +31,14 @@
* If a suitable clock is used the reference time can be used to identify the age of a value or
* ordering between values.
*
- * <p>To read and write a timestamped value from / to a Parcel see
- * {@link #readFromParcel(Parcel, ClassLoader, Class)} and
- * {@link #writeToParcel(Parcel, TimestampedValue)}.
+ * <p>This class implements {@link Parcelable} for convenience but instances will only actually be
+ * parcelable if the value type held is {@code null}, {@link Parcelable}, or one of the other types
+ * supported by {@link Parcel#writeValue(Object)} / {@link Parcel#readValue(ClassLoader)}.
*
* @param <T> the type of the value with an associated timestamp
* @hide
*/
-public final class TimestampedValue<T> {
+public final class TimestampedValue<T> implements Parcelable {
private final long mReferenceTimeMillis;
private final T mValue;
@@ -81,57 +82,43 @@
}
/**
- * Read a {@link TimestampedValue} from a parcel that was stored using
- * {@link #writeToParcel(Parcel, TimestampedValue)}.
- *
- * <p>The marshalling/unmarshalling of the value relies upon {@link Parcel#writeValue(Object)}
- * and {@link Parcel#readValue(ClassLoader)} and so this method can only be used with types
- * supported by those methods.
- *
- * @param in the Parcel to read from
- * @param classLoader the ClassLoader to pass to {@link Parcel#readValue(ClassLoader)}
- * @param valueClass the expected type of the value, typically the same as {@code <T>} but can
- * also be a subclass
- * @throws RuntimeException if the value read is not compatible with {@code valueClass} or the
- * object could not be read
- */
- @SuppressWarnings("unchecked")
- @NonNull
- public static <T> TimestampedValue<T> readFromParcel(
- @NonNull Parcel in, @Nullable ClassLoader classLoader, Class<? extends T> valueClass) {
- long referenceTimeMillis = in.readLong();
- T value = (T) in.readValue(classLoader);
- // Equivalent to static code: if (!(value.getClass() instanceof {valueClass})) {
- if (value != null && !valueClass.isAssignableFrom(value.getClass())) {
- throw new RuntimeException("Value was of type " + value.getClass()
- + " is not assignable to " + valueClass);
- }
- return new TimestampedValue<>(referenceTimeMillis, value);
- }
-
- /**
- * Write a {@link TimestampedValue} to a parcel so that it can be read using
- * {@link #readFromParcel(Parcel, ClassLoader, Class)}.
- *
- * <p>The marshalling/unmarshalling of the value relies upon {@link Parcel#writeValue(Object)}
- * and {@link Parcel#readValue(ClassLoader)} and so this method can only be used with types
- * supported by those methods.
- *
- * @param dest the Parcel
- * @param timestampedValue the value
- * @throws RuntimeException if the value could not be written to the Parcel
- */
- public static void writeToParcel(
- @NonNull Parcel dest, @NonNull TimestampedValue<?> timestampedValue) {
- dest.writeLong(timestampedValue.mReferenceTimeMillis);
- dest.writeValue(timestampedValue.mValue);
- }
-
- /**
* Returns the difference in milliseconds between two instance's reference times.
*/
public static long referenceTimeDifference(
@NonNull TimestampedValue<?> one, @NonNull TimestampedValue<?> two) {
return one.mReferenceTimeMillis - two.mReferenceTimeMillis;
}
+
+ public static final @NonNull Parcelable.Creator<TimestampedValue<?>> CREATOR =
+ new Parcelable.ClassLoaderCreator<TimestampedValue<?>>() {
+
+ @Override
+ public TimestampedValue<?> createFromParcel(@NonNull Parcel source) {
+ return createFromParcel(source, null);
+ }
+
+ @Override
+ public TimestampedValue<?> createFromParcel(
+ @NonNull Parcel source, @Nullable ClassLoader classLoader) {
+ long referenceTimeMillis = source.readLong();
+ Object value = source.readValue(classLoader);
+ return new TimestampedValue<>(referenceTimeMillis, value);
+ }
+
+ @Override
+ public TimestampedValue[] newArray(int size) {
+ return new TimestampedValue[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeLong(mReferenceTimeMillis);
+ dest.writeValue(mValue);
+ }
}
diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl
index 955be8d..762366e 100644
--- a/core/java/android/view/IRecentsAnimationController.aidl
+++ b/core/java/android/view/IRecentsAnimationController.aidl
@@ -120,4 +120,10 @@
* @see IRecentsAnimationRunner#onCancelled
*/
void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot);
+
+ /**
+ * Sets a state for controller to decide which surface is the destination when the recents
+ * animation is cancelled through fail safe mechanism.
+ */
+ void setWillFinishToHome(boolean willFinishToHome);
}
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 4b872d3..8bf99ec 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -81,6 +81,14 @@
*/
void showInsets(int types, boolean fromIme);
+ /**
+ * Called when a set of insets source window should be hidden by policy.
+ *
+ * @param types internal inset types (WindowInsets.Type.InsetType) to hide
+ * @param fromIme true if this request originated from IME (InputMethodService).
+ */
+ void hideInsets(int types, boolean fromIme);
+
void moved(int newX, int newY);
void dispatchAppVisibility(boolean visible);
void dispatchGetNewSurface();
diff --git a/core/java/android/view/InputMonitor.java b/core/java/android/view/InputMonitor.java
index 1a1d7e6..ad1f201 100644
--- a/core/java/android/view/InputMonitor.java
+++ b/core/java/android/view/InputMonitor.java
@@ -40,8 +40,6 @@
private static final boolean DEBUG = false;
@NonNull
- private final String mName;
- @NonNull
private final InputChannel mInputChannel;
@NonNull
private final IInputMonitorHost mHost;
@@ -81,23 +79,19 @@
- // Code below generated by codegen v1.0.1.
+ // Code below generated by codegen v1.0.7.
//
// DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/InputMonitor.java
- //
- // CHECKSTYLE:OFF Generated code
+
@DataClass.Generated.Member
public InputMonitor(
- @NonNull String name,
@NonNull InputChannel inputChannel,
@NonNull IInputMonitorHost host) {
- this.mName = name;
- com.android.internal.util.AnnotationValidations.validate(
- NonNull.class, null, mName);
this.mInputChannel = inputChannel;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mInputChannel);
@@ -109,11 +103,6 @@
}
@DataClass.Generated.Member
- public @NonNull String getName() {
- return mName;
- }
-
- @DataClass.Generated.Member
public @NonNull InputChannel getInputChannel() {
return mInputChannel;
}
@@ -130,7 +119,6 @@
// String fieldNameToString() { ... }
return "InputMonitor { " +
- "name = " + mName + ", " +
"inputChannel = " + mInputChannel + ", " +
"host = " + mHost +
" }";
@@ -142,7 +130,6 @@
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
- dest.writeString(mName);
dest.writeTypedObject(mInputChannel, flags);
dest.writeStrongInterface(mHost);
}
@@ -151,6 +138,26 @@
@DataClass.Generated.Member
public int describeContents() { return 0; }
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ InputMonitor(Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ InputChannel inputChannel = (InputChannel) in.readTypedObject(InputChannel.CREATOR);
+ IInputMonitorHost host = IInputMonitorHost.Stub.asInterface(in.readStrongBinder());
+
+ this.mInputChannel = inputChannel;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mInputChannel);
+ this.mHost = host;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHost);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
@DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<InputMonitor> CREATOR
= new Parcelable.Creator<InputMonitor>() {
@@ -160,26 +167,16 @@
}
@Override
- @SuppressWarnings({"unchecked", "RedundantCast"})
public InputMonitor createFromParcel(Parcel in) {
- // You can override field unparcelling by defining methods like:
- // static FieldType unparcelFieldName(Parcel in) { ... }
-
- String name = in.readString();
- InputChannel inputChannel = (InputChannel) in.readTypedObject(InputChannel.CREATOR);
- IInputMonitorHost host = IInputMonitorHost.Stub.asInterface(in.readStrongBinder());
- return new InputMonitor(
- name,
- inputChannel,
- host);
+ return new InputMonitor(in);
}
};
@DataClass.Generated(
- time = 1569871940995L,
- codegenVersion = "1.0.1",
+ time = 1571177265149L,
+ codegenVersion = "1.0.7",
sourceFile = "frameworks/base/core/java/android/view/InputMonitor.java",
- inputSignatures = "private static final java.lang.String TAG\nprivate static final boolean DEBUG\nprivate final @android.annotation.NonNull java.lang.String mName\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\npublic void pilferPointers()\npublic void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
+ inputSignatures = "private static final java.lang.String TAG\nprivate static final boolean DEBUG\nprivate final @android.annotation.NonNull android.view.InputChannel mInputChannel\nprivate final @android.annotation.NonNull android.view.IInputMonitorHost mHost\npublic void pilferPointers()\npublic void dispose()\nclass InputMonitor extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 10a9aaa..08e4eeb 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -38,10 +38,8 @@
// The input application handle.
public final InputApplicationHandle inputApplicationHandle;
- // The client window.
- public final IWindow clientWindow;
-
- // The token associated with the window.
+ // The token associates input data with a window and its input channel. The client input
+ // channel and the server input channel will both contain this token.
public IBinder token;
// The window name.
@@ -120,10 +118,8 @@
private native void nativeDispose();
- public InputWindowHandle(InputApplicationHandle inputApplicationHandle,
- IWindow clientWindow, int displayId) {
+ public InputWindowHandle(InputApplicationHandle inputApplicationHandle, int displayId) {
this.inputApplicationHandle = inputApplicationHandle;
- this.clientWindow = clientWindow;
this.displayId = displayId;
}
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 341c214..e4deffa 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -229,6 +229,10 @@
final InsetsSourceConsumer consumer = items.valueAt(i);
final InsetsSource source = mInitialInsetsState.getSource(consumer.getType());
final InsetsSourceControl control = consumer.getControl();
+ if (control == null) {
+ // Control may not be available for consumer yet or revoked.
+ continue;
+ }
final SurfaceControl leash = consumer.getControl().getLeash();
mTmpMatrix.setTranslate(control.getSurfacePosition().x, control.getSurfacePosition().y);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 5a8636d..5bb4f63 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -251,6 +251,10 @@
@Override
public void hide(@InsetType int types) {
+ hide(types, false /* fromIme */);
+ }
+
+ void hide(@InsetType int types, boolean fromIme) {
int typesReady = 0;
final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
for (int i = internalTypes.size() - 1; i >= 0; i--) {
@@ -265,7 +269,7 @@
}
typesReady |= InsetsState.toPublicType(consumer.getType());
}
- applyAnimation(typesReady, false /* show */, false /* fromIme */);
+ applyAnimation(typesReady, false /* show */, fromIme /* fromIme */);
}
@Override
@@ -331,42 +335,35 @@
boolean isReady = true;
for (int i = internalTypes.size() - 1; i >= 0; i--) {
InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
- // Double check for IME that IME target window has focus.
- if (consumer.getType() != TYPE_IME || consumer.hasWindowFocus()) {
- boolean setVisible = !consumer.isVisible();
- if (setVisible) {
- // Show request
- switch(consumer.requestShow(fromIme)) {
- case ShowResult.SHOW_IMMEDIATELY:
- typesReady |= InsetsState.toPublicType(consumer.getType());
- break;
- case ShowResult.SHOW_DELAYED:
- isReady = false;
- break;
- case ShowResult.SHOW_FAILED:
- // IME cannot be shown (since it didn't have focus), proceed
- // with animation of other types.
- if (mPendingTypesToShow != 0) {
- // remove IME from pending because view no longer has focus.
- mPendingTypesToShow &= ~InsetsState.toPublicType(TYPE_IME);
- }
- break;
- }
- } else {
- // Hide request
- // TODO: Move notifyHidden() to beginning of the hide animation
- // (when visibility actually changes using hideDirectly()).
- consumer.notifyHidden();
- typesReady |= InsetsState.toPublicType(consumer.getType());
+ boolean setVisible = !consumer.isVisible();
+ if (setVisible) {
+ // Show request
+ switch(consumer.requestShow(fromIme)) {
+ case ShowResult.SHOW_IMMEDIATELY:
+ typesReady |= InsetsState.toPublicType(consumer.getType());
+ break;
+ case ShowResult.SHOW_DELAYED:
+ isReady = false;
+ break;
+ case ShowResult.SHOW_FAILED:
+ // IME cannot be shown (since it didn't have focus), proceed
+ // with animation of other types.
+ if (mPendingTypesToShow != 0) {
+ // remove IME from pending because view no longer has focus.
+ mPendingTypesToShow &= ~InsetsState.toPublicType(TYPE_IME);
+ }
+ break;
}
- consumers.put(consumer.getType(), consumer);
} else {
- // window doesnt have focus, no-op.
- isReady = false;
- // TODO: Let the calling app know that window has lost focus and
- // show()/hide()/controlWindowInsetsAnimation requests will be ignored.
- typesReady &= ~InsetsState.toPublicType(consumer.getType());
+ // Hide request
+ // TODO: Move notifyHidden() to beginning of the hide animation
+ // (when visibility actually changes using hideDirectly()).
+ if (!fromIme) {
+ consumer.notifyHidden();
+ }
+ typesReady |= InsetsState.toPublicType(consumer.getType());
}
+ consumers.put(consumer.getType(), consumer);
}
return new Pair<>(typesReady, isReady);
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index b685cf0..8dd475e 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -181,7 +181,6 @@
private static native void nativeSeverChildren(long transactionObj, long nativeObject);
private static native void nativeSetOverrideScalingMode(long transactionObj, long nativeObject,
int scalingMode);
- private static native boolean nativeGetTransformToDisplayInverse(long nativeObject);
private static native Display.HdrCapabilities nativeGetHdrCapabilities(IBinder displayToken);
@@ -200,7 +199,10 @@
private final CloseGuard mCloseGuard = CloseGuard.get();
private String mName;
- long mNativeObject; // package visibility only for Surface.java access
+ /**
+ * @hide
+ */
+ public long mNativeObject;
// TODO: Move this to native.
private final Object mSizeLock = new Object();
@@ -303,8 +305,8 @@
/**
* Surface creation flag: Creates a Dim surface.
* Everything behind this surface is dimmed by the amount specified
- * in {@link #setAlpha}. It is an error to lock a Dim surface, since it
- * doesn't have a backing store.
+ * in {@link Transaction#setAlpha(SurfaceControl, float)}. It is an error to lock a Dim
+ * surface, since it doesn't have a backing store.
*
* @hide
*/
@@ -319,6 +321,11 @@
public static final int FX_SURFACE_CONTAINER = 0x00080000;
/**
+ * @hide
+ */
+ public static final int FX_SURFACE_BLAST = 0x00040000;
+
+ /**
* Mask used for FX values above.
*
* @hide
@@ -694,6 +701,14 @@
}
/**
+ * @hide
+ */
+ public Builder setBLASTLayer() {
+ unsetBufferSize();
+ return setFlags(FX_SURFACE_BLAST, FX_SURFACE_MASK);
+ }
+
+ /**
* Indicates whether a 'ContainerLayer' is to be constructed.
*
* Container layers will not be rendered in any fashion and instead are used
@@ -740,20 +755,20 @@
* <p>
* Good practice is to first create the surface with the {@link #HIDDEN} flag
* specified, open a transaction, set the surface layer, layer stack, alpha,
- * and position, call {@link #show} if appropriate, and close the transaction.
+ * and position, call {@link Transaction#show(SurfaceControl)} if appropriate, and close the
+ * transaction.
* <p>
* Bounds of the surface is determined by its crop and its buffer size. If the
* surface has no buffer or crop, the surface is boundless and only constrained
* by the size of its parent bounds.
*
- * @param session The surface session, must not be null.
- * @param name The surface name, must not be null.
- * @param w The surface initial width.
- * @param h The surface initial height.
- * @param flags The surface creation flags. Should always include {@link #HIDDEN}
- * in the creation flags.
+ * @param session The surface session, must not be null.
+ * @param name The surface name, must not be null.
+ * @param w The surface initial width.
+ * @param h The surface initial height.
+ * @param flags The surface creation flags. Should always include {@link #HIDDEN}
+ * in the creation flags.
* @param metadata Initial metadata.
- *
* @throws throws OutOfResourcesException If the SurfaceControl cannot be created.
*/
private SurfaceControl(SurfaceSession session, String name, int w, int h, int format, int flags,
@@ -1014,15 +1029,6 @@
/**
* @hide
*/
- public void deferTransactionUntil(Surface barrier, long frame) {
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.deferTransactionUntilSurface(this, barrier, frame);
- }
- }
-
- /**
- * @hide
- */
public void reparentChildren(SurfaceControl newParent) {
synchronized(SurfaceControl.class) {
sGlobalTransaction.reparentChildren(this, newParent);
@@ -1032,15 +1038,6 @@
/**
* @hide
*/
- public void reparent(SurfaceControl newParent) {
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.reparent(this, newParent);
- }
- }
-
- /**
- * @hide
- */
public void detachChildren() {
synchronized(SurfaceControl.class) {
sGlobalTransaction.detachChildren(this);
@@ -1060,15 +1057,6 @@
/**
* @hide
*/
- public static void setAnimationTransaction() {
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setAnimationTransaction();
- }
- }
-
- /**
- * @hide
- */
@UnsupportedAppUsage
public void setLayer(int zorder) {
checkNotReleased();
@@ -1080,16 +1068,6 @@
/**
* @hide
*/
- public void setRelativeLayer(SurfaceControl relativeTo, int zorder) {
- checkNotReleased();
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.setRelativeLayer(this, relativeTo, zorder);
- }
- }
-
- /**
- * @hide
- */
@UnsupportedAppUsage
public void setPosition(float x, float y) {
checkNotReleased();
@@ -1183,16 +1161,6 @@
/**
* @hide
*/
- public void setColor(@Size(3) float[] color) {
- checkNotReleased();
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setColor(this, color);
- }
- }
-
- /**
- * @hide
- */
public void setMatrix(float dsdx, float dtdx, float dtdy, float dsdy) {
checkNotReleased();
synchronized(SurfaceControl.class) {
@@ -1201,36 +1169,6 @@
}
/**
- * Sets the transform and position of a {@link SurfaceControl} from a 3x3 transformation matrix.
- *
- * @param matrix The matrix to apply.
- * @param float9 An array of 9 floats to be used to extract the values from the matrix.
- * @hide
- */
- public void setMatrix(Matrix matrix, float[] float9) {
- checkNotReleased();
- matrix.getValues(float9);
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setMatrix(this, float9[MSCALE_X], float9[MSKEW_Y],
- float9[MSKEW_X], float9[MSCALE_Y]);
- sGlobalTransaction.setPosition(this, float9[MTRANS_X], float9[MTRANS_Y]);
- }
- }
-
- /**
- * Sets the color transform for the Surface.
- * @param matrix A float array with 9 values represents a 3x3 transform matrix
- * @param translation A float array with 3 values represents a translation vector
- * @hide
- */
- public void setColorTransform(@Size(9) float[] matrix, @Size(3) float[] translation) {
- checkNotReleased();
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setColorTransform(this, matrix, translation);
- }
- }
-
- /**
* Sets the Surface to be color space agnostic. If a surface is color space agnostic,
* the color can be interpreted in any color space.
* @param agnostic A boolean to indicate whether the surface is color space agnostic
@@ -1260,43 +1198,6 @@
}
/**
- * Same as {@link SurfaceControl#setWindowCrop(Rect)} but sets the crop rect top left at 0, 0.
- *
- * @param width width of crop rect
- * @param height height of crop rect
- * @hide
- */
- public void setWindowCrop(int width, int height) {
- checkNotReleased();
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setWindowCrop(this, width, height);
- }
- }
-
- /**
- * Sets the corner radius of a {@link SurfaceControl}.
- *
- * @param cornerRadius Corner radius in pixels.
- * @hide
- */
- public void setCornerRadius(float cornerRadius) {
- checkNotReleased();
- synchronized (SurfaceControl.class) {
- sGlobalTransaction.setCornerRadius(this, cornerRadius);
- }
- }
-
- /**
- * @hide
- */
- public void setLayerStack(int layerStack) {
- checkNotReleased();
- synchronized(SurfaceControl.class) {
- sGlobalTransaction.setLayerStack(this, layerStack);
- }
- }
-
- /**
* @hide
*/
public void setOpaque(boolean isOpaque) {
@@ -2066,7 +1967,10 @@
public static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
Transaction.class.getClassLoader(),
nativeGetNativeTransactionFinalizer(), 512);
- private long mNativeObject;
+ /**
+ * @hide
+ */
+ public long mNativeObject;
private final ArrayMap<SurfaceControl, Point> mResizedSurfaces = new ArrayMap<>();
Runnable mFreeNativeResources;
@@ -2302,6 +2206,12 @@
}
/**
+ * Sets the transform and position of a {@link SurfaceControl} from a 3x3 transformation
+ * matrix.
+ *
+ * @param sc SurfaceControl to set matrix of
+ * @param matrix The matrix to apply.
+ * @param float9 An array of 9 floats to be used to extract the values from the matrix.
* @hide
*/
@UnsupportedAppUsage
@@ -2315,7 +2225,9 @@
/**
* Sets the color transform for the Surface.
- * @param matrix A float array with 9 values represents a 3x3 transform matrix
+ *
+ * @param sc SurfaceControl to set color transform of
+ * @param matrix A float array with 9 values represents a 3x3 transform matrix
* @param translation A float array with 3 values represents a translation vector
* @hide
*/
@@ -2339,6 +2251,13 @@
}
/**
+ * Bounds the surface and its children to the bounds specified. Size of the surface will be
+ * ignored and only the crop and buffer size will be used to determine the bounds of the
+ * surface. If no crop is specified and the surface has no buffer, the surface bounds is
+ * only constrained by the size of its parent bounds.
+ *
+ * @param sc SurfaceControl to set crop of.
+ * @param crop Bounds of the crop to apply.
* @hide
*/
@UnsupportedAppUsage
@@ -2355,6 +2274,12 @@
}
/**
+ * Same as {@link Transaction#setWindowCrop(SurfaceControl, Rect)} but sets the crop rect
+ * top left at 0, 0.
+ *
+ * @param sc SurfaceControl to set crop of.
+ * @param width width of crop rect
+ * @param height height of crop rect
* @hide
*/
public Transaction setWindowCrop(SurfaceControl sc, int width, int height) {
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index a858300..2f0a4eb 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -418,12 +418,7 @@
Log.d(TAG, System.identityHashCode(this)
+ " updateSurfaceAlpha: set alpha=" + alpha);
}
- SurfaceControl.openTransaction();
- try {
- mSurfaceControl.setAlpha(alpha);
- } finally {
- SurfaceControl.closeTransaction();
- }
+ mTmpTransaction.setAlpha(mSurfaceControl, alpha).apply();
}
mSurfaceAlpha = alpha;
}
@@ -701,17 +696,18 @@
}
}
- private void updateBackgroundVisibilityInTransaction() {
+ private void updateBackgroundVisibility(Transaction t) {
if (mBackgroundControl == null) {
return;
}
if ((mSubLayer < 0) && ((mSurfaceFlags & SurfaceControl.OPAQUE) != 0)) {
- mBackgroundControl.show();
+ t.show(mBackgroundControl);
} else {
- mBackgroundControl.hide();
+ t.hide(mBackgroundControl);
}
}
+
private void releaseSurfaces() {
mSurfaceAlpha = 1f;
@@ -853,60 +849,60 @@
if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
+ "Cur surface: " + mSurface);
- SurfaceControl.openTransaction();
- try {
- // If we are creating the surface control or the parent surface has not
- // changed, then set relative z. Otherwise allow the parent
- // SurfaceChangedCallback to update the relative z. This is needed so that
- // we do not change the relative z before the server is ready to swap the
- // parent surface.
- if (creating || (mParentSurfaceGenerationId
- == viewRoot.mSurface.getGenerationId())) {
- SurfaceControl.mergeToGlobalTransaction(updateRelativeZ());
- }
- mParentSurfaceGenerationId = viewRoot.mSurface.getGenerationId();
-
- if (mViewVisibility) {
- mSurfaceControl.show();
- } else {
- mSurfaceControl.hide();
- }
- updateBackgroundVisibilityInTransaction();
- if (mUseAlpha) {
- mSurfaceControl.setAlpha(alpha);
- mSurfaceAlpha = alpha;
- }
-
- // While creating the surface, we will set it's initial
- // geometry. Outside of that though, we should generally
- // leave it to the RenderThread.
- //
- // There is one more case when the buffer size changes we aren't yet
- // prepared to sync (as even following the transaction applying
- // we still need to latch a buffer).
- // b/28866173
- if (sizeChanged || creating || !mRtHandlingPositionUpdates) {
- mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top);
- mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth,
- 0.0f, 0.0f,
- mScreenRect.height() / (float) mSurfaceHeight);
- // Set a window crop when creating the surface or changing its size to
- // crop the buffer to the surface size since the buffer producer may
- // use SCALING_MODE_SCALE and submit a larger size than the surface
- // size.
- if (mClipSurfaceToBounds && mClipBounds != null) {
- mSurfaceControl.setWindowCrop(mClipBounds);
- } else {
- mSurfaceControl.setWindowCrop(mSurfaceWidth, mSurfaceHeight);
- }
- }
- mSurfaceControl.setCornerRadius(mCornerRadius);
- if (sizeChanged && !creating) {
- mSurfaceControl.setBufferSize(mSurfaceWidth, mSurfaceHeight);
- }
- } finally {
- SurfaceControl.closeTransaction();
+ // If we are creating the surface control or the parent surface has not
+ // changed, then set relative z. Otherwise allow the parent
+ // SurfaceChangedCallback to update the relative z. This is needed so that
+ // we do not change the relative z before the server is ready to swap the
+ // parent surface.
+ if (creating || (mParentSurfaceGenerationId
+ == viewRoot.mSurface.getGenerationId())) {
+ updateRelativeZ(mTmpTransaction);
}
+ mParentSurfaceGenerationId = viewRoot.mSurface.getGenerationId();
+
+ if (mViewVisibility) {
+ mTmpTransaction.show(mSurfaceControl);
+ } else {
+ mTmpTransaction.hide(mSurfaceControl);
+ }
+ updateBackgroundVisibility(mTmpTransaction);
+ if (mUseAlpha) {
+ mTmpTransaction.setAlpha(mSurfaceControl, alpha);
+ mSurfaceAlpha = alpha;
+ }
+
+ // While creating the surface, we will set it's initial
+ // geometry. Outside of that though, we should generally
+ // leave it to the RenderThread.
+ //
+ // There is one more case when the buffer size changes we aren't yet
+ // prepared to sync (as even following the transaction applying
+ // we still need to latch a buffer).
+ // b/28866173
+ if (sizeChanged || creating || !mRtHandlingPositionUpdates) {
+ mTmpTransaction.setPosition(mSurfaceControl, mScreenRect.left,
+ mScreenRect.top);
+ mTmpTransaction.setMatrix(mSurfaceControl,
+ mScreenRect.width() / (float) mSurfaceWidth, 0.0f, 0.0f,
+ mScreenRect.height() / (float) mSurfaceHeight);
+ // Set a window crop when creating the surface or changing its size to
+ // crop the buffer to the surface size since the buffer producer may
+ // use SCALING_MODE_SCALE and submit a larger size than the surface
+ // size.
+ if (mClipSurfaceToBounds && mClipBounds != null) {
+ mTmpTransaction.setWindowCrop(mSurfaceControl, mClipBounds);
+ } else {
+ mTmpTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth,
+ mSurfaceHeight);
+ }
+ }
+ mTmpTransaction.setCornerRadius(mSurfaceControl, mCornerRadius);
+ if (sizeChanged && !creating) {
+ mTmpTransaction.setBufferSize(mSurfaceControl, mSurfaceWidth,
+ mSurfaceHeight);
+ }
+
+ mTmpTransaction.apply();
if (sizeChanged || creating) {
redrawNeeded = true;
@@ -1260,12 +1256,7 @@
final float[] colorComponents = new float[] { Color.red(bgColor) / 255.f,
Color.green(bgColor) / 255.f, Color.blue(bgColor) / 255.f };
- SurfaceControl.openTransaction();
- try {
- mBackgroundControl.setColor(colorComponents);
- } finally {
- SurfaceControl.closeTransaction();
- }
+ mTmpTransaction.setColor(mBackgroundControl, colorComponents).apply();
}
@UnsupportedAppUsage
@@ -1480,15 +1471,13 @@
@Override
public void surfaceReplaced(Transaction t) {
if (mSurfaceControl != null && mBackgroundControl != null) {
- t.merge(updateRelativeZ());
+ updateRelativeZ(t);
}
}
- private Transaction updateRelativeZ() {
- Transaction t = new Transaction();
+ private void updateRelativeZ(Transaction t) {
SurfaceControl viewRoot = getViewRootImpl().getSurfaceControl();
t.setRelativeLayer(mBackgroundControl, viewRoot, Integer.MIN_VALUE);
t.setRelativeLayer(mSurfaceControl, viewRoot, mSubLayer);
- return t;
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cfb6a79a..1599afb 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -75,6 +75,7 @@
import android.graphics.Shader;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
import android.hardware.display.DisplayManagerGlobal;
import android.net.Uri;
import android.os.Build;
@@ -4496,8 +4497,9 @@
* When non-null and valid, this is expected to contain an up-to-date copy
* of the background drawable. It is cleared on temporary detach, and reset
* on cleanup.
+ * @hide
*/
- private RenderNode mBackgroundRenderNode;
+ RenderNode mBackgroundRenderNode;
@UnsupportedAppUsage
private int mBackgroundResource;
@@ -5228,6 +5230,8 @@
sBrokenWindowBackground = targetSdkVersion < Build.VERSION_CODES.Q;
+ GradientDrawable.sWrapNegativeAngleMeasurements =
+ targetSdkVersion >= Build.VERSION_CODES.Q;
sCompatibilityDone = true;
}
}
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index 8a1fd62..ad59ae5 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -32,6 +32,7 @@
import android.os.Debug;
import android.os.Handler;
import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -46,6 +47,7 @@
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
+import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -53,12 +55,12 @@
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
-import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.concurrent.Callable;
-import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
@@ -68,6 +70,7 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
+import java.util.stream.Stream;
/**
* Various debugging/tracing tools related to {@link View} and the view hierarchy.
@@ -331,11 +334,83 @@
public View findHierarchyView(String className, int hashCode);
}
- private static HashMap<Class<?>, Method[]> mCapturedViewMethodsForClasses = null;
- private static HashMap<Class<?>, Field[]> mCapturedViewFieldsForClasses = null;
+ private abstract static class PropertyInfo<T extends Annotation,
+ R extends AccessibleObject & Member> {
+
+ public final R member;
+ public final T property;
+ public final String name;
+ public final Class<?> returnType;
+
+ public String entrySuffix = "";
+ public String valueSuffix = "";
+
+ PropertyInfo(Class<T> property, R member, Class<?> returnType) {
+ this.member = member;
+ this.name = member.getName();
+ this.property = member.getAnnotation(property);
+ this.returnType = returnType;
+ }
+
+ public abstract Object invoke(Object target) throws Exception;
+
+ static <T extends Annotation> PropertyInfo<T, ?> forMethod(Method method,
+ Class<T> property) {
+ // Ensure the method return and parameter types can be resolved.
+ try {
+ if ((method.getReturnType() == Void.class)
+ || (method.getParameterTypes().length != 0)) {
+ return null;
+ }
+ } catch (NoClassDefFoundError e) {
+ return null;
+ }
+ if (!method.isAnnotationPresent(property)) {
+ return null;
+ }
+ method.setAccessible(true);
+
+ PropertyInfo info = new MethodPI(method, property);
+ info.entrySuffix = "()";
+ info.valueSuffix = ";";
+ return info;
+ }
+
+ static <T extends Annotation> PropertyInfo<T, ?> forField(Field field, Class<T> property) {
+ if (!field.isAnnotationPresent(property)) {
+ return null;
+ }
+ field.setAccessible(true);
+ return new FieldPI<>(field, property);
+ }
+ }
+
+ private static class MethodPI<T extends Annotation> extends PropertyInfo<T, Method> {
+
+ MethodPI(Method method, Class<T> property) {
+ super(property, method, method.getReturnType());
+ }
+
+ @Override
+ public Object invoke(Object target) throws Exception {
+ return member.invoke(target);
+ }
+ }
+
+ private static class FieldPI<T extends Annotation> extends PropertyInfo<T, Field> {
+
+ FieldPI(Field field, Class<T> property) {
+ super(property, field, field.getType());
+ }
+
+ @Override
+ public Object invoke(Object target) throws Exception {
+ return member.get(target);
+ }
+ }
// Maximum delay in ms after which we stop trying to capture a View's drawing
- private static final int CAPTURE_TIMEOUT = 4000;
+ private static final int CAPTURE_TIMEOUT = 6000;
private static final String REMOTE_COMMAND_CAPTURE = "CAPTURE";
private static final String REMOTE_COMMAND_DUMP = "DUMP";
@@ -346,9 +421,9 @@
private static final String REMOTE_COMMAND_CAPTURE_LAYERS = "CAPTURE_LAYERS";
private static final String REMOTE_COMMAND_OUTPUT_DISPLAYLIST = "OUTPUT_DISPLAYLIST";
- private static HashMap<Class<?>, Field[]> sFieldsForClasses;
- private static HashMap<Class<?>, Method[]> sMethodsForClasses;
- private static HashMap<AccessibleObject, ExportedProperty> sAnnotations;
+ private static HashMap<Class<?>, PropertyInfo<ExportedProperty, ?>[]> sExportProperties;
+ private static HashMap<Class<?>, PropertyInfo<CapturedViewProperty, ?>[]>
+ sCapturedViewProperties;
/**
* @deprecated This enum is now unused
@@ -1157,6 +1232,69 @@
private static void dumpViewHierarchy(Context context, ViewGroup group,
BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) {
+ cacheExportedProperties(group.getClass());
+ if (!skipChildren) {
+ cacheExportedPropertiesForChildren(group);
+ }
+ // Try to use the handler provided by the view
+ Handler handler = group.getHandler();
+ // Fall back on using the main thread
+ if (handler == null) {
+ handler = new Handler(Looper.getMainLooper());
+ }
+
+ if (handler.getLooper() == Looper.myLooper()) {
+ dumpViewHierarchyOnUIThread(context, group, out, level, skipChildren,
+ includeProperties);
+ } else {
+ FutureTask task = new FutureTask(() ->
+ dumpViewHierarchyOnUIThread(context, group, out, level, skipChildren,
+ includeProperties), null);
+ Message msg = Message.obtain(handler, task);
+ msg.setAsynchronous(true);
+ handler.sendMessage(msg);
+ while (true) {
+ try {
+ task.get(CAPTURE_TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS);
+ return;
+ } catch (InterruptedException e) {
+ // try again
+ } catch (ExecutionException | TimeoutException e) {
+ // Something unexpected happened.
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+
+ private static void cacheExportedPropertiesForChildren(ViewGroup group) {
+ final int count = group.getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View view = group.getChildAt(i);
+ cacheExportedProperties(view.getClass());
+ if (view instanceof ViewGroup) {
+ cacheExportedPropertiesForChildren((ViewGroup) view);
+ }
+ }
+ }
+
+ private static void cacheExportedProperties(Class<?> klass) {
+ if (sExportProperties != null && sExportProperties.containsKey(klass)) {
+ return;
+ }
+ do {
+ for (PropertyInfo<ExportedProperty, ?> info : getExportedProperties(klass)) {
+ if (!info.returnType.isPrimitive() && info.property.deepExport()) {
+ cacheExportedProperties(info.returnType);
+ }
+ }
+ klass = klass.getSuperclass();
+ } while (klass != Object.class);
+ }
+
+
+ private static void dumpViewHierarchyOnUIThread(Context context, ViewGroup group,
+ BufferedWriter out, int level, boolean skipChildren, boolean includeProperties) {
if (!dumpView(context, group, out, level, includeProperties)) {
return;
}
@@ -1169,16 +1307,16 @@
for (int i = 0; i < count; i++) {
final View view = group.getChildAt(i);
if (view instanceof ViewGroup) {
- dumpViewHierarchy(context, (ViewGroup) view, out, level + 1, skipChildren,
- includeProperties);
+ dumpViewHierarchyOnUIThread(context, (ViewGroup) view, out, level + 1,
+ skipChildren, includeProperties);
} else {
dumpView(context, view, out, level + 1, includeProperties);
}
if (view.mOverlay != null) {
ViewOverlay overlay = view.getOverlay();
ViewGroup overlayContainer = overlay.mOverlayViewGroup;
- dumpViewHierarchy(context, overlayContainer, out, level + 2, skipChildren,
- includeProperties);
+ dumpViewHierarchyOnUIThread(context, overlayContainer, out, level + 2,
+ skipChildren, includeProperties);
}
}
if (group instanceof HierarchyHandler) {
@@ -1212,81 +1350,28 @@
return true;
}
- private static Field[] getExportedPropertyFields(Class<?> klass) {
- if (sFieldsForClasses == null) {
- sFieldsForClasses = new HashMap<Class<?>, Field[]>();
- }
- if (sAnnotations == null) {
- sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
- }
-
- final HashMap<Class<?>, Field[]> map = sFieldsForClasses;
-
- Field[] fields = map.get(klass);
- if (fields != null) {
- return fields;
- }
-
- try {
- final Field[] declaredFields = klass.getDeclaredFieldsUnchecked(false);
- final ArrayList<Field> foundFields = new ArrayList<Field>();
- for (final Field field : declaredFields) {
- // Fields which can't be resolved have a null type.
- if (field.getType() != null && field.isAnnotationPresent(ExportedProperty.class)) {
- field.setAccessible(true);
- foundFields.add(field);
- sAnnotations.put(field, field.getAnnotation(ExportedProperty.class));
- }
- }
- fields = foundFields.toArray(new Field[foundFields.size()]);
- map.put(klass, fields);
- } catch (NoClassDefFoundError e) {
- throw new AssertionError(e);
- }
-
- return fields;
+ private static <T extends Annotation> PropertyInfo<T, ?>[] convertToPropertyInfos(
+ Method[] methods, Field[] fields, Class<T> property) {
+ return Stream.of(Arrays.stream(methods).map(m -> PropertyInfo.forMethod(m, property)),
+ Arrays.stream(fields).map(f -> PropertyInfo.forField(f, property)))
+ .flatMap(Function.identity())
+ .filter(i -> i != null)
+ .toArray(PropertyInfo[]::new);
}
- private static Method[] getExportedPropertyMethods(Class<?> klass) {
- if (sMethodsForClasses == null) {
- sMethodsForClasses = new HashMap<Class<?>, Method[]>(100);
+ private static PropertyInfo<ExportedProperty, ?>[] getExportedProperties(Class<?> klass) {
+ if (sExportProperties == null) {
+ sExportProperties = new HashMap<>();
}
- if (sAnnotations == null) {
- sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512);
+ final HashMap<Class<?>, PropertyInfo<ExportedProperty, ?>[]> map = sExportProperties;
+ PropertyInfo<ExportedProperty, ?>[] properties = sExportProperties.get(klass);
+
+ if (properties == null) {
+ properties = convertToPropertyInfos(klass.getDeclaredMethodsUnchecked(false),
+ klass.getDeclaredFieldsUnchecked(false), ExportedProperty.class);
+ map.put(klass, properties);
}
-
- final HashMap<Class<?>, Method[]> map = sMethodsForClasses;
-
- Method[] methods = map.get(klass);
- if (methods != null) {
- return methods;
- }
-
- methods = klass.getDeclaredMethodsUnchecked(false);
-
- final ArrayList<Method> foundMethods = new ArrayList<Method>();
- for (final Method method : methods) {
- // Ensure the method return and parameter types can be resolved.
- try {
- method.getReturnType();
- method.getParameterTypes();
- } catch (NoClassDefFoundError e) {
- continue;
- }
-
- if (method.getParameterTypes().length == 0 &&
- method.isAnnotationPresent(ExportedProperty.class) &&
- method.getReturnType() != Void.class) {
- method.setAccessible(true);
- foundMethods.add(method);
- sAnnotations.put(method, method.getAnnotation(ExportedProperty.class));
- }
- }
-
- methods = foundMethods.toArray(new Method[foundMethods.size()]);
- map.put(klass, methods);
-
- return methods;
+ return properties;
}
private static void dumpViewProperties(Context context, Object view,
@@ -1305,233 +1390,97 @@
Class<?> klass = view.getClass();
do {
- exportFields(context, view, out, klass, prefix);
- exportMethods(context, view, out, klass, prefix);
+ writeExportedProperties(context, view, out, klass, prefix);
klass = klass.getSuperclass();
} while (klass != Object.class);
}
- private static Object callMethodOnAppropriateTheadBlocking(final Method method,
- final Object object) throws IllegalAccessException, InvocationTargetException,
- TimeoutException {
- if (!(object instanceof View)) {
- return method.invoke(object, (Object[]) null);
- }
-
- final View view = (View) object;
- Callable<Object> callable = new Callable<Object>() {
- @Override
- public Object call() throws IllegalAccessException, InvocationTargetException {
- return method.invoke(view, (Object[]) null);
- }
- };
- FutureTask<Object> future = new FutureTask<Object>(callable);
- // Try to use the handler provided by the view
- Handler handler = view.getHandler();
- // Fall back on using the main thread
- if (handler == null) {
- handler = new Handler(android.os.Looper.getMainLooper());
- }
- handler.post(future);
- while (true) {
- try {
- return future.get(CAPTURE_TIMEOUT, java.util.concurrent.TimeUnit.MILLISECONDS);
- } catch (ExecutionException e) {
- Throwable t = e.getCause();
- if (t instanceof IllegalAccessException) {
- throw (IllegalAccessException)t;
- }
- if (t instanceof InvocationTargetException) {
- throw (InvocationTargetException)t;
- }
- throw new RuntimeException("Unexpected exception", t);
- } catch (InterruptedException e) {
- // Call get again
- } catch (CancellationException e) {
- throw new RuntimeException("Unexpected cancellation exception", e);
- }
- }
- }
-
private static String formatIntToHexString(int value) {
return "0x" + Integer.toHexString(value).toUpperCase();
}
- private static void exportMethods(Context context, Object view, BufferedWriter out,
+ private static void writeExportedProperties(Context context, Object view, BufferedWriter out,
Class<?> klass, String prefix) throws IOException {
-
- final Method[] methods = getExportedPropertyMethods(klass);
- int count = methods.length;
- for (int i = 0; i < count; i++) {
- final Method method = methods[i];
+ for (PropertyInfo<ExportedProperty, ?> info : getExportedProperties(klass)) {
//noinspection EmptyCatchBlock
+ Object value;
try {
- Object methodValue = callMethodOnAppropriateTheadBlocking(method, view);
- final Class<?> returnType = method.getReturnType();
- final ExportedProperty property = sAnnotations.get(method);
- String categoryPrefix =
- property.category().length() != 0 ? property.category() + ":" : "";
-
- if (returnType == int.class) {
- if (property.resolveId() && context != null) {
- final int id = (Integer) methodValue;
- methodValue = resolveId(context, id);
- } else {
- final FlagToString[] flagsMapping = property.flagMapping();
- if (flagsMapping.length > 0) {
- final int intValue = (Integer) methodValue;
- final String valuePrefix =
- categoryPrefix + prefix + method.getName() + '_';
- exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
- }
-
- final IntToString[] mapping = property.mapping();
- if (mapping.length > 0) {
- final int intValue = (Integer) methodValue;
- boolean mapped = false;
- int mappingCount = mapping.length;
- for (int j = 0; j < mappingCount; j++) {
- final IntToString mapper = mapping[j];
- if (mapper.from() == intValue) {
- methodValue = mapper.to();
- mapped = true;
- break;
- }
- }
-
- if (!mapped) {
- methodValue = intValue;
- }
- }
- }
- } else if (returnType == int[].class) {
- final int[] array = (int[]) methodValue;
- final String valuePrefix = categoryPrefix + prefix + method.getName() + '_';
- final String suffix = "()";
-
- exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
-
- continue;
- } else if (returnType == String[].class) {
- final String[] array = (String[]) methodValue;
- if (property.hasAdjacentMapping() && array != null) {
- for (int j = 0; j < array.length; j += 2) {
- if (array[j] != null) {
- writeEntry(out, categoryPrefix + prefix, array[j], "()",
- array[j + 1] == null ? "null" : array[j + 1]);
- }
-
- }
- }
-
- continue;
- } else if (!returnType.isPrimitive()) {
- if (property.deepExport()) {
- dumpViewProperties(context, methodValue, out, prefix + property.prefix());
- continue;
- }
- }
-
- writeEntry(out, categoryPrefix + prefix, method.getName(), "()", methodValue);
- } catch (IllegalAccessException e) {
- } catch (InvocationTargetException e) {
- } catch (TimeoutException e) {
+ value = info.invoke(view);
+ } catch (Exception e) {
+ // ignore
+ continue;
}
- }
- }
- private static void exportFields(Context context, Object view, BufferedWriter out,
- Class<?> klass, String prefix) throws IOException {
+ String categoryPrefix =
+ info.property.category().length() != 0 ? info.property.category() + ":" : "";
- final Field[] fields = getExportedPropertyFields(klass);
+ if (info.returnType == int.class || info.returnType == byte.class) {
+ if (info.property.resolveId() && context != null) {
+ final int id = (Integer) value;
+ value = resolveId(context, id);
- int count = fields.length;
- for (int i = 0; i < count; i++) {
- final Field field = fields[i];
-
- //noinspection EmptyCatchBlock
- try {
- Object fieldValue = null;
- final Class<?> type = field.getType();
- final ExportedProperty property = sAnnotations.get(field);
- String categoryPrefix =
- property.category().length() != 0 ? property.category() + ":" : "";
-
- if (type == int.class || type == byte.class) {
- if (property.resolveId() && context != null) {
- final int id = field.getInt(view);
- fieldValue = resolveId(context, id);
- } else {
- final FlagToString[] flagsMapping = property.flagMapping();
- if (flagsMapping.length > 0) {
- final int intValue = field.getInt(view);
- final String valuePrefix =
- categoryPrefix + prefix + field.getName() + '_';
- exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
- }
-
- final IntToString[] mapping = property.mapping();
- if (mapping.length > 0) {
- final int intValue = field.getInt(view);
- int mappingCount = mapping.length;
- for (int j = 0; j < mappingCount; j++) {
- final IntToString mapped = mapping[j];
- if (mapped.from() == intValue) {
- fieldValue = mapped.to();
- break;
- }
- }
-
- if (fieldValue == null) {
- fieldValue = intValue;
- }
- }
-
- if (property.formatToHexString()) {
- fieldValue = field.get(view);
- if (type == int.class) {
- fieldValue = formatIntToHexString((Integer) fieldValue);
- } else if (type == byte.class) {
- fieldValue = "0x"
- + HexEncoding.encodeToString((Byte) fieldValue, true);
- }
- }
+ } else if (info.property.formatToHexString()) {
+ if (info.returnType == int.class) {
+ value = formatIntToHexString((Integer) value);
+ } else if (info.returnType == byte.class) {
+ value = "0x"
+ + HexEncoding.encodeToString((Byte) value, true);
}
- } else if (type == int[].class) {
- final int[] array = (int[]) field.get(view);
- final String valuePrefix = categoryPrefix + prefix + field.getName() + '_';
- final String suffix = "";
-
- exportUnrolledArray(context, out, property, array, valuePrefix, suffix);
-
- continue;
- } else if (type == String[].class) {
- final String[] array = (String[]) field.get(view);
- if (property.hasAdjacentMapping() && array != null) {
- for (int j = 0; j < array.length; j += 2) {
- if (array[j] != null) {
- writeEntry(out, categoryPrefix + prefix, array[j], "",
- array[j + 1] == null ? "null" : array[j + 1]);
- }
- }
+ } else {
+ final ViewDebug.FlagToString[] flagsMapping = info.property.flagMapping();
+ if (flagsMapping.length > 0) {
+ final int intValue = (Integer) value;
+ final String valuePrefix =
+ categoryPrefix + prefix + info.name + '_';
+ exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix);
}
- continue;
- } else if (!type.isPrimitive()) {
- if (property.deepExport()) {
- dumpViewProperties(context, field.get(view), out, prefix +
- property.prefix());
- continue;
+ final ViewDebug.IntToString[] mapping = info.property.mapping();
+ if (mapping.length > 0) {
+ final int intValue = (Integer) value;
+ boolean mapped = false;
+ int mappingCount = mapping.length;
+ for (int j = 0; j < mappingCount; j++) {
+ final ViewDebug.IntToString mapper = mapping[j];
+ if (mapper.from() == intValue) {
+ value = mapper.to();
+ mapped = true;
+ break;
+ }
+ }
+
+ if (!mapped) {
+ value = intValue;
+ }
+ }
+ }
+ } else if (info.returnType == int[].class) {
+ final int[] array = (int[]) value;
+ final String valuePrefix = categoryPrefix + prefix + info.name + '_';
+ exportUnrolledArray(context, out, info.property, array, valuePrefix,
+ info.entrySuffix);
+
+ continue;
+ } else if (info.returnType == String[].class) {
+ final String[] array = (String[]) value;
+ if (info.property.hasAdjacentMapping() && array != null) {
+ for (int j = 0; j < array.length; j += 2) {
+ if (array[j] != null) {
+ writeEntry(out, categoryPrefix + prefix, array[j],
+ info.entrySuffix, array[j + 1] == null ? "null" : array[j + 1]);
+ }
}
}
- if (fieldValue == null) {
- fieldValue = field.get(view);
+ continue;
+ } else if (!info.returnType.isPrimitive()) {
+ if (info.property.deepExport()) {
+ dumpViewProperties(context, value, out, prefix + info.property.prefix());
+ continue;
}
-
- writeEntry(out, categoryPrefix + prefix, field.getName(), "", fieldValue);
- } catch (IllegalAccessException e) {
}
+
+ writeEntry(out, categoryPrefix + prefix, info.name, info.entrySuffix, value);
}
}
@@ -1721,91 +1670,40 @@
}
}
- private static Field[] capturedViewGetPropertyFields(Class<?> klass) {
- if (mCapturedViewFieldsForClasses == null) {
- mCapturedViewFieldsForClasses = new HashMap<Class<?>, Field[]>();
+ private static PropertyInfo<CapturedViewProperty, ?>[] getCapturedViewProperties(
+ Class<?> klass) {
+ if (sCapturedViewProperties == null) {
+ sCapturedViewProperties = new HashMap<>();
}
- final HashMap<Class<?>, Field[]> map = mCapturedViewFieldsForClasses;
+ final HashMap<Class<?>, PropertyInfo<CapturedViewProperty, ?>[]> map =
+ sCapturedViewProperties;
- Field[] fields = map.get(klass);
- if (fields != null) {
- return fields;
+ PropertyInfo<CapturedViewProperty, ?>[] infos = map.get(klass);
+ if (infos == null) {
+ infos = convertToPropertyInfos(klass.getMethods(), klass.getFields(),
+ CapturedViewProperty.class);
+ map.put(klass, infos);
}
-
- final ArrayList<Field> foundFields = new ArrayList<Field>();
- fields = klass.getFields();
-
- int count = fields.length;
- for (int i = 0; i < count; i++) {
- final Field field = fields[i];
- if (field.isAnnotationPresent(CapturedViewProperty.class)) {
- field.setAccessible(true);
- foundFields.add(field);
- }
- }
-
- fields = foundFields.toArray(new Field[foundFields.size()]);
- map.put(klass, fields);
-
- return fields;
+ return infos;
}
- private static Method[] capturedViewGetPropertyMethods(Class<?> klass) {
- if (mCapturedViewMethodsForClasses == null) {
- mCapturedViewMethodsForClasses = new HashMap<Class<?>, Method[]>();
- }
- final HashMap<Class<?>, Method[]> map = mCapturedViewMethodsForClasses;
-
- Method[] methods = map.get(klass);
- if (methods != null) {
- return methods;
- }
-
- final ArrayList<Method> foundMethods = new ArrayList<Method>();
- methods = klass.getMethods();
-
- int count = methods.length;
- for (int i = 0; i < count; i++) {
- final Method method = methods[i];
- if (method.getParameterTypes().length == 0 &&
- method.isAnnotationPresent(CapturedViewProperty.class) &&
- method.getReturnType() != Void.class) {
- method.setAccessible(true);
- foundMethods.add(method);
- }
- }
-
- methods = foundMethods.toArray(new Method[foundMethods.size()]);
- map.put(klass, methods);
-
- return methods;
- }
-
- private static String capturedViewExportMethods(Object obj, Class<?> klass,
- String prefix) {
-
+ private static String exportCapturedViewProperties(Object obj, Class<?> klass, String prefix) {
if (obj == null) {
return "null";
}
StringBuilder sb = new StringBuilder();
- final Method[] methods = capturedViewGetPropertyMethods(klass);
- int count = methods.length;
- for (int i = 0; i < count; i++) {
- final Method method = methods[i];
+ for (PropertyInfo<CapturedViewProperty, ?> pi : getCapturedViewProperties(klass)) {
try {
- Object methodValue = method.invoke(obj, (Object[]) null);
- final Class<?> returnType = method.getReturnType();
+ Object methodValue = pi.invoke(obj);
- CapturedViewProperty property = method.getAnnotation(CapturedViewProperty.class);
- if (property.retrieveReturn()) {
+ if (pi.property.retrieveReturn()) {
//we are interested in the second level data only
- sb.append(capturedViewExportMethods(methodValue, returnType, method.getName() + "#"));
+ sb.append(exportCapturedViewProperties(methodValue, pi.returnType,
+ pi.name + "#"));
} else {
- sb.append(prefix);
- sb.append(method.getName());
- sb.append("()=");
+ sb.append(prefix).append(pi.name).append(pi.entrySuffix).append("=");
if (methodValue != null) {
final String value = methodValue.toString().replace("\n", "\\n");
@@ -1813,47 +1711,10 @@
} else {
sb.append("null");
}
- sb.append("; ");
+ sb.append(pi.valueSuffix).append(" ");
}
- } catch (IllegalAccessException e) {
- //Exception IllegalAccess, it is OK here
- //we simply ignore this method
- } catch (InvocationTargetException e) {
- //Exception InvocationTarget, it is OK here
- //we simply ignore this method
- }
- }
- return sb.toString();
- }
-
- private static String capturedViewExportFields(Object obj, Class<?> klass, String prefix) {
- if (obj == null) {
- return "null";
- }
-
- StringBuilder sb = new StringBuilder();
- final Field[] fields = capturedViewGetPropertyFields(klass);
-
- int count = fields.length;
- for (int i = 0; i < count; i++) {
- final Field field = fields[i];
- try {
- Object fieldValue = field.get(obj);
-
- sb.append(prefix);
- sb.append(field.getName());
- sb.append("=");
-
- if (fieldValue != null) {
- final String value = fieldValue.toString().replace("\n", "\\n");
- sb.append(value);
- } else {
- sb.append("null");
- }
- sb.append(' ');
- } catch (IllegalAccessException e) {
- //Exception IllegalAccess, it is OK here
- //we simply ignore this field
+ } catch (Exception e) {
+ //It is OK here, we simply ignore this property
}
}
return sb.toString();
@@ -1869,8 +1730,7 @@
public static void dumpCapturedView(String tag, Object view) {
Class<?> klass = view.getClass();
StringBuilder sb = new StringBuilder(klass.getName() + ": ");
- sb.append(capturedViewExportFields(view, klass, ""));
- sb.append(capturedViewExportMethods(view, klass, ""));
+ sb.append(exportCapturedViewProperties(view, klass, ""));
Log.d(tag, sb.toString());
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index fedd6fb..20dc234 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -45,6 +45,7 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.BLASTBufferQueue;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.FrameInfo;
@@ -170,6 +171,8 @@
*/
private static final boolean MT_RENDERER_AVAILABLE = true;
+ private static final boolean USE_BLAST_BUFFERQUEUE = false;
+
/**
* If set to 2, the view system will switch from using rectangles retrieved from window to
* dispatch to the view hierarchy to using {@link InsetsController}, that derives the insets
@@ -475,6 +478,9 @@
@UnsupportedAppUsage
public final Surface mSurface = new Surface();
private final SurfaceControl mSurfaceControl = new SurfaceControl();
+ private SurfaceControl mBlastSurfaceControl;
+
+ private BLASTBufferQueue mBlastBufferQueue;
/**
* Transaction object that can be used to synchronize child SurfaceControl changes with
@@ -1282,6 +1288,11 @@
}
mWindowAttributes.privateFlags |= compatibleWindowFlag;
+ if (USE_BLAST_BUFFERQUEUE) {
+ mWindowAttributes.privateFlags =
+ WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
+ }
+
if (mWindowAttributes.preservePreviousSurfaceInsets) {
// Restore old surface insets.
mWindowAttributes.surfaceInsets.set(
@@ -1629,6 +1640,29 @@
return mBoundsLayer;
}
+ Surface getOrCreateBLASTSurface(int width, int height) {
+ if (mSurfaceControl == null || !mSurfaceControl.isValid()) {
+ return null;
+ }
+ if (mBlastSurfaceControl == null) {
+ mBlastSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
+ .setParent(mSurfaceControl)
+ .setName("BLAST")
+ .setBLASTLayer()
+ .build();
+ mBlastBufferQueue = new BLASTBufferQueue(
+ mBlastSurfaceControl, width, height);
+
+ }
+ mBlastBufferQueue.update(mSurfaceControl, width, height);
+
+ mTransaction.show(mBlastSurfaceControl)
+ .reparent(mBlastSurfaceControl, mSurfaceControl)
+ .apply();
+
+ return mBlastBufferQueue.getSurface();
+ }
+
private void setBoundsLayerCrop() {
// mWinFrame is already adjusted for surface insets. So offset it and use it as
// the cropping bounds.
@@ -1658,6 +1692,13 @@
}
mSurface.release();
mSurfaceControl.release();
+
+ if (mBlastBufferQueue != null) {
+ mTransaction.remove(mBlastSurfaceControl).apply();
+ mBlastSurfaceControl = null;
+ // We should probably add an explicit dispose.
+ mBlastBufferQueue = null;
+ }
}
/**
@@ -2413,10 +2454,9 @@
// will be transparent
if (mAttachInfo.mThreadedRenderer != null) {
try {
- hwInitialized = mAttachInfo.mThreadedRenderer.initialize(
- mSurface);
+ hwInitialized = mAttachInfo.mThreadedRenderer.initialize(mSurface);
if (hwInitialized && (host.mPrivateFlags
- & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) {
+ & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) {
// Don't pre-allocate if transparent regions
// are requested as they may not be needed
mAttachInfo.mThreadedRenderer.allocateBuffers();
@@ -4530,6 +4570,7 @@
private static final int MSG_SYSTEM_GESTURE_EXCLUSION_CHANGED = 32;
private static final int MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED = 33;
private static final int MSG_SHOW_INSETS = 34;
+ private static final int MSG_HIDE_INSETS = 35;
final class ViewRootHandler extends Handler {
@@ -4596,6 +4637,8 @@
return "MSG_LOCATION_IN_PARENT_DISPLAY_CHANGED";
case MSG_SHOW_INSETS:
return "MSG_SHOW_INSETS";
+ case MSG_HIDE_INSETS:
+ return "MSG_HIDE_INSETS";
}
return super.getMessageName(message);
}
@@ -4714,6 +4757,10 @@
mInsetsController.show(msg.arg1, msg.arg2 == 1);
break;
}
+ case MSG_HIDE_INSETS: {
+ mInsetsController.hide(msg.arg1, msg.arg2 == 1);
+ break;
+ }
case MSG_WINDOW_MOVED:
if (mAdded) {
final int w = mWinFrame.width();
@@ -7139,7 +7186,13 @@
mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
mPendingMergedConfiguration, mSurfaceControl, mTempInsets);
if (mSurfaceControl.isValid()) {
- mSurface.copyFrom(mSurfaceControl);
+ if (USE_BLAST_BUFFERQUEUE == false) {
+ mSurface.copyFrom(mSurfaceControl);
+ } else {
+ mSurface.transferFrom(getOrCreateBLASTSurface(
+ (int) (mView.getMeasuredWidth() * appScale + 0.5f),
+ (int) (mView.getMeasuredHeight() * appScale + 0.5f)));
+ }
} else {
destroySurface();
}
@@ -7297,26 +7350,42 @@
}
}
- public void dumpGfxInfo(int[] info) {
- info[0] = info[1] = 0;
- if (mView != null) {
- getGfxInfo(mView, info);
+ static final class GfxInfo {
+ public int viewCount;
+ public long renderNodeMemoryUsage;
+ public long renderNodeMemoryAllocated;
+
+ void add(GfxInfo other) {
+ viewCount += other.viewCount;
+ renderNodeMemoryUsage += other.renderNodeMemoryUsage;
+ renderNodeMemoryAllocated += other.renderNodeMemoryAllocated;
}
}
- private static void getGfxInfo(View view, int[] info) {
- RenderNode renderNode = view.mRenderNode;
- info[0]++;
- if (renderNode != null) {
- info[1] += (int) renderNode.computeApproximateMemoryUsage();
+ GfxInfo getGfxInfo() {
+ GfxInfo info = new GfxInfo();
+ if (mView != null) {
+ appendGfxInfo(mView, info);
}
+ return info;
+ }
+ private static void computeRenderNodeUsage(RenderNode node, GfxInfo info) {
+ if (node == null) return;
+ info.renderNodeMemoryUsage += node.computeApproximateMemoryUsage();
+ info.renderNodeMemoryAllocated += node.computeApproximateMemoryAllocated();
+ }
+
+ private static void appendGfxInfo(View view, GfxInfo info) {
+ info.viewCount++;
+ computeRenderNodeUsage(view.mRenderNode, info);
+ computeRenderNodeUsage(view.mBackgroundRenderNode, info);
if (view instanceof ViewGroup) {
ViewGroup group = (ViewGroup) view;
int count = group.getChildCount();
for (int i = 0; i < count; i++) {
- getGfxInfo(group.getChildAt(i), info);
+ appendGfxInfo(group.getChildAt(i), info);
}
}
}
@@ -7497,6 +7566,10 @@
mHandler.obtainMessage(MSG_SHOW_INSETS, types, fromIme ? 1 : 0).sendToTarget();
}
+ private void hideInsets(@InsetType int types, boolean fromIme) {
+ mHandler.obtainMessage(MSG_HIDE_INSETS, types, fromIme ? 1 : 0).sendToTarget();
+ }
+
public void dispatchMoved(int newX, int newY) {
if (DEBUG_LAYOUT) Log.v(mTag, "Window moved " + this + ": newX=" + newX + " newY=" + newY);
if (mTranslator != null) {
@@ -8620,6 +8693,14 @@
}
@Override
+ public void hideInsets(@InsetType int types, boolean fromIme) {
+ final ViewRootImpl viewAncestor = mViewAncestor.get();
+ if (viewAncestor != null) {
+ viewAncestor.hideInsets(types, fromIme);
+ }
+ }
+
+ @Override
public void moved(int newX, int newY) {
final ViewRootImpl viewAncestor = mViewAncestor.get();
if (viewAncestor != null) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 001ab66..db76bb6 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -197,12 +197,6 @@
int TRANSIT_TASK_OPEN_BEHIND = 16;
/**
- * A window in a task is being animated in-place.
- * @hide
- */
- int TRANSIT_TASK_IN_PLACE = 17;
-
- /**
* An activity is being relaunched (e.g. due to configuration change).
* @hide
*/
@@ -286,7 +280,6 @@
TRANSIT_WALLPAPER_INTRA_OPEN,
TRANSIT_WALLPAPER_INTRA_CLOSE,
TRANSIT_TASK_OPEN_BEHIND,
- TRANSIT_TASK_IN_PLACE,
TRANSIT_ACTIVITY_RELAUNCH,
TRANSIT_DOCK_TASK_FROM_RECENTS,
TRANSIT_KEYGUARD_GOING_AWAY,
@@ -1687,8 +1680,9 @@
* to determine its default behavior.
*
* {@hide} */
- @UnsupportedAppUsage
- public static final int PRIVATE_FLAG_SHOW_FOR_ALL_USERS = 0x00000010;
+ @SystemApi
+ @RequiresPermission(permission.INTERNAL_SYSTEM_WINDOW)
+ public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 0x00000010;
/**
* Never animate position changes of the window.
@@ -1834,6 +1828,13 @@
public static final int PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC = 0x01000000;
/**
+ * Flag to request creation of a BLAST (Buffer as LayerState) Layer.
+ * If not specified the client will receive a BufferQueue layer.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_USE_BLAST = 0x02000000;
+
+ /**
* An internal annotation for flags that can be specified to {@link #softInputMode}.
*
* @hide
@@ -1842,6 +1843,7 @@
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "SYSTEM_FLAG_" }, value = {
SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+ SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
})
public @interface SystemFlags {}
@@ -1863,8 +1865,8 @@
equals = PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
name = "WANTS_OFFSET_NOTIFICATIONS"),
@ViewDebug.FlagToString(
- mask = PRIVATE_FLAG_SHOW_FOR_ALL_USERS,
- equals = PRIVATE_FLAG_SHOW_FOR_ALL_USERS,
+ mask = SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
+ equals = SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
name = "SHOW_FOR_ALL_USERS"),
@ViewDebug.FlagToString(
mask = PRIVATE_FLAG_NO_MOVE_ANIMATION,
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 379acbe..55b2a2a 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -604,26 +604,24 @@
pw.println("\nView hierarchy:\n");
- int viewsCount = 0;
- int displayListsSize = 0;
- int[] info = new int[2];
+ ViewRootImpl.GfxInfo totals = new ViewRootImpl.GfxInfo();
for (int i = 0; i < count; i++) {
ViewRootImpl root = mRoots.get(i);
- root.dumpGfxInfo(info);
+ ViewRootImpl.GfxInfo info = root.getGfxInfo();
+ totals.add(info);
String name = getWindowName(root);
- pw.printf(" %s\n %d views, %.2f kB of display lists",
- name, info[0], info[1] / 1024.0f);
+ pw.printf(" %s\n %d views, %.2f kB of render nodes",
+ name, info.viewCount, info.renderNodeMemoryUsage / 1024.f);
pw.printf("\n\n");
-
- viewsCount += info[0];
- displayListsSize += info[1];
}
- pw.printf("\nTotal ViewRootImpl: %d\n", count);
- pw.printf("Total Views: %d\n", viewsCount);
- pw.printf("Total DisplayList: %.2f kB\n\n", displayListsSize / 1024.0f);
+ pw.printf("\nTotal %-15s: %d\n", "ViewRootImpl", count);
+ pw.printf("Total %-15s: %d\n", "attached Views", totals.viewCount);
+ pw.printf("Total %-15s: %.2f kB (used) / %.2f kB (capacity)\n\n", "RenderNode",
+ totals.renderNodeMemoryUsage / 1024.0f,
+ totals.renderNodeMemoryAllocated / 1024.0f);
}
} finally {
pw.flush();
diff --git a/core/java/android/view/inspector/OWNERS b/core/java/android/view/inspector/OWNERS
index 4554fdc..c2827cc 100644
--- a/core/java/android/view/inspector/OWNERS
+++ b/core/java/android/view/inspector/OWNERS
@@ -1,3 +1,3 @@
alanv@google.com
aurimas@google.com
-emberr@google.com
+emberrose@google.com
diff --git a/core/java/android/webkit/FindAddress.java b/core/java/android/webkit/FindAddress.java
index 9183227..b146e3f6 100644
--- a/core/java/android/webkit/FindAddress.java
+++ b/core/java/android/webkit/FindAddress.java
@@ -154,7 +154,7 @@
// A house number component is "one" or a number, optionally
// followed by a single alphabetic character, or
- private static final String HOUSE_COMPONENT = "(?:one|\\d+([a-z](?=[^a-z]|$)|st|nd|rd|th)?)";
+ private static final String HOUSE_COMPONENT = "(?:one|[0-9]+([a-z](?=[^a-z]|$)|st|nd|rd|th)?)";
// House numbers are a repetition of |HOUSE_COMPONENT|, separated by -, and followed by
// a delimiter character.
@@ -253,10 +253,10 @@
Pattern.CASE_INSENSITIVE);
private static final Pattern sSuffixedNumberRe =
- Pattern.compile("(\\d+)(st|nd|rd|th)", Pattern.CASE_INSENSITIVE);
+ Pattern.compile("([0-9]+)(st|nd|rd|th)", Pattern.CASE_INSENSITIVE);
private static final Pattern sZipCodeRe =
- Pattern.compile("(?:\\d{5}(?:-\\d{4})?)" + WORD_END, Pattern.CASE_INSENSITIVE);
+ Pattern.compile("(?:[0-9]{5}(?:-[0-9]{4})?)" + WORD_END, Pattern.CASE_INSENSITIVE);
private static boolean checkHouseNumber(String houseNumber) {
// Make sure that there are at most 5 digits.
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 925a589..0b15cd0 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -246,7 +246,7 @@
Toast warningToast = mFrameworkObjectProvider.makeToastFromText(
mContext, toastMessage, Toast.LENGTH_LONG);
warningToast.getWindowParams().privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
warningToast.show();
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 407a85f..068056f 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -388,21 +388,24 @@
mResolverDrawerLayout.setPadding(mSystemWindowInsets.left, mSystemWindowInsets.top,
mSystemWindowInsets.right, 0);
- View emptyView = findViewById(R.id.empty);
- if (emptyView != null) {
- emptyView.setPadding(0, 0, 0, mSystemWindowInsets.bottom
- + getResources().getDimensionPixelSize(
- R.dimen.chooser_edge_margin_normal) * 2);
- }
-
- if (mFooterSpacer == null) {
- mFooterSpacer = new Space(getApplicationContext());
+ // Need extra padding so the list can fully scroll up
+ if (useLayoutWithDefault()) {
+ if (mFooterSpacer == null) {
+ mFooterSpacer = new Space(getApplicationContext());
+ } else {
+ ((ListView) mAdapterView).removeFooterView(mFooterSpacer);
+ }
+ mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
+ mSystemWindowInsets.bottom));
+ ((ListView) mAdapterView).addFooterView(mFooterSpacer);
} else {
- ((ListView) mAdapterView).removeFooterView(mFooterSpacer);
+ View emptyView = findViewById(R.id.empty);
+ if (emptyView != null) {
+ emptyView.setPadding(0, 0, 0, mSystemWindowInsets.bottom
+ + getResources().getDimensionPixelSize(
+ R.dimen.chooser_edge_margin_normal) * 2);
+ }
}
- mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
- mSystemWindowInsets.bottom));
- ((ListView) mAdapterView).addFooterView(mFooterSpacer);
resetButtonBar();
@@ -561,7 +564,7 @@
intent.getData().getHost(),
mAdapter.getFilteredItem().getDisplayLabel());
} else if (mAdapter.areAllTargetsBrowsers()) {
- dialogTitle = getString(ActionTitle.BROWSABLE_TITLE_RES);
+ dialogTitle = getString(ActionTitle.BROWSABLE_TITLE_RES);
} else {
dialogTitle = getString(ActionTitle.BROWSABLE_HOST_TITLE_RES,
intent.getData().getHost());
@@ -1304,6 +1307,7 @@
// In case this method is called again (due to activity recreation), avoid adding a new
// header if one is already present.
if (useHeader && listView != null && listView.getHeaderViewsCount() == 0) {
+ listView.setHeaderDividersEnabled(true);
listView.addHeaderView(LayoutInflater.from(this).inflate(
R.layout.resolver_different_item_header, listView, false));
}
@@ -1346,11 +1350,13 @@
final ViewGroup buttonLayout = findViewById(R.id.button_bar);
if (buttonLayout != null) {
buttonLayout.setVisibility(View.VISIBLE);
- int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
- buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
- buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
- R.dimen.resolver_button_bar_spacing) + inset);
+ if (!useLayoutWithDefault()) {
+ int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
+ buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
+ buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
+ R.dimen.resolver_button_bar_spacing) + inset);
+ }
mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
@@ -2057,7 +2063,9 @@
CharSequence subLabel = info.getExtendedInfo();
if (TextUtils.equals(label, subLabel)) subLabel = null;
- if (!TextUtils.equals(holder.text2.getText(), subLabel)) {
+ if (!TextUtils.equals(holder.text2.getText(), subLabel)
+ && !TextUtils.isEmpty(subLabel)) {
+ holder.text2.setVisibility(View.VISIBLE);
holder.text2.setText(subLabel);
}
diff --git a/core/java/com/android/internal/app/procstats/AssociationState.java b/core/java/com/android/internal/app/procstats/AssociationState.java
index 2df5158..1d4239f 100644
--- a/core/java/com/android/internal/app/procstats/AssociationState.java
+++ b/core/java/com/android/internal/app/procstats/AssociationState.java
@@ -16,7 +16,6 @@
package com.android.internal.app.procstats;
-
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.SystemClock;
@@ -24,23 +23,37 @@
import android.service.procstats.PackageAssociationProcessStatsProto;
import android.service.procstats.PackageAssociationSourceProcessStatsProto;
import android.util.ArrayMap;
+import android.util.Pair;
import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.Objects;
public final class AssociationState {
private static final String TAG = "ProcessStats";
private static final boolean DEBUG = false;
+ private static final boolean VALIDATE_TIMES = false;
+
private final ProcessStats mProcessStats;
private final ProcessStats.PackageState mPackageState;
private final String mProcessName;
private final String mName;
+ private int mTotalNesting;
+ private long mTotalStartUptime;
+ private int mTotalCount;
+ private long mTotalDuration;
+ private int mTotalActiveNesting;
+ private long mTotalActiveStartUptime;
+ private int mTotalActiveCount;
+ private long mTotalActiveDuration;
+
public final class SourceState {
final SourceKey mKey;
int mProcStateSeq = -1;
@@ -55,7 +68,7 @@
int mActiveProcState = ProcessStats.STATE_NOTHING;
long mActiveStartUptime;
long mActiveDuration;
- DurationsTable mDurations;
+ DurationsTable mActiveDurations;
SourceState(SourceKey key) {
mKey = key;
@@ -97,9 +110,9 @@
public void stop() {
mNesting--;
if (mNesting == 0) {
- mDuration += SystemClock.uptimeMillis() - mStartUptime;
- mNumActive--;
- stopTracking(SystemClock.uptimeMillis());
+ final long now = SystemClock.uptimeMillis();
+ mDuration += now - mStartUptime;
+ stopTracking(now);
}
}
@@ -108,20 +121,25 @@
if (mActiveStartUptime == 0) {
mActiveStartUptime = now;
mActiveCount++;
+ AssociationState.this.mTotalActiveNesting++;
+ if (AssociationState.this.mTotalActiveNesting == 1) {
+ AssociationState.this.mTotalActiveCount++;
+ AssociationState.this.mTotalActiveStartUptime = now;
+ }
}
if (mActiveProcState != mProcState) {
if (mActiveProcState != ProcessStats.STATE_NOTHING) {
// Currently active proc state changed, need to store the duration
// so far and switch tracking to the new proc state.
- final long duration = mActiveDuration + now - mActiveStartUptime;
- if (duration != 0) {
- if (mDurations == null) {
+ final long addedDuration = mActiveDuration + now - mActiveStartUptime;
+ mActiveStartUptime = now;
+ if (addedDuration != 0) {
+ if (mActiveDurations == null) {
makeDurations();
}
- mDurations.addDuration(mActiveProcState, duration);
+ mActiveDurations.addDuration(mActiveProcState, addedDuration);
mActiveDuration = 0;
}
- mActiveStartUptime = now;
}
mActiveProcState = mProcState;
}
@@ -135,21 +153,44 @@
if (!mInTrackingList) {
Slog.wtf(TAG, "stopActive while not tracking: " + this);
}
- final long duration = mActiveDuration + now - mActiveStartUptime;
- if (mDurations != null) {
- mDurations.addDuration(mActiveProcState, duration);
- } else {
- mActiveDuration = duration;
- }
+ final long addedDuration = now - mActiveStartUptime;
mActiveStartUptime = 0;
+ if (mActiveDurations != null) {
+ mActiveDurations.addDuration(mActiveProcState, addedDuration);
+ } else {
+ mActiveDuration += addedDuration;
+ }
+ AssociationState.this.mTotalActiveNesting--;
+ if (AssociationState.this.mTotalActiveNesting == 0) {
+ AssociationState.this.mTotalActiveDuration += now
+ - AssociationState.this.mTotalActiveStartUptime;
+ AssociationState.this.mTotalActiveStartUptime = 0;
+ if (VALIDATE_TIMES) {
+ if (mActiveDuration > AssociationState.this.mTotalActiveDuration) {
+ RuntimeException ex = new RuntimeException();
+ ex.fillInStackTrace();
+ Slog.w(TAG, "Source act duration " + mActiveDurations
+ + " exceeds total " + AssociationState.this.mTotalActiveDuration
+ + " in procstate " + mActiveProcState + " in source "
+ + mKey.mProcess + " to assoc "
+ + AssociationState.this.mName, ex);
+ }
+
+ }
+ }
}
}
void makeDurations() {
- mDurations = new DurationsTable(mProcessStats.mTableData);
+ mActiveDurations = new DurationsTable(mProcessStats.mTableData);
}
void stopTracking(long now) {
+ AssociationState.this.mTotalNesting--;
+ if (AssociationState.this.mTotalNesting == 0) {
+ AssociationState.this.mTotalDuration += now
+ - AssociationState.this.mTotalStartUptime;
+ }
stopActive(now);
if (mInTrackingList) {
mInTrackingList = false;
@@ -181,7 +222,17 @@
}
}
- private final static class SourceKey {
+ public final class SourceDumpContainer {
+ public final SourceState mState;
+ public long mTotalTime;
+ public long mActiveTime;
+
+ public SourceDumpContainer(SourceState state) {
+ mState = state;
+ }
+ }
+
+ public static final class SourceKey {
/**
* UID, consider this final. Not final just to avoid a temporary object during lookup.
*/
@@ -239,12 +290,10 @@
*/
private final ArrayMap<SourceKey, SourceState> mSources = new ArrayMap<>();
- private final SourceKey mTmpSourceKey = new SourceKey(0, null, null);
+ private static final SourceKey sTmpSourceKey = new SourceKey(0, null, null);
private ProcessState mProc;
- private int mNumActive;
-
public AssociationState(ProcessStats processStats, ProcessStats.PackageState packageState,
String name, String processName, ProcessState proc) {
mProcessStats = processStats;
@@ -278,11 +327,24 @@
mProc = proc;
}
+ public long getTotalDuration(long now) {
+ return mTotalDuration
+ + (mTotalNesting > 0 ? (now - mTotalStartUptime) : 0);
+ }
+
+ public long getActiveDuration(long now) {
+ return mTotalActiveDuration
+ + (mTotalActiveNesting > 0 ? (now - mTotalActiveStartUptime) : 0);
+ }
+
public SourceState startSource(int uid, String processName, String packageName) {
- mTmpSourceKey.mUid = uid;
- mTmpSourceKey.mProcess = processName;
- mTmpSourceKey.mPackage = packageName;
- SourceState src = mSources.get(mTmpSourceKey);
+ SourceState src;
+ synchronized (sTmpSourceKey) {
+ sTmpSourceKey.mUid = uid;
+ sTmpSourceKey.mProcess = processName;
+ sTmpSourceKey.mPackage = packageName;
+ src = mSources.get(sTmpSourceKey);
+ }
if (src == null) {
SourceKey key = new SourceKey(uid, processName, packageName);
src = new SourceState(key);
@@ -290,43 +352,88 @@
}
src.mNesting++;
if (src.mNesting == 1) {
+ final long now = SystemClock.uptimeMillis();
src.mCount++;
- src.mStartUptime = SystemClock.uptimeMillis();
- mNumActive++;
+ src.mStartUptime = now;
+ mTotalNesting++;
+ if (mTotalNesting == 1) {
+ mTotalCount++;
+ mTotalStartUptime = now;
+ }
}
return src;
}
public void add(AssociationState other) {
+ mTotalCount += other.mTotalCount;
+ final long origDuration = mTotalDuration;
+ mTotalDuration += other.mTotalDuration;
+ mTotalActiveCount += other.mTotalActiveCount;
+ mTotalActiveDuration += other.mTotalActiveDuration;
for (int isrc = other.mSources.size() - 1; isrc >= 0; isrc--) {
final SourceKey key = other.mSources.keyAt(isrc);
final SourceState otherSrc = other.mSources.valueAt(isrc);
SourceState mySrc = mSources.get(key);
+ boolean newSrc = false;
if (mySrc == null) {
mySrc = new SourceState(key);
mSources.put(key, mySrc);
+ newSrc = true;
+ }
+ if (VALIDATE_TIMES) {
+ Slog.w(TAG, "Adding tot duration " + mySrc.mDuration + "+"
+ + otherSrc.mDuration
+ + (newSrc ? " (new)" : " (old)") + " (total "
+ + origDuration + "+" + other.mTotalDuration + ") in source "
+ + mySrc.mKey.mProcess + " to assoc " + mName);
+ if ((mySrc.mDuration + otherSrc.mDuration) > mTotalDuration) {
+ RuntimeException ex = new RuntimeException();
+ ex.fillInStackTrace();
+ Slog.w(TAG, "Source tot duration " + mySrc.mDuration + "+"
+ + otherSrc.mDuration
+ + (newSrc ? " (new)" : " (old)") + " exceeds total "
+ + origDuration + "+" + other.mTotalDuration + " in source "
+ + mySrc.mKey.mProcess + " to assoc " + mName, ex);
+ }
+ if (mySrc.mActiveDurations == null && otherSrc.mActiveDurations == null) {
+ Slog.w(TAG, "Adding act duration " + mySrc.mActiveDuration
+ + "+" + otherSrc.mActiveDuration
+ + (newSrc ? " (new)" : " (old)") + " (total "
+ + origDuration + "+" + other.mTotalDuration + ") in source "
+ + mySrc.mKey.mProcess + " to assoc " + mName);
+ if ((mySrc.mActiveDuration + otherSrc.mActiveDuration) > mTotalDuration) {
+ RuntimeException ex = new RuntimeException();
+ ex.fillInStackTrace();
+ Slog.w(TAG, "Source act duration " + mySrc.mActiveDuration + "+"
+ + otherSrc.mActiveDuration
+ + (newSrc ? " (new)" : " (old)") + " exceeds total "
+ + origDuration + "+" + other.mTotalDuration + " in source "
+ + mySrc.mKey.mProcess + " to assoc " + mName, ex);
+ }
+ }
}
mySrc.mCount += otherSrc.mCount;
mySrc.mDuration += otherSrc.mDuration;
mySrc.mActiveCount += otherSrc.mActiveCount;
- if (otherSrc.mActiveDuration != 0 || otherSrc.mDurations != null) {
+ if (otherSrc.mActiveDuration != 0 || otherSrc.mActiveDurations != null) {
// Only need to do anything if the other one has some duration data.
- if (mySrc.mDurations != null) {
+ if (mySrc.mActiveDurations != null) {
// If the target already has multiple durations, just add in whatever
// we have in the other.
- if (otherSrc.mDurations != null) {
- mySrc.mDurations.addDurations(otherSrc.mDurations);
+ if (otherSrc.mActiveDurations != null) {
+ mySrc.mActiveDurations.addDurations(otherSrc.mActiveDurations);
} else {
- mySrc.mDurations.addDuration(otherSrc.mActiveProcState,
+ mySrc.mActiveDurations.addDuration(otherSrc.mActiveProcState,
otherSrc.mActiveDuration);
}
- } else if (otherSrc.mDurations != null) {
+ } else if (otherSrc.mActiveDurations != null) {
// The other one has multiple durations, but we don't. Expand to
// multiple durations and copy over.
mySrc.makeDurations();
- mySrc.mDurations.addDurations(otherSrc.mDurations);
+ mySrc.mActiveDurations.addDurations(otherSrc.mActiveDurations);
if (mySrc.mActiveDuration != 0) {
- mySrc.mDurations.addDuration(mySrc.mActiveProcState, mySrc.mActiveDuration);
+ mySrc.mActiveDurations.addDuration(mySrc.mActiveProcState,
+ mySrc.mActiveDuration);
mySrc.mActiveDuration = 0;
mySrc.mActiveProcState = ProcessStats.STATE_NOTHING;
}
@@ -334,13 +441,14 @@
// Both have a single inline duration... we can either add them together,
// or need to expand to multiple durations.
if (mySrc.mActiveProcState == otherSrc.mActiveProcState) {
- mySrc.mDuration += otherSrc.mDuration;
+ mySrc.mActiveDuration += otherSrc.mActiveDuration;
} else {
// The two have durations with different proc states, need to turn
// in to multiple durations.
mySrc.makeDurations();
- mySrc.mDurations.addDuration(mySrc.mActiveProcState, mySrc.mActiveDuration);
- mySrc.mDurations.addDuration(otherSrc.mActiveProcState,
+ mySrc.mActiveDurations.addDuration(mySrc.mActiveProcState,
+ mySrc.mActiveDuration);
+ mySrc.mActiveDurations.addDuration(otherSrc.mActiveProcState,
otherSrc.mActiveDuration);
mySrc.mActiveDuration = 0;
mySrc.mActiveProcState = ProcessStats.STATE_NOTHING;
@@ -355,12 +463,13 @@
}
public boolean isInUse() {
- return mNumActive > 0;
+ return mTotalNesting > 0;
}
public void resetSafely(long now) {
if (!isInUse()) {
mSources.clear();
+ mTotalCount = mTotalActiveCount = 0;
} else {
// We have some active sources... clear out everything but those.
for (int isrc = mSources.size() - 1; isrc >= 0; isrc--) {
@@ -376,15 +485,28 @@
src.mActiveCount = 0;
}
src.mActiveDuration = 0;
- src.mDurations = null;
+ src.mActiveDurations = null;
} else {
mSources.removeAt(isrc);
}
}
+ mTotalCount = 1;
+ mTotalStartUptime = now;
+ if (mTotalActiveNesting > 0) {
+ mTotalActiveCount = 1;
+ mTotalActiveStartUptime = now;
+ } else {
+ mTotalActiveCount = 0;
+ }
}
+ mTotalDuration = mTotalActiveDuration = 0;
}
public void writeToParcel(ProcessStats stats, Parcel out, long nowUptime) {
+ out.writeInt(mTotalCount);
+ out.writeLong(mTotalDuration);
+ out.writeInt(mTotalActiveCount);
+ out.writeLong(mTotalActiveDuration);
final int NSRC = mSources.size();
out.writeInt(NSRC);
for (int isrc = 0; isrc < NSRC; isrc++) {
@@ -396,9 +518,9 @@
out.writeInt(src.mCount);
out.writeLong(src.mDuration);
out.writeInt(src.mActiveCount);
- if (src.mDurations != null) {
+ if (src.mActiveDurations != null) {
out.writeInt(1);
- src.mDurations.writeToParcel(out);
+ src.mActiveDurations.writeToParcel(out);
} else {
out.writeInt(0);
out.writeInt(src.mActiveProcState);
@@ -412,6 +534,10 @@
* caused it to fail.
*/
public String readFromParcel(ProcessStats stats, Parcel in, int parcelVersion) {
+ mTotalCount = in.readInt();
+ mTotalDuration = in.readLong();
+ mTotalActiveCount = in.readInt();
+ mTotalActiveDuration = in.readLong();
final int NSRC = in.readInt();
if (NSRC < 0 || NSRC > 100000) {
return "Association with bad src count: " + NSRC;
@@ -427,13 +553,29 @@
src.mActiveCount = in.readInt();
if (in.readInt() != 0) {
src.makeDurations();
- if (!src.mDurations.readFromParcel(in)) {
+ if (!src.mActiveDurations.readFromParcel(in)) {
return "Duration table corrupt: " + key + " <- " + src;
}
} else {
src.mActiveProcState = in.readInt();
src.mActiveDuration = in.readLong();
}
+ if (VALIDATE_TIMES) {
+ if (src.mDuration > mTotalDuration) {
+ RuntimeException ex = new RuntimeException();
+ ex.fillInStackTrace();
+ Slog.w(TAG, "Reading tot duration " + src.mDuration
+ + " exceeds total " + mTotalDuration + " in source "
+ + src.mKey.mProcess + " to assoc " + mName, ex);
+ }
+ if (src.mActiveDurations == null && src.mActiveDuration > mTotalDuration) {
+ RuntimeException ex = new RuntimeException();
+ ex.fillInStackTrace();
+ Slog.w(TAG, "Reading act duration " + src.mActiveDuration
+ + " exceeds total " + mTotalDuration + " in source "
+ + src.mKey.mProcess + " to assoc " + mName, ex);
+ }
+ }
mSources.put(key, src);
}
return null;
@@ -448,19 +590,30 @@
src.mStartUptime = nowUptime;
}
if (src.mActiveStartUptime > 0) {
- final long duration = src.mActiveDuration + nowUptime - src.mActiveStartUptime;
- if (src.mDurations != null) {
- src.mDurations.addDuration(src.mActiveProcState, duration);
- } else {
- src.mActiveDuration = duration;
- }
+ final long addedDuration = nowUptime - src.mActiveStartUptime;
src.mActiveStartUptime = nowUptime;
+ if (src.mActiveDurations != null) {
+ src.mActiveDurations.addDuration(src.mActiveProcState, addedDuration);
+ } else {
+ src.mActiveDuration += addedDuration;
+ }
}
}
+ if (mTotalNesting > 0) {
+ mTotalDuration += nowUptime - mTotalStartUptime;
+ mTotalStartUptime = nowUptime;
+ }
+ if (mTotalActiveNesting > 0) {
+ mTotalActiveDuration += nowUptime - mTotalActiveStartUptime;
+ mTotalActiveStartUptime = nowUptime;
+ }
}
}
public boolean hasProcessOrPackage(String procName) {
+ if (mProcessName.equals(procName)) {
+ return true;
+ }
final int NSRC = mSources.size();
for (int isrc = 0; isrc < NSRC; isrc++) {
final SourceKey key = mSources.keyAt(isrc);
@@ -471,22 +624,110 @@
return false;
}
- public void dumpStats(PrintWriter pw, String prefix, String prefixInner, String headerPrefix,
- long now, long totalTime, String reqPackage, boolean dumpDetails, boolean dumpAll) {
- if (dumpAll) {
- pw.print(prefix);
- pw.print("mNumActive=");
- pw.println(mNumActive);
+ static final Comparator<Pair<SourceKey, SourceDumpContainer>> ASSOCIATION_COMPARATOR =
+ (o1, o2) -> {
+ if (o1.second.mActiveTime != o2.second.mActiveTime) {
+ return o1.second.mActiveTime > o2.second.mActiveTime ? -1 : 1;
}
- final int NSRC = mSources.size();
- for (int isrc = 0; isrc < NSRC; isrc++) {
- final SourceKey key = mSources.keyAt(isrc);
- final SourceState src = mSources.valueAt(isrc);
- if (reqPackage != null && !reqPackage.equals(key.mProcess)
- && !reqPackage.equals(key.mPackage)) {
- continue;
+ if (o1.second.mTotalTime != o2.second.mTotalTime) {
+ return o1.second.mTotalTime > o2.second.mTotalTime ? -1 : 1;
+ }
+ if (o1.first.mUid != o2.first.mUid) {
+ return o1.first.mUid < o2.first.mUid ? -1 : 1;
+ }
+ if (o1.first.mProcess != o2.first.mProcess) {
+ int diff = o1.first.mProcess.compareTo(o2.first.mProcess);
+ if (diff != 0) {
+ return diff;
}
- pw.print(prefixInner);
+ }
+ return 0;
+ };
+
+ public ArrayList<Pair<SourceKey, SourceDumpContainer>> createSortedAssociations(long now,
+ long totalTime) {
+ final int NSRC = mSources.size();
+ ArrayList<Pair<SourceKey, SourceDumpContainer>> sources = new ArrayList<>(NSRC);
+ for (int isrc = 0; isrc < NSRC; isrc++) {
+ final SourceState src = mSources.valueAt(isrc);
+ final SourceDumpContainer cont = new SourceDumpContainer(src);
+ long duration = src.mDuration;
+ if (src.mNesting > 0) {
+ duration += now - src.mStartUptime;
+ }
+ cont.mTotalTime = duration;
+ cont.mActiveTime = dumpTime(null, null, src, totalTime, now, false, false);
+ if (cont.mActiveTime < 0) {
+ cont.mActiveTime = -cont.mActiveTime;
+ }
+ sources.add(new Pair<>(mSources.keyAt(isrc), cont));
+ }
+ Collections.sort(sources, ASSOCIATION_COMPARATOR);
+ return sources;
+ }
+
+ public void dumpStats(PrintWriter pw, String prefix, String prefixInner, String headerPrefix,
+ ArrayList<Pair<SourceKey, SourceDumpContainer>> sources, long now, long totalTime,
+ String reqPackage, boolean dumpDetails, boolean dumpAll) {
+ final String prefixInnerInner = prefixInner + " ";
+ long totalDuration = mTotalActiveDuration;
+ if (mTotalActiveNesting > 0) {
+ totalDuration += now - mTotalActiveStartUptime;
+ }
+ if (totalDuration > 0 || mTotalActiveCount != 0) {
+ pw.print(prefix);
+ pw.print("Active count ");
+ pw.print(mTotalActiveCount);
+ if (dumpAll) {
+ pw.print(": ");
+ TimeUtils.formatDuration(totalDuration, pw);
+ pw.print(" / ");
+ } else {
+ pw.print(": time ");
+ }
+ DumpUtils.printPercent(pw, (double) totalDuration / (double) totalTime);
+ pw.println();
+ }
+ if (dumpAll && mTotalActiveNesting != 0) {
+ pw.print(prefix);
+ pw.print("mTotalActiveNesting=");
+ pw.print(mTotalActiveNesting);
+ pw.print(" mTotalActiveStartUptime=");
+ TimeUtils.formatDuration(mTotalActiveStartUptime, now, pw);
+ pw.println();
+ }
+ totalDuration = mTotalDuration;
+ if (mTotalNesting > 0) {
+ totalDuration += now - mTotalStartUptime;
+ }
+ if (totalDuration > 0 || mTotalCount != 0) {
+ pw.print(prefix);
+ pw.print("Total count ");
+ pw.print(mTotalCount);
+ if (dumpAll) {
+ pw.print(": ");
+ TimeUtils.formatDuration(totalDuration, pw);
+ pw.print(" / ");
+ } else {
+ pw.print(": time ");
+ }
+ DumpUtils.printPercent(pw, (double) totalDuration / (double) totalTime);
+ pw.println();
+ }
+ if (dumpAll && mTotalNesting != 0) {
+ pw.print(prefix);
+ pw.print("mTotalNesting=");
+ pw.print(mTotalNesting);
+ pw.print(" mTotalStartUptime=");
+ TimeUtils.formatDuration(mTotalStartUptime, now, pw);
+ pw.println();
+ }
+ final int NSRC = sources.size();
+ for (int isrc = 0; isrc < NSRC; isrc++) {
+ final SourceKey key = sources.get(isrc).first;
+ final SourceDumpContainer cont = sources.get(isrc).second;
+ final SourceState src = cont.mState;
+ pw.print(prefix);
pw.print("<- ");
pw.print(key.mProcess);
pw.print("/");
@@ -496,24 +737,69 @@
pw.print(key.mPackage);
pw.print(")");
}
+ // If we are skipping this one, we still print the first line just to give
+ // context for the others (so it is clear the total times for the overall
+ // association come from other sources whose times are not shown).
+ if (reqPackage != null && !reqPackage.equals(key.mProcess)
+ && !reqPackage.equals(key.mPackage)) {
+ pw.println();
+ continue;
+ }
pw.println(":");
+ if (src.mActiveCount != 0 || src.mActiveDurations != null || src.mActiveDuration != 0
+ || src.mActiveStartUptime != 0) {
+ pw.print(prefixInner);
+ pw.print(" Active count ");
+ pw.print(src.mActiveCount);
+ if (dumpDetails) {
+ if (dumpAll) {
+ if (src.mActiveDurations != null) {
+ pw.print(" (multi-state)");
+ } else if (src.mActiveProcState >= ProcessStats.STATE_PERSISTENT) {
+ pw.print(" (");
+ pw.print(DumpUtils.STATE_NAMES[src.mActiveProcState]);
+ pw.print(")");
+ } else {
+ pw.print(" (*UNKNOWN STATE*)");
+ }
+ }
+ if (dumpAll) {
+ pw.print(": ");
+ TimeUtils.formatDuration(cont.mActiveTime, pw);
+ pw.print(" / ");
+ } else {
+ pw.print(": time ");
+ }
+ DumpUtils.printPercent(pw, (double) cont.mActiveTime / (double) totalTime);
+ if (src.mActiveStartUptime != 0) {
+ pw.print(" (running)");
+ }
+ pw.println();
+ if (src.mActiveDurations != null) {
+ dumpTime(pw, prefixInnerInner, src, totalTime, now, dumpDetails, dumpAll);
+ }
+ } else {
+ pw.print(": ");
+ dumpActiveDurationSummary(pw, src, totalTime, now, dumpAll);
+ }
+ }
pw.print(prefixInner);
pw.print(" Total count ");
pw.print(src.mCount);
- long duration = src.mDuration;
- if (src.mNesting > 0) {
- duration += now - src.mStartUptime;
- }
if (dumpAll) {
- pw.print(": Duration ");
- TimeUtils.formatDuration(duration, pw);
+ pw.print(": ");
+ TimeUtils.formatDuration(cont.mTotalTime, pw);
pw.print(" / ");
} else {
pw.print(": time ");
}
- DumpUtils.printPercent(pw, (double)duration/(double)totalTime);
+ DumpUtils.printPercent(pw, (double) cont.mTotalTime / (double) totalTime);
if (src.mNesting > 0) {
pw.print(" (running");
+ if (dumpAll) {
+ pw.print(" nest=");
+ pw.print(src.mNesting);
+ }
if (src.mProcState != ProcessStats.STATE_NOTHING) {
pw.print(" / ");
pw.print(DumpUtils.STATE_NAMES[src.mProcState]);
@@ -523,23 +809,6 @@
pw.print(")");
}
pw.println();
- if (src.mActiveCount > 0 || src.mDurations != null || src.mActiveDuration != 0
- || src.mActiveStartUptime != 0) {
- pw.print(prefixInner);
- pw.print(" Active count ");
- pw.print(src.mActiveCount);
- if (dumpDetails) {
- if (dumpAll) {
- pw.print(src.mDurations != null ? " (multi-field)" : " (inline)");
- }
- pw.println(":");
- dumpTime(pw, prefixInner, src, totalTime, now, dumpDetails, dumpAll);
- } else {
- pw.print(": ");
- dumpActiveDurationSummary(pw, src, totalTime, now, dumpAll);
- pw.println();
- }
- }
if (dumpAll) {
if (src.mInTrackingList) {
pw.print(prefixInner);
@@ -565,7 +834,6 @@
duration = -duration;
}
if (dumpAll) {
- pw.print("Duration ");
TimeUtils.formatDuration(duration, pw);
pw.print(" / ");
} else {
@@ -584,10 +852,10 @@
boolean isRunning = false;
for (int iprocstate = 0; iprocstate < ProcessStats.STATE_COUNT; iprocstate++) {
long time;
- if (src.mDurations != null) {
- time = src.mDurations.getValueForId((byte)iprocstate);
+ if (src.mActiveDurations != null) {
+ time = src.mActiveDurations.getValueForId((byte) iprocstate);
} else {
- time = src.mActiveProcState == iprocstate ? src.mDuration : 0;
+ time = src.mActiveProcState == iprocstate ? src.mActiveDuration : 0;
}
final String running;
if (src.mActiveStartUptime != 0 && src.mActiveProcState == iprocstate) {
@@ -600,11 +868,9 @@
if (time != 0) {
if (pw != null) {
pw.print(prefix);
- pw.print(" ");
pw.print(DumpUtils.STATE_LABELS[iprocstate]);
pw.print(": ");
if (dumpAll) {
- pw.print("Duration ");
TimeUtils.formatDuration(time, pw);
pw.print(" / ");
} else {
@@ -619,21 +885,6 @@
totalTime += time;
}
}
- if (totalTime != 0 && pw != null) {
- pw.print(prefix);
- pw.print(" ");
- pw.print(DumpUtils.STATE_LABEL_TOTAL);
- pw.print(": ");
- if (dumpAll) {
- pw.print("Duration ");
- TimeUtils.formatDuration(totalTime, pw);
- pw.print(" / ");
- } else {
- pw.print("time ");
- }
- DumpUtils.printPercent(pw, (double) totalTime / (double) overallTime);
- pw.println();
- }
return isRunning ? -totalTime : totalTime;
}
@@ -667,11 +918,11 @@
pw.print(",");
pw.print(src.mActiveCount);
final long timeNow = src.mActiveStartUptime != 0 ? (now-src.mActiveStartUptime) : 0;
- if (src.mDurations != null) {
- final int N = src.mDurations.getKeyCount();
+ if (src.mActiveDurations != null) {
+ final int N = src.mActiveDurations.getKeyCount();
for (int i=0; i<N; i++) {
- final int dkey = src.mDurations.getKeyAt(i);
- duration = src.mDurations.getValue(dkey);
+ final int dkey = src.mActiveDurations.getKeyAt(i);
+ duration = src.mActiveDurations.getValue(dkey);
if (dkey == src.mActiveProcState) {
duration += timeNow;
}
@@ -699,6 +950,14 @@
proto.write(PackageAssociationProcessStatsProto.COMPONENT_NAME, mName);
+ proto.write(PackageAssociationProcessStatsProto.TOTAL_COUNT, mTotalCount);
+ proto.write(PackageAssociationProcessStatsProto.TOTAL_DURATION_MS, getTotalDuration(now));
+ if (mTotalActiveCount != 0) {
+ proto.write(PackageAssociationProcessStatsProto.ACTIVE_COUNT, mTotalActiveCount);
+ proto.write(PackageAssociationProcessStatsProto.ACTIVE_DURATION_MS,
+ getActiveDuration(now));
+ }
+
final int NSRC = mSources.size();
for (int isrc = 0; isrc < NSRC; isrc++) {
final SourceKey key = mSources.keyAt(isrc);
@@ -718,11 +977,11 @@
src.mActiveCount);
}
final long timeNow = src.mActiveStartUptime != 0 ? (now-src.mActiveStartUptime) : 0;
- if (src.mDurations != null) {
- final int N = src.mDurations.getKeyCount();
+ if (src.mActiveDurations != null) {
+ final int N = src.mActiveDurations.getKeyCount();
for (int i=0; i<N; i++) {
- final int dkey = src.mDurations.getKeyAt(i);
- duration = src.mDurations.getValue(dkey);
+ final int dkey = src.mActiveDurations.getKeyAt(i);
+ duration = src.mActiveDurations.getValue(dkey);
if (dkey == src.mActiveProcState) {
duration += timeNow;
}
diff --git a/core/java/com/android/internal/app/procstats/ProcessStats.java b/core/java/com/android/internal/app/procstats/ProcessStats.java
index 8e88c51..875cff8 100644
--- a/core/java/com/android/internal/app/procstats/ProcessStats.java
+++ b/core/java/com/android/internal/app/procstats/ProcessStats.java
@@ -31,6 +31,7 @@
import android.util.ArraySet;
import android.util.DebugUtils;
import android.util.LongSparseArray;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -48,6 +49,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Comparator;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -179,7 +181,7 @@
{"proc", "pkg-proc", "pkg-svc", "pkg-asc", "pkg-all", "all"};
// Current version of the parcel format.
- private static final int PARCEL_VERSION = 36;
+ private static final int PARCEL_VERSION = 38;
// In-memory Parcel magic number, used to detect attempts to unmarshall bad data
private static final int MAGIC = 0x50535454;
@@ -196,6 +198,9 @@
public int mMemFactor = STATE_NOTHING;
public long mStartTime;
+ // Number of individual stats that have been aggregated to create this one.
+ public int mNumAggregated = 1;
+
public long mTimePeriodStartClock;
public long mTimePeriodStartRealtime;
public long mTimePeriodEndRealtime;
@@ -348,6 +353,8 @@
mSysMemUsage.mergeStats(other.mSysMemUsage);
+ mNumAggregated += other.mNumAggregated;
+
if (other.mTimePeriodStartClock < mTimePeriodStartClock) {
mTimePeriodStartClock = other.mTimePeriodStartClock;
mTimePeriodStartClockStr = other.mTimePeriodStartClockStr;
@@ -569,6 +576,7 @@
}
private void resetCommon() {
+ mNumAggregated = 1;
mTimePeriodStartClock = System.currentTimeMillis();
buildTimePeriodStartClockStr();
mTimePeriodStartRealtime = mTimePeriodEndRealtime = SystemClock.elapsedRealtime();
@@ -845,6 +853,7 @@
}
}
+ out.writeInt(mNumAggregated);
out.writeLong(mTimePeriodStartClock);
out.writeLong(mTimePeriodStartRealtime);
out.writeLong(mTimePeriodEndRealtime);
@@ -1031,6 +1040,7 @@
mIndexToCommonString = new ArrayList<String>();
+ mNumAggregated = in.readInt();
mTimePeriodStartClock = in.readLong();
buildTimePeriodStartClockStr();
mTimePeriodStartRealtime = in.readLong();
@@ -1457,15 +1467,79 @@
}
}
+ final class AssociationDumpContainer {
+ final AssociationState mState;
+ ArrayList<Pair<AssociationState.SourceKey, AssociationState.SourceDumpContainer>> mSources;
+ long mTotalTime;
+ long mActiveTime;
+
+ AssociationDumpContainer(AssociationState state) {
+ mState = state;
+ }
+ }
+
+ static final Comparator<AssociationDumpContainer> ASSOCIATION_COMPARATOR = (o1, o2) -> {
+ int diff = o1.mState.getProcessName().compareTo(o2.mState.getProcessName());
+ if (diff != 0) {
+ return diff;
+ }
+ if (o1.mActiveTime != o2.mActiveTime) {
+ return o1.mActiveTime > o2.mActiveTime ? -1 : 1;
+ }
+ if (o1.mTotalTime != o2.mTotalTime) {
+ return o1.mTotalTime > o2.mTotalTime ? -1 : 1;
+ }
+ diff = o1.mState.getName().compareTo(o2.mState.getName());
+ if (diff != 0) {
+ return diff;
+ }
+ return 0;
+ };
+
public void dumpLocked(PrintWriter pw, String reqPackage, long now, boolean dumpSummary,
boolean dumpDetails, boolean dumpAll, boolean activeOnly, int section) {
long totalTime = DumpUtils.dumpSingleTime(null, null, mMemFactorDurations, mMemFactor,
mStartTime, now);
- boolean sepNeeded = false;
+ pw.print(" Start time: ");
+ pw.print(DateFormat.format("yyyy-MM-dd HH:mm:ss", mTimePeriodStartClock));
+ pw.println();
+ pw.print(" Total uptime: ");
+ TimeUtils.formatDuration(
+ (mRunning ? SystemClock.uptimeMillis() : mTimePeriodEndUptime)
+ - mTimePeriodStartUptime, pw);
+ pw.println();
+ pw.print(" Total elapsed time: ");
+ TimeUtils.formatDuration(
+ (mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime)
+ - mTimePeriodStartRealtime, pw);
+ boolean partial = true;
+ if ((mFlags & FLAG_SHUTDOWN) != 0) {
+ pw.print(" (shutdown)");
+ partial = false;
+ }
+ if ((mFlags & FLAG_SYSPROPS) != 0) {
+ pw.print(" (sysprops)");
+ partial = false;
+ }
+ if ((mFlags & FLAG_COMPLETE) != 0) {
+ pw.print(" (complete)");
+ partial = false;
+ }
+ if (partial) {
+ pw.print(" (partial)");
+ }
+ if (mHasSwappedOutPss) {
+ pw.print(" (swapped-out-pss)");
+ }
+ pw.print(' ');
+ pw.print(mRuntime);
+ pw.println();
+ pw.print(" Aggregated over: ");
+ pw.println(mNumAggregated);
if (mSysMemUsage.getKeyCount() > 0) {
+ pw.println();
pw.println("System memory usage:");
mSysMemUsage.dump(pw, " ", ALL_SCREEN_ADJ, ALL_MEM_ADJ);
- sepNeeded = true;
}
boolean printedHeader = false;
if ((section & REPORT_PKG_STATS) != 0) {
@@ -1485,8 +1559,8 @@
final int NASCS = pkgState.mAssociations.size();
final boolean pkgMatch = reqPackage == null || reqPackage.equals(pkgName);
boolean onlyAssociations = false;
+ boolean procMatch = false;
if (!pkgMatch) {
- boolean procMatch = false;
for (int iproc = 0; iproc < NPROCS; iproc++) {
ProcessState proc = pkgState.mProcesses.valueAt(iproc);
if (reqPackage.equals(proc.getName())) {
@@ -1511,10 +1585,9 @@
}
if (NPROCS > 0 || NSRVS > 0 || NASCS > 0) {
if (!printedHeader) {
- if (sepNeeded) pw.println();
+ pw.println();
pw.println("Per-Package Stats:");
printedHeader = true;
- sepNeeded = true;
}
pw.print(" * ");
pw.print(pkgName);
@@ -1597,6 +1670,8 @@
}
}
if ((section & REPORT_PKG_ASC_STATS) != 0) {
+ ArrayList<AssociationDumpContainer> associations =
+ new ArrayList<>(NASCS);
for (int iasc = 0; iasc < NASCS; iasc++) {
AssociationState asc = pkgState.mAssociations.valueAt(iasc);
if (!pkgMatch && !reqPackage.equals(asc.getProcessName())) {
@@ -1604,6 +1679,18 @@
continue;
}
}
+ final AssociationDumpContainer cont =
+ new AssociationDumpContainer(asc);
+ cont.mSources = asc.createSortedAssociations(now, totalTime);
+ cont.mTotalTime = asc.getTotalDuration(now);
+ cont.mActiveTime = asc.getActiveDuration(now);
+ associations.add(cont);
+ }
+ Collections.sort(associations, ASSOCIATION_COMPARATOR);
+ final int NCONT = associations.size();
+ for (int iasc = 0; iasc < NCONT; iasc++) {
+ final AssociationDumpContainer cont = associations.get(iasc);
+ final AssociationState asc = cont.mState;
if (activeOnly && !asc.isInUse()) {
pw.print(" (Not active association: ");
pw.print(pkgState.mAssociations.keyAt(iasc));
@@ -1615,13 +1702,15 @@
} else {
pw.print(" * Asc ");
}
- pw.print(pkgState.mAssociations.keyAt(iasc));
+ pw.print(cont.mState.getName());
pw.println(":");
pw.print(" Process: ");
pw.println(asc.getProcessName());
asc.dumpStats(pw, " ", " ", " ",
- now, totalTime, onlyAssociations ? reqPackage : null,
- dumpDetails, dumpAll);
+ cont.mSources, now, totalTime,
+ onlyAssociations && !pkgMatch && !procMatch
+ && !asc.getProcessName().equals(reqPackage)
+ ? reqPackage : null, dumpDetails, dumpAll);
}
}
}
@@ -1651,10 +1740,7 @@
continue;
}
numShownProcs++;
- if (sepNeeded) {
- pw.println();
- }
- sepNeeded = true;
+ pw.println();
if (!printedHeader) {
pw.println("Multi-Package Common Processes:");
printedHeader = true;
@@ -1684,11 +1770,7 @@
}
if (dumpAll) {
- if (sepNeeded) {
- pw.println();
- }
- sepNeeded = true;
-
+ pw.println();
if (mTrackingAssociations.size() > 0) {
pw.println();
pw.println("Tracking associations:");
@@ -1734,9 +1816,7 @@
}
}
- if (sepNeeded) {
- pw.println();
- }
+ pw.println();
if (dumpSummary) {
pw.println("Process summary:");
dumpSummaryLocked(pw, reqPackage, now, activeOnly);
@@ -1861,41 +1941,6 @@
pw.print("x over ");
TimeUtils.formatDuration(mExternalSlowPssTime, pw);
pw.println();
- pw.println();
- pw.print(" Start time: ");
- pw.print(DateFormat.format("yyyy-MM-dd HH:mm:ss", mTimePeriodStartClock));
- pw.println();
- pw.print(" Total uptime: ");
- TimeUtils.formatDuration(
- (mRunning ? SystemClock.uptimeMillis() : mTimePeriodEndUptime)
- - mTimePeriodStartUptime, pw);
- pw.println();
- pw.print(" Total elapsed time: ");
- TimeUtils.formatDuration(
- (mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime)
- - mTimePeriodStartRealtime, pw);
- boolean partial = true;
- if ((mFlags&FLAG_SHUTDOWN) != 0) {
- pw.print(" (shutdown)");
- partial = false;
- }
- if ((mFlags&FLAG_SYSPROPS) != 0) {
- pw.print(" (sysprops)");
- partial = false;
- }
- if ((mFlags&FLAG_COMPLETE) != 0) {
- pw.print(" (complete)");
- partial = false;
- }
- if (partial) {
- pw.print(" (partial)");
- }
- if (mHasSwappedOutPss) {
- pw.print(" (swapped-out-pss)");
- }
- pw.print(' ');
- pw.print(mRuntime);
- pw.println();
}
void dumpFilteredSummaryLocked(PrintWriter pw, String header, String prefix, String prcLabel,
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
index 8283eb7..72b0ad7 100644
--- a/core/java/com/android/internal/compat/ChangeReporter.java
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -22,7 +22,9 @@
import com.android.internal.annotations.GuardedBy;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.Map;
import java.util.Objects;
import java.util.Set;
@@ -36,12 +38,10 @@
private int mSource;
private final class ChangeReport {
- int mUid;
long mChangeId;
int mState;
- ChangeReport(int uid, long changeId, int state) {
- mUid = uid;
+ ChangeReport(long changeId, int state) {
mChangeId = changeId;
mState = state;
}
@@ -51,40 +51,62 @@
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ChangeReport that = (ChangeReport) o;
- return mUid == that.mUid
- && mChangeId == that.mChangeId
+ return mChangeId == that.mChangeId
&& mState == that.mState;
}
@Override
public int hashCode() {
- return Objects.hash(mUid, mChangeId, mState);
+ return Objects.hash(mChangeId, mState);
}
}
+ // Maps uid to a set of ChangeReports (that were reported for that uid).
@GuardedBy("mReportedChanges")
- private Set<ChangeReport> mReportedChanges = new HashSet<>();
+ private final Map<Integer, Set<ChangeReport>> mReportedChanges;
public ChangeReporter(int source) {
mSource = source;
+ mReportedChanges = new HashMap<>();
}
/**
- * Report the change to stats log.
+ * Report the change to stats log and to the debug log if the change was not previously
+ * logged already.
*
* @param uid affected by the change
* @param changeId the reported change id
* @param state of the reported change - enabled/disabled/only logged
*/
public void reportChange(int uid, long changeId, int state) {
- ChangeReport report = new ChangeReport(uid, changeId, state);
+ ChangeReport report = new ChangeReport(changeId, state);
synchronized (mReportedChanges) {
- if (!mReportedChanges.contains(report)) {
+ Set<ChangeReport> reportedChangesForUid = mReportedChanges.get(uid);
+ if (reportedChangesForUid == null) {
+ mReportedChanges.put(uid, new HashSet<ChangeReport>());
+ reportedChangesForUid = mReportedChanges.get(uid);
+ }
+ if (!reportedChangesForUid.contains(report)) {
debugLog(uid, changeId, state);
StatsLog.write(StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid, changeId,
state, mSource);
- mReportedChanges.add(report);
+ reportedChangesForUid.add(report);
}
+
+ }
+ }
+
+ /**
+ * Clears the saved information about a given uid. Requests to report uid again will be reported
+ * regardless to the past reports.
+ *
+ * <p> Only intended to be called from PlatformCompat.
+ *
+ * @param uid to reset
+ */
+ public void resetReportedChanges(int uid) {
+ synchronized (mReportedChanges) {
+ mReportedChanges.remove(uid);
}
}
diff --git a/core/java/com/android/internal/compat/CompatibilityChangeConfig.aidl b/core/java/com/android/internal/compat/CompatibilityChangeConfig.aidl
new file mode 100644
index 0000000..434c1b8
--- /dev/null
+++ b/core/java/com/android/internal/compat/CompatibilityChangeConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.compat;
+
+parcelable CompatibilityChangeConfig;
diff --git a/core/java/com/android/internal/compat/CompatibilityChangeConfig.java b/core/java/com/android/internal/compat/CompatibilityChangeConfig.java
new file mode 100644
index 0000000..fd2ada0
--- /dev/null
+++ b/core/java/com/android/internal/compat/CompatibilityChangeConfig.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.compat;
+
+
+import android.compat.Compatibility.ChangeConfig;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Parcelable containing compat config overrides for a given application.
+ * @hide
+ */
+public final class CompatibilityChangeConfig implements Parcelable {
+ private final ChangeConfig mChangeConfig;
+
+ public CompatibilityChangeConfig(ChangeConfig changeConfig) {
+ mChangeConfig = changeConfig;
+ }
+
+ /**
+ * Changes forced to be enabled.
+ */
+ public Set<Long> enabledChanges() {
+ return mChangeConfig.forceEnabledSet();
+ }
+
+ /**
+ * Changes forced to be disabled.
+ */
+ public Set<Long> disabledChanges() {
+ return mChangeConfig.forceDisabledSet();
+ }
+
+ private CompatibilityChangeConfig(Parcel in) {
+ long[] enabledArray = in.createLongArray();
+ long[] disabledArray = in.createLongArray();
+ Set<Long> enabled = toLongSet(enabledArray);
+ Set<Long> disabled = toLongSet(disabledArray);
+ mChangeConfig = new ChangeConfig(enabled, disabled);
+ }
+
+ private static Set<Long> toLongSet(long[] values) {
+ Set<Long> ret = new HashSet<>();
+ for (long value: values) {
+ ret.add(value);
+ }
+ return ret;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ long[] enabled = mChangeConfig.forceEnabledChangesArray();
+ long[] disabled = mChangeConfig.forceDisabledChangesArray();
+
+ dest.writeLongArray(enabled);
+ dest.writeLongArray(disabled);
+ }
+
+ public static final Parcelable.Creator<CompatibilityChangeConfig> CREATOR =
+ new Parcelable.Creator<CompatibilityChangeConfig>() {
+
+ @Override
+ public CompatibilityChangeConfig createFromParcel(Parcel in) {
+ return new CompatibilityChangeConfig(in);
+ }
+
+ @Override
+ public CompatibilityChangeConfig[] newArray(int size) {
+ return new CompatibilityChangeConfig[size];
+ }
+ };
+}
diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
index 4d8378a..4099cfa 100644
--- a/core/java/com/android/internal/compat/IPlatformCompat.aidl
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -18,6 +18,8 @@
import android.content.pm.ApplicationInfo;
+parcelable CompatibilityChangeConfig;
+
/**
* Platform private API for talking with the PlatformCompat service.
*
@@ -125,4 +127,21 @@
* @return {@code true} if the change is enabled for the current app.
*/
boolean isChangeEnabledByUid(long changeId, int uid);
-}
\ No newline at end of file
+
+ /**
+ * Add overrides to compatibility changes.
+ *
+ * @param overrides Parcelable containing the compat change overrides to be applied.
+ * @param packageName The package name of the app whose changes will be overridden.
+ *
+ */
+ void setOverrides(in CompatibilityChangeConfig overrides, in String packageName);
+
+ /**
+ * Revert overrides to compatibility changes.
+ *
+ * @param packageName The package name of the app whose overrides will be cleared.
+ *
+ */
+ void clearOverrides(in String packageName);
+}
diff --git a/core/java/com/android/internal/compat/OWNERS b/core/java/com/android/internal/compat/OWNERS
new file mode 100644
index 0000000..2b7cdb0
--- /dev/null
+++ b/core/java/com/android/internal/compat/OWNERS
@@ -0,0 +1,7 @@
+# Use this reviewer by default.
+platform-compat-eng+reviews@google.com
+
+andreionea@google.com
+atrost@google.com
+mathewi@google.com
+satayev@google.com
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index cdb79ab..f5708a5c 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -17,6 +17,7 @@
package com.android.internal.content;
import android.annotation.CallSuper;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Intent;
@@ -552,6 +553,11 @@
flags |= Document.FLAG_SUPPORTS_DELETE;
flags |= Document.FLAG_SUPPORTS_RENAME;
flags |= Document.FLAG_SUPPORTS_MOVE;
+
+ if (shouldBlockFromTree(docId)) {
+ flags |= Document.FLAG_DIR_BLOCKS_TREE;
+ }
+
} else {
flags |= Document.FLAG_SUPPORTS_WRITE;
flags |= Document.FLAG_SUPPORTS_DELETE;
@@ -592,6 +598,10 @@
return row;
}
+ protected boolean shouldBlockFromTree(@NonNull String docId) {
+ return false;
+ }
+
protected boolean typeSupportsMetadata(String mimeType) {
return MetadataReader.isSupportedMimeType(mimeType)
|| Document.MIME_TYPE_DIR.equals(mimeType);
diff --git a/core/java/com/android/internal/os/KernelWakelockReader.java b/core/java/com/android/internal/os/KernelWakelockReader.java
index e09e0e6..cffb0ad 100644
--- a/core/java/com/android/internal/os/KernelWakelockReader.java
+++ b/core/java/com/android/internal/os/KernelWakelockReader.java
@@ -29,6 +29,7 @@
import java.io.File;
import java.io.FileInputStream;
+import java.util.Arrays;
import java.util.Iterator;
/**
@@ -66,6 +67,7 @@
private final String[] mProcWakelocksName = new String[3];
private final long[] mProcWakelocksData = new long[3];
private ISuspendControlService mSuspendControlService = null;
+ private byte[] mKernelWakelockBuffer = new byte[32 * 1024];
/**
* Reads kernel wakelock stats and updates the staleStats with the new information.
@@ -84,7 +86,7 @@
}
return removeOldStats(staleStats);
} else {
- byte[] buffer = new byte[32*1024];
+ Arrays.fill(mKernelWakelockBuffer, (byte) 0);
int len = 0;
boolean wakeup_sources;
final long startTime = SystemClock.uptimeMillis();
@@ -107,7 +109,8 @@
}
int cnt;
- while ((cnt = is.read(buffer, len, buffer.length - len)) > 0) {
+ while ((cnt = is.read(mKernelWakelockBuffer, len,
+ mKernelWakelockBuffer.length - len)) > 0) {
len += cnt;
}
@@ -125,12 +128,13 @@
}
if (len > 0) {
- if (len >= buffer.length) {
- Slog.wtf(TAG, "Kernel wake locks exceeded buffer size " + buffer.length);
+ if (len >= mKernelWakelockBuffer.length) {
+ Slog.wtf(TAG, "Kernel wake locks exceeded mKernelWakelockBuffer size "
+ + mKernelWakelockBuffer.length);
}
int i;
for (i=0; i<len; i++) {
- if (buffer[i] == '\0') {
+ if (mKernelWakelockBuffer[i] == '\0') {
len = i;
break;
}
@@ -143,7 +147,7 @@
Slog.w(TAG, "Failed to get Native wakelock stats from SystemSuspend");
}
// Get kernel wakelock stats
- parseProcWakelocks(buffer, len, wakeup_sources, staleStats);
+ parseProcWakelocks(mKernelWakelockBuffer, len, wakeup_sources, staleStats);
return removeOldStats(staleStats);
}
}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 158700b..363e549 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -184,6 +184,12 @@
System.loadLibrary("android");
System.loadLibrary("compiler_rt");
System.loadLibrary("jnigraphics");
+
+ try {
+ System.loadLibrary("sfplugin_ccodec");
+ } catch (Error | RuntimeException e) {
+ // tolerate missing sfplugin_ccodec which is only present on Codec 2 devices
+ }
}
native private static void nativePreloadAppProcessHALs();
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index a845b58..659134a 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -17,6 +17,7 @@
package com.android.internal.statusbar;
import android.app.Notification;
+import android.net.Uri;
import android.content.ComponentName;
import android.graphics.Rect;
import android.os.Bundle;
@@ -77,6 +78,7 @@
void onNotificationSettingsViewed(String key);
void setSystemUiVisibility(int displayId, int vis, int mask, String cause);
void onNotificationBubbleChanged(String key, boolean isBubble);
+ void grantInlineReplyUriPermission(String key, in Uri uri);
void onGlobalActionsShown();
void onGlobalActionsHidden();
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 0e078dd..7e1f13a 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -80,6 +80,10 @@
}
@Override
+ public void hideInsets(@InsetType int types, boolean fromIme) throws RemoteException {
+ }
+
+ @Override
public void moved(int newX, int newY) {
}
diff --git a/core/java/com/android/internal/widget/LockPatternChecker.java b/core/java/com/android/internal/widget/LockPatternChecker.java
index 09bc28c..85a45fd 100644
--- a/core/java/com/android/internal/widget/LockPatternChecker.java
+++ b/core/java/com/android/internal/widget/LockPatternChecker.java
@@ -1,13 +1,9 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
import android.os.AsyncTask;
import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* Helper class to check/verify PIN/Password/Pattern asynchronously.
*/
@@ -53,34 +49,28 @@
}
/**
- * Verify a pattern asynchronously.
+ * Verify a lockscreen credential asynchronously.
*
* @param utils The LockPatternUtils instance to use.
- * @param pattern The pattern to check.
- * @param challenge The challenge to verify against the pattern.
- * @param userId The user to check against the pattern.
+ * @param credential The credential to check.
+ * @param challenge The challenge to verify against the credential.
+ * @param userId The user to check against the credential.
* @param callback The callback to be invoked with the verification result.
*/
- public static AsyncTask<?, ?, ?> verifyPattern(final LockPatternUtils utils,
- final List<LockPatternView.Cell> pattern,
+ public static AsyncTask<?, ?, ?> verifyCredential(final LockPatternUtils utils,
+ final LockscreenCredential credential,
final long challenge,
final int userId,
final OnVerifyCallback callback) {
+ // Create a copy of the credential since checking credential is asynchrounous.
+ final LockscreenCredential credentialCopy = credential.duplicate();
AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
private int mThrottleTimeout;
- private List<LockPatternView.Cell> patternCopy;
-
- @Override
- protected void onPreExecute() {
- // Make a copy of the pattern to prevent race conditions.
- // No need to clone the individual cells because they are immutable.
- patternCopy = new ArrayList(pattern);
- }
@Override
protected byte[] doInBackground(Void... args) {
try {
- return utils.verifyPattern(patternCopy, challenge, userId);
+ return utils.verifyCredential(credentialCopy, challenge, userId);
} catch (RequestThrottledException ex) {
mThrottleTimeout = ex.getTimeoutMs();
return null;
@@ -90,6 +80,12 @@
@Override
protected void onPostExecute(byte[] result) {
callback.onVerified(result, mThrottleTimeout);
+ credentialCopy.zeroize();
+ }
+
+ @Override
+ protected void onCancelled() {
+ credentialCopy.zeroize();
}
};
task.execute();
@@ -97,32 +93,26 @@
}
/**
- * Checks a pattern asynchronously.
+ * Checks a lockscreen credential asynchronously.
*
* @param utils The LockPatternUtils instance to use.
- * @param pattern The pattern to check.
- * @param userId The user to check against the pattern.
+ * @param credential The credential to check.
+ * @param userId The user to check against the credential.
* @param callback The callback to be invoked with the check result.
*/
- public static AsyncTask<?, ?, ?> checkPattern(final LockPatternUtils utils,
- final List<LockPatternView.Cell> pattern,
+ public static AsyncTask<?, ?, ?> checkCredential(final LockPatternUtils utils,
+ final LockscreenCredential credential,
final int userId,
final OnCheckCallback callback) {
+ // Create a copy of the credential since checking credential is asynchrounous.
+ final LockscreenCredential credentialCopy = credential.duplicate();
AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() {
private int mThrottleTimeout;
- private List<LockPatternView.Cell> patternCopy;
-
- @Override
- protected void onPreExecute() {
- // Make a copy of the pattern to prevent race conditions.
- // No need to clone the individual cells because they are immutable.
- patternCopy = new ArrayList(pattern);
- }
@Override
protected Boolean doInBackground(Void... args) {
try {
- return utils.checkPattern(patternCopy, userId, callback::onEarlyMatched);
+ return utils.checkCredential(credentialCopy, userId, callback::onEarlyMatched);
} catch (RequestThrottledException ex) {
mThrottleTimeout = ex.getTimeoutMs();
return false;
@@ -132,11 +122,13 @@
@Override
protected void onPostExecute(Boolean result) {
callback.onChecked(result, mThrottleTimeout);
+ credentialCopy.zeroize();
}
@Override
protected void onCancelled() {
callback.onCancelled();
+ credentialCopy.zeroize();
}
};
task.execute();
@@ -144,84 +136,29 @@
}
/**
- * Verify a password asynchronously.
+ * Perform a lockscreen credential verification explicitly on a managed profile with unified
+ * challenge, using the parent user's credential.
*
* @param utils The LockPatternUtils instance to use.
- * @param password The password to check.
- * @param challenge The challenge to verify against the pattern.
- * @param userId The user to check against the pattern.
- * @param callback The callback to be invoked with the verification result.
- *
- * @deprecated Pass the password as a byte array.
- */
- @Deprecated
- public static AsyncTask<?, ?, ?> verifyPassword(final LockPatternUtils utils,
- final String password,
- final long challenge,
- final int userId,
- final OnVerifyCallback callback) {
- byte[] passwordBytes = password != null ? password.getBytes() : null;
- return verifyPassword(utils, passwordBytes, challenge, userId, callback);
- }
-
- /**
- * Verify a password asynchronously.
- *
- * @param utils The LockPatternUtils instance to use.
- * @param password The password to check.
- * @param challenge The challenge to verify against the pattern.
- * @param userId The user to check against the pattern.
- * @param callback The callback to be invoked with the verification result.
- */
- public static AsyncTask<?, ?, ?> verifyPassword(final LockPatternUtils utils,
- final byte[] password,
- final long challenge,
- final int userId,
- final OnVerifyCallback callback) {
- AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
- private int mThrottleTimeout;
-
- @Override
- protected byte[] doInBackground(Void... args) {
- try {
- return utils.verifyPassword(password, challenge, userId);
- } catch (RequestThrottledException ex) {
- mThrottleTimeout = ex.getTimeoutMs();
- return null;
- }
- }
-
- @Override
- protected void onPostExecute(byte[] result) {
- callback.onVerified(result, mThrottleTimeout);
- }
- };
- task.execute();
- return task;
- }
-
- /**
- * Verify a password asynchronously.
- *
- * @param utils The LockPatternUtils instance to use.
- * @param password The password to check.
- * @param challenge The challenge to verify against the pattern.
- * @param userId The user to check against the pattern.
+ * @param credential The credential to check.
+ * @param challenge The challenge to verify against the credential.
+ * @param userId The user to check against the credential.
* @param callback The callback to be invoked with the verification result.
*/
public static AsyncTask<?, ?, ?> verifyTiedProfileChallenge(final LockPatternUtils utils,
- final byte[] password,
- final boolean isPattern,
+ final LockscreenCredential credential,
final long challenge,
final int userId,
final OnVerifyCallback callback) {
+ // Create a copy of the credential since checking credential is asynchrounous.
+ final LockscreenCredential credentialCopy = credential.duplicate();
AsyncTask<Void, Void, byte[]> task = new AsyncTask<Void, Void, byte[]>() {
private int mThrottleTimeout;
@Override
protected byte[] doInBackground(Void... args) {
try {
- return utils.verifyTiedProfileChallenge(password, isPattern, challenge, userId);
+ return utils.verifyTiedProfileChallenge(credentialCopy, challenge, userId);
} catch (RequestThrottledException ex) {
mThrottleTimeout = ex.getTimeoutMs();
return null;
@@ -231,64 +168,12 @@
@Override
protected void onPostExecute(byte[] result) {
callback.onVerified(result, mThrottleTimeout);
- }
- };
- task.execute();
- return task;
- }
-
- /**
- * Checks a password asynchronously.
- *
- * @param utils The LockPatternUtils instance to use.
- * @param password The password to check.
- * @param userId The user to check against the pattern.
- * @param callback The callback to be invoked with the check result.
- * @deprecated Pass passwords as byte[]
- */
- @UnsupportedAppUsage
- @Deprecated
- public static AsyncTask<?, ?, ?> checkPassword(final LockPatternUtils utils,
- final String password,
- final int userId,
- final OnCheckCallback callback) {
- byte[] passwordBytes = password != null ? password.getBytes() : null;
- return checkPassword(utils, passwordBytes, userId, callback);
- }
-
- /**
- * Checks a password asynchronously.
- *
- * @param utils The LockPatternUtils instance to use.
- * @param passwordBytes The password to check.
- * @param userId The user to check against the pattern.
- * @param callback The callback to be invoked with the check result.
- */
- public static AsyncTask<?, ?, ?> checkPassword(final LockPatternUtils utils,
- final byte[] passwordBytes,
- final int userId,
- final OnCheckCallback callback) {
- AsyncTask<Void, Void, Boolean> task = new AsyncTask<Void, Void, Boolean>() {
- private int mThrottleTimeout;
-
- @Override
- protected Boolean doInBackground(Void... args) {
- try {
- return utils.checkPassword(passwordBytes, userId, callback::onEarlyMatched);
- } catch (RequestThrottledException ex) {
- mThrottleTimeout = ex.getTimeoutMs();
- return false;
- }
- }
-
- @Override
- protected void onPostExecute(Boolean result) {
- callback.onChecked(result, mThrottleTimeout);
+ credentialCopy.zeroize();
}
@Override
protected void onCancelled() {
- callback.onCancelled();
+ credentialCopy.zeroize();
}
};
task.execute();
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 070121c..8fea703 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -26,6 +26,7 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UnsupportedAppUsage;
import android.app.admin.DevicePolicyManager;
@@ -58,10 +59,10 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
-import com.google.android.collect.Lists;
-
import libcore.util.HexEncoding;
+import com.google.android.collect.Lists;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.security.MessageDigest;
@@ -77,7 +78,6 @@
* Utilities for the lock pattern and its settings.
*/
public class LockPatternUtils {
-
private static final String TAG = "LockPatternUtils";
private static final boolean FRP_CREDENTIAL_ENABLED = true;
@@ -114,6 +114,7 @@
*/
public static final int MIN_PATTERN_REGISTER_FAIL = MIN_LOCK_PATTERN_SIZE;
+ // NOTE: When modifying this, make sure credential sufficiency validation logic is intact.
public static final int CREDENTIAL_TYPE_NONE = -1;
public static final int CREDENTIAL_TYPE_PATTERN = 1;
public static final int CREDENTIAL_TYPE_PASSWORD = 2;
@@ -289,10 +290,10 @@
return getDevicePolicyManager().getPasswordMaximumLength(quality);
}
- /**
- * Gets the device policy password mode. If the mode is non-specific, returns
- * MODE_PATTERN which allows the user to choose anything.
- */
+ public PasswordMetrics getRequestedPasswordMetrics(int userId) {
+ return getDevicePolicyManager().getPasswordMinimumMetrics(userId);
+ }
+
public int getRequestedPasswordQuality(int userId) {
return getDevicePolicyManager().getPasswordQuality(null, userId);
}
@@ -365,11 +366,24 @@
null /* componentName */, userId);
}
- private byte[] verifyCredential(byte[] credential, int type, long challenge, int userId)
- throws RequestThrottledException {
+ /**
+ * Check to see if a credential matches the saved one.
+ * If credential matches, return an opaque attestation that the challenge was verified.
+ *
+ * @param credential The credential to check.
+ * @param challenge The challenge to verify against the credential
+ * @return the attestation that the challenge was verified, or null
+ * @param userId The user whose credential is being verified
+ * @throws RequestThrottledException if credential verification is being throttled due to
+ * to many incorrect attempts.
+ * @throws IllegalStateException if called on the main thread.
+ */
+ public byte[] verifyCredential(@NonNull LockscreenCredential credential, long challenge,
+ int userId) throws RequestThrottledException {
+ throwIfCalledOnMainThread();
try {
- VerifyCredentialResponse response = getLockSettings().verifyCredential(credential,
- type, challenge, userId);
+ VerifyCredentialResponse response = getLockSettings().verifyCredential(
+ credential.getCredential(), credential.getType(), challenge, userId);
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
return response.getPayload();
} else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
@@ -382,11 +396,24 @@
}
}
- private boolean checkCredential(byte[] credential, int type, int userId,
+ /**
+ * Check to see if a credential matches the saved one.
+ *
+ * @param credential The credential to check.
+ * @param userId The user whose credential is being checked
+ * @param progressCallback callback to deliver early signal that the credential matches
+ * @return {@code true} if credential matches, {@code false} otherwise
+ * @throws RequestThrottledException if credential verification is being throttled due to
+ * to many incorrect attempts.
+ * @throws IllegalStateException if called on the main thread.
+ */
+ public boolean checkCredential(@NonNull LockscreenCredential credential, int userId,
@Nullable CheckCredentialProgressCallback progressCallback)
throws RequestThrottledException {
+ throwIfCalledOnMainThread();
try {
- VerifyCredentialResponse response = getLockSettings().checkCredential(credential, type,
+ VerifyCredentialResponse response = getLockSettings().checkCredential(
+ credential.getCredential(), credential.getType(),
userId, wrapCallback(progressCallback));
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
@@ -402,79 +429,26 @@
}
/**
- * Check to see if a pattern matches the saved pattern.
- * If pattern matches, return an opaque attestation that the challenge
- * was verified.
+ * Check if the credential of a managed profile with unified challenge matches. In this context,
+ * The credential should be the parent user's lockscreen password. If credential matches,
+ * return an opaque attestation associated with the managed profile that the challenge was
+ * verified.
*
- * @param pattern The pattern to check.
- * @param challenge The challenge to verify against the pattern
- * @return the attestation that the challenge was verified, or null.
+ * @param credential The parent user's credential to check.
+ * @param challenge The challenge to verify against the credential
+ * @return the attestation that the challenge was verified, or null
+ * @param userId The managed profile user id
+ * @throws RequestThrottledException if credential verification is being throttled due to
+ * to many incorrect attempts.
+ * @throws IllegalStateException if called on the main thread.
*/
- public byte[] verifyPattern(List<LockPatternView.Cell> pattern, long challenge, int userId)
- throws RequestThrottledException {
- throwIfCalledOnMainThread();
- return verifyCredential(patternToByteArray(pattern), CREDENTIAL_TYPE_PATTERN, challenge,
- userId);
- }
-
- /**
- * Check to see if a pattern matches the saved pattern. If no pattern exists,
- * always returns true.
- * @param pattern The pattern to check.
- * @return Whether the pattern matches the stored one.
- */
- public boolean checkPattern(List<LockPatternView.Cell> pattern, int userId)
- throws RequestThrottledException {
- return checkPattern(pattern, userId, null /* progressCallback */);
- }
-
- /**
- * Check to see if a pattern matches the saved pattern. If no pattern exists,
- * always returns true.
- * @param pattern The pattern to check.
- * @return Whether the pattern matches the stored one.
- */
- public boolean checkPattern(List<LockPatternView.Cell> pattern, int userId,
- @Nullable CheckCredentialProgressCallback progressCallback)
- throws RequestThrottledException {
- throwIfCalledOnMainThread();
- return checkCredential(patternToByteArray(pattern), CREDENTIAL_TYPE_PATTERN, userId,
- progressCallback);
- }
-
- /**
- * Check to see if a password matches the saved password.
- * If password matches, return an opaque attestation that the challenge
- * was verified.
- *
- * @param password The password to check.
- * @param challenge The challenge to verify against the password
- * @return the attestation that the challenge was verified, or null.
- */
- public byte[] verifyPassword(byte[] password, long challenge, int userId)
- throws RequestThrottledException {
- throwIfCalledOnMainThread();
- return verifyCredential(password, CREDENTIAL_TYPE_PASSWORD, challenge, userId);
- }
-
-
- /**
- * Check to see if a password matches the saved password.
- * If password matches, return an opaque attestation that the challenge
- * was verified.
- *
- * @param password The password to check.
- * @param challenge The challenge to verify against the password
- * @return the attestation that the challenge was verified, or null.
- */
- public byte[] verifyTiedProfileChallenge(byte[] password, boolean isPattern, long challenge,
- int userId) throws RequestThrottledException {
+ public byte[] verifyTiedProfileChallenge(@NonNull LockscreenCredential credential,
+ long challenge, int userId) throws RequestThrottledException {
throwIfCalledOnMainThread();
try {
VerifyCredentialResponse response =
- getLockSettings().verifyTiedProfileChallenge(password,
- isPattern ? CREDENTIAL_TYPE_PATTERN : CREDENTIAL_TYPE_PASSWORD, challenge,
- userId);
+ getLockSettings().verifyTiedProfileChallenge(
+ credential.getCredential(), credential.getType(), challenge, userId);
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
return response.getPayload();
@@ -489,61 +463,6 @@
}
/**
- *
- * Check to see if a password matches the saved password. If no password exists,
- * always returns true.
- * @param password The password to check.
- * @return Whether the password matches the stored one.
- */
- @UnsupportedAppUsage
- public boolean checkPassword(String password, int userId) throws RequestThrottledException {
- byte[] passwordBytes = password != null ? password.getBytes() : null;
- return checkPassword(passwordBytes, userId, null /* progressCallback */);
- }
-
-
- /**
- *
- * Check to see if a password matches the saved password. If no password exists,
- * always returns true.
- * @param password The password to check.
- * @return Whether the password matches the stored one.
- */
- public boolean checkPassword(byte[] password, int userId) throws RequestThrottledException {
- return checkPassword(password, userId, null /* progressCallback */);
- }
-
- // TODO(b/120484642): This method is necessary for vendor/qcom code and is a hidden api
- /* *
- * Check to see if a password matches the saved password. If no password exists,
- * always returns true.
- * @param password The password to check.
- * @return Whether the password matches the stored one.
- */
- public boolean checkPassword(String password, int userId,
- @Nullable CheckCredentialProgressCallback progressCallback)
- throws RequestThrottledException {
- byte[] passwordBytes = password != null ? password.getBytes() : null;
- throwIfCalledOnMainThread();
- return checkCredential(passwordBytes, CREDENTIAL_TYPE_PASSWORD, userId, progressCallback);
-
- }
-
- /**
- * Check to see if a password matches the saved password. If no password exists,
- * always returns true.
- * @param password The password to check.
- * @return Whether the password matches the stored one.
- */
-
- public boolean checkPassword(byte[] password, int userId,
- @Nullable CheckCredentialProgressCallback progressCallback)
- throws RequestThrottledException {
- throwIfCalledOnMainThread();
- return checkCredential(password, CREDENTIAL_TYPE_PASSWORD, userId, progressCallback);
- }
-
- /**
* Check to see if vold already has the password.
* Note that this also clears vold's copy of the password.
* @return Whether the vold password matches or not.
@@ -560,9 +479,10 @@
* Returns the password history hash factor, needed to check new password against password
* history with {@link #checkPasswordHistory(byte[], byte[], int)}
*/
- public byte[] getPasswordHistoryHashFactor(byte[] currentPassword, int userId) {
+ public byte[] getPasswordHistoryHashFactor(@NonNull LockscreenCredential currentPassword,
+ int userId) {
try {
- return getLockSettings().getHashFactor(currentPassword, userId);
+ return getLockSettings().getHashFactor(currentPassword.getCredential(), userId);
} catch (RemoteException e) {
Log.e(TAG, "failed to get hash factor", e);
return null;
@@ -679,57 +599,6 @@
}
/**
- * Clear any lock pattern or password.
-
- * <p> This method will fail (returning {@code false}) if the previously
- * saved password provided is incorrect, or if the lockscreen verification
- * is still being throttled.
- *
- * @param savedCredential The previously saved credential
- * @param userHandle the user whose pattern is to be saved.
- * @return whether this was successful or not.
- * @throws RuntimeException if password change encountered an unrecoverable error.
- */
- public boolean clearLock(byte[] savedCredential, int userHandle) {
- return clearLock(savedCredential, userHandle, false);
- }
-
- /**
- * Clear any lock pattern or password, with the option to ignore incorrect existing credential.
- * <p> This method will fail (returning {@code false}) if the previously
- * saved password provided is incorrect, or if the lockscreen verification
- * is still being throttled.
- *
- * @param savedCredential The previously saved credential
- * @param userHandle the user whose pattern is to be saved.
- * @return whether this was successful or not.
- * @throws RuntimeException if password change encountered an unrecoverable error.
- */
- public boolean clearLock(byte[] savedCredential, int userHandle, boolean allowUntrustedChange) {
- final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
- setKeyguardStoredPasswordQuality(PASSWORD_QUALITY_UNSPECIFIED, userHandle);
-
- try {
- if (!getLockSettings().setLockCredential(null, CREDENTIAL_TYPE_NONE, savedCredential,
- PASSWORD_QUALITY_UNSPECIFIED, userHandle, allowUntrustedChange)) {
- return false;
- }
- } catch (RemoteException | RuntimeException e) {
- setKeyguardStoredPasswordQuality(currentQuality, userHandle);
- throw new RuntimeException("Failed to clear lock", e);
- }
-
- if (userHandle == UserHandle.USER_SYSTEM) {
- // Set the encryption password to default.
- updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
- setCredentialRequiredToDecrypt(false);
- }
-
- onAfterChangingPassword(userHandle);
- return true;
- }
-
- /**
* Disable showing lock screen at all for a given user.
* This is only meaningful if pattern, pin or password are not set.
*
@@ -762,75 +631,88 @@
|| isDemoUser;
}
- /**
- * Save a lock pattern.
- *
- * <p> This method will fail (returning {@code false}) if the previously saved pattern provided
- * is incorrect, or if the lockscreen verification is still being throttled.
- *
- * @param pattern The new pattern to save.
- * @param savedPattern The previously saved pattern, converted to byte[] format
- * @param userId the user whose pattern is to be saved.
- * @return whether this was successful or not.
- * @throws RuntimeException if password change encountered an unrecoverable error.
- */
- public boolean saveLockPattern(List<LockPatternView.Cell> pattern, byte[] savedPattern,
- int userId) {
- return saveLockPattern(pattern, savedPattern, userId, false);
+ /** Returns if the given quality maps to an alphabetic password */
+ public static boolean isQualityAlphabeticPassword(int quality) {
+ return quality >= PASSWORD_QUALITY_ALPHABETIC;
+ }
+
+ /** Returns if the given quality maps to an numeric pin */
+ public static boolean isQualityNumericPin(int quality) {
+ return quality == PASSWORD_QUALITY_NUMERIC || quality == PASSWORD_QUALITY_NUMERIC_COMPLEX;
}
/**
- * Save a lock pattern.
+ * Save a new lockscreen credential.
*
- * <p> This method will fail (returning {@code false}) if the previously saved pattern provided
- * is incorrect, or if the lockscreen verification is still being throttled.
+ * <p> This method will fail (returning {@code false}) if the previously saved credential
+ * provided is incorrect, or if the lockscreen verification is still being throttled.
*
- * @param pattern The new pattern to save.
- * @param savedPattern The previously saved pattern, converted to byte[] format
- * @param userId the user whose pattern is to be saved.
- * @param allowUntrustedChange whether we want to allow saving a new password if the existing
- * password being provided is incorrect.
- * @return whether this was successful or not.
+ * @param newCredential The new credential to save
+ * @param savedCredential The current credential
+ * @param userId the user whose lockscreen credential is to be changed
+ *
+ * @return whether this method saved the new password successfully or not. This flow will fail
+ * and return false if the given credential is wrong.
* @throws RuntimeException if password change encountered an unrecoverable error.
*/
- public boolean saveLockPattern(List<LockPatternView.Cell> pattern, byte[] savedPattern,
- int userId, boolean allowUntrustedChange) {
+ public boolean setLockCredential(@NonNull LockscreenCredential newCredential,
+ @NonNull LockscreenCredential savedCredential, int userId) {
+ return setLockCredential(newCredential, savedCredential, userId, false);
+ }
+
+ /**
+ * Save a new lockscreen credential.
+ * <p> This method will fail (returning {@code false}) if the previously saved pattern provided
+ * is incorrect and allowUntrustedChange is false, or if the lockscreen verification is still
+ * being throttled.
+ * @param newCredential The new credential to save
+ * @param savedCredential The current credential
+ * @param userHandle the user whose lockscreen credential is to be changed
+ * @param allowUntrustedChange whether we want to allow saving a new pattern if the existing
+ * credentialt being provided is incorrect.
+ *
+ * @return whether this method saved the new password successfully or not. This flow will fail
+ * and return false if the given credential is wrong and allowUntrustedChange is false.
+ * @throws RuntimeException if password change encountered an unrecoverable error.
+ */
+ public boolean setLockCredential(@NonNull LockscreenCredential newCredential,
+ @NonNull LockscreenCredential savedCredential, int userHandle,
+ boolean allowUntrustedChange) {
if (!hasSecureLockScreen()) {
throw new UnsupportedOperationException(
"This operation requires the lock screen feature.");
}
- if (pattern == null || pattern.size() < MIN_LOCK_PATTERN_SIZE) {
- throw new IllegalArgumentException("pattern must not be null and at least "
- + MIN_LOCK_PATTERN_SIZE + " dots long.");
- }
+ newCredential.checkLength();
- final byte[] bytePattern = patternToByteArray(pattern);
- final int currentQuality = getKeyguardStoredPasswordQuality(userId);
- setKeyguardStoredPasswordQuality(PASSWORD_QUALITY_SOMETHING, userId);
+ final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
+ setKeyguardStoredPasswordQuality(newCredential.getQuality(), userHandle);
+
try {
- if (!getLockSettings().setLockCredential(bytePattern, CREDENTIAL_TYPE_PATTERN,
- savedPattern, PASSWORD_QUALITY_SOMETHING, userId, allowUntrustedChange)) {
+ if (!getLockSettings().setLockCredential(
+ newCredential.getCredential(), newCredential.getType(),
+ savedCredential.getCredential(),
+ newCredential.getQuality(), userHandle, allowUntrustedChange)) {
+ setKeyguardStoredPasswordQuality(currentQuality, userHandle);
return false;
}
} catch (RemoteException | RuntimeException e) {
- setKeyguardStoredPasswordQuality(currentQuality, userId);
- throw new RuntimeException("Couldn't save lock pattern", e);
- }
- // Update the device encryption password.
- if (userId == UserHandle.USER_SYSTEM
- && LockPatternUtils.isDeviceEncryptionEnabled()) {
- if (!shouldEncryptWithCredentials(true)) {
- clearEncryptionPassword();
- } else {
- updateEncryptionPassword(StorageManager.CRYPT_TYPE_PATTERN, bytePattern);
- }
+ setKeyguardStoredPasswordQuality(currentQuality, userHandle);
+ throw new RuntimeException("Unable to save lock password", e);
}
- reportPatternWasChosen(userId);
- onAfterChangingPassword(userId);
+ onPostPasswordChanged(newCredential, userHandle);
return true;
}
+ private void onPostPasswordChanged(LockscreenCredential newCredential, int userHandle) {
+ updateEncryptionPasswordIfNeeded(newCredential, userHandle);
+ if (newCredential.isPattern()) {
+ reportPatternWasChosen(userHandle);
+ }
+ updatePasswordHistory(newCredential, userHandle);
+ reportEnabledTrustAgentsChanged(userHandle);
+ }
+
private void updateCryptoUserInfo(int userId) {
if (userId != UserHandle.USER_SYSTEM) {
return;
@@ -929,149 +811,35 @@
}
/**
- * Save a lock password. Does not ensure that the password is as good
- * as the requested mode, but will adjust the mode to be as good as the
- * password.
- *
- * <p> This method will fail (returning {@code false}) if the previously
- * saved password provided is incorrect, or if the lockscreen verification
- * is still being throttled.
- *
- * @param password The password to save
- * @param savedPassword The previously saved lock password, or null if none
- * @param requestedQuality {@see DevicePolicyManager#getPasswordQuality(
- * android.content.ComponentName)}
- * @param userHandle The userId of the user to change the password for
- * @return whether this was successful or not.
- * @throws RuntimeException if password change encountered an unrecoverable error.
- * @deprecated Pass password as a byte array
- */
- @Deprecated
- public boolean saveLockPassword(String password, String savedPassword, int requestedQuality,
- int userHandle) {
- byte[] passwordBytes = password != null ? password.getBytes() : null;
- byte[] savedPasswordBytes = savedPassword != null ? savedPassword.getBytes() : null;
- return saveLockPassword(passwordBytes, savedPasswordBytes, requestedQuality, userHandle);
- }
-
- /**
- * Save a lock password. Does not ensure that the password is as good
- * as the requested mode, but will adjust the mode to be as good as the
- * password.
- *
- * <p> This method will fail (returning {@code false}) if the previously
- * saved password provided is incorrect, or if the lockscreen verification
- * is still being throttled.
- *
- * @param password The password to save
- * @param savedPassword The previously saved lock password, or null if none
- * @param requestedQuality {@see DevicePolicyManager#getPasswordQuality(
- * android.content.ComponentName)}
- * @param userHandle The userId of the user to change the password for
- * @return whether this was successful or not.
- * @throws RuntimeException if password change encountered an unrecoverable error.
- */
- public boolean saveLockPassword(byte[] password, byte[] savedPassword, int requestedQuality,
- int userHandle) {
- return saveLockPassword(password, savedPassword, requestedQuality,
- userHandle, false);
- }
-
- /**
- * Save a lock password. Does not ensure that the password is as good
- * as the requested mode, but will adjust the mode to be as good as the
- * password.
- *
- * <p> This method will fail (returning {@code false}) if the previously
- * saved password provided is incorrect, or if the lockscreen verification
- * is still being throttled.
- *
- * @param password The password to save
- * @param savedPassword The previously saved lock password, or null if none
- * @param requestedQuality {@see DevicePolicyManager#getPasswordQuality(
- * android.content.ComponentName)}
- * @param userHandle The userId of the user to change the password for
- * @param allowUntrustedChange whether we want to allow saving a new password if the existing
- * password being provided is incorrect.
- * @return whether this method saved the new password successfully or not. This flow will fail
- * and return false if the given credential is wrong and allowUntrustedChange is false.
- * @throws RuntimeException if password change encountered an unrecoverable error.
- */
- public boolean saveLockPassword(byte[] password, byte[] savedPassword,
- int requestedQuality, int userHandle, boolean allowUntrustedChange) {
- if (!hasSecureLockScreen()) {
- throw new UnsupportedOperationException(
- "This operation requires the lock screen feature.");
- }
- if (password == null || password.length < MIN_LOCK_PASSWORD_SIZE) {
- throw new IllegalArgumentException("password must not be null and at least "
- + "of length " + MIN_LOCK_PASSWORD_SIZE);
- }
-
- if (requestedQuality < PASSWORD_QUALITY_NUMERIC) {
- throw new IllegalArgumentException("quality must be at least NUMERIC, but was "
- + requestedQuality);
- }
-
- final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
- final int passwordQuality = PasswordMetrics.computeForPassword(password).quality;
- final int newKeyguardQuality =
- computeKeyguardQuality(CREDENTIAL_TYPE_PASSWORD, requestedQuality, passwordQuality);
- setKeyguardStoredPasswordQuality(newKeyguardQuality, userHandle);
- try {
- getLockSettings().setLockCredential(password, CREDENTIAL_TYPE_PASSWORD, savedPassword,
- requestedQuality, userHandle, allowUntrustedChange);
- } catch (RemoteException | RuntimeException e) {
- setKeyguardStoredPasswordQuality(currentQuality, userHandle);
- throw new RuntimeException("Unable to save lock password", e);
- }
-
- updateEncryptionPasswordIfNeeded(password, passwordQuality, userHandle);
- updatePasswordHistory(password, userHandle);
- onAfterChangingPassword(userHandle);
- return true;
- }
-
- /**
- * Compute keyguard credential quality to store in PASSWORD_TYPE_KEY by computing max between
- * them so that digit-only password is distinguished from PIN.
- *
- * TODO: remove this method and make CREDENTIAL_TYPE distinguish between PIN and password, so
- * that this quality is no longer needs to be persisted.
- */
- private int computeKeyguardQuality(
- @CredentialType int credentialType, int requestedQuality, int passwordQuality) {
- return credentialType == CREDENTIAL_TYPE_PASSWORD
- ? Math.max(passwordQuality, requestedQuality) : passwordQuality;
- }
-
- /**
* Update device encryption password if calling user is USER_SYSTEM and device supports
* encryption.
*/
- private void updateEncryptionPasswordIfNeeded(byte[] password, int quality, int userHandle) {
+ private void updateEncryptionPasswordIfNeeded(LockscreenCredential credential, int userHandle) {
// Update the device encryption password.
- if (userHandle == UserHandle.USER_SYSTEM
- && LockPatternUtils.isDeviceEncryptionEnabled()) {
- if (!shouldEncryptWithCredentials(true)) {
- clearEncryptionPassword();
- } else {
- boolean numeric = quality == PASSWORD_QUALITY_NUMERIC;
- boolean numericComplex = quality == PASSWORD_QUALITY_NUMERIC_COMPLEX;
- int type = numeric || numericComplex ? StorageManager.CRYPT_TYPE_PIN
- : StorageManager.CRYPT_TYPE_PASSWORD;
- updateEncryptionPassword(type, password);
- }
+ if (userHandle != UserHandle.USER_SYSTEM || !isDeviceEncryptionEnabled()) {
+ return;
}
+ if (!shouldEncryptWithCredentials(true)) {
+ updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
+ return;
+ }
+ if (credential.isNone()) {
+ // Set the encryption password to default.
+ setCredentialRequiredToDecrypt(false);
+ }
+ updateEncryptionPassword(credential.getStorageCryptType(), credential.getCredential());
}
/**
* Store the hash of the *current* password in the password history list, if device policy
* enforces password history requirement.
*/
- private void updatePasswordHistory(byte[] password, int userHandle) {
- if (password == null || password.length == 0) {
- Log.e(TAG, "checkPasswordHistory: empty password");
+ private void updatePasswordHistory(LockscreenCredential password, int userHandle) {
+ if (password.isNone()) {
+ return;
+ }
+ if (password.isPattern()) {
+ // Do not keep track of historical patterns
return;
}
// Add the password to the password history. We assume all
@@ -1085,10 +853,10 @@
passwordHistory = "";
} else {
final byte[] hashFactor = getPasswordHistoryHashFactor(password, userHandle);
- String hash = passwordToHistoryHash(password, hashFactor, userHandle);
+ String hash = passwordToHistoryHash(password.getCredential(), hashFactor, userHandle);
if (hash == null) {
Log.e(TAG, "Compute new style password hash failed, fallback to legacy style");
- hash = legacyPasswordToHash(password, userHandle);
+ hash = legacyPasswordToHash(password.getCredential(), userHandle);
}
if (TextUtils.isEmpty(passwordHistory)) {
passwordHistory = hash;
@@ -1132,8 +900,8 @@
}
/**
- * Retrieves the quality mode for {@param userHandle}.
- * {@see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
+ * Retrieves the quality mode for {@code userHandle}.
+ * @see DevicePolicyManager#getPasswordQuality(android.content.ComponentName)
*
* @return stored password quality
*/
@@ -1147,37 +915,37 @@
}
/**
- * Enables/disables the Separate Profile Challenge for this {@param userHandle}. This is a no-op
+ * Enables/disables the Separate Profile Challenge for this {@code userHandle}. This is a no-op
* for user handles that do not belong to a managed profile.
*
* @param userHandle Managed profile user id
* @param enabled True if separate challenge is enabled
- * @param managedUserPassword Managed profile previous password. Null when {@param enabled} is
+ * @param managedUserPassword Managed profile previous password. Null when {@code enabled} is
* true
*/
public void setSeparateProfileChallengeEnabled(int userHandle, boolean enabled,
- byte[] managedUserPassword) {
+ LockscreenCredential managedUserPassword) {
if (!isManagedProfile(userHandle)) {
return;
}
try {
getLockSettings().setSeparateProfileChallengeEnabled(userHandle, enabled,
- managedUserPassword);
- onAfterChangingPassword(userHandle);
+ managedUserPassword.getCredential());
+ reportEnabledTrustAgentsChanged(userHandle);
} catch (RemoteException e) {
Log.e(TAG, "Couldn't update work profile challenge enabled");
}
}
/**
- * Returns true if {@param userHandle} is a managed profile with separate challenge.
+ * Returns true if {@code userHandle} is a managed profile with separate challenge.
*/
public boolean isSeparateProfileChallengeEnabled(int userHandle) {
return isManagedProfile(userHandle) && hasSeparateChallenge(userHandle);
}
/**
- * Returns true if {@param userHandle} is a managed profile with unified challenge.
+ * Returns true if {@code userHandle} is a managed profile with unified challenge.
*/
public boolean isManagedProfileWithUnifiedChallenge(int userHandle) {
return isManagedProfile(userHandle) && !hasSeparateChallenge(userHandle);
@@ -1218,20 +986,6 @@
/**
* Deserialize a pattern.
- * @param string The pattern serialized with {@link #patternToString}
- * @return The pattern.
- * @deprecated Pass patterns as byte[] and use byteArrayToPattern
- */
- @Deprecated
- public static List<LockPatternView.Cell> stringToPattern(String string) {
- if (string == null) {
- return null;
- }
- return byteArrayToPattern(string.getBytes());
- }
-
- /**
- * Deserialize a pattern.
* @param bytes The pattern serialized with {@link #patternToByteArray}
* @return The pattern.
*/
@@ -1252,19 +1006,6 @@
/**
* Serialize a pattern.
* @param pattern The pattern.
- * @return The pattern in string form.
- * @deprecated Use patternToByteArray instead.
- */
- @UnsupportedAppUsage
- @Deprecated
- public static String patternToString(List<LockPatternView.Cell> pattern) {
- return new String(patternToByteArray(pattern));
- }
-
-
- /**
- * Serialize a pattern.
- * @param pattern The pattern.
* @return The pattern in byte array form.
*/
public static byte[] patternToByteArray(List<LockPatternView.Cell> pattern) {
@@ -1281,34 +1022,6 @@
return res;
}
- /*
- * Generate an SHA-1 hash for the pattern. Not the most secure, but it is
- * at least a second level of protection. First level is that the file
- * is in a location only readable by the system process.
- * @param pattern the gesture pattern.
- * @return the hash of the pattern in a byte array.
- */
- @UnsupportedAppUsage
- public static byte[] patternToHash(List<LockPatternView.Cell> pattern) {
- if (pattern == null) {
- return null;
- }
-
- final int patternSize = pattern.size();
- byte[] res = new byte[patternSize];
- for (int i = 0; i < patternSize; i++) {
- LockPatternView.Cell cell = pattern.get(i);
- res[i] = (byte) (cell.getRow() * 3 + cell.getColumn());
- }
- try {
- MessageDigest md = MessageDigest.getInstance("SHA-1");
- byte[] hash = md.digest(res);
- return hash;
- } catch (NoSuchAlgorithmException nsa) {
- return res;
- }
- }
-
private String getSalt(int userId) {
long salt = getLong(LOCK_PASSWORD_SALT_KEY, 0, userId);
if (salt == 0) {
@@ -1332,6 +1045,7 @@
* @param password the gesture pattern.
*
* @return the hash of the pattern in a byte array.
+ * TODO: move to LockscreenCredential class
*/
public String legacyPasswordToHash(byte[] password, int userId) {
if (password == null || password.length == 0) {
@@ -1362,6 +1076,7 @@
/**
* Hash the password for password history check purpose.
+ * TODO: move to LockscreenCredential class
*/
private String passwordToHistoryHash(byte[] passwordToHash, byte[] hashFactor, int userId) {
if (passwordToHash == null || passwordToHash.length == 0 || hashFactor == null) {
@@ -1629,7 +1344,7 @@
}
/**
- * Disable trust until credentials have been entered for user {@param userId}.
+ * Disable trust until credentials have been entered for user {@code userId}.
*
* Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
*
@@ -1640,7 +1355,7 @@
}
/**
- * Requests strong authentication for user {@param userId}.
+ * Requests strong authentication for user {@code userId}.
*
* Requires the {@link android.Manifest.permission#ACCESS_KEYGUARD_SECURE_STORAGE} permission.
*
@@ -1657,7 +1372,7 @@
}
}
- private void onAfterChangingPassword(int userHandle) {
+ private void reportEnabledTrustAgentsChanged(int userHandle) {
getTrustManager().reportEnabledTrustAgentsChanged(userHandle);
}
@@ -1823,54 +1538,36 @@
* <p>This method is only available to code running in the system server process itself.
*
* @param credential The new credential to be set
- * @param type Credential type: password / pattern / none.
- * @param requestedQuality the requested password quality by DevicePolicyManager.
- * See {@link DevicePolicyManager#getPasswordQuality(android.content.ComponentName)}
* @param tokenHandle Handle of the escrow token
* @param token Escrow token
- * @param userId The user who's lock credential to be changed
+ * @param userHandle The user who's lock credential to be changed
* @return {@code true} if the operation is successful.
*/
- public boolean setLockCredentialWithToken(byte[] credential, int type, int requestedQuality,
- long tokenHandle, byte[] token, int userId) {
+ public boolean setLockCredentialWithToken(LockscreenCredential credential, long tokenHandle,
+ byte[] token, int userHandle) {
if (!hasSecureLockScreen()) {
throw new UnsupportedOperationException(
"This operation requires the lock screen feature.");
}
+ credential.checkLength();
LockSettingsInternal localService = getLockSettingsInternal();
- if (type != CREDENTIAL_TYPE_NONE) {
- if (credential == null || credential.length < MIN_LOCK_PASSWORD_SIZE) {
- throw new IllegalArgumentException("password must not be null and at least "
- + "of length " + MIN_LOCK_PASSWORD_SIZE);
- }
- final int quality = PasswordMetrics.computeForCredential(type, credential).quality;
- final int keyguardQuality = computeKeyguardQuality(type, quality, requestedQuality);
- if (!localService.setLockCredentialWithToken(credential, type, tokenHandle, token,
- keyguardQuality, userId)) {
+
+ final int currentQuality = getKeyguardStoredPasswordQuality(userHandle);
+ setKeyguardStoredPasswordQuality(credential.getQuality(), userHandle);
+
+ try {
+ if (!localService.setLockCredentialWithToken(credential.getCredential(),
+ credential.getType(),
+ tokenHandle, token, credential.getType(), userHandle)) {
+ setKeyguardStoredPasswordQuality(currentQuality, userHandle);
return false;
}
- setKeyguardStoredPasswordQuality(quality, userId);
-
- updateEncryptionPasswordIfNeeded(credential, quality, userId);
- updatePasswordHistory(credential, userId);
- onAfterChangingPassword(userId);
- } else {
- if (!(credential == null || credential.length == 0)) {
- throw new IllegalArgumentException("password must be emtpy for NONE type");
- }
- if (!localService.setLockCredentialWithToken(null, CREDENTIAL_TYPE_NONE, tokenHandle,
- token, PASSWORD_QUALITY_UNSPECIFIED, userId)) {
- return false;
- }
- setKeyguardStoredPasswordQuality(PASSWORD_QUALITY_UNSPECIFIED, userId);
-
- if (userId == UserHandle.USER_SYSTEM) {
- // Set the encryption password to default.
- updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null);
- setCredentialRequiredToDecrypt(false);
- }
+ } catch (RuntimeException e) {
+ setKeyguardStoredPasswordQuality(currentQuality, userHandle);
+ throw new RuntimeException("Unable to save lock credential", e);
}
- onAfterChangingPassword(userId);
+
+ onPostPasswordChanged(credential, userHandle);
return true;
}
@@ -1997,7 +1694,7 @@
}
/**
- * @return true if unlocking with trust alone is allowed for {@param userId} by the current
+ * @return true if unlocking with trust alone is allowed for {@code userId} by the current
* strong authentication requirements.
*/
public boolean isTrustAllowedForUser(int userId) {
@@ -2005,7 +1702,7 @@
}
/**
- * @return true if unlocking with a biometric method alone is allowed for {@param userId}
+ * @return true if unlocking with a biometric method alone is allowed for {@code userId}
* by the current strong authentication requirements.
*/
public boolean isBiometricAllowedForUser(int userId) {
@@ -2013,7 +1710,7 @@
}
/**
- * Called when the strong authentication requirements for {@param userId} changed.
+ * Called when the strong authentication requirements for {@code userId} changed.
*/
public void onStrongAuthRequiredChanged(int userId) {
}
@@ -2102,22 +1799,4 @@
return FRP_CREDENTIAL_ENABLED && context.getResources().getBoolean(
com.android.internal.R.bool.config_enableCredentialFactoryResetProtection);
}
-
- /**
- * Converts a CharSequence to a byte array without requiring a toString(), which creates an
- * additional copy.
- *
- * @param chars The CharSequence to convert
- * @return A byte array representing the input
- */
- public static byte[] charSequenceToByteArray(CharSequence chars) {
- if (chars == null) {
- return null;
- }
- byte[] bytes = new byte[chars.length()];
- for (int i = 0; i < chars.length(); i++) {
- bytes[i] = (byte) chars.charAt(i);
- }
- return bytes;
- }
}
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 3f6c4d4..74a0aa3 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -1318,7 +1318,7 @@
super.onRestoreInstanceState(ss.getSuperState());
setPattern(
DisplayMode.Correct,
- LockPatternUtils.stringToPattern(ss.getSerializedPattern()));
+ LockPatternUtils.byteArrayToPattern(ss.getSerializedPattern().getBytes()));
mPatternDisplayMode = DisplayMode.values()[ss.getDisplayMode()];
mInputEnabled = ss.isInputEnabled();
mInStealthMode = ss.isInStealthMode();
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
new file mode 100644
index 0000000..19e6d97
--- /dev/null
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.widget;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.storage.StorageManager;
+import android.text.TextUtils;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A class representing a lockscreen credential. It can be either an empty password, a pattern
+ * or a password (or PIN).
+ *
+ * <p> As required by some security certification, the framework tries its best to
+ * remove copies of the lockscreen credential bytes from memory. In this regard, this class
+ * abuses the {@link AutoCloseable} interface for sanitizing memory. This
+ * presents a nice syntax to auto-zeroize memory with the try-with-resource statement:
+ * <pre>
+ * try {LockscreenCredential credential = LockscreenCredential.createPassword(...) {
+ * // Process the credential in some way
+ * }
+ * </pre>
+ * With this construct, we can garantee that there will be no copies of the password left in
+ * memory when the credential goes out of scope. This should help mitigate certain class of
+ * attacks where the attcker gains read-only access to full device memory (cold boot attack,
+ * unsecured software/hardware memory dumping interfaces such as JTAG).
+ */
+public class LockscreenCredential implements Parcelable, AutoCloseable {
+
+ private final int mType;
+ // Stores raw credential bytes, or null if credential has been zeroized. An empty password
+ // is represented as a byte array of length 0.
+ private byte[] mCredential;
+ // Store the quality of the password, this is used to distinguish between pin
+ // (PASSWORD_QUALITY_NUMERIC) and password (PASSWORD_QUALITY_ALPHABETIC).
+ private final int mQuality;
+
+ /**
+ * Private constructor, use static builder methods instead.
+ *
+ * <p> Builder methods should create a private copy of the credential bytes and pass in here.
+ * LockscreenCredential will only store the reference internally without copying. This is to
+ * minimize the number of extra copies introduced.
+ */
+ private LockscreenCredential(int type, int quality, byte[] credential) {
+ Preconditions.checkNotNull(credential);
+ if (type == CREDENTIAL_TYPE_NONE) {
+ Preconditions.checkArgument(credential.length == 0);
+ } else {
+ Preconditions.checkArgument(credential.length > 0);
+ }
+ mType = type;
+ mQuality = quality;
+ mCredential = credential;
+ }
+
+ /**
+ * Creates a LockscreenCredential object representing empty password.
+ */
+ public static LockscreenCredential createNone() {
+ return new LockscreenCredential(CREDENTIAL_TYPE_NONE, PASSWORD_QUALITY_UNSPECIFIED,
+ new byte[0]);
+ }
+
+ /**
+ * Creates a LockscreenCredential object representing the given pattern.
+ */
+ public static LockscreenCredential createPattern(@NonNull List<LockPatternView.Cell> pattern) {
+ return new LockscreenCredential(CREDENTIAL_TYPE_PATTERN,
+ PASSWORD_QUALITY_SOMETHING,
+ LockPatternUtils.patternToByteArray(pattern));
+ }
+
+ /**
+ * Creates a LockscreenCredential object representing the given alphabetic password.
+ */
+ public static LockscreenCredential createPassword(@NonNull CharSequence password) {
+ return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
+ PASSWORD_QUALITY_ALPHABETIC,
+ charSequenceToByteArray(password));
+ }
+
+ /**
+ * Creates a LockscreenCredential object representing the given numeric PIN.
+ */
+ public static LockscreenCredential createPin(@NonNull CharSequence pin) {
+ return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
+ PASSWORD_QUALITY_NUMERIC,
+ charSequenceToByteArray(pin));
+ }
+
+ /**
+ * Creates a LockscreenCredential object representing the given alphabetic password.
+ * If the supplied password is empty, create an empty credential object.
+ */
+ public static LockscreenCredential createPasswordOrNone(@Nullable CharSequence password) {
+ if (TextUtils.isEmpty(password)) {
+ return createNone();
+ } else {
+ return createPassword(password);
+ }
+ }
+
+ /**
+ * Creates a LockscreenCredential object representing the given numeric PIN.
+ * If the supplied password is empty, create an empty credential object.
+ */
+ public static LockscreenCredential createPinOrNone(@Nullable CharSequence pin) {
+ if (TextUtils.isEmpty(pin)) {
+ return createNone();
+ } else {
+ return createPin(pin);
+ }
+ }
+
+ /**
+ * Create a LockscreenCredential object based on raw credential and type
+ * TODO: Remove once LSS.setUserPasswordMetrics accepts a LockscreenCredential
+ */
+ public static LockscreenCredential createRaw(int type, byte[] credential) {
+ if (type == CREDENTIAL_TYPE_NONE) {
+ return createNone();
+ } else {
+ return new LockscreenCredential(type, PASSWORD_QUALITY_UNSPECIFIED, credential);
+ }
+ }
+
+ private void ensureNotZeroized() {
+ Preconditions.checkState(mCredential != null, "Credential is already zeroized");
+ }
+ /**
+ * Returns the type of this credential. Can be one of {@link #CREDENTIAL_TYPE_NONE},
+ * {@link #CREDENTIAL_TYPE_PATTERN} or {@link #CREDENTIAL_TYPE_PASSWORD}.
+ *
+ * TODO: Remove once credential type is internal. Callers should use {@link #isNone},
+ * {@link #isPattern} and {@link #isPassword} instead.
+ */
+ public int getType() {
+ ensureNotZeroized();
+ return mType;
+ }
+
+ /**
+ * Returns the quality type of the credential
+ */
+ public int getQuality() {
+ ensureNotZeroized();
+ return mQuality;
+ }
+
+ /**
+ * Returns the credential bytes. This is a direct reference of the internal field so
+ * callers should not modify it.
+ *
+ */
+ public byte[] getCredential() {
+ ensureNotZeroized();
+ return mCredential;
+ }
+
+ /**
+ * Returns the credential type recognized by {@link StorageManager}. Can be one of
+ * {@link StorageManager#CRYPT_TYPE_DEFAULT}, {@link StorageManager#CRYPT_TYPE_PATTERN},
+ * {@link StorageManager#CRYPT_TYPE_PIN} or {@link StorageManager#CRYPT_TYPE_PASSWORD}.
+ */
+ public int getStorageCryptType() {
+ if (isNone()) {
+ return StorageManager.CRYPT_TYPE_DEFAULT;
+ }
+ if (isPattern()) {
+ return StorageManager.CRYPT_TYPE_PATTERN;
+ }
+ if (isPassword()) {
+ return mQuality == PASSWORD_QUALITY_NUMERIC
+ ? StorageManager.CRYPT_TYPE_PIN : StorageManager.CRYPT_TYPE_PASSWORD;
+ }
+ throw new IllegalStateException("Unhandled credential type");
+ }
+
+ /** Returns whether this is an empty credential */
+ public boolean isNone() {
+ ensureNotZeroized();
+ return mType == CREDENTIAL_TYPE_NONE;
+ }
+
+ /** Returns whether this is a pattern credential */
+ public boolean isPattern() {
+ ensureNotZeroized();
+ return mType == CREDENTIAL_TYPE_PATTERN;
+ }
+
+ /** Returns whether this is a password credential */
+ public boolean isPassword() {
+ ensureNotZeroized();
+ return mType == CREDENTIAL_TYPE_PASSWORD;
+ }
+
+ /** Returns the length of the credential */
+ public int size() {
+ ensureNotZeroized();
+ return mCredential.length;
+ }
+
+ /** Create a copy of the credential */
+ public LockscreenCredential duplicate() {
+ return new LockscreenCredential(mType, mQuality,
+ mCredential != null ? Arrays.copyOf(mCredential, mCredential.length) : null);
+ }
+
+ /**
+ * Zeroize the credential bytes.
+ */
+ public void zeroize() {
+ if (mCredential != null) {
+ Arrays.fill(mCredential, (byte) 0);
+ mCredential = null;
+ }
+ }
+
+ /**
+ * Check if the credential meets minimal length requirement.
+ *
+ * @throws IllegalArgumentException if the credential is too short.
+ */
+ public void checkLength() {
+ if (isNone()) {
+ return;
+ }
+ if (isPattern()) {
+ if (size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
+ throw new IllegalArgumentException("pattern must not be null and at least "
+ + LockPatternUtils.MIN_LOCK_PATTERN_SIZE + " dots long.");
+ }
+ return;
+ }
+ if (isPassword()) {
+ if (size() < LockPatternUtils.MIN_LOCK_PASSWORD_SIZE) {
+ throw new IllegalArgumentException("password must not be null and at least "
+ + "of length " + LockPatternUtils.MIN_LOCK_PASSWORD_SIZE);
+ }
+ return;
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeInt(mQuality);
+ dest.writeByteArray(mCredential);
+ }
+
+ public static final Parcelable.Creator<LockscreenCredential> CREATOR =
+ new Parcelable.Creator<LockscreenCredential>() {
+
+ @Override
+ public LockscreenCredential createFromParcel(Parcel source) {
+ return new LockscreenCredential(source.readInt(), source.readInt(),
+ source.createByteArray());
+ }
+
+ @Override
+ public LockscreenCredential[] newArray(int size) {
+ return new LockscreenCredential[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void close() {
+ zeroize();
+ }
+
+ @Override
+ public int hashCode() {
+ // Effective Java — Item 9
+ return ((17 + mType) * 31 + mQuality) * 31 + mCredential.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if (!(o instanceof LockscreenCredential)) return false;
+ final LockscreenCredential other = (LockscreenCredential) o;
+ return mType == other.mType && mQuality == other.mQuality
+ && Arrays.equals(mCredential, other.mCredential);
+ }
+
+ /**
+ * Converts a CharSequence to a byte array without requiring a toString(), which creates an
+ * additional copy.
+ *
+ * @param chars The CharSequence to convert
+ * @return A byte array representing the input
+ */
+ private static byte[] charSequenceToByteArray(CharSequence chars) {
+ if (chars == null) {
+ return new byte[0];
+ }
+ byte[] bytes = new byte[chars.length()];
+ for (int i = 0; i < chars.length(); i++) {
+ bytes[i] = (byte) chars.charAt(i);
+ }
+ return bytes;
+ }
+}
diff --git a/core/java/com/android/internal/widget/PasswordValidationError.java b/core/java/com/android/internal/widget/PasswordValidationError.java
new file mode 100644
index 0000000..41b234e
--- /dev/null
+++ b/core/java/com/android/internal/widget/PasswordValidationError.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.widget;
+
+/**
+ * Password validation error containing an error code and optional requirement.
+ */
+public class PasswordValidationError {
+ // Password validation error codes
+ public static final int WEAK_CREDENTIAL_TYPE = 1;
+ public static final int CONTAINS_INVALID_CHARACTERS = 2;
+ public static final int TOO_SHORT = 3;
+ public static final int TOO_LONG = 4;
+ public static final int CONTAINS_SEQUENCE = 5;
+ public static final int NOT_ENOUGH_LETTERS = 6;
+ public static final int NOT_ENOUGH_UPPER_CASE = 7;
+ public static final int NOT_ENOUGH_LOWER_CASE = 8;
+ public static final int NOT_ENOUGH_DIGITS = 9;
+ public static final int NOT_ENOUGH_SYMBOLS = 10;
+ public static final int NOT_ENOUGH_NON_LETTER = 11;
+ public static final int NOT_ENOUGH_NON_DIGITS = 12;
+ public static final int RECENTLY_USED = 13;
+ // WARNING: if you add a new error, make sure it is presented to the user correctly in Settings.
+
+ public final int errorCode;
+ public final int requirement;
+
+ public PasswordValidationError(int errorCode) {
+ this(errorCode, 0);
+ }
+
+ public PasswordValidationError(int errorCode, int requirement) {
+ this.errorCode = errorCode;
+ this.requirement = requirement;
+ }
+
+ @Override
+ public String toString() {
+ return errorCodeToString(errorCode) + (requirement > 0 ? "; required: " + requirement : "");
+ }
+
+ /**
+ * Returns textual representation of the error for logging purposes.
+ */
+ private static String errorCodeToString(int error) {
+ switch (error) {
+ case WEAK_CREDENTIAL_TYPE: return "Weak credential type";
+ case CONTAINS_INVALID_CHARACTERS: return "Contains an invalid character";
+ case TOO_SHORT: return "Password too short";
+ case TOO_LONG: return "Password too long";
+ case CONTAINS_SEQUENCE: return "Sequence too long";
+ case NOT_ENOUGH_LETTERS: return "Too few letters";
+ case NOT_ENOUGH_UPPER_CASE: return "Too few upper case letters";
+ case NOT_ENOUGH_LOWER_CASE: return "Too few lower case letters";
+ case NOT_ENOUGH_DIGITS: return "Too few numeric characters";
+ case NOT_ENOUGH_SYMBOLS: return "Too few symbols";
+ case NOT_ENOUGH_NON_LETTER: return "Too few non-letter characters";
+ case NOT_ENOUGH_NON_DIGITS: return "Too few non-numeric characters";
+ case RECENTLY_USED: return "Pin or password was recently used";
+ default: return "Unknown error " + error;
+ }
+ }
+
+}
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 697825d..ea0389f 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -168,6 +168,10 @@
// These are the permitted backup transport service components
final ArraySet<ComponentName> mBackupTransportWhitelist = new ArraySet<>();
+ // These are packages mapped to maps of component class name to default enabled state.
+ final ArrayMap<String, ArrayMap<String, Boolean>> mPackageComponentEnabledState =
+ new ArrayMap<>();
+
// Package names that are exempted from private API blacklisting
final ArraySet<String> mHiddenApiPackageWhitelist = new ArraySet<>();
@@ -301,6 +305,10 @@
return mBackupTransportWhitelist;
}
+ public ArrayMap<String, Boolean> getComponentsEnabledStates(String packageName) {
+ return mPackageComponentEnabledState.get(packageName);
+ }
+
public ArraySet<String> getDisabledUntilUsedPreinstalledCarrierApps() {
return mDisabledUntilUsedPreinstalledCarrierApps;
}
@@ -846,6 +854,14 @@
}
XmlUtils.skipCurrentTag(parser);
} break;
+ case "component-override": {
+ if (allowAppConfigs) {
+ readComponentOverrides(parser, permFile);
+ } else {
+ logNotAllowedInPartition(name, permFile, parser);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
case "backup-transport-whitelisted-service": {
if (allowFeatures) {
String serviceName = parser.getAttributeValue(null, "service");
@@ -1269,6 +1285,54 @@
}
}
+ private void readComponentOverrides(XmlPullParser parser, File permFile)
+ throws IOException, XmlPullParserException {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<component-override> without package in "
+ + permFile + " at " + parser.getPositionDescription());
+ return;
+ }
+
+ pkgname = pkgname.intern();
+
+ final int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ String name = parser.getName();
+ if ("component".equals(name)) {
+ String clsname = parser.getAttributeValue(null, "class");
+ String enabled = parser.getAttributeValue(null, "enabled");
+ if (clsname == null) {
+ Slog.w(TAG, "<component> without class in "
+ + permFile + " at " + parser.getPositionDescription());
+ return;
+ } else if (enabled == null) {
+ Slog.w(TAG, "<component> without enabled in "
+ + permFile + " at " + parser.getPositionDescription());
+ return;
+ }
+
+ if (clsname.startsWith(".")) {
+ clsname = pkgname + clsname;
+ }
+
+ clsname = clsname.intern();
+
+ ArrayMap<String, Boolean> componentEnabledStates =
+ mPackageComponentEnabledState.get(pkgname);
+ if (componentEnabledStates == null) {
+ componentEnabledStates = new ArrayMap<>();
+ mPackageComponentEnabledState.put(pkgname,
+ componentEnabledStates);
+ }
+
+ componentEnabledStates.put(clsname, !"false".equals(enabled));
+ } else {
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
private static boolean isSystemProcess() {
return Process.myUid() == Process.SYSTEM_UID;
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 0feab7f..ce405fe 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -117,6 +117,7 @@
"android_view_RenderNodeAnimator.cpp",
"android_view_Surface.cpp",
"android_view_SurfaceControl.cpp",
+ "android_graphics_BLASTBufferQueue.cpp",
"android_view_SurfaceSession.cpp",
"android_view_TextureView.cpp",
"android_view_VelocityTracker.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index d476d2d..3497f92 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -115,6 +115,7 @@
extern int register_android_content_StringBlock(JNIEnv* env);
extern int register_android_content_XmlBlock(JNIEnv* env);
extern int register_android_content_res_ApkAssets(JNIEnv* env);
+extern int register_android_graphics_BLASTBufferQueue(JNIEnv* env);
extern int register_android_view_DisplayEventReceiver(JNIEnv* env);
extern int register_android_view_InputApplicationHandle(JNIEnv* env);
extern int register_android_view_InputWindowHandle(JNIEnv* env);
@@ -1458,10 +1459,9 @@
REG_JNI(register_android_opengl_jni_GLES31),
REG_JNI(register_android_opengl_jni_GLES31Ext),
REG_JNI(register_android_opengl_jni_GLES32),
-
REG_JNI(register_android_graphics_classes),
+ REG_JNI(register_android_graphics_BLASTBufferQueue),
REG_JNI(register_android_graphics_GraphicBuffer),
-
REG_JNI(register_android_database_CursorWindow),
REG_JNI(register_android_database_SQLiteConnection),
REG_JNI(register_android_database_SQLiteGlobal),
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index bd4862d..6370253 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -16,6 +16,7 @@
#define ATRACE_TAG ATRACE_TAG_RESOURCES
+#include "android-base/logging.h"
#include "android-base/macros.h"
#include "android-base/stringprintf.h"
#include "android-base/unique_fd.h"
@@ -32,7 +33,7 @@
namespace android {
static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system,
- jboolean force_shared_lib, jboolean overlay) {
+ jboolean force_shared_lib, jboolean overlay, jboolean for_loader) {
ScopedUtfChars path(env, java_path);
if (path.c_str() == nullptr) {
return 0;
@@ -46,7 +47,7 @@
} else if (force_shared_lib) {
apk_assets = ApkAssets::LoadAsSharedLibrary(path.c_str(), system);
} else {
- apk_assets = ApkAssets::Load(path.c_str(), system);
+ apk_assets = ApkAssets::Load(path.c_str(), system, for_loader);
}
if (apk_assets == nullptr) {
@@ -58,7 +59,8 @@
}
static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor,
- jstring friendly_name, jboolean system, jboolean force_shared_lib) {
+ jstring friendly_name, jboolean system, jboolean force_shared_lib,
+ jboolean for_loader) {
ScopedUtfChars friendly_name_utf8(env, friendly_name);
if (friendly_name_utf8.c_str() == nullptr) {
return 0;
@@ -80,7 +82,9 @@
std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd),
friendly_name_utf8.c_str(),
- system, force_shared_lib);
+ system, force_shared_lib,
+ for_loader);
+
if (apk_assets == nullptr) {
std::string error_msg = base::StringPrintf("Failed to load asset path %s from fd %d",
friendly_name_utf8.c_str(), dup_fd.get());
@@ -90,6 +94,60 @@
return reinterpret_cast<jlong>(apk_assets.release());
}
+static jlong NativeLoadArsc(JNIEnv* env, jclass /*clazz*/, jstring java_path,
+ jboolean for_loader) {
+ ScopedUtfChars path(env, java_path);
+ if (path.c_str() == nullptr) {
+ return 0;
+ }
+
+ ATRACE_NAME(base::StringPrintf("LoadApkAssetsArsc(%s)", path.c_str()).c_str());
+
+ std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadArsc(path.c_str(), for_loader);
+
+ if (apk_assets == nullptr) {
+ std::string error_msg = base::StringPrintf("Failed to load asset path %s", path.c_str());
+ jniThrowException(env, "java/io/IOException", error_msg.c_str());
+ return 0;
+ }
+ return reinterpret_cast<jlong>(apk_assets.release());
+}
+
+static jlong NativeLoadArscFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor,
+ jstring friendly_name, jboolean for_loader) {
+ ScopedUtfChars friendly_name_utf8(env, friendly_name);
+ if (friendly_name_utf8.c_str() == nullptr) {
+ return 0;
+ }
+
+ int fd = jniGetFDFromFileDescriptor(env, file_descriptor);
+ ATRACE_NAME(base::StringPrintf("LoadApkAssetsArscFd(%d)", fd).c_str());
+ if (fd < 0) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
+ return 0;
+ }
+
+ unique_fd dup_fd(::fcntl(fd, F_DUPFD_CLOEXEC, 0));
+ if (dup_fd < 0) {
+ jniThrowIOException(env, errno);
+ return 0;
+ }
+
+ std::unique_ptr<const ApkAssets> apk_assets =
+ ApkAssets::LoadArsc(std::move(dup_fd), friendly_name_utf8.c_str(), for_loader);
+ if (apk_assets == nullptr) {
+ std::string error_msg = base::StringPrintf("Failed to load asset path from fd %d", fd);
+ jniThrowException(env, "java/io/IOException", error_msg.c_str());
+ return 0;
+ }
+ return reinterpret_cast<jlong>(apk_assets.release());
+}
+
+static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jboolean for_loader) {
+ std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadEmpty(for_loader);
+ return reinterpret_cast<jlong>(apk_assets.release());
+}
+
static void NativeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
delete reinterpret_cast<ApkAssets*>(ptr);
}
@@ -138,9 +196,13 @@
// JNI registration.
static const JNINativeMethod gApkAssetsMethods[] = {
- {"nativeLoad", "(Ljava/lang/String;ZZZ)J", (void*)NativeLoad},
- {"nativeLoadFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;ZZ)J",
+ {"nativeLoad", "(Ljava/lang/String;ZZZZ)J", (void*)NativeLoad},
+ {"nativeLoadFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;ZZZ)J",
(void*)NativeLoadFromFd},
+ {"nativeLoadArsc", "(Ljava/lang/String;Z)J", (void*)NativeLoadArsc},
+ {"nativeLoadArscFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;Z)J",
+ (void*)NativeLoadArscFromFd},
+ {"nativeLoadEmpty", "(Z)J", (void*)NativeLoadEmpty},
{"nativeDestroy", "(J)V", (void*)NativeDestroy},
{"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
{"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
new file mode 100644
index 0000000..185e581
--- /dev/null
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "BLASTBufferQueue"
+
+#include <nativehelper/JNIHelp.h>
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/android_view_Surface.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+
+#include <gui/BLASTBufferQueue.h>
+#include <gui/Surface.h>
+#include <gui/SurfaceComposerClient.h>
+
+namespace android {
+
+static jlong nativeCreate(JNIEnv* env, jclass clazz, jlong surfaceControl, jlong width, jlong height) {
+ sp<BLASTBufferQueue> queue = new BLASTBufferQueue(
+ reinterpret_cast<SurfaceControl*>(surfaceControl), width, height);
+ queue->incStrong((void*)nativeCreate);
+ return reinterpret_cast<jlong>(queue.get());
+}
+
+static void nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
+ sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
+ queue->decStrong((void*)nativeCreate);
+}
+
+static jobject nativeGetSurface(JNIEnv* env, jclass clazz, jlong ptr) {
+ sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
+ return android_view_Surface_createFromIGraphicBufferProducer(env, queue->getIGraphicBufferProducer());
+}
+
+static void nativeSetNextTransaction(JNIEnv* env, jclass clazz, jlong ptr, jlong transactionPtr) {
+ sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionPtr);
+ queue->setNextTransaction(transaction);
+}
+
+static void nativeUpdate(JNIEnv*env, jclass clazz, jlong ptr, jlong surfaceControl, jlong width, jlong height) {
+ sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
+ queue->update(reinterpret_cast<SurfaceControl*>(surfaceControl), width, height);
+}
+
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ { "nativeCreate", "(JJJ)J",
+ (void*)nativeCreate },
+ { "nativeGetSurface", "(J)Landroid/view/Surface;",
+ (void*)nativeGetSurface },
+ { "nativeDestroy", "(J)V",
+ (void*)nativeDestroy },
+ { "nativeSetNextTransaction", "(JJ)V",
+ (void*)nativeSetNextTransaction },
+ { "nativeUpdate", "(JJJJ)V",
+ (void*)nativeUpdate }
+};
+
+int register_android_graphics_BLASTBufferQueue(JNIEnv* env) {
+ int res = jniRegisterNativeMethods(env, "android/graphics/BLASTBufferQueue",
+ gMethods, NELEM(gMethods));
+ LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+
+ return 0;
+}
+
+} // namespace android
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 9445319..d62d2d9 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -139,8 +139,8 @@
"nativePrivateClean", "nativeSharedClean", "nativeSwappedOut", "nativeSwappedOutPss" }
};
-jfieldID otherStats_field;
-jfieldID hasSwappedOutPss_field;
+static jfieldID otherStats_field;
+static jfieldID hasSwappedOutPss_field;
struct stats_t {
int pss;
diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index be9aee4..7582cae 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -85,6 +85,10 @@
}
}
+bool setInjectLayersPrSetDumpable_native() {
+ return android::GraphicsEnv::getInstance().setInjectLayersPrSetDumpable();
+}
+
void hintActivityLaunch_native(JNIEnv* env, jobject clazz) {
android::GraphicsEnv::getInstance().hintActivityLaunch();
}
@@ -93,6 +97,7 @@
{ "getCanLoadSystemLibraries", "()I", reinterpret_cast<void*>(getCanLoadSystemLibraries_native) },
{ "setDriverPathAndSphalLibraries", "(Ljava/lang/String;Ljava/lang/String;)V", reinterpret_cast<void*>(setDriverPathAndSphalLibraries_native) },
{ "setGpuStats", "(Ljava/lang/String;Ljava/lang/String;JJLjava/lang/String;I)V", reinterpret_cast<void*>(setGpuStats_native) },
+ { "setInjectLayersPrSetDumpable", "()Z", reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native) },
{ "setAngleInfo", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/io/FileDescriptor;JJ)V", reinterpret_cast<void*>(setAngleInfo_native) },
{ "getShouldUseAngle", "(Ljava/lang/String;)Z", reinterpret_cast<void*>(shouldUseAngle_native) },
{ "setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V", reinterpret_cast<void*>(setLayerPaths_native) },
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index daf33f6..c7b36d0 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -108,7 +108,7 @@
jmethodID put;
} gArrayMapOffsets;
-jclass g_stringClass = nullptr;
+static jclass g_stringClass = nullptr;
// ----------------------------------------------------------------------------
@@ -763,6 +763,41 @@
return reinterpret_cast<jlong>(xml_tree.release());
}
+static jlong NativeOpenXmlAssetFd(JNIEnv* env, jobject /*clazz*/, jlong ptr, int jcookie,
+ jobject file_descriptor) {
+ int fd = jniGetFDFromFileDescriptor(env, file_descriptor);
+ ATRACE_NAME(base::StringPrintf("AssetManager::OpenXmlAssetFd(%d)", fd).c_str());
+ if (fd < 0) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
+ return 0;
+ }
+
+ base::unique_fd dup_fd(::fcntl(fd, F_DUPFD_CLOEXEC, 0));
+ if (dup_fd < 0) {
+ jniThrowIOException(env, errno);
+ return 0;
+ }
+
+ std::unique_ptr<Asset>
+ asset(Asset::createFromFd(dup_fd.release(), nullptr, Asset::AccessMode::ACCESS_BUFFER));
+
+ ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+ ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie);
+
+ // May be nullptr.
+ const DynamicRefTable* dynamic_ref_table = assetmanager->GetDynamicRefTableForCookie(cookie);
+
+ std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(dynamic_ref_table);
+ status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true);
+ asset.reset();
+
+ if (err != NO_ERROR) {
+ jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
+ return 0;
+ }
+ return reinterpret_cast<jlong>(xml_tree.release());
+}
+
static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
jshort density, jobject typed_value,
jboolean resolve_references) {
@@ -1564,6 +1599,7 @@
{"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
(void*)NativeOpenNonAssetFd},
{"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset},
+ {"nativeOpenXmlAssetFd", "(JILjava/io/FileDescriptor;)J", (void*)NativeOpenXmlAssetFd},
// AssetManager resource methods.
{"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I", (void*)NativeGetResourceValue},
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index e406e22..2232393 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -992,6 +992,31 @@
return IPCThreadState::self()->blockUntilThreadAvailable();
}
+static jobject android_os_Binder_waitForService(
+ JNIEnv *env,
+ jclass /* clazzObj */,
+ jstring serviceNameObj) {
+
+ const jchar* serviceName = env->GetStringCritical(serviceNameObj, nullptr);
+ if (!serviceName) {
+ signalExceptionForError(env, nullptr, BAD_VALUE, true /*canThrowRemoteException*/);
+ return nullptr;
+ }
+ String16 nameCopy = String16(reinterpret_cast<const char16_t *>(serviceName),
+ env->GetStringLength(serviceNameObj));
+ env->ReleaseStringCritical(serviceNameObj, serviceName);
+
+ auto sm = android::defaultServiceManager();
+ sp<IBinder> service = sm->waitForService(nameCopy);
+
+ if (!service) {
+ signalExceptionForError(env, nullptr, NAME_NOT_FOUND, true /*canThrowRemoteException*/);
+ return nullptr;
+ }
+
+ return javaObjectForIBinder(env, service);
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gBinderMethods[] = {
@@ -1019,7 +1044,8 @@
{ "flushPendingCommands", "()V", (void*)android_os_Binder_flushPendingCommands },
{ "getNativeBBinderHolder", "()J", (void*)android_os_Binder_getNativeBBinderHolder },
{ "getNativeFinalizer", "()J", (void*)android_os_Binder_getNativeFinalizer },
- { "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable }
+ { "blockUntilThreadAvailable", "()V", (void*)android_os_Binder_blockUntilThreadAvailable },
+ { "waitForService", "(Ljava/lang/String;)Landroid/os/IBinder;", (void*)android_os_Binder_waitForService }
};
const char* const kBinderPathName = "android/os/Binder";
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 0fada1b..49c5cad 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -35,7 +35,6 @@
#include <limits>
#include <memory>
#include <string>
-#include <unordered_map>
#include <vector>
#include "core_jni_helpers.h"
@@ -62,45 +61,26 @@
using namespace android;
-static const bool kDebugPolicy = false;
-static const bool kDebugProc = false;
+static constexpr bool kDebugPolicy = false;
+static constexpr bool kDebugProc = false;
// Stack reservation for reading small proc files. Most callers of
// readProcFile() are reading files under this threshold, e.g.,
// /proc/pid/stat. /proc/pid/time_in_state ends up being about 520
// bytes, so use 1024 for the stack to provide a bit of slack.
-static const ssize_t kProcReadStackBufferSize = 1024;
+static constexpr ssize_t kProcReadStackBufferSize = 1024;
// The other files we read from proc tend to be a bit larger (e.g.,
// /proc/stat is about 3kB), so once we exhaust the stack buffer,
// retry with a relatively large heap-allocated buffer. We double
// this size and retry until the whole file fits.
-static const ssize_t kProcReadMinHeapBufferSize = 4096;
+static constexpr ssize_t kProcReadMinHeapBufferSize = 4096;
#if GUARD_THREAD_PRIORITY
Mutex gKeyCreateMutex;
static pthread_key_t gBgKey = -1;
#endif
-/*
- * cpuset/sched aggregate profile mappings
- */
-static const std::unordered_map<int, std::string> kCpusetProfileMap = {
- {SP_DEFAULT, "CPUSET_SP_DEFAULT"}, {SP_BACKGROUND, "CPUSET_SP_BACKGROUND"},
- {SP_FOREGROUND, "CPUSET_SP_FOREGROUND"},{SP_SYSTEM, "CPUSET_SP_SYSTEM"},
- {SP_AUDIO_APP, "CPUSET_SP_FOREGROUND"}, {SP_AUDIO_SYS, "CPUSET_SP_FOREGROUND"},
- {SP_TOP_APP, "CPUSET_SP_TOP_APP"}, {SP_RT_APP, "CPUSET_SP_DEFAULT"},
- {SP_RESTRICTED, "CPUSET_SP_RESTRICTED"}
-};
-
-static const std::unordered_map<int, std::string> kSchedProfileMap = {
- {SP_DEFAULT, "SCHED_SP_DEFAULT"}, {SP_BACKGROUND, "SCHED_SP_BACKGROUND"},
- {SP_FOREGROUND, "SCHED_SP_FOREGROUND"}, {SP_SYSTEM, "SCHED_SP_DEFAULT"},
- {SP_AUDIO_APP, "SCHED_SP_FOREGROUND"}, {SP_AUDIO_SYS, "SCHED_SP_FOREGROUND"},
- {SP_TOP_APP, "SCHED_SP_TOP_APP"}, {SP_RT_APP, "SCHED_SP_RT_APP"},
- {SP_RESTRICTED, "SCHED_SP_DEFAULT"}
-};
-
// For both of these, err should be in the errno range (positive), not a status_t (negative)
static void signalExceptionForError(JNIEnv* env, int err, int tid) {
switch (err) {
@@ -227,7 +207,7 @@
return;
}
- int res = SetTaskProfiles(tid, {kSchedProfileMap.at(grp)}, true) ? 0 : -1;
+ int res = SetTaskProfiles(tid, {get_sched_policy_name((SchedPolicy)grp)}, true) ? 0 : -1;
if (res != NO_ERROR) {
signalExceptionForGroupError(env, -res, tid);
@@ -241,7 +221,7 @@
return;
}
- int res = SetTaskProfiles(tid, {kCpusetProfileMap.at(grp)}, true) ? 0 : -1;
+ int res = SetTaskProfiles(tid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}, true) ? 0 : -1;
if (res != NO_ERROR) {
signalExceptionForGroupError(env, -res, tid);
@@ -328,7 +308,7 @@
if (t_pri >= ANDROID_PRIORITY_BACKGROUND) {
// This task wants to stay at background
// update its cpuset so it doesn't only run on bg core(s)
- err = SetTaskProfiles(t_pid, {kCpusetProfileMap.at(grp)}, true) ? 0 : -1;
+ err = SetTaskProfiles(t_pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}, true) ? 0 : -1;
if (err != NO_ERROR) {
signalExceptionForGroupError(env, -err, t_pid);
break;
@@ -337,7 +317,7 @@
}
}
- err = SetTaskProfiles(t_pid, {kCpusetProfileMap.at(grp)}, true) ? 0 : -1;
+ err = SetTaskProfiles(t_pid, {get_cpuset_policy_profile_name((SchedPolicy)grp)}, true) ? 0 : -1;
if (err != NO_ERROR) {
signalExceptionForGroupError(env, -err, t_pid);
break;
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index af34e7b7..bf1cea8 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -112,7 +112,9 @@
}
static jobject android_view_InputChannel_createInputChannel(JNIEnv* env,
- std::unique_ptr<NativeInputChannel> nativeInputChannel) {
+ sp<InputChannel> inputChannel) {
+ std::unique_ptr<NativeInputChannel> nativeInputChannel =
+ std::make_unique<NativeInputChannel>(inputChannel);
jobject inputChannelObj = env->NewObject(gInputChannelClassInfo.clazz,
gInputChannelClassInfo.ctor);
if (inputChannelObj) {
@@ -143,14 +145,12 @@
return nullptr;
}
- jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
- std::make_unique<NativeInputChannel>(serverChannel));
+ jobject serverChannelObj = android_view_InputChannel_createInputChannel(env, serverChannel);
if (env->ExceptionCheck()) {
return nullptr;
}
- jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
- std::make_unique<NativeInputChannel>(clientChannel));
+ jobject clientChannelObj = android_view_InputChannel_createInputChannel(env, clientChannel);
if (env->ExceptionCheck()) {
return nullptr;
}
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 222a873..538861e 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -52,9 +52,14 @@
renderNode->output();
}
-static jint android_view_RenderNode_getDebugSize(JNIEnv* env, jobject clazz, jlong renderNodePtr) {
+static jint android_view_RenderNode_getUsageSize(JNIEnv* env, jobject clazz, jlong renderNodePtr) {
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
- return renderNode->getDebugSize();
+ return renderNode->getUsageSize();
+}
+
+static jint android_view_RenderNode_getAllocatedSize(JNIEnv* env, jobject clazz, jlong renderNodePtr) {
+ RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+ return renderNode->getAllocatedSize();
}
static jlong android_view_RenderNode_create(JNIEnv* env, jobject, jstring name) {
@@ -647,7 +652,8 @@
{ "nCreate", "(Ljava/lang/String;)J", (void*) android_view_RenderNode_create },
{ "nGetNativeFinalizer", "()J", (void*) android_view_RenderNode_getNativeFinalizer },
{ "nOutput", "(J)V", (void*) android_view_RenderNode_output },
- { "nGetDebugSize", "(J)I", (void*) android_view_RenderNode_getDebugSize },
+ { "nGetUsageSize", "(J)I", (void*) android_view_RenderNode_getUsageSize },
+ { "nGetAllocatedSize", "(J)I", (void*) android_view_RenderNode_getAllocatedSize },
{ "nAddAnimator", "(JJ)V", (void*) android_view_RenderNode_addAnimator },
{ "nEndAllAnimators", "(J)V", (void*) android_view_RenderNode_endAllAnimators },
{ "nRequestPositionUpdates", "(JLandroid/graphics/RenderNode$PositionUpdateListener;)V", (void*) android_view_RenderNode_requestPositionUpdates },
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index b8c5270..94be61f 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2412,4 +2412,18 @@
// OS: R
SETTINGS_WIFI_CONFIGURE_NETWORK = 1800;
+ // OPEN: Settings > Accessibility > Magnification
+ // CATEGORY: SETTINGS
+ // OS: R
+ // Note: Shows up only when Magnify with shortcut is enabled
+ // and under accessibility button mode.
+ DIALOG_TOGGLE_SCREEN_MAGNIFICATION_ACCESSIBILITY_BUTTON = 1801;
+
+ // OPEN: Settings > Accessibility > Magnification
+ // CATEGORY: SETTINGS
+ // OS: R
+ // Note: Shows up only when Magnify with shortcut is enabled.
+ // and under gesture navigation mode.
+ DIALOG_TOGGLE_SCREEN_MAGNIFICATION_GESTURE_NAVIGATION = 1802;
+
}
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 15b98af..06040a5 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -43,7 +43,7 @@
reserved 15; // next_heartbeat
reserved 16; // last_heartbeat_time_millis
reserved 17; // next_heartbeat_time_millis
- optional bool in_parole = 18;
+ reserved 18; // in_parole
optional bool in_thermal = 19;
repeated int32 started_users = 2;
@@ -534,7 +534,7 @@
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
optional bool is_charging = 1;
- optional bool is_in_parole = 2;
+ reserved 2; // is_in_parole
optional int64 elapsed_realtime = 6;
// List of UIDs currently in the foreground.
diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto
index 301fa13..d010c8f 100644
--- a/core/proto/android/service/package.proto
+++ b/core/proto/android/service/package.proto
@@ -114,6 +114,13 @@
optional int32 distraction_flags = 10;
}
+ message InstallSourceProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ // The package that requested the installation of this one.
+ optional string initiating_package_name = 1;
+ }
+
// Name of package. e.g. "com.android.providers.telephony".
optional string name = 1;
// UID for this package as assigned by Android OS.
@@ -133,4 +140,6 @@
repeated SplitProto splits = 8;
// Per-user package info.
repeated UserInfoProto users = 9;
+ // Where the request to install this package came from,
+ optional InstallSourceProto install_source = 10;
}
diff --git a/core/proto/android/service/procstats.proto b/core/proto/android/service/procstats.proto
index f49a044..ad7299d 100644
--- a/core/proto/android/service/procstats.proto
+++ b/core/proto/android/service/procstats.proto
@@ -241,12 +241,25 @@
repeated StateStats active_state_stats = 6;
}
-// Next Tag: 3
+// Next Tag: 7
message PackageAssociationProcessStatsProto {
option (android.msg_privacy).dest = DEST_AUTOMATIC;
// Name of the target component.
optional string component_name = 1;
+
+ // Total count of the times this association appeared.
+ optional int32 total_count = 3;
+
+ // Millisecond uptime total duration this association was around.
+ optional int64 total_duration_ms = 4;
+
+ // Total count of the times this association became actively impacting its target process.
+ optional int32 active_count = 5;
+
+ // Millisecond uptime total duration this association was around.
+ optional int64 active_duration_ms = 6;
+
// Information on one source in this association.
repeated PackageAssociationSourceProcessStatsProto sources = 2;
}
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index 0821d14..15813a1 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -113,9 +113,9 @@
PROVISIONING_PREPROVISIONING_ACTIVITY_TIME_MS = 87;
PROVISIONING_ENCRYPT_DEVICE_ACTIVITY_TIME_MS = 88;
PROVISIONING_WEB_ACTIVITY_TIME_MS = 89;
- PROVISIONING_TRAMPOLINE_ACTIVITY_TIME_MS = 90;
- PROVISIONING_POST_ENCRYPTION_ACTIVITY_TIME_MS = 91;
- PROVISIONING_FINALIZATION_ACTIVITY_TIME_MS = 92;
+ PROVISIONING_TRAMPOLINE_ACTIVITY_TIME_MS = 90 [deprecated=true];
+ PROVISIONING_POST_ENCRYPTION_ACTIVITY_TIME_MS = 91 [deprecated=true];
+ PROVISIONING_FINALIZATION_ACTIVITY_TIME_MS = 92 [deprecated=true];
PROVISIONING_NETWORK_TYPE = 93;
PROVISIONING_ACTION = 94;
PROVISIONING_EXTRAS = 95;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b030b33..f98ea2c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1624,7 +1624,7 @@
@hide This should only be used by Settings and SystemUI.
-->
<permission android:name="android.permission.NETWORK_SETTINGS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- Allows SetupWizard to call methods in Networking services
<p>Not for use by any other third-party or privileged applications.
@@ -2138,12 +2138,12 @@
<!-- Must be required by a telephony data service to ensure that only the
system can bind to it.
- <p>Protection level: signature
+ <p>Protection level: signature|telephony
@SystemApi
@hide
-->
<permission android:name="android.permission.BIND_TELEPHONY_DATA_SERVICE"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- Must be required by a NetworkService to ensure that only the
system can bind to it.
@@ -2164,11 +2164,11 @@
<!-- @SystemApi Must be required by an EuiccService to ensure that only the system can bind to
it.
- <p>Protection level: signature
+ <p>Protection level: signature|telephony
@hide
-->
<permission android:name="android.permission.BIND_EUICC_SERVICE"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- ================================== -->
<!-- Permissions for sdcard interaction -->
@@ -2955,7 +2955,7 @@
@hide
-->
<permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony|wifi" />
<!-- @SystemApi Allows an application to use
{@link android.view.WindowManager.LayoutsParams#SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
@@ -3511,6 +3511,13 @@
<permission android:name="android.permission.GET_RUNTIME_PERMISSIONS"
android:protectionLevel="signature" />
+ <!-- @SystemApi Allows the system to restore runtime permission state. This might grant
+ permissions, hence this is a more scoped, less powerful variant of GRANT_RUNTIME_PERMISSIONS.
+ Among other restrictions this cannot override user choices.
+ @hide -->
+ <permission android:name="android.permission.RESTORE_RUNTIME_PERMISSIONS"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows an application to change policy_fixed permissions.
@hide -->
<permission android:name="android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"
@@ -3740,7 +3747,7 @@
@hide
-->
<permission android:name="android.permission.DEVICE_POWER"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- Allows toggling battery saver on the system.
Superseded by DEVICE_POWER permission. @hide @SystemApi
@@ -3775,13 +3782,13 @@
<p>Not for use by third-party applications.
-->
<permission android:name="android.permission.BROADCAST_SMS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- Allows an application to broadcast a WAP PUSH receipt notification.
<p>Not for use by third-party applications.
-->
<permission android:name="android.permission.BROADCAST_WAP_PUSH"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- @SystemApi Allows an application to broadcast privileged networking requests.
<p>Not for use by third-party applications.
@@ -4396,13 +4403,13 @@
{@link android.provider.BlockedNumberContract}.
@hide -->
<permission android:name="android.permission.READ_BLOCKED_NUMBERS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- Allows the holder to write blocked numbers. See
{@link android.provider.BlockedNumberContract}.
@hide -->
<permission android:name="android.permission.WRITE_BLOCKED_NUMBERS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|telephony" />
<!-- Must be required by an {@link android.service.vr.VrListenerService}, to ensure that only
the system can bind to it.
diff --git a/core/res/res/layout/resolve_list_item.xml b/core/res/res/layout/resolve_list_item.xml
index 0bdb25a..4857095 100644
--- a/core/res/res/layout/resolve_list_item.xml
+++ b/core/res/res/layout/resolve_list_item.xml
@@ -22,8 +22,6 @@
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:minHeight="?attr/listPreferredItemHeightSmall"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
android:background="?attr/activatedBackgroundIndicator">
<!-- Activity icon when presenting dialog
@@ -32,7 +30,8 @@
android:layout_width="@dimen/resolver_icon_size"
android:layout_height="@dimen/resolver_icon_size"
android:layout_gravity="start|center_vertical"
- android:layout_marginStart="?attr/listPreferredItemPaddingStart"
+ android:layout_marginStart="@dimen/resolver_icon_margin"
+ android:layout_marginEnd="@dimen/resolver_icon_margin"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:scaleType="fitCenter" />
@@ -40,8 +39,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:gravity="start|center_vertical"
android:orientation="vertical"
- android:paddingStart="?attr/listPreferredItemPaddingStart"
- android:paddingEnd="?attr/listPreferredItemPaddingEnd"
+ android:paddingEnd="@dimen/resolver_edge_margin"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="start|center_vertical">
@@ -49,14 +47,20 @@
<TextView android:id="@android:id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="?attr/textAppearanceMedium"
- android:textColor="?attr/textColorPrimary"
+ android:layout_gravity="start|center_vertical"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@android:string/config_bodyFontFamily"
+ android:textSize="16sp"
android:minLines="1"
android:maxLines="1"
android:ellipsize="marquee" />
<!-- Extended activity info to distinguish between duplicate activity names -->
<TextView android:id="@android:id/text2"
- android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="?android:attr/textColorSecondary"
+ android:fontFamily="@android:string/config_bodyFontFamily"
+ android:layout_gravity="start|center_vertical"
+ android:textSize="14sp"
+ android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minLines="1"
diff --git a/core/res/res/layout/resolver_different_item_header.xml b/core/res/res/layout/resolver_different_item_header.xml
index 7d9ffd7..0a35edc 100644
--- a/core/res/res/layout/resolver_different_item_header.xml
+++ b/core/res/res/layout/resolver_different_item_header.xml
@@ -22,12 +22,12 @@
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
android:text="@string/use_a_different_app"
- android:minHeight="56dp"
- android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:textSize="16sp"
android:gravity="start|center_vertical"
- android:paddingStart="16dp"
- android:paddingEnd="16dp"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:elevation="8dp"
- />
+ android:paddingStart="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_edge_margin"
+ android:paddingTop="@dimen/resolver_small_margin"
+ android:paddingBottom="@dimen/resolver_edge_margin"
+ android:elevation="1dp" />
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index 1dd4207..6e45e7a 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -29,16 +29,18 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
- android:elevation="8dp"
- android:background="?attr/colorBackgroundFloating">
+ android:elevation="@dimen/resolver_elevation"
+ android:paddingTop="@dimen/resolver_small_margin"
+ android:paddingStart="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_edge_margin"
+ android:paddingBottom="@dimen/resolver_edge_margin"
+ android:background="@drawable/bottomsheet_background">
<TextView
android:id="@+id/profile_button"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginEnd="8dp"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
android:visibility="gone"
style="?attr/borderlessButtonStyle"
android:textAppearance="?attr/textAppearanceButton"
@@ -50,36 +52,49 @@
<TextView
android:id="@+id/title"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:minHeight="56dp"
- android:textAppearance="?attr/textAppearanceMedium"
- android:gravity="start|center_vertical"
- android:paddingStart="?attr/dialogPreferredPadding"
- android:paddingEnd="?attr/dialogPreferredPadding"
- android:paddingTop="8dp"
android:layout_below="@id/profile_button"
android:layout_alignParentStart="true"
- android:paddingBottom="8dp" />
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:textSize="16sp"
+ android:gravity="start|center_vertical" />
</RelativeLayout>
+ <View
+ android:layout_alwaysShow="true"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorBackgroundFloating"
+ android:foreground="?attr/dividerVertical" />
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/resolver_list"
android:clipToPadding="false"
- android:scrollbarStyle="outsideOverlay"
android:background="?attr/colorBackgroundFloating"
- android:elevation="8dp"
+ android:elevation="@dimen/resolver_elevation"
android:nestedScrollingEnabled="true"
+ android:scrollbarStyle="outsideOverlay"
android:scrollIndicators="top|bottom"
- android:divider="@null" />
+ android:divider="?attr/dividerVertical"
+ android:footerDividersEnabled="false"
+ android:headerDividersEnabled="false"
+ android:dividerHeight="1dp" />
+ <View
+ android:layout_alwaysShow="true"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorBackgroundFloating"
+ android:foreground="?attr/dividerVertical" />
+
<TextView android:id="@+id/empty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorBackgroundFloating"
- android:elevation="8dp"
+ android:elevation="@dimen/resolver_elevation"
android:layout_alwaysShow="true"
android:text="@string/noApplications"
android:padding="32dp"
@@ -102,18 +117,19 @@
android:background="?attr/colorBackgroundFloating"
android:paddingTop="@dimen/resolver_button_bar_spacing"
android:paddingBottom="@dimen/resolver_button_bar_spacing"
- android:paddingStart="12dp"
- android:paddingEnd="12dp"
- android:elevation="8dp">
+ android:paddingStart="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_small_margin"
+ android:elevation="@dimen/resolver_elevation">
<Button
android:id="@+id/button_once"
android:layout_width="wrap_content"
android:layout_gravity="start"
android:maxLines="2"
- style="?attr/buttonBarNegativeButtonStyle"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
+ style="?attr/buttonBarButtonStyle"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
android:layout_height="wrap_content"
+ android:textAllCaps="false"
android:enabled="false"
android:text="@string/activity_resolver_use_once"
android:onClick="onButtonClick" />
@@ -123,8 +139,9 @@
android:layout_width="wrap_content"
android:layout_gravity="end"
android:maxLines="2"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- style="?attr/buttonBarPositiveButtonStyle"
+ style="?attr/buttonBarButtonStyle"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:textAllCaps="false"
android:layout_height="wrap_content"
android:enabled="false"
android:text="@string/activity_resolver_use_always"
diff --git a/core/res/res/layout/resolver_list_with_default.xml b/core/res/res/layout/resolver_list_with_default.xml
index 740a7eb..dbba0b7 100644
--- a/core/res/res/layout/resolver_list_with_default.xml
+++ b/core/res/res/layout/resolver_list_with_default.xml
@@ -29,22 +29,22 @@
android:layout_height="wrap_content"
android:layout_alwaysShow="true"
android:orientation="vertical"
- android:background="?attr/colorBackgroundFloating"
- android:elevation="8dp">
+ android:background="@drawable/bottomsheet_background"
+ android:paddingTop="@dimen/resolver_small_margin"
+ android:elevation="@dimen/resolver_elevation">
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="64dp"
- android:orientation="horizontal">
-
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingBottom="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_edge_margin">
<ImageView
android:id="@+id/icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
+ android:layout_width="@dimen/resolver_icon_size"
+ android:layout_height="@dimen/resolver_icon_size"
android:layout_gravity="start|top"
- android:layout_marginStart="16dp"
- android:layout_marginEnd="16dp"
- android:layout_marginTop="20dp"
+ android:layout_marginStart="@dimen/resolver_icon_margin"
android:src="@drawable/resolver_icon_placeholder"
android:scaleType="fitCenter" />
@@ -52,9 +52,11 @@
android:id="@+id/title"
android:layout_width="0dp"
android:layout_weight="1"
- android:layout_height="?attr/listPreferredItemHeight"
- android:layout_marginStart="16dp"
- android:textAppearance="?attr/textAppearanceMedium"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/resolver_icon_margin"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+ android:textSize="16sp"
android:gravity="start|center_vertical"
android:paddingEnd="16dp" />
@@ -107,21 +109,22 @@
android:orientation="horizontal"
android:layoutDirection="locale"
android:measureWithLargestChild="true"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:paddingStart="12dp"
- android:paddingEnd="12dp"
- android:elevation="8dp">
+ android:paddingTop="@dimen/resolver_button_bar_spacing"
+ android:paddingBottom="@dimen/resolver_button_bar_spacing"
+ android:paddingStart="@dimen/resolver_edge_margin"
+ android:paddingEnd="@dimen/resolver_small_margin"
+ android:elevation="@dimen/resolver_elevation">
<Button
android:id="@+id/button_once"
android:layout_width="wrap_content"
android:layout_gravity="start"
android:maxLines="2"
- style="?attr/buttonBarNegativeButtonStyle"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
+ style="?attr/buttonBarButtonStyle"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
android:layout_height="wrap_content"
android:enabled="false"
+ android:textAllCaps="false"
android:text="@string/activity_resolver_use_once"
android:onClick="onButtonClick" />
@@ -130,29 +133,40 @@
android:layout_width="wrap_content"
android:layout_gravity="end"
android:maxLines="2"
- android:minHeight="@dimen/alert_dialog_button_bar_height"
- style="?attr/buttonBarPositiveButtonStyle"
+ style="?attr/buttonBarButtonStyle"
+ android:fontFamily="@android:string/config_headlineFontFamilyMedium"
android:layout_height="wrap_content"
android:enabled="false"
+ android:textAllCaps="false"
android:text="@string/activity_resolver_use_always"
android:onClick="onButtonClick" />
</LinearLayout>
-
- <View
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?attr/dividerVertical" />
</LinearLayout>
+ <View
+ android:layout_alwaysShow="true"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorBackgroundFloating"
+ android:foreground="?attr/dividerVertical" />
<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/resolver_list"
android:clipToPadding="false"
- android:scrollbarStyle="outsideOverlay"
android:background="?attr/colorBackgroundFloating"
- android:elevation="8dp"
+ android:elevation="@dimen/resolver_elevation"
android:nestedScrollingEnabled="true"
- android:divider="@null" />
-
+ android:scrollbarStyle="outsideOverlay"
+ android:scrollIndicators="top|bottom"
+ android:divider="?attr/dividerVertical"
+ android:footerDividersEnabled="false"
+ android:headerDividersEnabled="false"
+ android:dividerHeight="1dp" />
+ <View
+ android:layout_alwaysShow="true"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorBackgroundFloating"
+ android:foreground="?attr/dividerVertical" />
</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 381ed7f..ffcfe43 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -295,6 +295,12 @@
<!-- Additional flag from base permission type: this permission can be automatically
granted to the system app predictor -->
<flag name="appPredictor" value="0x200000" />
+ <!-- Additional flag from base permission type: this permission can be automatically
+ granted to the system telephony apps -->
+ <flag name="telephony" value="0x400000" />
+ <!-- Additional flag from base permission type: this permission can be automatically
+ granted to the system wifi app-->
+ <flag name="wifi" value="0x800000" />
</attr>
<!-- Flags indicating more context for a permission group. -->
@@ -2840,6 +2846,9 @@
<!-- The name of the overlayable whose resources will be overlaid. -->
<attr name="targetName" />
+
+ <!-- The xml file that defines the target id to overlay value mappings. -->
+ <attr name="resourcesMap" format="reference" />
</declare-styleable>
<!-- Declaration of an {@link android.content.Intent} object in XML. May
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3fef7a2d..9bff88a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3687,6 +3687,21 @@
-->
<string name="config_defaultWellbeingPackage" translatable="false"></string>
+ <!-- The package name for the system telephony apps.
+ This package must be trusted, as it will be granted with permissions with special telephony
+ protection level. Note, framework by default support multiple telephony apps, each package
+ name is separated by comma.
+ Example: "com.android.phone,com.android.stk,com.android.providers.telephony"
+ -->
+ <string name="config_telephonyPackages" translatable="false">"com.android.phone,com.android.stk,com.android.providers.telephony,com.android.ons"</string>
+
+ <!-- The package name for the default system wifi app.
+ This package must be trusted, as it has the permissions to control wifi
+ connectivity on the device.
+ Example: "com.android.wifi"
+ -->
+ <string name="config_wifiPackage" translatable="false">"com.android.wifi"</string>
+
<!-- The component name for the default system attention service.
This service must be trusted, as it can be activated without explicit consent of the user.
See android.attention.AttentionManagerService.
@@ -4304,11 +4319,11 @@
<!-- Trigger a warning for notifications with RemoteView objects that are larger in bytes than
this value (default 1MB)-->
- <integer name="config_notificationWarnRemoteViewSizeBytes">1000000</integer>
+ <integer name="config_notificationWarnRemoteViewSizeBytes">2000000</integer>
<!-- Strip notification RemoteView objects that are larger in bytes than this value (also log)
(default 2MB) -->
- <integer name="config_notificationStripRemoteViewSizeBytes">2000000</integer>
+ <integer name="config_notificationStripRemoteViewSizeBytes">5000000</integer>
<!-- Contains a blacklist of apps that should not get pre-installed carrier app permission
grants, even if the UICC claims that the app should be privileged. See b/138150105 -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 609659b..a01bbe3 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -750,7 +750,7 @@
<dimen name="seekbar_thumb_exclusion_max_size">48dp</dimen>
- <!-- chooser (sharesheet) spacing -->
+ <!-- chooser/resolver (sharesheet) spacing -->
<dimen name="chooser_corner_radius">8dp</dimen>
<dimen name="chooser_row_text_option_translate">25dp</dimen>
<dimen name="chooser_view_spacing">18dp</dimen>
@@ -759,11 +759,15 @@
<dimen name="chooser_preview_image_font_size">20sp</dimen>
<dimen name="chooser_preview_image_border">1dp</dimen>
<dimen name="chooser_preview_width">-1px</dimen>
- <dimen name="resolver_icon_size">42dp</dimen>
- <dimen name="resolver_button_bar_spacing">8dp</dimen>
- <dimen name="resolver_badge_size">18dp</dimen>
<dimen name="chooser_target_width">90dp</dimen>
<dimen name="chooser_header_scroll_elevation">4dp</dimen>
<dimen name="chooser_max_collapsed_height">288dp</dimen>
<dimen name="chooser_direct_share_label_placeholder_max_width">72dp</dimen>
+ <dimen name="resolver_icon_size">32dp</dimen>
+ <dimen name="resolver_button_bar_spacing">8dp</dimen>
+ <dimen name="resolver_badge_size">18dp</dimen>
+ <dimen name="resolver_icon_margin">16dp</dimen>
+ <dimen name="resolver_small_margin">18dp</dimen>
+ <dimen name="resolver_edge_margin">24dp</dimen>
+ <dimen name="resolver_elevation">1dp</dimen>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 96c0cf3..9724c41 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3000,6 +3000,8 @@
<public-group type="attr" first-id="0x01010607">
<public name="importantForContentCapture" />
<public name="forceQueryable" />
+ <!-- @hide @SystemApi -->
+ <public name="resourcesMap" />
</public-group>
<public-group type="drawable" first-id="0x010800b5">
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 363bc9d..42cd2cd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3467,6 +3467,8 @@
<java-symbol type="string" name="config_defaultAutofillService" />
<java-symbol type="string" name="config_defaultTextClassifierPackage" />
<java-symbol type="string" name="config_defaultWellbeingPackage" />
+ <java-symbol type="string" name="config_telephonyPackages" />
+ <java-symbol type="string" name="config_wifiPackage" />
<java-symbol type="string" name="config_defaultContentCaptureService" />
<java-symbol type="string" name="config_defaultAugmentedAutofillService" />
<java-symbol type="string" name="config_defaultAppPredictionService" />
@@ -3819,6 +3821,10 @@
<java-symbol type="dimen" name="resolver_icon_size"/>
<java-symbol type="dimen" name="resolver_badge_size"/>
<java-symbol type="dimen" name="resolver_button_bar_spacing"/>
+ <java-symbol type="dimen" name="resolver_icon_margin"/>
+ <java-symbol type="dimen" name="resolver_small_margin"/>
+ <java-symbol type="dimen" name="resolver_edge_margin"/>
+ <java-symbol type="dimen" name="resolver_elevation"/>
<!-- For DropBox -->
<java-symbol type="integer" name="config_dropboxLowPriorityBroadcastRateLimitPeriod" />
diff --git a/core/tests/ResourceLoaderTests/Android.bp b/core/tests/ResourceLoaderTests/Android.bp
new file mode 100644
index 0000000..53db832
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/Android.bp
@@ -0,0 +1,63 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test {
+ name: "FrameworksResourceLoaderTests",
+ srcs: [
+ "src/**/*.kt"
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ static_libs: [
+ "androidx.test.espresso.core",
+ "androidx.test.ext.junit",
+ "androidx.test.runner",
+ "androidx.test.rules",
+ "mockito-target-minus-junit4",
+ "truth-prebuilt",
+ ],
+ resource_zips: [ ":FrameworksResourceLoaderTestsAssets" ],
+ test_suites: ["device-tests"],
+ sdk_version: "test_current",
+ aaptflags: [
+ "--no-compress",
+ ],
+ data: [
+ ":FrameworksResourceLoaderTestsOverlay",
+ ":FrameworksResourceLoaderTestsSplitOne",
+ ":FrameworksResourceLoaderTestsSplitTwo",
+ ],
+ java_resources: [ "NonAsset.txt" ]
+}
+
+filegroup {
+ name: "FrameworksResourceLoaderTestsResources",
+ srcs: ["resources"],
+}
+
+genrule {
+ name: "FrameworksResourceLoaderTestsAssets",
+ srcs: [
+ ":framework-res",
+ ":FrameworksResourceLoaderTestsResources",
+ ],
+ tools: [ ":aapt2", ":soong_zip" ],
+ tool_files: [ "resources/compileAndLink.sh" ],
+ cmd: "$(location resources/compileAndLink.sh) $(location :aapt2) $(location :soong_zip) $(genDir) $(in) $(in)",
+ out: [ "out.zip" ]
+}
diff --git a/core/tests/ResourceLoaderTests/AndroidManifest.xml b/core/tests/ResourceLoaderTests/AndroidManifest.xml
new file mode 100644
index 0000000..00b4ccb
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<!-- Split loading is tested separately, so this must be marked isolated -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.res.loader.test"
+ android:isolatedSplits="true"
+ >
+
+ <uses-sdk android:minSdkVersion="29"/>
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+
+ <activity
+ android:name=".TestActivity"
+ android:configChanges="orientation"
+ />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="ResourceLoaderTests"
+ android:targetPackage="android.content.res.loader.test"
+ />
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/AndroidTest.xml b/core/tests/ResourceLoaderTests/AndroidTest.xml
new file mode 100644
index 0000000..702151d
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/AndroidTest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="Test module config for ResourceLoaderTests">
+ <option name="test-tag" value="ResourceLoaderTests" />
+
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="cleanup-apks" value="true" />
+ <!-- The following value cannot be multi-line as whitespace is parsed by the installer -->
+ <option name="split-apk-file-names"
+ value="FrameworksResourceLoaderTests.apk,FrameworksResourceLoaderTestsSplitOne.apk,FrameworksResourceLoaderTestsSplitTwo.apk" />
+ <option name="test-file-name" value="FrameworksResourceLoaderTestsOverlay.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command"
+ value="cmd overlay disable android.content.res.loader.test.overlay" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.content.res.loader.test" />
+ </test>
+</configuration>
diff --git a/core/tests/ResourceLoaderTests/NonAsset.txt b/core/tests/ResourceLoaderTests/NonAsset.txt
new file mode 100644
index 0000000..5c0b2cc
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/NonAsset.txt
@@ -0,0 +1 @@
+Outside assets directory
diff --git a/core/tests/ResourceLoaderTests/SplitOne/Android.bp b/core/tests/ResourceLoaderTests/SplitOne/Android.bp
new file mode 100644
index 0000000..897897f
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/SplitOne/Android.bp
@@ -0,0 +1,19 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+ name: "FrameworksResourceLoaderTestsSplitOne"
+}
diff --git a/core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml b/core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml
new file mode 100644
index 0000000..b14bd86
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="android.content.res.loader.test"
+ split="split_one"
+ >
+
+ <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
+ <application android:hasCode="false" />
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml b/core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml
new file mode 100644
index 0000000..3c215eb
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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>
+ <public type="string" name="split_overlaid" id="0x7f040001" />
+ <string name="split_overlaid">Split ONE Overlaid</string>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/assets/Asset.txt b/core/tests/ResourceLoaderTests/assets/Asset.txt
new file mode 100644
index 0000000..03f9a0f
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/assets/Asset.txt
@@ -0,0 +1 @@
+In assets directory
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar
new file mode 100644
index 0000000..a12e33a
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar b/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar
new file mode 100644
index 0000000..182cbab
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar
new file mode 100644
index 0000000..e6b5f15b
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar
new file mode 100644
index 0000000..e9c743c
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar
new file mode 100644
index 0000000..cd05360
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar
new file mode 100644
index 0000000..dc8aa90
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar
new file mode 100644
index 0000000..8a672ba
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar
new file mode 100644
index 0000000..56f3d1e
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar
new file mode 100644
index 0000000..663d312
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-test.jar b/core/tests/ResourceLoaderTests/lib/kotlin-test.jar
new file mode 100644
index 0000000..5f6e4b8
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/lib/kotlin-test.jar
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/overlay/Android.bp b/core/tests/ResourceLoaderTests/overlay/Android.bp
new file mode 100644
index 0000000..63e7e61
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/overlay/Android.bp
@@ -0,0 +1,20 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+ name: "FrameworksResourceLoaderTestsOverlay",
+ sdk_version: "current",
+
+ aaptflags: ["--no-resource-removal"],
+}
diff --git a/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml b/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml
new file mode 100644
index 0000000..942f7da
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="android.content.res.loader.test.overlay"
+ >
+
+ <application android:hasCode="false" />
+
+ <overlay android:targetPackage="android.content.res.loader.test" />
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
new file mode 100644
index 0000000..348bb35
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+
+ <string name="loader_path_change_test">Overlaid</string>
+
+</resources>
diff --git a/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png
new file mode 100644
index 0000000..efd71ee
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_drawable.xml b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_drawable.xml
new file mode 100644
index 0000000..d1211c5
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_drawable.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<color
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="#B2D2F2"
+ />
diff --git a/core/tests/ResourceLoaderTests/res/layout/layout.xml b/core/tests/ResourceLoaderTests/res/layout/layout.xml
new file mode 100644
index 0000000..d59059b
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/res/layout/layout.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+
diff --git a/core/tests/ResourceLoaderTests/res/values/strings.xml b/core/tests/ResourceLoaderTests/res/values/strings.xml
new file mode 100644
index 0000000..28b8f73
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources>
+
+ <string name="loader_path_change_test">Not overlaid</string>
+ <string name="split_overlaid">Not overlaid</string>
+
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml
new file mode 100644
index 0000000..5dd8a96
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="android.content.res.loader.test"
+ >
+
+ <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
+ <application/>
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml
new file mode 100644
index 0000000..5a92ae9
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!-- Mocks the framework package name so that AAPT2 assigns the correct package -->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android"
+ >
+
+ <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
+ <application/>
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/resources/compileAndLink.sh b/core/tests/ResourceLoaderTests/resources/compileAndLink.sh
new file mode 100755
index 0000000..885f681
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/compileAndLink.sh
@@ -0,0 +1,108 @@
+#!/bin/bash
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+aapt2=$1
+soong_zip=$2
+genDir=$3
+FRAMEWORK_RES_APK=$4
+inDir=$5
+
+# (String name, boolean retainFiles = false, String... files)
+function compileAndLink {
+ moduleName=$1
+ mkdir "$genDir"/out/"$moduleName"
+
+ args=""
+ for arg in "${@:4}"; do
+ if [[ $arg == res* ]]; then
+ args="$args $inDir/$arg"
+ else
+ args="$args $arg"
+ fi
+ done
+
+ $aapt2 compile -o "$genDir"/out/"$moduleName" $args
+
+ $aapt2 link \
+ -I "$FRAMEWORK_RES_APK" \
+ --manifest "$inDir"/"$3" \
+ -o "$genDir"/out/"$moduleName"/apk.apk \
+ "$genDir"/out/"$moduleName"/*.flat \
+ --no-compress
+
+ unzip -qq "$genDir"/out/"$moduleName"/apk.apk -d "$genDir"/out/"$moduleName"/unzip
+
+ if [[ "$2" == "APK_WITHOUT_FILE" || "$2" == "BOTH_WITHOUT_FILE" ]]; then
+ zip -q -d "$genDir"/out/"$moduleName"/apk.apk "res/*"
+ cp "$genDir"/out/"$moduleName"/apk.apk "$genDir"/output/raw/"$moduleName"Apk.apk
+ elif [[ "$2" == "APK" || "$2" == "BOTH" ]]; then
+ cp "$genDir"/out/"$moduleName"/apk.apk "$genDir"/output/raw/"$moduleName"Apk.apk
+ fi
+
+ if [[ "$2" == "ARSC" || "$2" == "BOTH" || "$2" == "BOTH_WITHOUT_FILE" ]]; then
+ zip -d "$genDir"/out/"$moduleName"/apk.apk "res/*"
+ cp "$genDir"/out/"$moduleName"/unzip/resources.arsc "$genDir"/output/raw/"$moduleName"Arsc.arsc
+ fi
+}
+
+rm -r "$genDir"/out
+rm -r "$genDir"/output
+rm -r "$genDir"/temp
+
+mkdir "$genDir"/out
+mkdir -p "$genDir"/output/raw
+mkdir -p "$genDir"/temp/res/drawable-nodpi
+mkdir -p "$genDir"/temp/res/layout
+
+compileAndLink stringOne BOTH AndroidManifestFramework.xml res/values/string_one.xml
+compileAndLink stringTwo BOTH AndroidManifestFramework.xml res/values/string_two.xml
+
+compileAndLink dimenOne BOTH AndroidManifestFramework.xml res/values/dimen_one.xml
+compileAndLink dimenTwo BOTH AndroidManifestFramework.xml res/values/dimen_two.xml
+
+compileAndLink drawableMdpiWithoutFile BOTH_WITHOUT_FILE AndroidManifestFramework.xml res/values/drawable_one.xml res/drawable-mdpi/ic_delete.png
+compileAndLink drawableMdpiWithFile APK AndroidManifestFramework.xml res/values/drawable_one.xml res/drawable-mdpi/ic_delete.png
+
+compileAndLink layoutWithoutFile BOTH_WITHOUT_FILE AndroidManifestFramework.xml res/values/activity_list_item_id.xml res/layout/activity_list_item.xml
+compileAndLink layoutWithFile APK AndroidManifestFramework.xml res/values/activity_list_item_id.xml res/layout/activity_list_item.xml
+
+cp -f "$inDir"/res/layout/layout_one.xml "$genDir"/temp/res/layout/layout.xml
+compileAndLink layoutOne ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml
+cp -f "$genDir"/out/layoutOne/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutOne.xml
+
+cp -f "$inDir"/res/layout/layout_two.xml "$genDir"/temp/res/layout/layout.xml
+compileAndLink layoutTwo ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml
+cp -f "$genDir"/out/layoutTwo/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutTwo.xml
+
+drawableNoDpi="/res/drawable-nodpi"
+inDirDrawableNoDpi="$inDir$drawableNoDpi"
+
+cp -f "$inDirDrawableNoDpi"/nonAssetDrawableOne.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml
+compileAndLink nonAssetDrawableOne ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml
+cp -f "$genDir"/out/nonAssetDrawableOne/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableOne.xml
+
+cp -f "$inDirDrawableNoDpi"/nonAssetDrawableTwo.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml
+compileAndLink nonAssetDrawableTwo ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml
+cp -f "$genDir"/out/nonAssetDrawableTwo/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableTwo.xml
+
+cp -f "$inDirDrawableNoDpi"/nonAssetBitmapGreen.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png
+compileAndLink nonAssetBitmapGreen BOTH AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml
+cp -f "$genDir"/out/nonAssetBitmapGreen/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapGreen.png
+
+cp -f "$inDirDrawableNoDpi"/nonAssetBitmapBlue.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png
+compileAndLink nonAssetBitmapBlue ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml
+cp -f "$genDir"/out/nonAssetBitmapBlue/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapBlue.png
+
+$soong_zip -o "$genDir"/out.zip -C "$genDir"/output/ -D "$genDir"/output/
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-mdpi/ic_delete.png b/core/tests/ResourceLoaderTests/resources/res/drawable-mdpi/ic_delete.png
new file mode 100644
index 0000000..f3e53d7
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-mdpi/ic_delete.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapBlue.png b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapBlue.png
new file mode 100644
index 0000000..5231d17
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapBlue.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapGreen.png b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapGreen.png
new file mode 100644
index 0000000..671d6d0
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapGreen.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml
new file mode 100644
index 0000000..f1a93d2
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<color
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="#A3C3E3"
+ />
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml
new file mode 100644
index 0000000..7c455a5
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<color
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="#3A3C3E"
+ />
diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/activity_list_item.xml b/core/tests/ResourceLoaderTests/resources/res/layout/activity_list_item.xml
new file mode 100644
index 0000000..d59059b
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/layout/activity_list_item.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+
diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/layout_one.xml b/core/tests/ResourceLoaderTests/resources/res/layout/layout_one.xml
new file mode 100644
index 0000000..ede3838
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/layout/layout_one.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+
diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/layout_two.xml b/core/tests/ResourceLoaderTests/resources/res/layout/layout_two.xml
new file mode 100644
index 0000000..d8bff90
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/layout/layout_two.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
+
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/activity_list_item_id.xml b/core/tests/ResourceLoaderTests/resources/res/values/activity_list_item_id.xml
new file mode 100644
index 0000000..a552431
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/activity_list_item_id.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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>
+ <public type="layout" name="activity_list_item" id="0x01090000" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml
new file mode 100644
index 0000000..69ecf23
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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>
+ <public type="dimen" name="app_icon_size" id="0x01050000" />
+ <dimen name="app_icon_size">564716dp</dimen>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml
new file mode 100644
index 0000000..4d55def
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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>
+ <public type="dimen" name="app_icon_size" id="0x01050000" />
+ <dimen name="app_icon_size">565717dp</dimen>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/drawable_one.xml b/core/tests/ResourceLoaderTests/resources/res/values/drawable_one.xml
new file mode 100644
index 0000000..b5b4dfd
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/drawable_one.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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>
+ <public type="drawable" name="ic_delete" id="0x0108001d" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/layout_id.xml b/core/tests/ResourceLoaderTests/resources/res/values/layout_id.xml
new file mode 100644
index 0000000..4962a07
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/layout_id.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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>
+ <public type="layout" name="layout" id="0x7f020000" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/non_asset_bitmap_id.xml b/core/tests/ResourceLoaderTests/resources/res/values/non_asset_bitmap_id.xml
new file mode 100644
index 0000000..38b152b
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/non_asset_bitmap_id.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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>
+ <public type="drawable" name="non_asset_bitmap" id="0x7f010000" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/non_asset_drawable_id.xml b/core/tests/ResourceLoaderTests/resources/res/values/non_asset_drawable_id.xml
new file mode 100644
index 0000000..bdd6f58
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/non_asset_drawable_id.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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>
+ <public type="drawable" name="non_asset_drawable" id="0x7f010001" />
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/string_one.xml b/core/tests/ResourceLoaderTests/resources/res/values/string_one.xml
new file mode 100644
index 0000000..4fc5272
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/string_one.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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>
+ <public type="string" name="cancel" id="0x01040000" />
+ <string name="cancel">SomeRidiculouslyUnlikelyStringOne</string>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/resources/res/values/string_two.xml b/core/tests/ResourceLoaderTests/resources/res/values/string_two.xml
new file mode 100644
index 0000000..3604d7b
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/values/string_two.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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>
+ <public type="string" name="cancel" id="0x01040000" />
+ <string name="cancel">SomeRidiculouslyUnlikelyStringTwo</string>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/splits/Android.bp b/core/tests/ResourceLoaderTests/splits/Android.bp
new file mode 100644
index 0000000..4582808
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/splits/Android.bp
@@ -0,0 +1,19 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+ name: "FrameworksResourceLoaderTestsSplitTwo"
+}
diff --git a/core/tests/ResourceLoaderTests/splits/AndroidManifest.xml b/core/tests/ResourceLoaderTests/splits/AndroidManifest.xml
new file mode 100644
index 0000000..aad8c27
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/splits/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="android.content.res.loader.test"
+ split="split_two"
+ >
+
+ <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
+ <application android:hasCode="false" />
+
+</manifest>
diff --git a/core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml b/core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml
new file mode 100644
index 0000000..a367063
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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>
+ <public type="string" name="split_overlaid" id="0x7f040001" />
+ <string name="split_overlaid">Split TWO Overlaid</string>
+</resources>
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt
new file mode 100644
index 0000000..b1bdc96
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader.test
+
+import android.content.res.loader.DirectoryResourceLoader
+import android.content.res.loader.ResourceLoader
+import android.graphics.Color
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.ColorDrawable
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import java.io.File
+
+class DirectoryResourceLoaderTest : ResourceLoaderTestBase() {
+
+ @get:Rule
+ val testName = TestName()
+
+ private lateinit var testDir: File
+ private lateinit var loader: ResourceLoader
+
+ @Before
+ fun setUpTestDir() {
+ testDir = context.filesDir.resolve("DirectoryResourceLoaderTest_${testName.methodName}")
+ loader = DirectoryResourceLoader(testDir)
+ }
+
+ @After
+ fun deleteTestFiles() {
+ testDir.deleteRecursively()
+ }
+
+ @Test
+ fun loadDrawableXml() {
+ "nonAssetDrawableOne" writeTo "res/drawable-nodpi-v4/non_asset_drawable.xml"
+ val provider = openArsc("nonAssetDrawableOne")
+
+ fun getValue() = (resources.getDrawable(R.drawable.non_asset_drawable) as ColorDrawable)
+ .color
+
+ assertThat(getValue()).isEqualTo(Color.parseColor("#B2D2F2"))
+
+ addLoader(loader to provider)
+
+ assertThat(getValue()).isEqualTo(Color.parseColor("#A3C3E3"))
+ }
+
+ @Test
+ fun loadDrawableBitmap() {
+ "nonAssetBitmapGreen" writeTo "res/drawable-nodpi-v4/non_asset_bitmap.png"
+ val provider = openArsc("nonAssetBitmapGreen")
+
+ fun getValue() = (resources.getDrawable(R.drawable.non_asset_bitmap) as BitmapDrawable)
+ .bitmap.getColor(0, 0).toArgb()
+
+ assertThat(getValue()).isEqualTo(Color.RED)
+
+ addLoader(loader to provider)
+
+ assertThat(getValue()).isEqualTo(Color.GREEN)
+ }
+
+ @Test
+ fun loadXml() {
+ "layoutOne" writeTo "res/layout/layout.xml"
+ val provider = openArsc("layoutOne")
+
+ fun getValue() = resources.getLayout(R.layout.layout).advanceToRoot().name
+
+ assertThat(getValue()).isEqualTo("FrameLayout")
+
+ addLoader(loader to provider)
+
+ assertThat(getValue()).isEqualTo("RelativeLayout")
+ }
+
+ private infix fun String.writeTo(path: String) {
+ val testFile = testDir.resolve(path)
+ testFile.parentFile!!.mkdirs()
+ resources.openRawResource(rawFile(this))
+ .copyTo(testFile.outputStream())
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt
new file mode 100644
index 0000000..a6a8378
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader.test
+
+import android.content.res.AssetManager
+import android.content.res.loader.DirectoryResourceLoader
+import android.content.res.loader.ResourceLoader
+import android.content.res.loader.ResourcesProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestName
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.mock
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.IOException
+import java.nio.file.Paths
+
+@RunWith(Parameterized::class)
+class ResourceLoaderAssetTest : ResourceLoaderTestBase() {
+
+ companion object {
+ private const val BASE_TEST_PATH = "android/content/res/loader/test/file.txt"
+ private const val TEST_TEXT = "some text"
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun parameters(): Array<Array<out Any?>> {
+ val fromInputStream: ResourceLoader.(String) -> Any? = {
+ loadAsset(eq(it), anyInt())
+ }
+
+ val fromFileDescriptor: ResourceLoader.(String) -> Any? = {
+ loadAssetFd(eq(it))
+ }
+
+ val openAsset: AssetManager.() -> String? = {
+ open(BASE_TEST_PATH).reader().readText()
+ }
+
+ val openNonAsset: AssetManager.() -> String? = {
+ openNonAssetFd(BASE_TEST_PATH).readText()
+ }
+
+ return arrayOf(
+ arrayOf("assets", fromInputStream, openAsset),
+ arrayOf("", fromFileDescriptor, openNonAsset)
+ )
+ }
+ }
+
+ @get:Rule
+ val testName = TestName()
+
+ @JvmField
+ @field:Parameterized.Parameter(0)
+ var prefix: String? = null
+
+ @field:Parameterized.Parameter(1)
+ lateinit var loadAssetFunction: ResourceLoader.(String) -> Any?
+
+ @field:Parameterized.Parameter(2)
+ lateinit var openAssetFunction: AssetManager.() -> String?
+
+ private val testPath: String
+ get() = Paths.get(prefix.orEmpty(), BASE_TEST_PATH).toString()
+
+ private fun ResourceLoader.loadAsset() = loadAssetFunction(testPath)
+
+ private fun AssetManager.openAsset() = openAssetFunction()
+
+ private lateinit var testDir: File
+
+ @Before
+ fun setUpTestDir() {
+ testDir = context.filesDir.resolve("DirectoryResourceLoaderTest_${testName.methodName}")
+ testDir.resolve(testPath).apply { parentFile!!.mkdirs() }.writeText(TEST_TEXT)
+ }
+
+ @Test
+ fun multipleLoadersSearchesBackwards() {
+ // DirectoryResourceLoader relies on a private field and can't be spied directly, so wrap it
+ val loader = DirectoryResourceLoader(testDir)
+ val loaderWrapper = mock(ResourceLoader::class.java).apply {
+ doAnswer { loader.loadAsset(it.arguments[0] as String, it.arguments[1] as Int) }
+ .`when`(this).loadAsset(anyString(), anyInt())
+ doAnswer { loader.loadAssetFd(it.arguments[0] as String) }
+ .`when`(this).loadAssetFd(anyString())
+ }
+
+ val one = loaderWrapper to ResourcesProvider.empty()
+ val two = mockLoader {
+ doReturn(null).`when`(it).loadAsset()
+ }
+
+ addLoader(one, two)
+
+ assertOpenedAsset()
+ inOrder(two.first, one.first).apply {
+ verify(two.first).loadAsset()
+ verify(one.first).loadAsset()
+ }
+ }
+
+ @Test(expected = FileNotFoundException::class)
+ fun failToFindThrowsFileNotFound() {
+ val one = mockLoader {
+ doReturn(null).`when`(it).loadAsset()
+ }
+ val two = mockLoader {
+ doReturn(null).`when`(it).loadAsset()
+ }
+
+ addLoader(one, two)
+
+ assertOpenedAsset()
+ }
+
+ @Test
+ fun throwingIOExceptionIsSkipped() {
+ val one = DirectoryResourceLoader(testDir) to ResourcesProvider.empty()
+ val two = mockLoader {
+ doAnswer { throw IOException() }.`when`(it).loadAsset()
+ }
+
+ addLoader(one, two)
+
+ assertOpenedAsset()
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun throwingNonIOExceptionCausesFailure() {
+ val one = DirectoryResourceLoader(testDir) to ResourcesProvider.empty()
+ val two = mockLoader {
+ doAnswer { throw IllegalStateException() }.`when`(it).loadAsset()
+ }
+
+ addLoader(one, two)
+
+ assertOpenedAsset()
+ }
+
+ private fun assertOpenedAsset() {
+ assertThat(resources.assets.openAsset()).isEqualTo(TEST_TEXT)
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt
new file mode 100644
index 0000000..e01e254
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader.test
+
+import android.app.Activity
+import android.app.Instrumentation
+import android.app.UiAutomation
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.graphics.Color
+import android.os.Bundle
+import android.os.ParcelFileDescriptor
+import android.widget.FrameLayout
+import androidx.test.InstrumentationRegistry
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
+import androidx.test.runner.lifecycle.Stage
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import java.util.Arrays
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
+import java.util.concurrent.FutureTask
+import java.util.concurrent.TimeUnit
+
+// @Ignore("UiAutomation is crashing with not connected, not sure why")
+@RunWith(Parameterized::class)
+class ResourceLoaderChangesTest : ResourceLoaderTestBase() {
+
+ companion object {
+ private const val TIMEOUT = 30L
+ private const val OVERLAY_PACKAGE = "android.content.res.loader.test.overlay"
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun data() = arrayOf(DataType.APK, DataType.ARSC)
+ }
+
+ @field:Parameterized.Parameter(0)
+ override lateinit var dataType: DataType
+
+ @get:Rule
+ val activityRule: ActivityTestRule<TestActivity> =
+ ActivityTestRule<TestActivity>(TestActivity::class.java, false, true)
+
+ // Redirect to the Activity's resources
+ override val resources: Resources
+ get() = activityRule.getActivity().resources
+
+ private val activity: TestActivity
+ get() = activityRule.getActivity()
+
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+
+ @Before
+ @After
+ fun disableOverlay() {
+// enableOverlay(OVERLAY_PACKAGE, false)
+ }
+
+ @Test
+ fun activityRecreate() = verifySameBeforeAndAfter {
+ val oldActivity = activity
+ var newActivity: Activity? = null
+ instrumentation.runOnMainSync { oldActivity.recreate() }
+ instrumentation.waitForIdleSync()
+ instrumentation.runOnMainSync {
+ newActivity = ActivityLifecycleMonitorRegistry.getInstance()
+ .getActivitiesInStage(Stage.RESUMED)
+ .single()
+ }
+
+ assertThat(newActivity).isNotNull()
+ assertThat(newActivity).isNotSameAs(oldActivity)
+
+ // Return the new resources to assert on
+ return@verifySameBeforeAndAfter newActivity!!.resources
+ }
+
+ @Test
+ fun activityHandledOrientationChange() = verifySameBeforeAndAfter {
+ val latch = CountDownLatch(1)
+ val oldConfig = Configuration().apply { setTo(resources.configuration) }
+ var changedConfig: Configuration? = null
+
+ activity.callback = object : TestActivity.Callback {
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ changedConfig = newConfig
+ latch.countDown()
+ }
+ }
+
+ val isPortrait = resources.displayMetrics.run { widthPixels < heightPixels }
+ val newRotation = if (isPortrait) {
+ UiAutomation.ROTATION_FREEZE_90
+ } else {
+ UiAutomation.ROTATION_FREEZE_0
+ }
+
+ instrumentation.uiAutomation.setRotation(newRotation)
+
+ assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).isTrue()
+ assertThat(changedConfig).isNotEqualTo(oldConfig)
+ return@verifySameBeforeAndAfter activity.resources
+ }
+
+ @Test
+ fun enableOverlayCausingPathChange() = verifySameBeforeAndAfter {
+ assertThat(getString(R.string.loader_path_change_test)).isEqualTo("Not overlaid")
+
+ enableOverlay(OVERLAY_PACKAGE, true)
+
+ assertThat(getString(R.string.loader_path_change_test)).isEqualTo("Overlaid")
+
+ return@verifySameBeforeAndAfter activity.resources
+ }
+
+ @Test
+ fun enableOverlayChildContextUnaffected() {
+ val childContext = activity.createConfigurationContext(Configuration())
+ val childResources = childContext.resources
+ val originalValue = childResources.getString(android.R.string.cancel)
+ assertThat(childResources.getString(R.string.loader_path_change_test))
+ .isEqualTo("Not overlaid")
+
+ verifySameBeforeAndAfter {
+ enableOverlay(OVERLAY_PACKAGE, true)
+ return@verifySameBeforeAndAfter activity.resources
+ }
+
+ // Loader not applied, but overlay change propagated
+ assertThat(childResources.getString(android.R.string.cancel)).isEqualTo(originalValue)
+ assertThat(childResources.getString(R.string.loader_path_change_test))
+ .isEqualTo("Overlaid")
+ }
+
+ // All these tests assert for the exact same loaders/values, so extract that logic out
+ private fun verifySameBeforeAndAfter(block: () -> Resources) {
+ // TODO(chiuwinson): atest doesn't work with @Ignore, UiAutomation not connected error
+ Assume.assumeFalse(true)
+
+ val originalValue = resources.getString(android.R.string.cancel)
+
+ val loader = "stringOne".openLoader()
+ addLoader(loader)
+
+ val oldLoaders = resources.loaders
+ val oldValue = resources.getString(android.R.string.cancel)
+
+ assertThat(oldValue).isNotEqualTo(originalValue)
+
+ val newResources = block()
+
+ val newLoaders = newResources.loaders
+ val newValue = newResources.getString(android.R.string.cancel)
+
+ assertThat(newValue).isEqualTo(oldValue)
+ assertThat(newLoaders).isEqualTo(oldLoaders)
+ }
+
+ // Copied from overlaytests LocalOverlayManager
+ private fun enableOverlay(packageName: String, enable: Boolean) {
+ val executor = Executor { Thread(it).start() }
+ val pattern = (if (enable) "[x]" else "[ ]") + " " + packageName
+ if (executeShellCommand("cmd overlay list").contains(pattern)) {
+ // nothing to do, overlay already in the requested state
+ return
+ }
+
+ val oldApkPaths = resources.assets.apkPaths
+ val task = FutureTask {
+ while (true) {
+ if (!Arrays.equals(oldApkPaths, resources.assets.apkPaths)) {
+ return@FutureTask true
+ }
+ Thread.sleep(10)
+ }
+
+ @Suppress("UNREACHABLE_CODE")
+ return@FutureTask false
+ }
+
+ val command = if (enable) "enable" else "disable"
+ executeShellCommand("cmd overlay $command $packageName")
+ executor.execute(task)
+ assertThat(task.get(TIMEOUT, TimeUnit.SECONDS)).isTrue()
+ }
+
+ private fun executeShellCommand(command: String): String {
+ val uiAutomation = instrumentation.uiAutomation
+ val pfd = uiAutomation.executeShellCommand(command)
+ return ParcelFileDescriptor.AutoCloseInputStream(pfd).use { it.reader().readText() }
+ }
+}
+
+class TestActivity : Activity() {
+
+ var callback: Callback? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContentView(FrameLayout(this).apply {
+ setBackgroundColor(Color.BLUE)
+ })
+ }
+
+ override fun onConfigurationChanged(newConfig: Configuration) {
+ super.onConfigurationChanged(newConfig)
+ callback?.onConfigurationChanged(newConfig)
+ }
+
+ interface Callback {
+ fun onConfigurationChanged(newConfig: Configuration)
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt
new file mode 100644
index 0000000..09fd27e
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.content.res.loader.test
+
+import android.content.res.Resources
+import android.content.res.loader.ResourceLoader
+import android.content.res.loader.ResourcesProvider
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import com.google.common.truth.Truth.assertThat
+import org.hamcrest.CoreMatchers.not
+import org.junit.Assume.assumeThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@RunWith(Parameterized::class)
+class ResourceLoaderDrawableTest : ResourceLoaderTestBase() {
+
+ companion object {
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun data() = arrayOf(DataType.APK, DataType.ARSC)
+ }
+
+ @field:Parameterized.Parameter(0)
+ override lateinit var dataType: DataType
+
+ @Test
+ fun matchingConfig() {
+ val original = getDrawable(android.R.drawable.ic_delete)
+ val loader = "drawableMdpiWithoutFile".openLoader()
+ `when`(loader.first.loadDrawable(any(), anyInt(), anyInt(), any()))
+ .thenReturn(ColorDrawable(Color.BLUE))
+
+ addLoader(loader)
+
+ updateConfiguration { densityDpi = 160 /* mdpi */ }
+
+ val drawable = getDrawable(android.R.drawable.ic_delete)
+
+ loader.verifyLoadDrawableCalled()
+
+ assertThat(drawable).isNotEqualTo(original)
+ assertThat(drawable).isInstanceOf(ColorDrawable::class.java)
+ assertThat((drawable as ColorDrawable).color).isEqualTo(Color.BLUE)
+ }
+
+ @Test
+ fun worseConfig() {
+ val loader = "drawableMdpiWithoutFile".openLoader()
+ addLoader(loader)
+
+ updateConfiguration { densityDpi = 480 /* xhdpi */ }
+
+ getDrawable(android.R.drawable.ic_delete)
+
+ verify(loader.first, never()).loadDrawable(any(), anyInt(), anyInt(), any())
+ }
+
+ @Test
+ fun multipleLoaders() {
+ val original = getDrawable(android.R.drawable.ic_delete)
+ val loaderOne = "drawableMdpiWithoutFile".openLoader()
+ val loaderTwo = "drawableMdpiWithoutFile".openLoader()
+
+ `when`(loaderTwo.first.loadDrawable(any(), anyInt(), anyInt(), any()))
+ .thenReturn(ColorDrawable(Color.BLUE))
+
+ addLoader(loaderOne, loaderTwo)
+
+ updateConfiguration { densityDpi = 160 /* mdpi */ }
+
+ val drawable = getDrawable(android.R.drawable.ic_delete)
+ loaderOne.verifyLoadDrawableNotCalled()
+ loaderTwo.verifyLoadDrawableCalled()
+
+ assertThat(drawable).isNotEqualTo(original)
+ assertThat(drawable).isInstanceOf(ColorDrawable::class.java)
+ assertThat((drawable as ColorDrawable).color).isEqualTo(Color.BLUE)
+ }
+
+ @Test(expected = Resources.NotFoundException::class)
+ fun multipleLoadersNoReturnWithoutFile() {
+ val loaderOne = "drawableMdpiWithoutFile".openLoader()
+ val loaderTwo = "drawableMdpiWithoutFile".openLoader()
+
+ addLoader(loaderOne, loaderTwo)
+
+ updateConfiguration { densityDpi = 160 /* mdpi */ }
+
+ try {
+ getDrawable(android.R.drawable.ic_delete)
+ } finally {
+ // We expect the call to fail because at least the loader won't resolve the overridden
+ // drawable, but we should still verify that both loaders were called before allowing
+ // the exception to propagate.
+ loaderOne.verifyLoadDrawableNotCalled()
+ loaderTwo.verifyLoadDrawableCalled()
+ }
+ }
+
+ @Test
+ fun multipleLoadersReturnWithFile() {
+ // Can't return a file if an ARSC
+ assumeThat(dataType, not(DataType.ARSC))
+
+ val original = getDrawable(android.R.drawable.ic_delete)
+ val loaderOne = "drawableMdpiWithFile".openLoader()
+ val loaderTwo = "drawableMdpiWithFile".openLoader()
+
+ addLoader(loaderOne, loaderTwo)
+
+ updateConfiguration { densityDpi = 160 /* mdpi */ }
+
+ val drawable = getDrawable(android.R.drawable.ic_delete)
+ loaderOne.verifyLoadDrawableNotCalled()
+ loaderTwo.verifyLoadDrawableCalled()
+
+ assertThat(drawable).isNotNull()
+ assertThat(drawable).isInstanceOf(original.javaClass)
+ }
+
+ @Test
+ fun unhandledResourceIgnoresLoaders() {
+ val loader = "drawableMdpiWithoutFile".openLoader()
+ `when`(loader.first.loadDrawable(any(), anyInt(), anyInt(), any()))
+ .thenReturn(ColorDrawable(Color.BLUE))
+ addLoader(loader)
+
+ getDrawable(android.R.drawable.ic_menu_add)
+
+ loader.verifyLoadDrawableNotCalled()
+
+ getDrawable(android.R.drawable.ic_delete)
+
+ loader.verifyLoadDrawableCalled()
+ }
+
+ private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadDrawableCalled() {
+ verify(first).loadDrawable(
+ argThat {
+ it.density == 160 &&
+ it.resourceId == android.R.drawable.ic_delete &&
+ it.string == "res/drawable-mdpi-v4/ic_delete.png"
+ },
+ eq(android.R.drawable.ic_delete),
+ eq(0),
+ any()
+ )
+ }
+
+ private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadDrawableNotCalled() {
+ verify(first, never()).loadDrawable(
+ any(),
+ anyInt(),
+ anyInt(),
+ any()
+ )
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt
new file mode 100644
index 0000000..1ec2094
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.content.res.loader.test
+
+import android.content.res.Resources
+import android.content.res.XmlResourceParser
+import android.content.res.loader.ResourceLoader
+import android.content.res.loader.ResourcesProvider
+import com.google.common.truth.Truth.assertThat
+import org.hamcrest.CoreMatchers.not
+import org.junit.Assume.assumeThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@RunWith(Parameterized::class)
+class ResourceLoaderLayoutTest : ResourceLoaderTestBase() {
+
+ companion object {
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "{0}")
+ fun data() = arrayOf(DataType.APK, DataType.ARSC)
+ }
+
+ @field:Parameterized.Parameter(0)
+ override lateinit var dataType: DataType
+
+ @Test
+ fun singleLoader() {
+ val original = getLayout(android.R.layout.activity_list_item)
+ val mockXml = mock(XmlResourceParser::class.java)
+ val loader = "layoutWithoutFile".openLoader()
+ `when`(loader.first.loadXmlResourceParser(any(), anyInt()))
+ .thenReturn(mockXml)
+
+ addLoader(loader)
+
+ val layout = getLayout(android.R.layout.activity_list_item)
+ loader.verifyLoadLayoutCalled()
+
+ assertThat(layout).isNotEqualTo(original)
+ assertThat(layout).isSameAs(mockXml)
+ }
+
+ @Test
+ fun multipleLoaders() {
+ val original = getLayout(android.R.layout.activity_list_item)
+ val loaderOne = "layoutWithoutFile".openLoader()
+ val loaderTwo = "layoutWithoutFile".openLoader()
+
+ val mockXml = mock(XmlResourceParser::class.java)
+ `when`(loaderTwo.first.loadXmlResourceParser(any(), anyInt()))
+ .thenReturn(mockXml)
+
+ addLoader(loaderOne, loaderTwo)
+
+ val layout = getLayout(android.R.layout.activity_list_item)
+ loaderOne.verifyLoadLayoutNotCalled()
+ loaderTwo.verifyLoadLayoutCalled()
+
+ assertThat(layout).isNotEqualTo(original)
+ assertThat(layout).isSameAs(mockXml)
+ }
+
+ @Test(expected = Resources.NotFoundException::class)
+ fun multipleLoadersNoReturnWithoutFile() {
+ val loaderOne = "layoutWithoutFile".openLoader()
+ val loaderTwo = "layoutWithoutFile".openLoader()
+
+ addLoader(loaderOne, loaderTwo)
+
+ try {
+ getLayout(android.R.layout.activity_list_item)
+ } finally {
+ // We expect the call to fail because at least one loader must resolve the overridden
+ // layout, but we should still verify that both loaders were called before allowing
+ // the exception to propagate.
+ loaderOne.verifyLoadLayoutNotCalled()
+ loaderTwo.verifyLoadLayoutCalled()
+ }
+ }
+
+ @Test
+ fun multipleLoadersReturnWithFile() {
+ // Can't return a file if an ARSC
+ assumeThat(dataType, not(DataType.ARSC))
+
+ val loaderOne = "layoutWithFile".openLoader()
+ val loaderTwo = "layoutWithFile".openLoader()
+
+ addLoader(loaderOne, loaderTwo)
+
+ val xml = getLayout(android.R.layout.activity_list_item)
+ loaderOne.verifyLoadLayoutNotCalled()
+ loaderTwo.verifyLoadLayoutCalled()
+
+ assertThat(xml).isNotNull()
+ }
+
+ @Test
+ fun unhandledResourceIgnoresLoaders() {
+ val loader = "layoutWithoutFile".openLoader()
+ val mockXml = mock(XmlResourceParser::class.java)
+ `when`(loader.first.loadXmlResourceParser(any(), anyInt()))
+ .thenReturn(mockXml)
+ addLoader(loader)
+
+ getLayout(android.R.layout.preference_category)
+
+ verify(loader.first, never())
+ .loadXmlResourceParser(anyString(), anyInt())
+
+ getLayout(android.R.layout.activity_list_item)
+
+ loader.verifyLoadLayoutCalled()
+ }
+
+ private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadLayoutCalled() {
+ verify(first).loadXmlResourceParser(
+ "res/layout/activity_list_item.xml",
+ android.R.layout.activity_list_item
+ )
+ }
+
+ private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadLayoutNotCalled() {
+ verify(first, never()).loadXmlResourceParser(
+ anyString(),
+ anyInt()
+ )
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt
new file mode 100644
index 0000000..5af453d
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.content.res.loader.test
+
+import android.content.Context
+import android.content.res.AssetManager
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.content.res.loader.ResourceLoader
+import android.content.res.loader.ResourcesProvider
+import android.os.ParcelFileDescriptor
+import android.util.TypedValue
+import androidx.annotation.DimenRes
+import androidx.annotation.DrawableRes
+import androidx.annotation.LayoutRes
+import androidx.annotation.StringRes
+import androidx.test.InstrumentationRegistry
+import org.junit.After
+import org.junit.Before
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.argThat
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import java.io.Closeable
+
+abstract class ResourceLoaderTestBase {
+
+ open lateinit var dataType: DataType
+
+ protected lateinit var context: Context
+ protected open val resources: Resources
+ get() = context.resources
+ protected open val assets: AssetManager
+ get() = resources.assets
+
+ // Track opened streams and ResourcesProviders to close them after testing
+ private val openedObjects = mutableListOf<Closeable>()
+
+ @Before
+ fun setUpBase() {
+ context = InstrumentationRegistry.getTargetContext()
+ }
+
+ @After
+ fun removeAllLoaders() {
+ resources.setLoaders(null)
+ openedObjects.forEach {
+ try {
+ it.close()
+ } catch (ignored: Exception) {
+ }
+ }
+ }
+
+ protected fun getString(@StringRes stringRes: Int, debugLog: Boolean = false) =
+ logResolution(debugLog) { getString(stringRes) }
+
+ protected fun getDrawable(@DrawableRes drawableRes: Int, debugLog: Boolean = false) =
+ logResolution(debugLog) { getDrawable(drawableRes) }
+
+ protected fun getLayout(@LayoutRes layoutRes: Int, debugLog: Boolean = false) =
+ logResolution(debugLog) { getLayout(layoutRes) }
+
+ protected fun getDimensionPixelSize(@DimenRes dimenRes: Int, debugLog: Boolean = false) =
+ logResolution(debugLog) { getDimensionPixelSize(dimenRes) }
+
+ private fun <T> logResolution(debugLog: Boolean = false, block: Resources.() -> T): T {
+ if (debugLog) {
+ resources.assets.setResourceResolutionLoggingEnabled(true)
+ }
+
+ var thrown = false
+
+ try {
+ return resources.block()
+ } catch (t: Throwable) {
+ // No good way to log to test output other than throwing an exception
+ if (debugLog) {
+ thrown = true
+ throw IllegalStateException(resources.assets.lastResourceResolution, t)
+ } else {
+ throw t
+ }
+ } finally {
+ if (!thrown && debugLog) {
+ throw IllegalStateException(resources.assets.lastResourceResolution)
+ }
+ }
+ }
+
+ protected fun updateConfiguration(block: Configuration.() -> Unit) {
+ val configuration = Configuration().apply {
+ setTo(resources.configuration)
+ block()
+ }
+
+ resources.updateConfiguration(configuration, resources.displayMetrics)
+ }
+
+ protected fun String.openLoader(
+ dataType: DataType = this@ResourceLoaderTestBase.dataType
+ ): Pair<ResourceLoader, ResourcesProvider> = when (dataType) {
+ DataType.APK -> {
+ mock(ResourceLoader::class.java) to context.copiedRawFile("${this}Apk").use {
+ ResourcesProvider.loadFromApk(it)
+ }.also { openedObjects += it }
+ }
+ DataType.ARSC -> {
+ mock(ResourceLoader::class.java) to openArsc(this)
+ }
+ DataType.SPLIT -> {
+ mock(ResourceLoader::class.java) to ResourcesProvider.loadFromSplit(context, this)
+ }
+ DataType.ASSET -> mockLoader {
+ doAnswer { byteInputStream() }.`when`(it)
+ .loadAsset(eq("assets/Asset.txt"), anyInt())
+ }
+ DataType.ASSET_FD -> mockLoader {
+ doAnswer {
+ val file = context.filesDir.resolve("Asset.txt")
+ file.writeText(this)
+ ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
+ }.`when`(it).loadAssetFd("assets/Asset.txt")
+ }
+ DataType.NON_ASSET -> mockLoader {
+ doAnswer {
+ val file = context.filesDir.resolve("NonAsset.txt")
+ file.writeText(this)
+ ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
+ }.`when`(it).loadAssetFd("NonAsset.txt")
+ }
+ DataType.NON_ASSET_DRAWABLE -> mockLoader(openArsc(this)) {
+ doReturn(null).`when`(it).loadDrawable(argThat { value ->
+ value.type == TypedValue.TYPE_STRING &&
+ value.resourceId == 0x7f010001 &&
+ value.string == "res/drawable-nodpi-v4/non_asset_drawable.xml"
+ }, eq(0x7f010001), anyInt(), ArgumentMatchers.any())
+
+ doAnswer { context.copiedRawFile(this) }.`when`(it)
+ .loadAssetFd("res/drawable-nodpi-v4/non_asset_drawable.xml")
+ }
+ DataType.NON_ASSET_BITMAP -> mockLoader(openArsc(this)) {
+ doReturn(null).`when`(it).loadDrawable(argThat { value ->
+ value.type == TypedValue.TYPE_STRING &&
+ value.resourceId == 0x7f010000 &&
+ value.string == "res/drawable-nodpi-v4/non_asset_bitmap.png"
+ }, eq(0x7f010000), anyInt(), ArgumentMatchers.any())
+
+ doAnswer { resources.openRawResourceFd(rawFile(this)).createInputStream() }
+ .`when`(it)
+ .loadAsset(eq("res/drawable-nodpi-v4/non_asset_bitmap.png"), anyInt())
+ }
+ DataType.NON_ASSET_LAYOUT -> mockLoader(openArsc(this)) {
+ doReturn(null).`when`(it)
+ .loadXmlResourceParser("res/layout/layout.xml", 0x7f020000)
+
+ doAnswer { context.copiedRawFile(this) }.`when`(it)
+ .loadAssetFd("res/layout/layout.xml")
+ }
+ }
+
+ protected fun mockLoader(
+ provider: ResourcesProvider = ResourcesProvider.empty(),
+ block: (ResourceLoader) -> Unit = {}
+ ): Pair<ResourceLoader, ResourcesProvider> {
+ return mock(ResourceLoader::class.java, Utils.ANSWER_THROWS)
+ .apply(block) to provider
+ }
+
+ protected fun openArsc(rawName: String): ResourcesProvider {
+ return context.copiedRawFile("${rawName}Arsc")
+ .use { ResourcesProvider.loadFromArsc(it) }
+ .also { openedObjects += it }
+ }
+
+ // This specifically uses addLoader so both behaviors are tested
+ protected fun addLoader(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) {
+ pairs.forEach { resources.addLoader(it.first, it.second) }
+ }
+
+ protected fun setLoaders(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) {
+ resources.setLoaders(pairs.map { android.util.Pair(it.first, it.second) })
+ }
+
+ protected fun addLoader(pair: Pair<out ResourceLoader, ResourcesProvider>, index: Int) {
+ resources.addLoader(pair.first, pair.second, index)
+ }
+
+ protected fun removeLoader(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) {
+ pairs.forEach { resources.removeLoader(it.first) }
+ }
+
+ protected fun getLoaders(): MutableList<Pair<ResourceLoader, ResourcesProvider>> {
+ // Cast instead of toMutableList to maintain the same object
+ return resources.getLoaders() as MutableList<Pair<ResourceLoader, ResourcesProvider>>
+ }
+
+ enum class DataType {
+ APK,
+ ARSC,
+ SPLIT,
+ ASSET,
+ ASSET_FD,
+ NON_ASSET,
+ NON_ASSET_DRAWABLE,
+ NON_ASSET_BITMAP,
+ NON_ASSET_LAYOUT,
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
new file mode 100644
index 0000000..017552a
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.content.res.loader.test
+
+import android.graphics.Color
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.ColorDrawable
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Tests generic ResourceLoader behavior. Intentionally abstract in its test methodology because
+ * the behavior being verified isn't specific to any resource type. As long as it can pass an
+ * equals check.
+ *
+ * Currently tests strings and dimens since String and any Number seemed most relevant to verify.
+ */
+@RunWith(Parameterized::class)
+class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
+
+ companion object {
+ @Parameterized.Parameters(name = "{1} {0}")
+ @JvmStatic
+ fun parameters(): Array<Any> {
+ val parameters = mutableListOf<Parameter>()
+
+ // R.string
+ parameters += Parameter(
+ { getString(android.R.string.cancel) },
+ "stringOne", { "SomeRidiculouslyUnlikelyStringOne" },
+ "stringTwo", { "SomeRidiculouslyUnlikelyStringTwo" },
+ listOf(DataType.APK, DataType.ARSC)
+ )
+
+ // R.dimen
+ parameters += Parameter(
+ { resources.getDimensionPixelSize(android.R.dimen.app_icon_size) },
+ "dimenOne", { 564716.dpToPx(resources) },
+ "dimenTwo", { 565717.dpToPx(resources) },
+ listOf(DataType.APK, DataType.ARSC)
+ )
+
+ // File in the assets directory
+ parameters += Parameter(
+ { assets.open("Asset.txt").reader().readText() },
+ "assetOne", { "assetOne" },
+ "assetTwo", { "assetTwo" },
+ listOf(DataType.ASSET)
+ )
+
+ // From assets directory returning file descriptor
+ parameters += Parameter(
+ { assets.openFd("Asset.txt").readText() },
+ "assetOne", { "assetOne" },
+ "assetTwo", { "assetTwo" },
+ listOf(DataType.ASSET_FD)
+ )
+
+ // From root directory returning file descriptor
+ parameters += Parameter(
+ { assets.openNonAssetFd("NonAsset.txt").readText() },
+ "NonAssetOne", { "NonAssetOne" },
+ "NonAssetTwo", { "NonAssetTwo" },
+ listOf(DataType.NON_ASSET)
+ )
+
+ // Asset as compiled XML drawable
+ parameters += Parameter(
+ { (getDrawable(R.drawable.non_asset_drawable) as ColorDrawable).color },
+ "nonAssetDrawableOne", { Color.parseColor("#A3C3E3") },
+ "nonAssetDrawableTwo", { Color.parseColor("#3A3C3E") },
+ listOf(DataType.NON_ASSET_DRAWABLE)
+ )
+
+ // Asset as compiled bitmap drawable
+ parameters += Parameter(
+ {
+ (getDrawable(R.drawable.non_asset_bitmap) as BitmapDrawable)
+ .bitmap.getColor(0, 0).toArgb()
+ },
+ "nonAssetBitmapGreen", { Color.GREEN },
+ "nonAssetBitmapBlue", { Color.BLUE },
+ listOf(DataType.NON_ASSET_BITMAP)
+ )
+
+ // Asset as compiled XML layout
+ parameters += Parameter(
+ { getLayout(R.layout.layout).advanceToRoot().name },
+ "layoutOne", { "RelativeLayout" },
+ "layoutTwo", { "LinearLayout" },
+ listOf(DataType.NON_ASSET_LAYOUT)
+ )
+
+ // Isolated resource split
+ parameters += Parameter(
+ { getString(R.string.split_overlaid) },
+ "split_one", { "Split ONE Overlaid" },
+ "split_two", { "Split TWO Overlaid" },
+ listOf(DataType.SPLIT)
+ )
+
+ return parameters.flatMap { parameter ->
+ parameter.dataTypes.map { dataType ->
+ arrayOf(dataType, parameter)
+ }
+ }.toTypedArray()
+ }
+ }
+
+ @Suppress("LateinitVarOverridesLateinitVar")
+ @field:Parameterized.Parameter(0)
+ override lateinit var dataType: DataType
+
+ @field:Parameterized.Parameter(1)
+ lateinit var parameter: Parameter
+
+ private val valueOne by lazy { parameter.valueOne(this) }
+ private val valueTwo by lazy { parameter.valueTwo(this) }
+
+ private fun openOne() = parameter.loaderOne.openLoader()
+ private fun openTwo() = parameter.loaderTwo.openLoader()
+
+ // Class method for syntax highlighting purposes
+ private fun getValue() = parameter.getValue(this)
+
+ @Test
+ fun verifyValueUniqueness() {
+ // Ensure the parameters are valid in case of coding errors
+ assertNotEquals(valueOne, getValue())
+ assertNotEquals(valueTwo, getValue())
+ assertNotEquals(valueOne, valueTwo)
+ }
+
+ @Test
+ fun addMultipleLoaders() {
+ val originalValue = getValue()
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne, testTwo)
+
+ assertEquals(valueTwo, getValue())
+
+ removeLoader(testTwo)
+
+ assertEquals(valueOne, getValue())
+
+ removeLoader(testOne)
+
+ assertEquals(originalValue, getValue())
+ }
+
+ @Test
+ fun setMultipleLoaders() {
+ val originalValue = getValue()
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ setLoaders(testOne, testTwo)
+
+ assertEquals(valueTwo, getValue())
+
+ removeLoader(testTwo)
+
+ assertEquals(valueOne, getValue())
+
+ setLoaders()
+
+ assertEquals(originalValue, getValue())
+ }
+
+ @Test
+ fun getLoadersContainsAll() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne, testTwo)
+
+ assertThat(getLoaders()).containsAllOf(testOne, testTwo)
+ }
+
+ @Test
+ fun getLoadersDoesNotLeakMutability() {
+ val originalValue = getValue()
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+
+ assertEquals(valueOne, getValue())
+
+ val loaders = getLoaders()
+ loaders += testTwo
+
+ assertEquals(valueOne, getValue())
+
+ removeLoader(testOne)
+
+ assertEquals(originalValue, getValue())
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun alreadyAddedThrows() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+ addLoader(testTwo)
+ addLoader(testOne)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun alreadyAddedAndSetThrows() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+ addLoader(testTwo)
+ setLoaders(testTwo)
+ }
+
+ @Test
+ fun repeatedRemoveSucceeds() {
+ val originalValue = getValue()
+ val testOne = openOne()
+
+ addLoader(testOne)
+
+ assertNotEquals(originalValue, getValue())
+
+ removeLoader(testOne)
+
+ assertEquals(originalValue, getValue())
+
+ removeLoader(testOne)
+
+ assertEquals(originalValue, getValue())
+ }
+
+ @Test
+ fun addToFront() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+
+ assertEquals(valueOne, getValue())
+
+ addLoader(testTwo, 0)
+
+ assertEquals(valueOne, getValue())
+
+ // Remove top loader, so previously added to front should now resolve
+ removeLoader(testOne)
+ assertEquals(valueTwo, getValue())
+ }
+
+ @Test
+ fun addToEnd() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+
+ assertEquals(valueOne, getValue())
+
+ addLoader(testTwo, 1)
+
+ assertEquals(valueTwo, getValue())
+ }
+
+ @Test(expected = IndexOutOfBoundsException::class)
+ fun addPastEnd() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+
+ assertEquals(valueOne, getValue())
+
+ addLoader(testTwo, 2)
+ }
+
+ @Test(expected = IndexOutOfBoundsException::class)
+ fun addBeforeFront() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne)
+
+ assertEquals(valueOne, getValue())
+
+ addLoader(testTwo, -1)
+ }
+
+ @Test
+ fun reorder() {
+ val originalValue = getValue()
+ val testOne = openOne()
+ val testTwo = openTwo()
+
+ addLoader(testOne, testTwo)
+
+ assertEquals(valueTwo, getValue())
+
+ removeLoader(testOne)
+
+ assertEquals(valueTwo, getValue())
+
+ addLoader(testOne)
+
+ assertEquals(valueOne, getValue())
+
+ removeLoader(testTwo)
+
+ assertEquals(valueOne, getValue())
+
+ removeLoader(testOne)
+
+ assertEquals(originalValue, getValue())
+ }
+
+ data class Parameter(
+ val getValue: ResourceLoaderValuesTest.() -> Any,
+ val loaderOne: String,
+ val valueOne: ResourceLoaderValuesTest.() -> Any,
+ val loaderTwo: String,
+ val valueTwo: ResourceLoaderValuesTest.() -> Any,
+ val dataTypes: List<DataType>
+ ) {
+ override fun toString(): String {
+ val prefix = loaderOne.commonPrefixWith(loaderTwo)
+ return "$prefix${loaderOne.removePrefix(prefix)}|${loaderTwo.removePrefix(prefix)}"
+ }
+ }
+}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt
new file mode 100644
index 0000000..df2d09a
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader.test
+
+import android.content.Context
+import android.content.res.AssetFileDescriptor
+import android.content.res.Resources
+import android.os.ParcelFileDescriptor
+import android.util.TypedValue
+import org.mockito.Answers
+import org.mockito.stubbing.Answer
+import org.xmlpull.v1.XmlPullParser
+import java.io.File
+
+// Enforce use of [android.util.Pair] instead of Kotlin's so it matches the ResourceLoader APIs
+typealias Pair<F, S> = android.util.Pair<F, S>
+infix fun <A, B> A.to(that: B): Pair<A, B> = Pair.create(this, that)!!
+
+object Utils {
+ val ANSWER_THROWS = Answer<Any> {
+ when (val name = it.method.name) {
+ "toString" -> return@Answer Answers.CALLS_REAL_METHODS.answer(it)
+ else -> throw UnsupportedOperationException("$name with " +
+ "${it.arguments?.joinToString()} should not be called")
+ }
+ }
+}
+
+fun Int.dpToPx(resources: Resources) = TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_DIP,
+ this.toFloat(),
+ resources.displayMetrics
+).toInt()
+
+fun AssetFileDescriptor.readText() = createInputStream().reader().readText()
+
+fun rawFile(fileName: String) = R.raw::class.java.getDeclaredField(fileName).getInt(null)
+
+fun XmlPullParser.advanceToRoot() = apply {
+ while (next() != XmlPullParser.START_TAG) {
+ // Empty
+ }
+}
+
+fun Context.copiedRawFile(fileName: String): ParcelFileDescriptor {
+ return resources.openRawResourceFd(rawFile(fileName)).use { asset ->
+ // AssetManager doesn't expose a direct file descriptor to the asset, so copy it to
+ // an individual file so one can be created manually.
+ val copiedFile = File(filesDir, fileName)
+ asset.createInputStream().use { input ->
+ copiedFile.outputStream().use { output ->
+ input.copyTo(output)
+ }
+ }
+
+ ParcelFileDescriptor.open(copiedFile, ParcelFileDescriptor.MODE_READ_WRITE)
+ }
+}
diff --git a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
index 4ae9494..fb0dd46 100644
--- a/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
+++ b/core/tests/coretests/src/android/app/admin/PasswordMetricsTest.java
@@ -20,52 +20,44 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_LOW;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
import static android.app.admin.PasswordMetrics.complexityLevelToMinQuality;
-import static android.app.admin.PasswordMetrics.getActualRequiredQuality;
-import static android.app.admin.PasswordMetrics.getMinimumMetrics;
-import static android.app.admin.PasswordMetrics.getTargetQualityMetrics;
import static android.app.admin.PasswordMetrics.sanitizeComplexityLevel;
+import static android.app.admin.PasswordMetrics.validatePasswordMetrics;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.widget.PasswordValidationError;
+
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
/** Unit tests for {@link PasswordMetrics}. */
@RunWith(AndroidJUnit4.class)
@SmallTest
+@Presubmit
public class PasswordMetricsTest {
-
- @Test
- public void testIsDefault() {
- final PasswordMetrics metrics = new PasswordMetrics();
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, metrics.quality);
- assertEquals(0, metrics.length);
- assertEquals(0, metrics.letters);
- assertEquals(0, metrics.upperCase);
- assertEquals(0, metrics.lowerCase);
- assertEquals(0, metrics.numeric);
- assertEquals(0, metrics.symbols);
- assertEquals(0, metrics.nonLetter);
- }
-
@Test
public void testParceling() {
- final int quality = 0;
+ final int credType = CREDENTIAL_TYPE_PASSWORD;
final int length = 1;
final int letters = 2;
final int upperCase = 3;
@@ -73,20 +65,21 @@
final int numeric = 5;
final int symbols = 6;
final int nonLetter = 7;
+ final int nonNumeric = 8;
+ final int seqLength = 9;
final Parcel parcel = Parcel.obtain();
- final PasswordMetrics metrics;
+ PasswordMetrics metrics = new PasswordMetrics(credType, length, letters, upperCase,
+ lowerCase, numeric, symbols, nonLetter, nonNumeric, seqLength);
try {
- new PasswordMetrics(
- quality, length, letters, upperCase, lowerCase, numeric, symbols, nonLetter)
- .writeToParcel(parcel, 0);
+ metrics.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
metrics = PasswordMetrics.CREATOR.createFromParcel(parcel);
} finally {
parcel.recycle();
}
- assertEquals(quality, metrics.quality);
+ assertEquals(credType, metrics.credType);
assertEquals(length, metrics.length);
assertEquals(letters, metrics.letters);
assertEquals(upperCase, metrics.upperCase);
@@ -94,7 +87,8 @@
assertEquals(numeric, metrics.numeric);
assertEquals(symbols, metrics.symbols);
assertEquals(nonLetter, metrics.nonLetter);
-
+ assertEquals(nonNumeric, metrics.nonNumeric);
+ assertEquals(seqLength, metrics.seqLength);
}
@Test
@@ -111,23 +105,6 @@
}
@Test
- public void testComputeForPassword_quality() {
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC,
- PasswordMetrics.computeForPassword("a1".getBytes()).quality);
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC,
- PasswordMetrics.computeForPassword("a".getBytes()).quality);
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC,
- PasswordMetrics.computeForPassword("*~&%$".getBytes()).quality);
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX,
- PasswordMetrics.computeForPassword("1".getBytes()).quality);
- // contains a long sequence so isn't complex
- assertEquals(PASSWORD_QUALITY_NUMERIC,
- PasswordMetrics.computeForPassword("1234".getBytes()).quality);
- assertEquals(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
- PasswordMetrics.computeForPassword("".getBytes()).quality);
- }
-
- @Test
public void testMaxLengthSequence() {
assertEquals(4, PasswordMetrics.maxLengthSequence("1234".getBytes()));
assertEquals(5, PasswordMetrics.maxLengthSequence("13579".getBytes()));
@@ -142,69 +119,15 @@
}
@Test
- public void testEquals() {
- PasswordMetrics metrics0 = new PasswordMetrics();
- PasswordMetrics metrics1 = new PasswordMetrics();
- assertNotEquals(metrics0, null);
- assertNotEquals(metrics0, new Object());
- assertEquals(metrics0, metrics0);
- assertEquals(metrics0, metrics1);
-
- assertEquals(new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4));
-
- assertNotEquals(new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4),
- new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 5));
-
- assertNotEquals(new PasswordMetrics(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, 4),
- new PasswordMetrics(PASSWORD_QUALITY_COMPLEX, 4));
-
- metrics0 = PasswordMetrics.computeForPassword("1234abcd,./".getBytes());
- metrics1 = PasswordMetrics.computeForPassword("1234abcd,./".getBytes());
- assertEquals(metrics0, metrics1);
- metrics1.letters++;
- assertNotEquals(metrics0, metrics1);
- metrics1.letters--;
- metrics1.upperCase++;
- assertNotEquals(metrics0, metrics1);
- metrics1.upperCase--;
- metrics1.lowerCase++;
- assertNotEquals(metrics0, metrics1);
- metrics1.lowerCase--;
- metrics1.numeric++;
- assertNotEquals(metrics0, metrics1);
- metrics1.numeric--;
- metrics1.symbols++;
- assertNotEquals(metrics0, metrics1);
- metrics1.symbols--;
- metrics1.nonLetter++;
- assertNotEquals(metrics0, metrics1);
- metrics1.nonLetter--;
- assertEquals(metrics0, metrics1);
-
-
- }
-
- @Test
- public void testConstructQuality() {
- PasswordMetrics expected = new PasswordMetrics();
- expected.quality = PASSWORD_QUALITY_COMPLEX;
-
- PasswordMetrics actual = new PasswordMetrics(PASSWORD_QUALITY_COMPLEX);
-
- assertEquals(expected, actual);
- }
-
- @Test
public void testDetermineComplexity_none() {
assertEquals(PASSWORD_COMPLEXITY_NONE,
- PasswordMetrics.computeForPassword("".getBytes()).determineComplexity());
+ new PasswordMetrics(CREDENTIAL_TYPE_NONE).determineComplexity());
}
@Test
public void testDetermineComplexity_lowSomething() {
assertEquals(PASSWORD_COMPLEXITY_LOW,
- new PasswordMetrics(PASSWORD_QUALITY_SOMETHING).determineComplexity());
+ new PasswordMetrics(CREDENTIAL_TYPE_PATTERN).determineComplexity());
}
@Test
@@ -324,122 +247,84 @@
}
@Test
- public void testGetTargetQualityMetrics_noneComplexityReturnsDefaultMetrics() {
- PasswordMetrics metrics =
- getTargetQualityMetrics(PASSWORD_COMPLEXITY_NONE, PASSWORD_QUALITY_ALPHANUMERIC);
-
- assertTrue(metrics.isDefault());
+ public void testMerge_single() {
+ PasswordMetrics metrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
+ assertEquals(CREDENTIAL_TYPE_PASSWORD,
+ PasswordMetrics.merge(Collections.singletonList(metrics)).credType);
}
@Test
- public void testGetTargetQualityMetrics_qualityNotAllowedReturnsMinQualityMetrics() {
- PasswordMetrics metrics =
- getTargetQualityMetrics(PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_NUMERIC);
-
- assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
- assertEquals(/* expected= */ 4, metrics.length);
+ public void testMerge_credentialTypes() {
+ PasswordMetrics none = new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+ PasswordMetrics pattern = new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
+ PasswordMetrics password = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
+ assertEquals(CREDENTIAL_TYPE_PATTERN,
+ PasswordMetrics.merge(Arrays.asList(new PasswordMetrics[]{none, pattern}))
+ .credType);
+ assertEquals(CREDENTIAL_TYPE_PASSWORD,
+ PasswordMetrics.merge(Arrays.asList(new PasswordMetrics[]{none, password}))
+ .credType);
+ assertEquals(CREDENTIAL_TYPE_PASSWORD,
+ PasswordMetrics.merge(Arrays.asList(new PasswordMetrics[]{password, pattern}))
+ .credType);
}
@Test
- public void testGetTargetQualityMetrics_highComplexityNumericComplex() {
- PasswordMetrics metrics = getTargetQualityMetrics(
- PASSWORD_COMPLEXITY_HIGH, PASSWORD_QUALITY_NUMERIC_COMPLEX);
+ public void testValidatePasswordMetrics_credentialTypes() {
+ PasswordMetrics none = new PasswordMetrics(CREDENTIAL_TYPE_NONE);
+ PasswordMetrics pattern = new PasswordMetrics(CREDENTIAL_TYPE_PATTERN);
+ PasswordMetrics password = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD);
- assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
- assertEquals(/* expected= */ 8, metrics.length);
+ // To pass minimal length check.
+ password.length = 4;
+
+ // No errors expected, credential is of stronger or equal type.
+ assertValidationErrors(
+ validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, false, none));
+ assertValidationErrors(
+ validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, false, pattern));
+ assertValidationErrors(
+ validatePasswordMetrics(none, PASSWORD_COMPLEXITY_NONE, false, password));
+ assertValidationErrors(
+ validatePasswordMetrics(pattern, PASSWORD_COMPLEXITY_NONE, false, pattern));
+ assertValidationErrors(
+ validatePasswordMetrics(pattern, PASSWORD_COMPLEXITY_NONE, false, password));
+ assertValidationErrors(
+ validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, false, password));
+
+ // Now actual credential type is weaker than required:
+ assertValidationErrors(
+ validatePasswordMetrics(pattern, PASSWORD_COMPLEXITY_NONE, false, none),
+ PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0);
+ assertValidationErrors(
+ validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, false, none),
+ PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0);
+ assertValidationErrors(
+ validatePasswordMetrics(password, PASSWORD_COMPLEXITY_NONE, false, pattern),
+ PasswordValidationError.WEAK_CREDENTIAL_TYPE, 0);
}
- @Test
- public void testGetTargetQualityMetrics_mediumComplexityAlphabetic() {
- PasswordMetrics metrics = getTargetQualityMetrics(
- PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_ALPHABETIC);
+ /**
+ * @param expected sequense of validation error codes followed by requirement values, must have
+ * even number of elements. Empty means no errors.
+ */
+ private void assertValidationErrors(
+ List<PasswordValidationError> actualErrors, int... expected) {
+ assertEquals("Test programming error: content shoud have even number of elements",
+ 0, expected.length % 2);
+ assertEquals("wrong number of validation errors", expected.length / 2, actualErrors.size());
+ HashMap<Integer, Integer> errorMap = new HashMap<>();
+ for (PasswordValidationError error : actualErrors) {
+ errorMap.put(error.errorCode, error.requirement);
+ }
- assertEquals(PASSWORD_QUALITY_ALPHABETIC, metrics.quality);
- assertEquals(/* expected= */ 4, metrics.length);
- }
-
- @Test
- public void testGetTargetQualityMetrics_lowComplexityAlphanumeric() {
- PasswordMetrics metrics = getTargetQualityMetrics(
- PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_ALPHANUMERIC);
-
- assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, metrics.quality);
- assertEquals(/* expected= */ 4, metrics.length);
- }
-
- @Test
- public void testGetActualRequiredQuality_nonComplex() {
- int actual = getActualRequiredQuality(
- PASSWORD_QUALITY_NUMERIC_COMPLEX,
- /* requiresNumeric= */ false,
- /* requiresLettersOrSymbols= */ false);
-
- assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, actual);
- }
-
- @Test
- public void testGetActualRequiredQuality_complexRequiresNone() {
- int actual = getActualRequiredQuality(
- PASSWORD_QUALITY_COMPLEX,
- /* requiresNumeric= */ false,
- /* requiresLettersOrSymbols= */ false);
-
- assertEquals(PASSWORD_QUALITY_UNSPECIFIED, actual);
- }
-
- @Test
- public void testGetActualRequiredQuality_complexRequiresNumeric() {
- int actual = getActualRequiredQuality(
- PASSWORD_QUALITY_COMPLEX,
- /* requiresNumeric= */ true,
- /* requiresLettersOrSymbols= */ false);
-
- assertEquals(PASSWORD_QUALITY_NUMERIC, actual);
- }
-
- @Test
- public void testGetActualRequiredQuality_complexRequiresLetters() {
- int actual = getActualRequiredQuality(
- PASSWORD_QUALITY_COMPLEX,
- /* requiresNumeric= */ false,
- /* requiresLettersOrSymbols= */ true);
-
- assertEquals(PASSWORD_QUALITY_ALPHABETIC, actual);
- }
-
- @Test
- public void testGetActualRequiredQuality_complexRequiresNumericAndLetters() {
- int actual = getActualRequiredQuality(
- PASSWORD_QUALITY_COMPLEX,
- /* requiresNumeric= */ true,
- /* requiresLettersOrSymbols= */ true);
-
- assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, actual);
- }
-
- @Test
- public void testGetMinimumMetrics_userInputStricter() {
- PasswordMetrics metrics = getMinimumMetrics(
- PASSWORD_COMPLEXITY_HIGH,
- PASSWORD_QUALITY_ALPHANUMERIC,
- PASSWORD_QUALITY_NUMERIC,
- /* requiresNumeric= */ false,
- /* requiresLettersOrSymbols= */ false);
-
- assertEquals(PASSWORD_QUALITY_ALPHANUMERIC, metrics.quality);
- assertEquals(/* expected= */ 6, metrics.length);
- }
-
- @Test
- public void testGetMinimumMetrics_actualRequiredQualityStricter() {
- PasswordMetrics metrics = getMinimumMetrics(
- PASSWORD_COMPLEXITY_HIGH,
- PASSWORD_QUALITY_UNSPECIFIED,
- PASSWORD_QUALITY_NUMERIC,
- /* requiresNumeric= */ false,
- /* requiresLettersOrSymbols= */ false);
-
- assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality);
- assertEquals(/* expected= */ 8, metrics.length);
+ for (int i = 0; i < expected.length / 2; i++) {
+ final int expectedError = expected[i * 2];
+ final int expectedRequirement = expected[i * 2 + 1];
+ assertTrue("error expected but not reported: " + expectedError,
+ errorMap.containsKey(expectedError));
+ assertEquals("unexpected requirement for error: " + expectedError,
+ Integer.valueOf(expectedRequirement), errorMap.get(expectedError));
+ }
}
}
diff --git a/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java b/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java
new file mode 100644
index 0000000..e951054
--- /dev/null
+++ b/core/tests/coretests/src/android/app/admin/PasswordPolicyTest.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.devicepolicy;
+
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.admin.PasswordMetrics;
+import android.app.admin.PasswordPolicy;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class PasswordPolicyTest {
+
+ public static final int TEST_VALUE = 10;
+
+ @Test
+ public void testGetMinMetrics_unspecified() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_UNSPECIFIED);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_NONE, minMetrics.credType);
+ assertEquals(0, minMetrics.length);
+ assertEquals(0, minMetrics.numeric);
+ }
+
+ @Test
+ public void testGetMinMetrics_something() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_SOMETHING);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PATTERN, minMetrics.credType);
+ assertEquals(0, minMetrics.length);
+ assertEquals(0, minMetrics.numeric);
+ }
+
+ @Test
+ public void testGetMinMetrics_biometricWeak() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_BIOMETRIC_WEAK);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PATTERN, minMetrics.credType);
+ assertEquals(0, minMetrics.length);
+ assertEquals(0, minMetrics.numeric);
+ }
+
+ @Test
+ public void testGetMinMetrics_numeric() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_NUMERIC);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(TEST_VALUE, minMetrics.length);
+ assertEquals(0, minMetrics.numeric); // numeric can doesn't really require digits.
+ assertEquals(0, minMetrics.letters);
+ assertEquals(0, minMetrics.lowerCase);
+ assertEquals(0, minMetrics.upperCase);
+ assertEquals(0, minMetrics.symbols);
+ assertEquals(0, minMetrics.nonLetter);
+ assertEquals(0, minMetrics.nonNumeric);
+ assertEquals(Integer.MAX_VALUE, minMetrics.seqLength);
+ }
+
+ @Test
+ public void testGetMinMetrics_numericDefaultLength() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_NUMERIC);
+ policy.length = 0; // reset to default
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(0, minMetrics.length);
+ }
+
+ @Test
+ public void testGetMinMetrics_numericComplex() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_NUMERIC_COMPLEX);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(TEST_VALUE, minMetrics.length);
+ assertEquals(0, minMetrics.numeric);
+ assertEquals(0, minMetrics.letters);
+ assertEquals(0, minMetrics.lowerCase);
+ assertEquals(0, minMetrics.upperCase);
+ assertEquals(0, minMetrics.symbols);
+ assertEquals(0, minMetrics.nonLetter);
+ assertEquals(0, minMetrics.nonNumeric);
+ assertEquals(PasswordMetrics.MAX_ALLOWED_SEQUENCE, minMetrics.seqLength);
+ }
+
+ @Test
+ public void testGetMinMetrics_alphabetic() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_ALPHABETIC);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(TEST_VALUE, minMetrics.length);
+ assertEquals(0, minMetrics.numeric);
+ assertEquals(0, minMetrics.letters);
+ assertEquals(0, minMetrics.lowerCase);
+ assertEquals(0, minMetrics.upperCase);
+ assertEquals(0, minMetrics.symbols);
+ assertEquals(0, minMetrics.nonLetter);
+ assertEquals(1, minMetrics.nonNumeric);
+ assertEquals(Integer.MAX_VALUE, minMetrics.seqLength);
+ }
+
+ @Test
+ public void testGetMinMetrics_alphanumeric() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_ALPHANUMERIC);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(TEST_VALUE, minMetrics.length);
+ assertEquals(1, minMetrics.numeric);
+ assertEquals(0, minMetrics.letters);
+ assertEquals(0, minMetrics.lowerCase);
+ assertEquals(0, minMetrics.upperCase);
+ assertEquals(0, minMetrics.symbols);
+ assertEquals(0, minMetrics.nonLetter);
+ assertEquals(1, minMetrics.nonNumeric);
+ assertEquals(Integer.MAX_VALUE, minMetrics.seqLength);
+ }
+
+ @Test
+ public void testGetMinMetrics_complex() {
+ PasswordPolicy policy = testPolicy(PASSWORD_QUALITY_COMPLEX);
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(TEST_VALUE, minMetrics.length);
+ assertEquals(TEST_VALUE, minMetrics.letters);
+ assertEquals(TEST_VALUE, minMetrics.lowerCase);
+ assertEquals(TEST_VALUE, minMetrics.upperCase);
+ assertEquals(TEST_VALUE, minMetrics.symbols);
+ assertEquals(TEST_VALUE, minMetrics.numeric);
+ assertEquals(TEST_VALUE, minMetrics.nonLetter);
+ assertEquals(0, minMetrics.nonNumeric);
+ assertEquals(Integer.MAX_VALUE, minMetrics.seqLength);
+ }
+
+ @Test
+ public void testGetMinMetrics_complexDefault() {
+ PasswordPolicy policy = new PasswordPolicy();
+ policy.quality = PASSWORD_QUALITY_COMPLEX;
+ PasswordMetrics minMetrics = policy.getMinMetrics();
+ assertEquals(CREDENTIAL_TYPE_PASSWORD, minMetrics.credType);
+ assertEquals(0, minMetrics.length);
+ assertEquals(1, minMetrics.letters);
+ assertEquals(0, minMetrics.lowerCase);
+ assertEquals(0, minMetrics.upperCase);
+ assertEquals(1, minMetrics.symbols);
+ assertEquals(1, minMetrics.numeric);
+ assertEquals(0, minMetrics.nonLetter);
+ assertEquals(0, minMetrics.nonNumeric);
+ assertEquals(Integer.MAX_VALUE, minMetrics.seqLength);
+ }
+
+ private PasswordPolicy testPolicy(int quality) {
+ PasswordPolicy result = new PasswordPolicy();
+ result.quality = quality;
+ result.length = TEST_VALUE;
+ result.letters = TEST_VALUE;
+ result.lowerCase = TEST_VALUE;
+ result.upperCase = TEST_VALUE;
+ result.numeric = TEST_VALUE;
+ result.symbols = TEST_VALUE;
+ result.nonLetter = TEST_VALUE;
+ return result;
+ }
+}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 51da0c8..39bf742 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -611,6 +611,10 @@
}
@Override
+ public void attachStartupAgents(String s) throws RemoteException {
+ }
+
+ @Override
public void scheduleApplicationInfoChanged(ApplicationInfo applicationInfo)
throws RemoteException {
}
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index 23fabce..77b7f2a 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -17,6 +17,7 @@
package android.provider;
import static android.provider.DeviceConfig.OnPropertiesChangedListener;
+import static android.provider.DeviceConfig.Properties;
import static com.google.common.truth.Truth.assertThat;
@@ -42,27 +43,33 @@
@RunWith(AndroidJUnit4.class)
@SmallTest
public class DeviceConfigTest {
- // TODO(b/109919982): Migrate tests to CTS
- private static final String sNamespace = "namespace1";
- private static final String sKey = "key1";
- private static final String sValue = "value1";
private static final long WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS = 2000; // 2 sec
+ private static final String DEFAULT_VALUE = "test_default_value";
+ private static final String NAMESPACE = "namespace1";
+ private static final String KEY = "key1";
+ private static final String KEY2 = "key2";
+ private static final String KEY3 = "key3";
+ private static final String VALUE = "value1";
+ private static final String VALUE2 = "value2";
+ private static final String VALUE3 = "value3";
@After
public void cleanUp() {
- deleteViaContentProvider(sNamespace, sKey);
+ deleteViaContentProvider(NAMESPACE, KEY);
+ deleteViaContentProvider(NAMESPACE, KEY2);
+ deleteViaContentProvider(NAMESPACE, KEY3);
}
@Test
public void getProperty_empty() {
- String result = DeviceConfig.getProperty(sNamespace, sKey);
+ String result = DeviceConfig.getProperty(NAMESPACE, KEY);
assertThat(result).isNull();
}
@Test
public void getProperty_nullNamespace() {
try {
- DeviceConfig.getProperty(null, sKey);
+ DeviceConfig.getProperty(null, KEY);
Assert.fail("Null namespace should have resulted in an NPE.");
} catch (NullPointerException e) {
// expected
@@ -72,7 +79,7 @@
@Test
public void getProperty_nullName() {
try {
- DeviceConfig.getProperty(sNamespace, null);
+ DeviceConfig.getProperty(NAMESPACE, null);
Assert.fail("Null name should have resulted in an NPE.");
} catch (NullPointerException e) {
// expected
@@ -82,13 +89,13 @@
@Test
public void getString_empty() {
final String default_value = "default_value";
- final String result = DeviceConfig.getString(sNamespace, sKey, default_value);
+ final String result = DeviceConfig.getString(NAMESPACE, KEY, default_value);
assertThat(result).isEqualTo(default_value);
}
@Test
public void getString_nullDefault() {
- final String result = DeviceConfig.getString(sNamespace, sKey, null);
+ final String result = DeviceConfig.getString(NAMESPACE, KEY, null);
assertThat(result).isNull();
}
@@ -96,16 +103,16 @@
public void getString_nonEmpty() {
final String value = "new_value";
final String default_value = "default";
- DeviceConfig.setProperty(sNamespace, sKey, value, false);
+ DeviceConfig.setProperty(NAMESPACE, KEY, value, false);
- final String result = DeviceConfig.getString(sNamespace, sKey, default_value);
+ final String result = DeviceConfig.getString(NAMESPACE, KEY, default_value);
assertThat(result).isEqualTo(value);
}
@Test
public void getString_nullNamespace() {
try {
- DeviceConfig.getString(null, sKey, "default_value");
+ DeviceConfig.getString(null, KEY, "default_value");
Assert.fail("Null namespace should have resulted in an NPE.");
} catch (NullPointerException e) {
// expected
@@ -115,7 +122,7 @@
@Test
public void getString_nullName() {
try {
- DeviceConfig.getString(sNamespace, null, "default_value");
+ DeviceConfig.getString(NAMESPACE, null, "default_value");
Assert.fail("Null name should have resulted in an NPE.");
} catch (NullPointerException e) {
// expected
@@ -125,7 +132,7 @@
@Test
public void getBoolean_empty() {
final boolean default_value = true;
- final boolean result = DeviceConfig.getBoolean(sNamespace, sKey, default_value);
+ final boolean result = DeviceConfig.getBoolean(NAMESPACE, KEY, default_value);
assertThat(result).isEqualTo(default_value);
}
@@ -133,18 +140,18 @@
public void getBoolean_valid() {
final boolean value = true;
final boolean default_value = false;
- DeviceConfig.setProperty(sNamespace, sKey, String.valueOf(value), false);
+ DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false);
- final boolean result = DeviceConfig.getBoolean(sNamespace, sKey, default_value);
+ final boolean result = DeviceConfig.getBoolean(NAMESPACE, KEY, default_value);
assertThat(result).isEqualTo(value);
}
@Test
public void getBoolean_invalid() {
final boolean default_value = true;
- DeviceConfig.setProperty(sNamespace, sKey, "not_a_boolean", false);
+ DeviceConfig.setProperty(NAMESPACE, KEY, "not_a_boolean", false);
- final boolean result = DeviceConfig.getBoolean(sNamespace, sKey, default_value);
+ final boolean result = DeviceConfig.getBoolean(NAMESPACE, KEY, default_value);
// Anything non-null other than case insensitive "true" parses to false.
assertThat(result).isFalse();
}
@@ -152,7 +159,7 @@
@Test
public void getBoolean_nullNamespace() {
try {
- DeviceConfig.getBoolean(null, sKey, false);
+ DeviceConfig.getBoolean(null, KEY, false);
Assert.fail("Null namespace should have resulted in an NPE.");
} catch (NullPointerException e) {
// expected
@@ -162,7 +169,7 @@
@Test
public void getBoolean_nullName() {
try {
- DeviceConfig.getBoolean(sNamespace, null, false);
+ DeviceConfig.getBoolean(NAMESPACE, null, false);
Assert.fail("Null name should have resulted in an NPE.");
} catch (NullPointerException e) {
// expected
@@ -172,7 +179,7 @@
@Test
public void getInt_empty() {
final int default_value = 999;
- final int result = DeviceConfig.getInt(sNamespace, sKey, default_value);
+ final int result = DeviceConfig.getInt(NAMESPACE, KEY, default_value);
assertThat(result).isEqualTo(default_value);
}
@@ -180,18 +187,18 @@
public void getInt_valid() {
final int value = 123;
final int default_value = 999;
- DeviceConfig.setProperty(sNamespace, sKey, String.valueOf(value), false);
+ DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false);
- final int result = DeviceConfig.getInt(sNamespace, sKey, default_value);
+ final int result = DeviceConfig.getInt(NAMESPACE, KEY, default_value);
assertThat(result).isEqualTo(value);
}
@Test
public void getInt_invalid() {
final int default_value = 999;
- DeviceConfig.setProperty(sNamespace, sKey, "not_an_int", false);
+ DeviceConfig.setProperty(NAMESPACE, KEY, "not_an_int", false);
- final int result = DeviceConfig.getInt(sNamespace, sKey, default_value);
+ final int result = DeviceConfig.getInt(NAMESPACE, KEY, default_value);
// Failure to parse results in using the default value
assertThat(result).isEqualTo(default_value);
}
@@ -199,7 +206,7 @@
@Test
public void getInt_nullNamespace() {
try {
- DeviceConfig.getInt(null, sKey, 0);
+ DeviceConfig.getInt(null, KEY, 0);
Assert.fail("Null namespace should have resulted in an NPE.");
} catch (NullPointerException e) {
// expected
@@ -209,7 +216,7 @@
@Test
public void getInt_nullName() {
try {
- DeviceConfig.getInt(sNamespace, null, 0);
+ DeviceConfig.getInt(NAMESPACE, null, 0);
Assert.fail("Null name should have resulted in an NPE.");
} catch (NullPointerException e) {
// expected
@@ -219,7 +226,7 @@
@Test
public void getLong_empty() {
final long default_value = 123456;
- final long result = DeviceConfig.getLong(sNamespace, sKey, default_value);
+ final long result = DeviceConfig.getLong(NAMESPACE, KEY, default_value);
assertThat(result).isEqualTo(default_value);
}
@@ -227,18 +234,18 @@
public void getLong_valid() {
final long value = 456789;
final long default_value = 123456;
- DeviceConfig.setProperty(sNamespace, sKey, String.valueOf(value), false);
+ DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false);
- final long result = DeviceConfig.getLong(sNamespace, sKey, default_value);
+ final long result = DeviceConfig.getLong(NAMESPACE, KEY, default_value);
assertThat(result).isEqualTo(value);
}
@Test
public void getLong_invalid() {
final long default_value = 123456;
- DeviceConfig.setProperty(sNamespace, sKey, "not_a_long", false);
+ DeviceConfig.setProperty(NAMESPACE, KEY, "not_a_long", false);
- final long result = DeviceConfig.getLong(sNamespace, sKey, default_value);
+ final long result = DeviceConfig.getLong(NAMESPACE, KEY, default_value);
// Failure to parse results in using the default value
assertThat(result).isEqualTo(default_value);
}
@@ -246,7 +253,7 @@
@Test
public void getLong_nullNamespace() {
try {
- DeviceConfig.getLong(null, sKey, 0);
+ DeviceConfig.getLong(null, KEY, 0);
Assert.fail("Null namespace should have resulted in an NPE.");
} catch (NullPointerException e) {
// expected
@@ -256,7 +263,7 @@
@Test
public void getLong_nullName() {
try {
- DeviceConfig.getLong(sNamespace, null, 0);
+ DeviceConfig.getLong(NAMESPACE, null, 0);
Assert.fail("Null name should have resulted in an NPE.");
} catch (NullPointerException e) {
// expected
@@ -266,7 +273,7 @@
@Test
public void getFloat_empty() {
final float default_value = 123.456f;
- final float result = DeviceConfig.getFloat(sNamespace, sKey, default_value);
+ final float result = DeviceConfig.getFloat(NAMESPACE, KEY, default_value);
assertThat(result).isEqualTo(default_value);
}
@@ -274,18 +281,18 @@
public void getFloat_valid() {
final float value = 456.789f;
final float default_value = 123.456f;
- DeviceConfig.setProperty(sNamespace, sKey, String.valueOf(value), false);
+ DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false);
- final float result = DeviceConfig.getFloat(sNamespace, sKey, default_value);
+ final float result = DeviceConfig.getFloat(NAMESPACE, KEY, default_value);
assertThat(result).isEqualTo(value);
}
@Test
public void getFloat_invalid() {
final float default_value = 123.456f;
- DeviceConfig.setProperty(sNamespace, sKey, "not_a_float", false);
+ DeviceConfig.setProperty(NAMESPACE, KEY, "not_a_float", false);
- final float result = DeviceConfig.getFloat(sNamespace, sKey, default_value);
+ final float result = DeviceConfig.getFloat(NAMESPACE, KEY, default_value);
// Failure to parse results in using the default value
assertThat(result).isEqualTo(default_value);
}
@@ -293,7 +300,7 @@
@Test
public void getFloat_nullNamespace() {
try {
- DeviceConfig.getFloat(null, sKey, 0);
+ DeviceConfig.getFloat(null, KEY, 0);
Assert.fail("Null namespace should have resulted in an NPE.");
} catch (NullPointerException e) {
// expected
@@ -303,7 +310,7 @@
@Test
public void getFloat_nullName() {
try {
- DeviceConfig.getFloat(sNamespace, null, 0);
+ DeviceConfig.getFloat(NAMESPACE, null, 0);
Assert.fail("Null name should have resulted in an NPE.");
} catch (NullPointerException e) {
// expected
@@ -313,7 +320,7 @@
@Test
public void setProperty_nullNamespace() {
try {
- DeviceConfig.setProperty(null, sKey, sValue, false);
+ DeviceConfig.setProperty(null, KEY, VALUE, false);
Assert.fail("Null namespace should have resulted in an NPE.");
} catch (NullPointerException e) {
// expected
@@ -323,7 +330,7 @@
@Test
public void setProperty_nullName() {
try {
- DeviceConfig.setProperty(sNamespace, null, sValue, false);
+ DeviceConfig.setProperty(NAMESPACE, null, VALUE, false);
Assert.fail("Null name should have resulted in an NPE.");
} catch (NullPointerException e) {
// expected
@@ -332,16 +339,16 @@
@Test
public void setAndGetProperty_sameNamespace() {
- DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
- String result = DeviceConfig.getProperty(sNamespace, sKey);
- assertThat(result).isEqualTo(sValue);
+ DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
+ String result = DeviceConfig.getProperty(NAMESPACE, KEY);
+ assertThat(result).isEqualTo(VALUE);
}
@Test
public void setAndGetProperty_differentNamespace() {
String newNamespace = "namespace2";
- DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
- String result = DeviceConfig.getProperty(newNamespace, sKey);
+ DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
+ String result = DeviceConfig.getProperty(newNamespace, KEY);
assertThat(result).isNull();
}
@@ -349,41 +356,147 @@
public void setAndGetProperty_multipleNamespaces() {
String newNamespace = "namespace2";
String newValue = "value2";
- DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
- DeviceConfig.setProperty(newNamespace, sKey, newValue, false);
- String result = DeviceConfig.getProperty(sNamespace, sKey);
- assertThat(result).isEqualTo(sValue);
- result = DeviceConfig.getProperty(newNamespace, sKey);
+ DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
+ DeviceConfig.setProperty(newNamespace, KEY, newValue, false);
+ String result = DeviceConfig.getProperty(NAMESPACE, KEY);
+ assertThat(result).isEqualTo(VALUE);
+ result = DeviceConfig.getProperty(newNamespace, KEY);
assertThat(result).isEqualTo(newValue);
// clean up
- deleteViaContentProvider(newNamespace, sKey);
+ deleteViaContentProvider(newNamespace, KEY);
}
@Test
public void setAndGetProperty_overrideValue() {
String newValue = "value2";
- DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
- DeviceConfig.setProperty(sNamespace, sKey, newValue, false);
- String result = DeviceConfig.getProperty(sNamespace, sKey);
+ DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
+ DeviceConfig.setProperty(NAMESPACE, KEY, newValue, false);
+ String result = DeviceConfig.getProperty(NAMESPACE, KEY);
assertThat(result).isEqualTo(newValue);
}
@Test
+ public void getProperties_fullNamespace() {
+ Properties properties = DeviceConfig.getProperties(NAMESPACE);
+ assertThat(properties.getKeyset()).isEmpty();
+
+ DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
+ DeviceConfig.setProperty(NAMESPACE, KEY2, VALUE2, false);
+ properties = DeviceConfig.getProperties(NAMESPACE);
+ assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
+ assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
+ assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
+
+ DeviceConfig.setProperty(NAMESPACE, KEY, VALUE3, false);
+ properties = DeviceConfig.getProperties(NAMESPACE);
+ assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
+ assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE3);
+ assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
+ }
+
+ @Test
+ public void getProperties_getString() {
+ DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
+ DeviceConfig.setProperty(NAMESPACE, KEY2, VALUE2, false);
+
+ Properties properties = DeviceConfig.getProperties(NAMESPACE, KEY, KEY2);
+ assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
+ assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
+ assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
+ }
+
+ @Test
+ public void getProperties_getBoolean() {
+ DeviceConfig.setProperty(NAMESPACE, KEY, "true", false);
+ DeviceConfig.setProperty(NAMESPACE, KEY2, "false", false);
+ DeviceConfig.setProperty(NAMESPACE, KEY3, "not a valid boolean", false);
+
+ Properties properties = DeviceConfig.getProperties(NAMESPACE, KEY, KEY2, KEY3);
+ assertThat(properties.getKeyset()).containsExactly(KEY, KEY2, KEY3);
+ assertThat(properties.getBoolean(KEY, true)).isTrue();
+ assertThat(properties.getBoolean(KEY, false)).isTrue();
+ assertThat(properties.getBoolean(KEY2, true)).isFalse();
+ assertThat(properties.getBoolean(KEY2, false)).isFalse();
+ // KEY3 was set to garbage, anything nonnull but "true" will parse as false
+ assertThat(properties.getBoolean(KEY3, true)).isFalse();
+ assertThat(properties.getBoolean(KEY3, false)).isFalse();
+ // If a key was not set, it will return the default value
+ assertThat(properties.getBoolean("missing_key", true)).isTrue();
+ assertThat(properties.getBoolean("missing_key", false)).isFalse();
+ }
+
+ @Test
+ public void getProperties_getInt() {
+ final int value = 101;
+
+ DeviceConfig.setProperty(NAMESPACE, KEY, Integer.toString(value), false);
+ DeviceConfig.setProperty(NAMESPACE, KEY2, "not a valid int", false);
+
+ Properties properties = DeviceConfig.getProperties(NAMESPACE, KEY, KEY2);
+ assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
+ assertThat(properties.getInt(KEY, -1)).isEqualTo(value);
+ // KEY2 was set to garbage, the default value is returned if an int cannot be parsed
+ assertThat(properties.getInt(KEY2, -1)).isEqualTo(-1);
+ }
+
+ @Test
+ public void getProperties_getFloat() {
+ final float value = 101.010f;
+
+ DeviceConfig.setProperty(NAMESPACE, KEY, Float.toString(value), false);
+ DeviceConfig.setProperty(NAMESPACE, KEY2, "not a valid float", false);
+
+ Properties properties = DeviceConfig.getProperties(NAMESPACE, KEY, KEY2);
+ assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
+ assertThat(properties.getFloat(KEY, -1.0f)).isEqualTo(value);
+ // KEY2 was set to garbage, the default value is returned if a float cannot be parsed
+ assertThat(properties.getFloat(KEY2, -1.0f)).isEqualTo(-1.0f);
+ }
+
+ @Test
+ public void getProperties_getLong() {
+ final long value = 101;
+
+ DeviceConfig.setProperty(NAMESPACE, KEY, Long.toString(value), false);
+ DeviceConfig.setProperty(NAMESPACE, KEY2, "not a valid long", false);
+
+ Properties properties = DeviceConfig.getProperties(NAMESPACE, KEY, KEY2);
+ assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
+ assertThat(properties.getLong(KEY, -1)).isEqualTo(value);
+ // KEY2 was set to garbage, the default value is returned if a long cannot be parsed
+ assertThat(properties.getLong(KEY2, -1)).isEqualTo(-1);
+ }
+
+ @Test
+ public void getProperties_defaults() {
+ DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
+ DeviceConfig.setProperty(NAMESPACE, KEY3, VALUE3, false);
+
+ Properties properties = DeviceConfig.getProperties(NAMESPACE, KEY, KEY2);
+ assertThat(properties.getKeyset()).containsExactly(KEY);
+ assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
+ // not set in DeviceConfig, but requested in getProperties
+ assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
+ // set in DeviceConfig, but not requested in getProperties
+ assertThat(properties.getString(KEY3, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
+ }
+
+ @Test
public void testOnPropertiesChangedListener() throws InterruptedException {
final CountDownLatch countDownLatch = new CountDownLatch(1);
OnPropertiesChangedListener changeListener = (properties) -> {
- assertThat(properties.getNamespace()).isEqualTo(sNamespace);
- assertThat(properties.getKeyset()).contains(sKey);
- assertThat(properties.getString(sKey, "default_value")).isEqualTo(sValue);
+ assertThat(properties.getNamespace()).isEqualTo(NAMESPACE);
+ assertThat(properties.getKeyset()).contains(KEY);
+ assertThat(properties.getString(KEY, "default_value")).isEqualTo(VALUE);
countDownLatch.countDown();
};
try {
- DeviceConfig.addOnPropertiesChangedListener(sNamespace,
+ DeviceConfig.addOnPropertiesChangedListener(NAMESPACE,
ActivityThread.currentApplication().getMainExecutor(), changeListener);
- DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
+ DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
assertThat(countDownLatch.await(
WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
} catch (InterruptedException e) {
diff --git a/core/tests/coretests/src/android/util/TimestampedValueTest.java b/core/tests/coretests/src/android/util/TimestampedValueTest.java
index 6e3ab79..6fc2400 100644
--- a/core/tests/coretests/src/android/util/TimestampedValueTest.java
+++ b/core/tests/coretests/src/android/util/TimestampedValueTest.java
@@ -55,12 +55,12 @@
TimestampedValue<String> stringValue = new TimestampedValue<>(1000, "Hello");
Parcel parcel = Parcel.obtain();
try {
- TimestampedValue.writeToParcel(parcel, stringValue);
+ parcel.writeParcelable(stringValue, 0);
parcel.setDataPosition(0);
TimestampedValue<String> stringValueCopy =
- TimestampedValue.readFromParcel(parcel, null /* classLoader */, String.class);
+ parcel.readParcelable(null /* classLoader */);
assertEquals(stringValue, stringValueCopy);
} finally {
parcel.recycle();
@@ -72,12 +72,12 @@
TimestampedValue<String> stringValue = new TimestampedValue<>(1000, "Hello");
Parcel parcel = Parcel.obtain();
try {
- TimestampedValue.writeToParcel(parcel, stringValue);
+ parcel.writeParcelable(stringValue, 0);
parcel.setDataPosition(0);
- TimestampedValue<Object> stringValueCopy =
- TimestampedValue.readFromParcel(parcel, null /* classLoader */, Object.class);
+ TimestampedValue<String> stringValueCopy =
+ parcel.readParcelable(null /* classLoader */);
assertEquals(stringValue, stringValueCopy);
} finally {
parcel.recycle();
@@ -85,15 +85,15 @@
}
@Test
- public void testParceling_valueClassIncompatible() {
- TimestampedValue<String> stringValue = new TimestampedValue<>(1000, "Hello");
+ public void testParceling_valueClassNotParcelable() {
+ // This class is not one supported by Parcel.writeValue().
+ class NotParcelable {}
+
+ TimestampedValue<NotParcelable> notParcelableValue =
+ new TimestampedValue<>(1000, new NotParcelable());
Parcel parcel = Parcel.obtain();
try {
- TimestampedValue.writeToParcel(parcel, stringValue);
-
- parcel.setDataPosition(0);
-
- TimestampedValue.readFromParcel(parcel, null /* classLoader */, Double.class);
+ parcel.writeParcelable(notParcelableValue, 0);
fail();
} catch (RuntimeException expected) {
} finally {
@@ -106,12 +106,11 @@
TimestampedValue<String> nullValue = new TimestampedValue<>(1000, null);
Parcel parcel = Parcel.obtain();
try {
- TimestampedValue.writeToParcel(parcel, nullValue);
+ parcel.writeParcelable(nullValue, 0);
parcel.setDataPosition(0);
- TimestampedValue<Object> nullValueCopy =
- TimestampedValue.readFromParcel(parcel, null /* classLoader */, String.class);
+ TimestampedValue<String> nullValueCopy = parcel.readParcelable(null /* classLoader */);
assertEquals(nullValue, nullValueCopy);
} finally {
parcel.recycle();
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index abee1da2..7b405434 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -345,9 +345,9 @@
accessibilityShortcutController.performAccessibilityShortcut();
accessibilityShortcutController.performAccessibilityShortcut();
verify(mToast).show();
- assertEquals(WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS,
+ assertEquals(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
mLayoutParams.privateFlags
- & WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS);
+ & WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut();
}
diff --git a/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
new file mode 100644
index 0000000..5eec91c
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/LockscreenCredentialTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.widget;
+
+
+import android.test.AndroidTestCase;
+
+import java.util.Arrays;
+
+
+public class LockscreenCredentialTest extends AndroidTestCase {
+
+ public void testEmptyCredential() {
+ LockscreenCredential empty = LockscreenCredential.createNone();
+
+ assertTrue(empty.isNone());
+ assertEquals(0, empty.size());
+ assertNotNull(empty.getCredential());
+
+ assertFalse(empty.isPassword());
+ assertFalse(empty.isPattern());
+ }
+
+ public void testPasswordCredential() {
+ LockscreenCredential password = LockscreenCredential.createPassword("password");
+
+ assertTrue(password.isPassword());
+ assertEquals(8, password.size());
+ assertTrue(Arrays.equals("password".getBytes(), password.getCredential()));
+
+ assertFalse(password.isNone());
+ assertFalse(password.isPattern());
+ }
+
+ public void testPatternCredential() {
+ LockscreenCredential pattern = LockscreenCredential.createPattern(Arrays.asList(
+ LockPatternView.Cell.of(0, 0),
+ LockPatternView.Cell.of(0, 1),
+ LockPatternView.Cell.of(0, 2),
+ LockPatternView.Cell.of(1, 2),
+ LockPatternView.Cell.of(2, 2)
+ ));
+
+ assertTrue(pattern.isPattern());
+ assertEquals(5, pattern.size());
+ assertTrue(Arrays.equals("12369".getBytes(), pattern.getCredential()));
+
+ assertFalse(pattern.isNone());
+ assertFalse(pattern.isPassword());
+ }
+
+ public void testPasswordOrNoneCredential() {
+ assertEquals(LockscreenCredential.createNone(),
+ LockscreenCredential.createPasswordOrNone(null));
+ assertEquals(LockscreenCredential.createNone(),
+ LockscreenCredential.createPasswordOrNone(""));
+ assertEquals(LockscreenCredential.createPassword("abcd"),
+ LockscreenCredential.createPasswordOrNone("abcd"));
+ }
+
+ public void testSanitize() {
+ LockscreenCredential password = LockscreenCredential.createPassword("password");
+ password.zeroize();
+
+ try {
+ password.isNone();
+ fail("Sanitized credential still accessible");
+ } catch (IllegalStateException expected) { }
+
+ try {
+ password.isPattern();
+ fail("Sanitized credential still accessible");
+ } catch (IllegalStateException expected) { }
+ try {
+ password.isPassword();
+ fail("Sanitized credential still accessible");
+ } catch (IllegalStateException expected) { }
+ try {
+ password.size();
+ fail("Sanitized credential still accessible");
+ } catch (IllegalStateException expected) { }
+ try {
+ password.getCredential();
+ fail("Sanitized credential still accessible");
+ } catch (IllegalStateException expected) { }
+ }
+
+ public void testEquals() {
+ assertEquals(LockscreenCredential.createNone(), LockscreenCredential.createNone());
+ assertEquals(LockscreenCredential.createPassword("1234"),
+ LockscreenCredential.createPassword("1234"));
+ assertEquals(LockscreenCredential.createPin("4321"),
+ LockscreenCredential.createPin("4321"));
+ assertEquals(createPattern("1234"), createPattern("1234"));
+
+ assertNotSame(LockscreenCredential.createPassword("1234"),
+ LockscreenCredential.createNone());
+ assertNotSame(LockscreenCredential.createPassword("1234"),
+ LockscreenCredential.createPassword("4321"));
+ assertNotSame(LockscreenCredential.createPassword("1234"),
+ createPattern("1234"));
+ assertNotSame(LockscreenCredential.createPassword("1234"),
+ LockscreenCredential.createPin("1234"));
+
+ assertNotSame(LockscreenCredential.createPin("1111"),
+ LockscreenCredential.createNone());
+ assertNotSame(LockscreenCredential.createPin("1111"),
+ LockscreenCredential.createPin("2222"));
+ assertNotSame(LockscreenCredential.createPin("1111"),
+ createPattern("1111"));
+ assertNotSame(LockscreenCredential.createPin("1111"),
+ LockscreenCredential.createPassword("1111"));
+
+ assertNotSame(createPattern("5678"),
+ LockscreenCredential.createNone());
+ assertNotSame(createPattern("5678"),
+ createPattern("1234"));
+ assertNotSame(createPattern("5678"),
+ LockscreenCredential.createPassword("5678"));
+ assertNotSame(createPattern("5678"),
+ LockscreenCredential.createPin("5678"));
+ }
+
+ public void testDuplicate() {
+ LockscreenCredential credential;
+
+ credential = LockscreenCredential.createNone();
+ assertEquals(credential, credential.duplicate());
+ credential = LockscreenCredential.createPassword("abcd");
+ assertEquals(credential, credential.duplicate());
+ credential = LockscreenCredential.createPin("1234");
+ assertEquals(credential, credential.duplicate());
+ credential = createPattern("5678");
+ assertEquals(credential, credential.duplicate());
+ }
+
+ private LockscreenCredential createPattern(String patternString) {
+ return LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
+ patternString.getBytes()));
+ }
+}
diff --git a/data/etc/car/com.google.android.car.kitchensink.xml b/data/etc/car/com.google.android.car.kitchensink.xml
index d36a826..61281ee 100644
--- a/data/etc/car/com.google.android.car.kitchensink.xml
+++ b/data/etc/car/com.google.android.car.kitchensink.xml
@@ -16,17 +16,30 @@
-->
<permissions>
<privapp-permissions package="com.google.android.car.kitchensink">
+ <permission name="android.permission.ACCESS_NETWORK_STATE"/>
+ <permission name="android.permission.ACCESS_WIFI_STATE"/>
+ <permission name="android.permission.ACTIVITY_EMBEDDING"/>
+ <permission name="android.permission.INJECT_EVENTS"/>
+ <!-- use for CarServiceUnitTest and CarServiceTest -->
+ <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ <!-- use for CarServiceUnitTest -->
+ <permission name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
<permission name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS"/>
<permission name="android.permission.LOCATION_HARDWARE"/>
<permission name="android.permission.MANAGE_USB"/>
<permission name="android.permission.MANAGE_USERS"/>
+ <!-- use for CarServiceTest -->
+ <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
<permission name="android.permission.MODIFY_AUDIO_ROUTING"/>
<permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
<permission name="android.permission.PROVIDE_TRUST_AGENT"/>
+ <permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
<permission name="android.permission.REAL_GET_TASKS"/>
<permission name="android.permission.READ_LOGS"/>
<permission name="android.permission.REBOOT"/>
+ <!-- use for CarServiceTest -->
+ <permission name="android.permission.SET_ACTIVITY_WATCHER"/>
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
</privapp-permissions>
</permissions>
diff --git a/data/etc/framework-sysconfig.xml b/data/etc/framework-sysconfig.xml
index 987c3b4..7296cfd 100644
--- a/data/etc/framework-sysconfig.xml
+++ b/data/etc/framework-sysconfig.xml
@@ -29,6 +29,8 @@
'service' attribute here is a flattened ComponentName string. -->
<backup-transport-whitelisted-service
service="com.android.localtransport/.LocalTransportService" />
+ <backup-transport-whitelisted-service
+ service="com.android.encryptedlocaltransport/.EncryptedLocalTransportService" />
<!-- Whitelist Shell to use the bugreport API -->
<bugreport-whitelisted package="com.android.shell" />
diff --git a/data/etc/hiddenapi-package-whitelist.xml b/data/etc/hiddenapi-package-whitelist.xml
index f1ba3f6..07a5617 100644
--- a/data/etc/hiddenapi-package-whitelist.xml
+++ b/data/etc/hiddenapi-package-whitelist.xml
@@ -56,7 +56,7 @@
<!-- TODO (b/141954427): Remove networkstack -->
<hidden-api-whitelisted-app package="com.android.networkstack" />
<!-- TODO (b/141954427): Remove wifistack -->
- <hidden-api-whitelisted-app package="com.android.server.wifistack" />
+ <hidden-api-whitelisted-app package="com.android.wifi" />
<hidden-api-whitelisted-app package="com.android.smspush" />
<hidden-api-whitelisted-app package="com.android.spare_parts" />
<hidden-api-whitelisted-app package="com.android.statementservice" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 51136b9..4b4e416 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -121,6 +121,7 @@
<permission name="android.permission.APPROVE_INCIDENT_REPORTS"/>
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
<permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME" />
+ <permission name="android.permission.PACKAGE_USAGE_STATS" />
</privapp-permissions>
<privapp-permissions package="com.android.phone">
@@ -354,11 +355,12 @@
<permission name="android.permission.MANAGE_DYNAMIC_SYSTEM"/>
</privapp-permissions>
- <privapp-permissions package="com.android.server.wifistack">
+ <privapp-permissions package="com.android.wifi">
<permission name="android.permission.CHANGE_CONFIGURATION"/>
<permission name="android.permission.CONNECTIVITY_INTERNAL"/>
<permission name="android.permission.DUMP"/>
<permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ <permission name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
<permission name="android.permission.LOCAL_MAC_ADDRESS"/>
<permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.PACKAGE_USAGE_STATS"/>
diff --git a/data/fonts/Android.mk b/data/fonts/Android.mk
index 454dceb..4226e08 100644
--- a/data/fonts/Android.mk
+++ b/data/fonts/Android.mk
@@ -83,8 +83,8 @@
################################
# Copies the font configuration file into system/etc for the product as fonts.xml.
-# In the case where $(ADDITIONAL_FONTS_FILE) is defined, the content of $(ADDITIONAL_FONTS_FILE)
-# is added to the $(AOSP_FONTS_FILE).
+# Additional fonts should be installed to /product/fonts/ alongside a corresponding
+# fonts_customiztion.xml in /product/etc/
include $(CLEAR_VARS)
LOCAL_MODULE := fonts.xml
diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java
new file mode 100644
index 0000000..8c6a9371
--- /dev/null
+++ b/graphics/java/android/graphics/BLASTBufferQueue.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.graphics;
+
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+/**
+ * @hide
+ */
+public final class BLASTBufferQueue {
+ // Note: This field is accessed by native code.
+ private long mNativeObject; // BLASTBufferQueue*
+
+ private static native long nativeCreate(long surfaceControl, long width, long height);
+ private static native void nativeDestroy(long ptr);
+ private static native Surface nativeGetSurface(long ptr);
+ private static native void nativeSetNextTransaction(long ptr, long transactionPtr);
+ private static native void nativeUpdate(long ptr, long surfaceControl, long width, long height);
+
+ /** Create a new connection with the surface flinger. */
+ public BLASTBufferQueue(SurfaceControl sc, int width, int height) {
+ mNativeObject = nativeCreate(sc.mNativeObject, width, height);
+ }
+
+ public void destroy() {
+ nativeDestroy(mNativeObject);
+ }
+
+ public Surface getSurface() {
+ return nativeGetSurface(mNativeObject);
+ }
+
+ public void setNextTransaction(SurfaceControl.Transaction t) {
+ nativeSetNextTransaction(mNativeObject, t.mNativeObject);
+ }
+
+ public void update(SurfaceControl sc, int width, int height) {
+ nativeUpdate(mNativeObject, sc.mNativeObject, width, height);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ if (mNativeObject != 0) {
+ nativeDestroy(mNativeObject);
+ }
+ } finally {
+ super.finalize();
+ }
+ }
+}
+
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 150a941..6619dba 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -286,6 +286,9 @@
return createFromStream(is, true, preferAnimation, this);
}
+ if (assetFd == null) {
+ throw new FileNotFoundException(mUri.toString());
+ }
return createFromAssetFileDescriptor(assetFd, preferAnimation, this);
}
}
@@ -341,6 +344,9 @@
@NonNull
private static ImageDecoder createFromAssetFileDescriptor(@NonNull AssetFileDescriptor assetFd,
boolean preferAnimation, Source source) throws IOException {
+ if (assetFd == null) {
+ throw new FileNotFoundException();
+ }
final FileDescriptor fd = assetFd.getFileDescriptor();
final long offset = assetFd.getStartOffset();
diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java
index 0e635c7..17e3b44 100644
--- a/graphics/java/android/graphics/RenderNode.java
+++ b/graphics/java/android/graphics/RenderNode.java
@@ -1380,7 +1380,22 @@
* @return Approximate memory usage in bytes.
*/
public @BytesLong long computeApproximateMemoryUsage() {
- return nGetDebugSize(mNativeRenderNode);
+ return nGetUsageSize(mNativeRenderNode);
+ }
+
+ /**
+ * Gets the approximate amount of memory allocated for the RenderNode for debug purposes.
+ * Does not include the memory allocated by any child RenderNodes nor any bitmaps, only the
+ * memory allocated for this RenderNode and any data it owns.
+ *
+ * The difference between this and {@link #computeApproximateMemoryUsage()} is this includes
+ * memory allocated but not used. In particular structures such as DisplayLists are similar
+ * to things like ArrayLists - they need to resize as commands are added to them. As such,
+ * memory used can be less than memory allocated.
+ *
+ * @hide */
+ public @BytesLong long computeApproximateMemoryAllocated() {
+ return nGetAllocatedSize(mNativeRenderNode);
}
/**
@@ -1485,7 +1500,8 @@
private static native void nOutput(long renderNode);
- private static native int nGetDebugSize(long renderNode);
+ private static native int nGetUsageSize(long renderNode);
+ private static native int nGetAllocatedSize(long renderNode);
private static native void nRequestPositionUpdates(long renderNode,
PositionUpdateListener callback);
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 96ac0f9..c6586ec 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -97,6 +97,14 @@
* @attr ref android.R.styleable#GradientDrawablePadding_bottom
*/
public class GradientDrawable extends Drawable {
+
+ /**
+ * Flag to determine if we should wrap negative gradient angle measurements
+ * for API levels that support it
+ * @hide
+ */
+ public static boolean sWrapNegativeAngleMeasurements = true;
+
/**
* Shape is a rectangle, possibly with rounded corners
*/
@@ -151,6 +159,9 @@
/** Radius is a fraction of the bounds size. */
private static final int RADIUS_TYPE_FRACTION_PARENT = 2;
+ /** Default orientation for GradientDrawable **/
+ private static final Orientation DEFAULT_ORIENTATION = Orientation.TOP_BOTTOM;
+
/** @hide */
@IntDef({RADIUS_TYPE_PIXELS, RADIUS_TYPE_FRACTION, RADIUS_TYPE_FRACTION_PARENT})
@Retention(RetentionPolicy.SOURCE)
@@ -207,7 +218,7 @@
}
public GradientDrawable() {
- this(new GradientState(Orientation.TOP_BOTTOM, null), null);
+ this(new GradientState(DEFAULT_ORIENTATION, null), null);
}
/**
@@ -1757,33 +1768,48 @@
}
int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle);
- st.mAngle = ((angle % 360) + 360) % 360; // offset negative angle measures
- switch (st.mAngle) {
- case 0:
- st.mOrientation = Orientation.LEFT_RIGHT;
- break;
- case 45:
- st.mOrientation = Orientation.BL_TR;
- break;
- case 90:
- st.mOrientation = Orientation.BOTTOM_TOP;
- break;
- case 135:
- st.mOrientation = Orientation.BR_TL;
- break;
- case 180:
- st.mOrientation = Orientation.RIGHT_LEFT;
- break;
- case 225:
- st.mOrientation = Orientation.TR_BL;
- break;
- case 270:
- st.mOrientation = Orientation.TOP_BOTTOM;
- break;
- case 315:
- st.mOrientation = Orientation.TL_BR;
- break;
+ // GradientDrawable historically has not parsed negative angle measurements and always
+ // stays on the default orientation for API levels older than Q.
+ // Only configure the orientation if the angle is greater than zero.
+ // Otherwise fallback on Orientation.TOP_BOTTOM
+ // In Android Q and later, actually wrap the negative angle measurement to the correct
+ // value
+ if (sWrapNegativeAngleMeasurements) {
+ st.mAngle = ((angle % 360) + 360) % 360; // offset negative angle measures
+ } else {
+ st.mAngle = angle % 360;
+ }
+
+ if (st.mAngle >= 0) {
+ switch (st.mAngle) {
+ case 0:
+ st.mOrientation = Orientation.LEFT_RIGHT;
+ break;
+ case 45:
+ st.mOrientation = Orientation.BL_TR;
+ break;
+ case 90:
+ st.mOrientation = Orientation.BOTTOM_TOP;
+ break;
+ case 135:
+ st.mOrientation = Orientation.BR_TL;
+ break;
+ case 180:
+ st.mOrientation = Orientation.RIGHT_LEFT;
+ break;
+ case 225:
+ st.mOrientation = Orientation.TR_BL;
+ break;
+ case 270:
+ st.mOrientation = Orientation.TOP_BOTTOM;
+ break;
+ case 315:
+ st.mOrientation = Orientation.TL_BR;
+ break;
+ }
+ } else {
+ st.mOrientation = DEFAULT_ORIENTATION;
}
final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius);
diff --git a/graphics/java/android/graphics/pdf/TEST_MAPPING b/graphics/java/android/graphics/pdf/TEST_MAPPING
new file mode 100644
index 0000000..d763598
--- /dev/null
+++ b/graphics/java/android/graphics/pdf/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsPdfTestCases"
+ }
+ ]
+}
diff --git a/keystore/java/android/security/KeyChain.java b/keystore/java/android/security/KeyChain.java
index bf23634..254456c 100644
--- a/keystore/java/android/security/KeyChain.java
+++ b/keystore/java/android/security/KeyChain.java
@@ -350,7 +350,7 @@
* access manually.
*/
public static final String KEY_ALIAS_SELECTION_DENIED =
- "alias-selection-denied-ef829e15-210a-409d-96c9-ee684fc41972";
+ "android:alias-selection-denied";
/**
* Returns an {@code Intent} that can be used for credential
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index eeaefc5..a34a6c0 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -166,7 +166,10 @@
static_libs: common_test_libs + ["liblog", "libz"],
},
},
- data: ["tests/data/**/*.apk"],
+ data: [
+ "tests/data/**/*.apk",
+ "tests/data/**/*.arsc",
+ ],
test_suites: ["device-tests"],
}
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index cf2ef30..b309621 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -42,12 +42,16 @@
ApkAssets::ApkAssets(ZipArchiveHandle unmanaged_handle,
const std::string& path,
- time_t last_mod_time)
- : zip_handle_(unmanaged_handle, ::CloseArchive), path_(path), last_mod_time_(last_mod_time) {
+ time_t last_mod_time,
+ bool for_loader)
+ : zip_handle_(unmanaged_handle, ::CloseArchive), path_(path), last_mod_time_(last_mod_time),
+ for_loader(for_loader) {
}
-std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system) {
- return LoadImpl({} /*fd*/, path, nullptr, nullptr, system, false /*load_as_shared_library*/);
+std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system,
+ bool for_loader) {
+ return LoadImpl({} /*fd*/, path, nullptr, nullptr, system, false /*load_as_shared_library*/,
+ for_loader);
}
std::unique_ptr<const ApkAssets> ApkAssets::LoadAsSharedLibrary(const std::string& path,
@@ -76,9 +80,21 @@
std::unique_ptr<const ApkAssets> ApkAssets::LoadFromFd(unique_fd fd,
const std::string& friendly_name,
- bool system, bool force_shared_lib) {
+ bool system, bool force_shared_lib,
+ bool for_loader) {
return LoadImpl(std::move(fd), friendly_name, nullptr /*idmap_asset*/, nullptr /*loaded_idmap*/,
- system, force_shared_lib);
+ system, force_shared_lib, for_loader);
+}
+
+std::unique_ptr<const ApkAssets> ApkAssets::LoadArsc(const std::string& path,
+ bool for_loader) {
+ return LoadArscImpl({} /*fd*/, path, for_loader);
+}
+
+std::unique_ptr<const ApkAssets> ApkAssets::LoadArsc(unique_fd fd,
+ const std::string& friendly_name,
+ bool for_loader) {
+ return LoadArscImpl(std::move(fd), friendly_name, for_loader);
}
std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) {
@@ -104,7 +120,8 @@
std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
unique_fd fd, const std::string& path, std::unique_ptr<Asset> idmap_asset,
- std::unique_ptr<const LoadedIdmap> loaded_idmap, bool system, bool load_as_shared_library) {
+ std::unique_ptr<const LoadedIdmap> loaded_idmap, bool system, bool load_as_shared_library,
+ bool for_loader) {
::ZipArchiveHandle unmanaged_handle;
int32_t result;
if (fd >= 0) {
@@ -123,7 +140,8 @@
time_t last_mod_time = getFileModDate(path.c_str());
// Wrap the handle in a unique_ptr so it gets automatically closed.
- std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(unmanaged_handle, path, last_mod_time));
+ std::unique_ptr<ApkAssets>
+ loaded_apk(new ApkAssets(unmanaged_handle, path, last_mod_time, for_loader));
// Find the resource table.
::ZipEntry entry;
@@ -152,7 +170,7 @@
reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)),
loaded_apk->resources_asset_->getLength());
loaded_apk->loaded_arsc_ =
- LoadedArsc::Load(data, loaded_idmap.get(), system, load_as_shared_library);
+ LoadedArsc::Load(data, loaded_idmap.get(), system, load_as_shared_library, for_loader);
if (loaded_apk->loaded_arsc_ == nullptr) {
LOG(ERROR) << "Failed to load '" << kResourcesArsc << "' in APK '" << path << "'.";
return {};
@@ -162,8 +180,53 @@
return std::move(loaded_apk);
}
+std::unique_ptr<const ApkAssets> ApkAssets::LoadArscImpl(unique_fd fd,
+ const std::string& path,
+ bool for_loader) {
+ std::unique_ptr<Asset> resources_asset;
+
+ if (fd >= 0) {
+ resources_asset = std::unique_ptr<Asset>(Asset::createFromFd(fd.release(), nullptr,
+ Asset::AccessMode::ACCESS_BUFFER));
+ } else {
+ resources_asset = CreateAssetFromFile(path);
+ }
+
+ if (resources_asset == nullptr) {
+ LOG(ERROR) << "Failed to open ARSC '" << path;
+ return {};
+ }
+
+ time_t last_mod_time = getFileModDate(path.c_str());
+
+ std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(nullptr, path, last_mod_time, for_loader));
+ loaded_apk->resources_asset_ = std::move(resources_asset);
+
+ const StringPiece data(
+ reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)),
+ loaded_apk->resources_asset_->getLength());
+ loaded_apk->loaded_arsc_ = LoadedArsc::Load(data, nullptr, false, false, for_loader);
+ if (loaded_apk->loaded_arsc_ == nullptr) {
+ LOG(ERROR) << "Failed to load '" << kResourcesArsc << path;
+ return {};
+ }
+
+ // Need to force a move for mingw32.
+ return std::move(loaded_apk);
+}
+
+std::unique_ptr<const ApkAssets> ApkAssets::LoadEmpty(bool for_loader) {
+ std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(nullptr, "", -1, for_loader));
+ loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();
+ // Need to force a move for mingw32.
+ return std::move(loaded_apk);
+}
+
std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMode mode) const {
- CHECK(zip_handle_ != nullptr);
+ // If this is a resource loader from an .arsc, there will be no zip handle
+ if (zip_handle_ == nullptr) {
+ return {};
+ }
::ZipEntry entry;
int32_t result = ::FindEntry(zip_handle_.get(), path, &entry);
@@ -205,7 +268,10 @@
bool ApkAssets::ForEachFile(const std::string& root_path,
const std::function<void(const StringPiece&, FileType)>& f) const {
- CHECK(zip_handle_ != nullptr);
+ // If this is a resource loader from an .arsc, there will be no zip handle
+ if (zip_handle_ == nullptr) {
+ return false;
+ }
std::string root_path_full = root_path;
if (root_path_full.back() != '/') {
@@ -252,6 +318,11 @@
}
bool ApkAssets::IsUpToDate() const {
+ // Loaders are invalidated by the app, not the system, so assume up to date
+ if (for_loader) {
+ return true;
+ }
+
return last_mod_time_ == getFileModDate(path_.c_str());
}
diff --git a/libs/androidfw/Asset.cpp b/libs/androidfw/Asset.cpp
index 92125c9..c132f34 100644
--- a/libs/androidfw/Asset.cpp
+++ b/libs/androidfw/Asset.cpp
@@ -133,14 +133,24 @@
*/
/*static*/ Asset* Asset::createFromFile(const char* fileName, AccessMode mode)
{
+ return createFromFd(open(fileName, O_RDONLY | O_BINARY), fileName, mode);
+}
+
+/*
+ * Create a new Asset from a file on disk. There is a fair chance that
+ * the file doesn't actually exist.
+ *
+ * We can use "mode" to decide how we want to go about it.
+ */
+/*static*/ Asset* Asset::createFromFd(const int fd, const char* fileName, AccessMode mode)
+{
+ if (fd < 0) {
+ return NULL;
+ }
+
_FileAsset* pAsset;
status_t result;
off64_t length;
- int fd;
-
- fd = open(fileName, O_RDONLY | O_BINARY);
- if (fd < 0)
- return NULL;
/*
* Under Linux, the lseek fails if we actually opened a directory. To
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index eec49df..e914f37 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -493,8 +493,12 @@
type_flags |= type_spec->GetFlagsForEntryIndex(local_entry_idx);
- // If the package is an overlay, then even configurations that are the same MUST be chosen.
+
+ // If the package is an overlay or custom loader,
+ // then even configurations that are the same MUST be chosen.
const bool package_is_overlay = loaded_package->IsOverlay();
+ const bool package_is_loader = loaded_package->IsCustomLoader();
+ const bool should_overlay = package_is_overlay || package_is_loader;
if (use_fast_path) {
const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx];
@@ -508,10 +512,28 @@
if (best_config == nullptr) {
resolution_type = Resolution::Step::Type::INITIAL;
} else if (this_config.isBetterThan(*best_config, desired_config)) {
- resolution_type = Resolution::Step::Type::BETTER_MATCH;
- } else if (package_is_overlay && this_config.compare(*best_config) == 0) {
- resolution_type = Resolution::Step::Type::OVERLAID;
+ if (package_is_loader) {
+ resolution_type = Resolution::Step::Type::BETTER_MATCH_LOADER;
+ } else {
+ resolution_type = Resolution::Step::Type::BETTER_MATCH;
+ }
+ } else if (should_overlay && this_config.compare(*best_config) == 0) {
+ if (package_is_loader) {
+ resolution_type = Resolution::Step::Type::OVERLAID_LOADER;
+ } else if (package_is_overlay) {
+ resolution_type = Resolution::Step::Type::OVERLAID;
+ }
} else {
+ if (resource_resolution_logging_enabled_) {
+ if (package_is_loader) {
+ resolution_type = Resolution::Step::Type::SKIPPED_LOADER;
+ } else {
+ resolution_type = Resolution::Step::Type::SKIPPED;
+ }
+ resolution_steps.push_back(Resolution::Step{resolution_type,
+ this_config.toString(),
+ &loaded_package->GetPackageName()});
+ }
continue;
}
@@ -520,6 +542,16 @@
const ResTable_type* type = filtered_group.types[i];
const uint32_t offset = LoadedPackage::GetEntryOffset(type, local_entry_idx);
if (offset == ResTable_type::NO_ENTRY) {
+ if (resource_resolution_logging_enabled_) {
+ if (package_is_loader) {
+ resolution_type = Resolution::Step::Type::NO_ENTRY_LOADER;
+ } else {
+ resolution_type = Resolution::Step::Type::NO_ENTRY;
+ }
+ resolution_steps.push_back(Resolution::Step{Resolution::Step::Type::NO_ENTRY,
+ this_config.toString(),
+ &loaded_package->GetPackageName()});
+ }
continue;
}
@@ -554,9 +586,17 @@
if (best_config == nullptr) {
resolution_type = Resolution::Step::Type::INITIAL;
} else if (this_config.isBetterThan(*best_config, desired_config)) {
- resolution_type = Resolution::Step::Type::BETTER_MATCH;
- } else if (package_is_overlay && this_config.compare(*best_config) == 0) {
- resolution_type = Resolution::Step::Type::OVERLAID;
+ if (package_is_loader) {
+ resolution_type = Resolution::Step::Type::BETTER_MATCH_LOADER;
+ } else {
+ resolution_type = Resolution::Step::Type::BETTER_MATCH;
+ }
+ } else if (should_overlay && this_config.compare(*best_config) == 0) {
+ if (package_is_overlay) {
+ resolution_type = Resolution::Step::Type::OVERLAID;
+ } else if (package_is_loader) {
+ resolution_type = Resolution::Step::Type::OVERLAID_LOADER;
+ }
} else {
continue;
}
@@ -678,9 +718,27 @@
case Resolution::Step::Type::BETTER_MATCH:
prefix = "Found better";
break;
+ case Resolution::Step::Type::BETTER_MATCH_LOADER:
+ prefix = "Found better in loader";
+ break;
case Resolution::Step::Type::OVERLAID:
prefix = "Overlaid";
break;
+ case Resolution::Step::Type::OVERLAID_LOADER:
+ prefix = "Overlaid by loader";
+ break;
+ case Resolution::Step::Type::SKIPPED:
+ prefix = "Skipped";
+ break;
+ case Resolution::Step::Type::SKIPPED_LOADER:
+ prefix = "Skipped loader";
+ break;
+ case Resolution::Step::Type::NO_ENTRY:
+ prefix = "No entry";
+ break;
+ case Resolution::Step::Type::NO_ENTRY_LOADER:
+ prefix = "No entry for loader";
+ break;
}
if (!prefix.empty()) {
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 72873ab..882dc0d 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -401,7 +401,9 @@
std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
const LoadedIdmap* loaded_idmap,
- bool system, bool load_as_shared_library) {
+ bool system,
+ bool load_as_shared_library,
+ bool for_loader) {
ATRACE_NAME("LoadedPackage::Load");
std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage());
@@ -430,6 +432,10 @@
loaded_package->overlay_ = true;
}
+ if (for_loader) {
+ loaded_package->custom_loader_ = true;
+ }
+
if (header->header.headerSize >= sizeof(ResTable_package)) {
uint32_t type_id_offset = dtohl(header->typeIdOffset);
if (type_id_offset > std::numeric_limits<uint8_t>::max()) {
@@ -696,7 +702,7 @@
}
bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
- bool load_as_shared_library) {
+ bool load_as_shared_library, bool for_loader) {
const ResTable_header* header = chunk.header<ResTable_header>();
if (header == nullptr) {
LOG(ERROR) << "RES_TABLE_TYPE too small.";
@@ -735,7 +741,11 @@
packages_seen++;
std::unique_ptr<const LoadedPackage> loaded_package =
- LoadedPackage::Load(child_chunk, loaded_idmap, system_, load_as_shared_library);
+ LoadedPackage::Load(child_chunk,
+ loaded_idmap,
+ system_,
+ load_as_shared_library,
+ for_loader);
if (!loaded_package) {
return false;
}
@@ -758,9 +768,11 @@
}
std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const StringPiece& data,
- const LoadedIdmap* loaded_idmap, bool system,
- bool load_as_shared_library) {
- ATRACE_NAME("LoadedArsc::LoadTable");
+ const LoadedIdmap* loaded_idmap,
+ bool system,
+ bool load_as_shared_library,
+ bool for_loader) {
+ ATRACE_NAME("LoadedArsc::Load");
// Not using make_unique because the constructor is private.
std::unique_ptr<LoadedArsc> loaded_arsc(new LoadedArsc());
@@ -771,7 +783,10 @@
const Chunk chunk = iter.Next();
switch (chunk.type()) {
case RES_TABLE_TYPE:
- if (!loaded_arsc->LoadTable(chunk, loaded_idmap, load_as_shared_library)) {
+ if (!loaded_arsc->LoadTable(chunk,
+ loaded_idmap,
+ load_as_shared_library,
+ for_loader)) {
return {};
}
break;
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 5353869..3fe2c5b 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -1063,6 +1063,11 @@
return (mError == NO_ERROR) ? mHeader->header.size : 0;
}
+const void* ResStringPool::data() const
+{
+ return mHeader;
+}
+
bool ResStringPool::isSorted() const
{
return (mHeader->flags&ResStringPool_header::SORTED_FLAG)!=0;
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index 49fc82b..625b6820 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -40,7 +40,8 @@
// Creates an ApkAssets.
// If `system` is true, the package is marked as a system package, and allows some functions to
// filter out this package when computing what configurations/resources are available.
- static std::unique_ptr<const ApkAssets> Load(const std::string& path, bool system = false);
+ static std::unique_ptr<const ApkAssets> Load(const std::string& path, bool system = false,
+ bool for_loader = false);
// Creates an ApkAssets, but forces any package with ID 0x7f to be loaded as a shared library.
// If `system` is true, the package is marked as a system package, and allows some functions to
@@ -63,7 +64,21 @@
// If `force_shared_lib` is true, any package with ID 0x7f is loaded as a shared library.
static std::unique_ptr<const ApkAssets> LoadFromFd(base::unique_fd fd,
const std::string& friendly_name, bool system,
- bool force_shared_lib);
+ bool force_shared_lib,
+ bool for_loader = false);
+
+ // Creates an empty wrapper ApkAssets from the given path which points to an .arsc.
+ static std::unique_ptr<const ApkAssets> LoadArsc(const std::string& path,
+ bool for_loader = false);
+
+ // Creates an empty wrapper ApkAssets from the given file descriptor which points to an .arsc,
+ // Takes ownership of the file descriptor.
+ static std::unique_ptr<const ApkAssets> LoadArsc(base::unique_fd fd,
+ const std::string& friendly_name,
+ bool resource_loader = false);
+
+ // Creates a totally empty ApkAssets with no resources table and no file entries.
+ static std::unique_ptr<const ApkAssets> LoadEmpty(bool resource_loader = false);
std::unique_ptr<Asset> Open(const std::string& path,
Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const;
@@ -86,24 +101,33 @@
bool IsUpToDate() const;
+ // Creates an Asset from any file on the file system.
+ static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
+
private:
DISALLOW_COPY_AND_ASSIGN(ApkAssets);
static std::unique_ptr<const ApkAssets> LoadImpl(base::unique_fd fd, const std::string& path,
std::unique_ptr<Asset> idmap_asset,
std::unique_ptr<const LoadedIdmap> loaded_idmap,
- bool system, bool load_as_shared_library);
+ bool system, bool load_as_shared_library,
+ bool resource_loader = false);
- // Creates an Asset from any file on the file system.
- static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
+ static std::unique_ptr<const ApkAssets> LoadArscImpl(base::unique_fd fd,
+ const std::string& path,
+ bool resource_loader = false);
- ApkAssets(ZipArchiveHandle unmanaged_handle, const std::string& path, time_t last_mod_time);
+ ApkAssets(ZipArchiveHandle unmanaged_handle,
+ const std::string& path,
+ time_t last_mod_time,
+ bool for_loader = false);
- using ZipArchivePtr = std::unique_ptr<ZipArchive, void(*)(ZipArchiveHandle)>;
+ using ZipArchivePtr = std::unique_ptr<ZipArchive, void (*)(ZipArchiveHandle)>;
ZipArchivePtr zip_handle_;
const std::string path_;
time_t last_mod_time_;
+ bool for_loader;
std::unique_ptr<Asset> resources_asset_;
std::unique_ptr<Asset> idmap_asset_;
std::unique_ptr<const LoadedArsc> loaded_arsc_;
diff --git a/libs/androidfw/include/androidfw/Asset.h b/libs/androidfw/include/androidfw/Asset.h
index 9d12a35..053dbb7 100644
--- a/libs/androidfw/include/androidfw/Asset.h
+++ b/libs/androidfw/include/androidfw/Asset.h
@@ -121,6 +121,11 @@
*/
const char* getAssetSource(void) const { return mAssetSource.string(); }
+ /*
+ * Create the asset from a file descriptor.
+ */
+ static Asset* createFromFd(const int fd, const char* fileName, AccessMode mode);
+
protected:
/*
* Adds this Asset to the global Asset list for debugging and
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index de46081..c7348b1 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -382,7 +382,13 @@
enum class Type {
INITIAL,
BETTER_MATCH,
- OVERLAID
+ BETTER_MATCH_LOADER,
+ OVERLAID,
+ OVERLAID_LOADER,
+ SKIPPED,
+ SKIPPED_LOADER,
+ NO_ENTRY,
+ NO_ENTRY_LOADER,
};
// Marks what kind of override this step was.
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 950f541..1a56876 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -137,7 +137,8 @@
static std::unique_ptr<const LoadedPackage> Load(const Chunk& chunk,
const LoadedIdmap* loaded_idmap, bool system,
- bool load_as_shared_library);
+ bool load_as_shared_library,
+ bool load_as_custom_loader);
~LoadedPackage();
@@ -187,6 +188,11 @@
return overlay_;
}
+ // Returns true if this package is a custom loader and should behave like an overlay
+ inline bool IsCustomLoader() const {
+ return custom_loader_;
+ }
+
// Returns the map of package name to package ID used in this LoadedPackage. At runtime, a
// package could have been assigned a different package ID than what this LoadedPackage was
// compiled with. AssetManager rewrites the package IDs so that they are compatible at runtime.
@@ -260,6 +266,7 @@
bool dynamic_ = false;
bool system_ = false;
bool overlay_ = false;
+ bool custom_loader_ = false;
bool defines_overlayable_ = false;
ByteBucketArray<TypeSpecPtr> type_specs_;
@@ -282,7 +289,8 @@
static std::unique_ptr<const LoadedArsc> Load(const StringPiece& data,
const LoadedIdmap* loaded_idmap = nullptr,
bool system = false,
- bool load_as_shared_library = false);
+ bool load_as_shared_library = false,
+ bool for_loader = false);
// Create an empty LoadedArsc. This is used when an APK has no resources.arsc.
static std::unique_ptr<const LoadedArsc> CreateEmpty();
@@ -311,7 +319,19 @@
DISALLOW_COPY_AND_ASSIGN(LoadedArsc);
LoadedArsc() = default;
- bool LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, bool load_as_shared_library);
+ bool LoadTable(
+ const Chunk& chunk,
+ const LoadedIdmap* loaded_idmap,
+ bool load_as_shared_library,
+ bool for_loader
+ );
+
+ static std::unique_ptr<const LoadedArsc> LoadData(std::unique_ptr<LoadedArsc>& loaded_arsc,
+ const char* data,
+ size_t length,
+ const LoadedIdmap* loaded_idmap = nullptr,
+ bool load_as_shared_library = false,
+ bool for_loader = false);
ResStringPool global_string_pool_;
std::vector<std::unique_ptr<const LoadedPackage>> packages_;
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index fc635aa..c8ace90 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -523,6 +523,8 @@
size_t size() const;
size_t styleCount() const;
size_t bytes() const;
+ const void* data() const;
+
bool isSorted() const;
bool isUTF8() const;
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index d58e8d2..fd57a92 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -25,6 +25,7 @@
#include "data/overlayable/R.h"
#include "data/sparse/R.h"
#include "data/styles/R.h"
+#include "data/system/R.h"
namespace app = com::android::app;
namespace basic = com::android::basic;
@@ -387,6 +388,39 @@
ASSERT_EQ(map.at("OverlayableResources2"), "overlay://com.android.overlayable");
}
+TEST(LoadedArscTest, LoadCustomLoader) {
+ std::string contents;
+
+ std::unique_ptr<Asset>
+ asset = ApkAssets::CreateAssetFromFile(GetTestDataPath() + "/loader/resources.arsc");
+
+ MockLoadedIdmap loaded_idmap;
+ const StringPiece data(
+ reinterpret_cast<const char*>(asset->getBuffer(true /*wordAligned*/)),
+ asset->getLength());
+
+ std::unique_ptr<const LoadedArsc> loaded_arsc =
+ LoadedArsc::Load(data, nullptr, false, false, true);
+ ASSERT_THAT(loaded_arsc, NotNull());
+
+ const LoadedPackage* package =
+ loaded_arsc->GetPackageById(get_package_id(android::R::string::cancel));
+ ASSERT_THAT(package, NotNull());
+ EXPECT_THAT(package->GetPackageName(), StrEq("android"));
+ EXPECT_THAT(package->GetPackageId(), Eq(0x01));
+
+ const uint8_t type_index = get_type_id(android::R::string::cancel) - 1;
+ const uint16_t entry_index = get_entry_id(android::R::string::cancel);
+
+ const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+ ASSERT_THAT(type_spec, NotNull());
+ ASSERT_THAT(type_spec->type_count, Ge(1u));
+
+ const ResTable_type* type = type_spec->types[0];
+ ASSERT_THAT(type, NotNull());
+ ASSERT_THAT(LoadedPackage::GetEntry(type, entry_index), NotNull());
+}
+
// structs with size fields (like Res_value, ResTable_entry) should be
// backwards and forwards compatible (aka checking the size field against
// sizeof(Res_value) might not be backwards compatible.
diff --git a/libs/androidfw/tests/data/loader/resources.arsc b/libs/androidfw/tests/data/loader/resources.arsc
new file mode 100644
index 0000000..2c881f2
--- /dev/null
+++ b/libs/androidfw/tests/data/loader/resources.arsc
Binary files differ
diff --git a/libs/androidfw/tests/data/system/R.h b/libs/androidfw/tests/data/system/R.h
index becb388..3741074 100644
--- a/libs/androidfw/tests/data/system/R.h
+++ b/libs/androidfw/tests/data/system/R.h
@@ -40,6 +40,12 @@
number = 0x01030000, // sv
};
};
+
+ struct string {
+ enum : uint32_t {
+ cancel = 0x01040000,
+ };
+ };
};
} // namespace android
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index ae90f11..61b72cf 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -26,8 +26,10 @@
// a problem
"-Wno-free-nonheap-object",
- // clang's warning is broken, see: https://llvm.org/bugs/show_bug.cgi?id=21629
- "-Wno-missing-braces",
+ // Clang is producing non-determistic binary when the new pass manager is
+ // enabled. Disable the new PM as a temporary workaround.
+ // b/142372146
+ "-fno-experimental-new-pass-manager",
],
include_dirs: [
diff --git a/libs/hwui/DamageAccumulator.h b/libs/hwui/DamageAccumulator.h
index 7d0b687..030a20f 100644
--- a/libs/hwui/DamageAccumulator.h
+++ b/libs/hwui/DamageAccumulator.h
@@ -27,7 +27,7 @@
// Smaller than INT_MIN/INT_MAX because we offset these values
// and thus don't want to be adding offsets to INT_MAX, that's bad
#define DIRTY_MIN (-0x7ffffff - 1)
-#define DIRTY_MAX (0x7ffffff)
+#define DIRTY_MAX (0x8000000)
namespace android {
namespace uirenderer {
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index a79b7c0..322eff2 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -69,6 +69,7 @@
bool hasText() const { return mHasText; }
size_t usedSize() const { return fUsed; }
+ size_t allocatedSize() const { return fReserved; }
private:
friend class RecordingCanvas;
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 8eb5e3d..6761435 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -108,7 +108,7 @@
output << std::endl;
}
-int RenderNode::getDebugSize() {
+int RenderNode::getUsageSize() {
int size = sizeof(RenderNode);
if (mStagingDisplayList) {
size += mStagingDisplayList->getUsedSize();
@@ -119,6 +119,18 @@
return size;
}
+int RenderNode::getAllocatedSize() {
+ int size = sizeof(RenderNode);
+ if (mStagingDisplayList) {
+ size += mStagingDisplayList->getAllocatedSize();
+ }
+ if (mDisplayList && mDisplayList != mStagingDisplayList) {
+ size += mDisplayList->getAllocatedSize();
+ }
+ return size;
+}
+
+
void RenderNode::prepareTree(TreeInfo& info) {
ATRACE_CALL();
LOG_ALWAYS_FATAL_IF(!info.damageAccumulator, "DamageAccumulator missing");
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index c6db7f1..d55e5b0 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -102,7 +102,8 @@
ANDROID_API void setStagingDisplayList(DisplayList* newData);
ANDROID_API void output();
- ANDROID_API int getDebugSize();
+ ANDROID_API int getUsageSize();
+ ANDROID_API int getAllocatedSize();
bool isRenderable() const { return mDisplayList && !mDisplayList->isEmpty(); }
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index e3c3273..cdd00db 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -47,6 +47,7 @@
class SkiaDisplayList {
public:
size_t getUsedSize() { return allocator.usedSize() + mDisplayList.usedSize(); }
+ size_t getAllocatedSize() { return allocator.allocatedSize() + mDisplayList.allocatedSize(); }
~SkiaDisplayList() {
/* Given that we are using a LinearStdAllocator to store some of the
diff --git a/libs/hwui/utils/LinearAllocator.h b/libs/hwui/utils/LinearAllocator.h
index 9c4a1be..539e654 100644
--- a/libs/hwui/utils/LinearAllocator.h
+++ b/libs/hwui/utils/LinearAllocator.h
@@ -115,6 +115,7 @@
* wasted)
*/
size_t usedSize() const { return mTotalAllocated - mWastedSpace; }
+ size_t allocatedSize() const { return mTotalAllocated; }
private:
LinearAllocator(const LinearAllocator& other);
diff --git a/location/java/android/location/AbstractListenerManager.java b/location/java/android/location/AbstractListenerManager.java
index c41023e..944ebf9 100644
--- a/location/java/android/location/AbstractListenerManager.java
+++ b/location/java/android/location/AbstractListenerManager.java
@@ -42,8 +42,8 @@
@Nullable private volatile T mListener;
private Registration(Executor executor, T listener) {
- Preconditions.checkArgument(listener != null);
- Preconditions.checkArgument(executor != null);
+ Preconditions.checkArgument(listener != null, "invalid null listener/callback");
+ Preconditions.checkArgument(executor != null, "invalid null executor");
mExecutor = executor;
mListener = listener;
}
@@ -83,16 +83,18 @@
return addInternal(listener, executor);
}
- protected final boolean addInternal(Object listener, Handler handler) throws RemoteException {
+ protected final boolean addInternal(@NonNull Object listener, @NonNull Handler handler)
+ throws RemoteException {
return addInternal(listener, new HandlerExecutor(handler));
}
- protected final boolean addInternal(Object listener, Executor executor) throws RemoteException {
+ protected final boolean addInternal(@NonNull Object listener, @NonNull Executor executor)
+ throws RemoteException {
+ Preconditions.checkArgument(listener != null, "invalid null listener/callback");
return addInternal(listener, new Registration<>(executor, convertKey(listener)));
}
private boolean addInternal(Object key, Registration<T> registration) throws RemoteException {
- Preconditions.checkNotNull(key);
Preconditions.checkNotNull(registration);
synchronized (mListeners) {
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index d06ba12..daa2e08 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -42,11 +42,11 @@
interface ILocationManager
{
void requestLocationUpdates(in LocationRequest request, in ILocationListener listener,
- in PendingIntent intent, String packageName);
+ in PendingIntent intent, String packageName, String listenerIdentifier);
void removeUpdates(in ILocationListener listener, in PendingIntent intent, String packageName);
void requestGeofence(in LocationRequest request, in Geofence geofence,
- in PendingIntent intent, String packageName);
+ in PendingIntent intent, String packageName, String listenerIdentifier);
void removeGeofence(in Geofence fence, in PendingIntent intent, String packageName);
Location getLastLocation(in LocationRequest request, String packageName);
@@ -64,22 +64,23 @@
boolean sendNiResponse(int notifId, int userResponse);
- boolean addGnssMeasurementsListener(in IGnssMeasurementsListener listener, in String packageName);
+ boolean addGnssMeasurementsListener(in IGnssMeasurementsListener listener,
+ String packageName, String listenerIdentifier);
void injectGnssMeasurementCorrections(in GnssMeasurementCorrections corrections,
in String packageName);
long getGnssCapabilities(in String packageName);
void removeGnssMeasurementsListener(in IGnssMeasurementsListener listener);
- boolean addGnssNavigationMessageListener(
- in IGnssNavigationMessageListener listener,
- in String packageName);
+ boolean addGnssNavigationMessageListener(in IGnssNavigationMessageListener listener,
+ String packageName, String listenerIdentifier);
void removeGnssNavigationMessageListener(in IGnssNavigationMessageListener listener);
int getGnssYearOfHardware();
String getGnssHardwareModelName();
int getGnssBatchSize(String packageName);
- boolean addGnssBatchingCallback(in IBatchedLocationCallback callback, String packageName);
+ boolean addGnssBatchingCallback(in IBatchedLocationCallback callback, String packageName,
+ String listenerIdentifier);
void removeGnssBatchingCallback();
boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName);
void flushGnssBatch(String packageName);
@@ -92,6 +93,7 @@
String getBestProvider(in Criteria criteria, boolean enabledOnly);
ProviderProperties getProviderProperties(String provider);
boolean isProviderPackage(String packageName);
+ List<String> getProviderPackages(String provider);
void setExtraLocationControllerPackage(String packageName);
String getExtraLocationControllerPackage();
diff --git a/location/java/android/location/Location.java b/location/java/android/location/Location.java
index 9c36d76..27274d1 100644
--- a/location/java/android/location/Location.java
+++ b/location/java/android/location/Location.java
@@ -16,6 +16,7 @@
package android.location;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
@@ -79,6 +80,8 @@
*
* @hide
*/
+ @TestApi
+ @SystemApi
public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation";
/**
@@ -1208,14 +1211,16 @@
}
/**
- * Attaches an extra {@link Location} to this Location.
+ * Attaches an extra {@link Location} to this Location. This is useful for location providers
+ * to set the {@link #EXTRA_NO_GPS_LOCATION} extra to provide coarse locations for clients.
*
* @param key the key associated with the Location extra
* @param value the Location to attach
* @hide
*/
- @UnsupportedAppUsage
- public void setExtraLocation(String key, Location value) {
+ @TestApi
+ @SystemApi
+ public void setExtraLocation(@Nullable String key, @Nullable Location value) {
if (mExtras == null) {
mExtras = new Bundle();
}
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index f3d6875..70bfb54 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -51,6 +51,7 @@
import com.android.internal.location.ProviderProperties;
import com.android.internal.util.Preconditions;
+import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
@@ -145,7 +146,7 @@
* Key used for an extra holding a boolean enabled/disabled status value when a provider
* enabled/disabled event is broadcast using a PendingIntent.
*
- * @see #requestLocationUpdates(String, long, long, PendingIntent)
+ * @see #requestLocationUpdates(String, long, float, PendingIntent)
*/
public static final String KEY_PROVIDER_ENABLED = "providerEnabled";
@@ -153,7 +154,7 @@
* Key used for an extra holding a {@link Location} value when a location change is broadcast
* using a PendingIntent.
*
- * @see #requestLocationUpdates(String, long, long, PendingIntent)
+ * @see #requestLocationUpdates(String, long, float, PendingIntent)
*/
public static final String KEY_LOCATION_CHANGED = "location";
@@ -542,6 +543,23 @@
}
/**
+ * Create a string that allows an app to identify a listener
+ *
+ * @param listener The listener
+ *
+ * @return A identifying string
+ */
+ private static String getListenerIdentifier(@NonNull Object listener) {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(listener.getClass().getName());
+ sb.append('@');
+ sb.append(Integer.toHexString(System.identityHashCode(listener)));
+
+ return sb.toString();
+ }
+
+ /**
* Register for a single location update using the named provider and
* a callback.
*
@@ -981,7 +999,7 @@
boolean registered = false;
try {
mService.requestLocationUpdates(locationRequest, transport, null,
- mContext.getPackageName());
+ mContext.getPackageName(), getListenerIdentifier(listener));
registered = true;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -1026,7 +1044,7 @@
try {
mService.requestLocationUpdates(locationRequest, null, pendingIntent,
- mContext.getPackageName());
+ mContext.getPackageName(), getListenerIdentifier(pendingIntent));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1235,6 +1253,24 @@
}
/**
+ * Returns a list of packages associated with the given provider,
+ * and an empty list if no package is associated with the provider.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
+ @Nullable
+ public List<String> getProviderPackages(@NonNull String provider) {
+ try {
+ return mService.getProviderPackages(provider);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ return Collections.emptyList();
+ }
+ }
+
+ /**
* Sends additional commands to a location provider. Can be used to support provider specific
* extensions to the Location Manager API.
*
@@ -1256,44 +1292,46 @@
}
/**
- * Creates a mock location provider and adds it to the set of active providers.
+ * Creates a test location provider and adds it to the set of active providers. This provider
+ * will replace any provider with the same name that exists prior to this call.
*
- * @param name the provider name
+ * @param provider the provider name
*
+ * @throws IllegalArgumentException if provider is null
* @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION
* mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED
* allowed} for your app.
- * @throws IllegalArgumentException if a provider with the given name already exists
*/
public void addTestProvider(
- @NonNull String name, boolean requiresNetwork, boolean requiresSatellite,
+ @NonNull String provider, boolean requiresNetwork, boolean requiresSatellite,
boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude,
boolean supportsSpeed, boolean supportsBearing, int powerRequirement, int accuracy) {
+ Preconditions.checkArgument(provider != null, "invalid null provider");
+
ProviderProperties properties = new ProviderProperties(requiresNetwork,
requiresSatellite, requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed,
supportsBearing, powerRequirement, accuracy);
- if (name.matches(LocationProvider.BAD_CHARS_REGEX)) {
- throw new IllegalArgumentException("provider name contains illegal character: " + name);
- }
-
try {
- mService.addTestProvider(name, properties, mContext.getOpPackageName());
+ mService.addTestProvider(provider, properties, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Removes the mock location provider with the given name.
+ * Removes the test location provider with the given name or does nothing if no such test
+ * location provider exists.
*
* @param provider the provider name
*
+ * @throws IllegalArgumentException if provider is null
* @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION
* mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED
* allowed} for your app.
- * @throws IllegalArgumentException if no provider with the given name exists
*/
public void removeTestProvider(@NonNull String provider) {
+ Preconditions.checkArgument(provider != null, "invalid null provider");
+
try {
mService.removeTestProvider(provider, mContext.getOpPackageName());
} catch (RemoteException e) {
@@ -1302,60 +1340,53 @@
}
/**
- * Sets a mock location for the given provider.
- * <p>This location will be used in place of any actual location from the provider.
- * The location object must have a minimum number of fields set to be
- * considered a valid LocationProvider Location, as per documentation
- * on {@link Location} class.
+ * Sets a new location for the given test provider. This location will be identiable as a mock
+ * location to all clients via {@link Location#isFromMockProvider()}.
+ *
+ * <p>The location object must have a minimum number of fields set to be considered valid, as
+ * per documentation on {@link Location} class.
*
* @param provider the provider name
- * @param loc the mock location
+ * @param location the mock location
*
* @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION
* mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED
* allowed} for your app.
- * @throws IllegalArgumentException if no provider with the given name exists
- * @throws IllegalArgumentException if the location is incomplete
+ * @throws IllegalArgumentException if the provider is null or not a test provider
+ * @throws IllegalArgumentException if the location is null or incomplete
*/
- public void setTestProviderLocation(@NonNull String provider, @NonNull Location loc) {
- if (!loc.isComplete()) {
+ public void setTestProviderLocation(@NonNull String provider, @NonNull Location location) {
+ Preconditions.checkArgument(provider != null, "invalid null provider");
+ Preconditions.checkArgument(location != null, "invalid null location");
+
+ if (!location.isComplete()) {
IllegalArgumentException e = new IllegalArgumentException(
- "Incomplete location object, missing timestamp or accuracy? " + loc);
+ "Incomplete location object, missing timestamp or accuracy? " + location);
if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN) {
- // just log on old platform (for backwards compatibility)
Log.w(TAG, e);
- loc.makeComplete();
+ location.makeComplete();
} else {
- // really throw it!
throw e;
}
}
try {
- mService.setTestProviderLocation(provider, loc, mContext.getOpPackageName());
+ mService.setTestProviderLocation(provider, location, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Removes any mock location associated with the given provider.
+ * Does nothing.
*
- * @param provider the provider name
- *
- * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION
- * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED
- * allowed} for your app.
- * @throws IllegalArgumentException if no provider with the given name exists
- *
- * @deprecated This function has always been a no-op, and may be removed in the future.
+ * @deprecated This method has always been a no-op, and may be removed in the future.
*/
@Deprecated
public void clearTestProviderLocation(@NonNull String provider) {}
/**
- * Sets a mock enabled value for the given provider. This value will be used in place
- * of any actual value from the provider.
+ * Sets the given test provider to be enabled or disabled.
*
* @param provider the provider name
* @param enabled the mock enabled value
@@ -1363,9 +1394,11 @@
* @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION
* mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED
* allowed} for your app.
- * @throws IllegalArgumentException if no provider with the given name exists
+ * @throws IllegalArgumentException if provider is null or not a test provider
*/
public void setTestProviderEnabled(@NonNull String provider, boolean enabled) {
+ Preconditions.checkArgument(provider != null, "invalid null provider");
+
try {
mService.setTestProviderEnabled(provider, enabled, mContext.getOpPackageName());
} catch (RemoteException e) {
@@ -1374,14 +1407,8 @@
}
/**
- * Removes any mock enabled value associated with the given provider.
- *
- * @param provider the provider name
- *
- * @throws SecurityException if {@link android.app.AppOpsManager#OPSTR_MOCK_LOCATION
- * mock location app op} is not set to {@link android.app.AppOpsManager#MODE_ALLOWED
- * allowed} for your app.
- * @throws IllegalArgumentException if no provider with the given name exists
+ * Equivalent to calling {@link #setTestProviderEnabled(String, boolean)} to disable a test
+ * provider.
*
* @deprecated Use {@link #setTestProviderEnabled(String, boolean)} instead.
*/
@@ -1480,7 +1507,8 @@
Geofence fence = Geofence.createCircle(latitude, longitude, radius);
LocationRequest request = new LocationRequest().setExpireIn(expiration);
try {
- mService.requestGeofence(request, fence, intent, mContext.getPackageName());
+ mService.requestGeofence(request, fence, intent, mContext.getPackageName(),
+ getListenerIdentifier(intent));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1557,7 +1585,8 @@
Preconditions.checkArgument(fence != null, "invalid null geofence");
try {
- mService.requestGeofence(request, fence, intent, mContext.getPackageName());
+ mService.requestGeofence(request, fence, intent, mContext.getPackageName(),
+ getListenerIdentifier(intent));
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1771,6 +1800,7 @@
* @param handler a handler with a looper that the callback runs on
* @return true if the listener was successfully added
*
+ * @throws IllegalArgumentException if callback is null
* @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
@@ -1790,10 +1820,12 @@
/**
* Registers a GNSS status callback.
*
- * @param callback GNSS status callback object to register
* @param executor the executor that the callback runs on
+ * @param callback GNSS status callback object to register
* @return true if the listener was successfully added
*
+ * @throws IllegalArgumentException if executor is null
+ * @throws IllegalArgumentException if callback is null
* @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
@@ -1862,6 +1894,8 @@
* @param listener a {@link OnNmeaMessageListener} object to register
* @param handler a handler with the looper that the listener runs on.
* @return true if the listener was successfully added
+ *
+ * @throws IllegalArgumentException if listener is null
* @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
@@ -1883,6 +1917,9 @@
* @param listener a {@link OnNmeaMessageListener} object to register
* @param executor the {@link Executor} that the listener runs on.
* @return true if the listener was successfully added
+ *
+ * @throws IllegalArgumentException if executor is null
+ * @throws IllegalArgumentException if listener is null
* @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
@@ -1934,7 +1971,7 @@
public void removeGpsMeasurementListener(GpsMeasurementsEvent.Listener listener) {}
/**
- * Registers a GPS Measurement callback.
+ * Registers a GPS Measurement callback which will run on a binder threadS.
*
* @param callback a {@link GnssMeasurementsEvent.Callback} object to register.
* @return {@code true} if the callback was added successfully, {@code false} otherwise.
@@ -1946,7 +1983,7 @@
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean registerGnssMeasurementsCallback(
@NonNull GnssMeasurementsEvent.Callback callback) {
- return registerGnssMeasurementsCallback(callback, null);
+ return registerGnssMeasurementsCallback(Runnable::run, callback);
}
/**
@@ -1955,6 +1992,9 @@
* @param callback a {@link GnssMeasurementsEvent.Callback} object to register.
* @param handler the handler that the callback runs on.
* @return {@code true} if the callback was added successfully, {@code false} otherwise.
+ *
+ * @throws IllegalArgumentException if callback is null
+ * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean registerGnssMeasurementsCallback(
@@ -1975,6 +2015,10 @@
* @param callback a {@link GnssMeasurementsEvent.Callback} object to register.
* @param executor the executor that the callback runs on.
* @return {@code true} if the callback was added successfully, {@code false} otherwise.
+ *
+ * @throws IllegalArgumentException if executor is null
+ * @throws IllegalArgumentException if callback is null
+ * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean registerGnssMeasurementsCallback(
@@ -1992,6 +2036,9 @@
*
* @param measurementCorrections a {@link GnssMeasurementCorrections} object with the GNSS
* measurement corrections to be injected into the GNSS chipset.
+ *
+ * @throws IllegalArgumentException if measurementCorrections is null
+ * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
* @hide
*/
@SystemApi
@@ -2066,6 +2113,9 @@
* @param callback a {@link GnssNavigationMessage.Callback} object to register.
* @param handler the handler that the callback runs on.
* @return {@code true} if the callback was added successfully, {@code false} otherwise.
+ *
+ * @throws IllegalArgumentException if callback is null
+ * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean registerGnssNavigationMessageCallback(
@@ -2087,6 +2137,10 @@
* @param callback a {@link GnssNavigationMessage.Callback} object to register.
* @param executor the looper that the callback runs on.
* @return {@code true} if the callback was added successfully, {@code false} otherwise.
+ *
+ * @throws IllegalArgumentException if executor is null
+ * @throws IllegalArgumentException if callback is null
+ * @throws SecurityException if the ACCESS_FINE_LOCATION permission is not present
*/
@RequiresPermission(ACCESS_FINE_LOCATION)
public boolean registerGnssNavigationMessageCallback(
@@ -2547,7 +2601,7 @@
mListenerTransport = new GnssMeasurementsListener();
return mService.addGnssMeasurementsListener(mListenerTransport,
- mContext.getPackageName());
+ mContext.getPackageName(), "gnss measurement callback");
}
@Override
@@ -2583,7 +2637,7 @@
mListenerTransport = new GnssNavigationMessageListener();
return mService.addGnssNavigationMessageListener(mListenerTransport,
- mContext.getPackageName());
+ mContext.getPackageName(), "gnss navigation callback");
}
@Override
@@ -2618,7 +2672,8 @@
Preconditions.checkState(mListenerTransport == null);
mListenerTransport = new BatchedLocationCallback();
- return mService.addGnssBatchingCallback(mListenerTransport, mContext.getPackageName());
+ return mService.addGnssBatchingCallback(mListenerTransport, mContext.getPackageName(),
+ "batched location callback");
}
@Override
diff --git a/location/java/android/location/LocationProvider.java b/location/java/android/location/LocationProvider.java
index b69a9d7..52a03b6 100644
--- a/location/java/android/location/LocationProvider.java
+++ b/location/java/android/location/LocationProvider.java
@@ -53,28 +53,10 @@
@Deprecated
public static final int AVAILABLE = 2;
- /**
- * A regular expression matching characters that may not appear
- * in the name of a LocationProvider
- * @hide
- */
- public static final String BAD_CHARS_REGEX = "[^a-zA-Z0-9]";
-
private final String mName;
private final ProviderProperties mProperties;
- /**
- * Constructs a LocationProvider with the given name. Provider names must
- * consist only of the characters [a-zA-Z0-9].
- *
- * @throws IllegalArgumentException if name contains an illegal character
- *
- * @hide
- */
- public LocationProvider(String name, ProviderProperties properties) {
- if (name.matches(BAD_CHARS_REGEX)) {
- throw new IllegalArgumentException("provider name contains illegal character: " + name);
- }
+ LocationProvider(String name, ProviderProperties properties) {
mName = name;
mProperties = properties;
}
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
index 5341d07..33ddfa7 100644
--- a/media/java/android/media/ExifInterface.java
+++ b/media/java/android/media/ExifInterface.java
@@ -2776,7 +2776,7 @@
updateImageSizeValues(in, IFD_TYPE_THUMBNAIL);
// Check if each image data is in valid position.
- validateImages(in);
+ validateImages();
if (mMimeType == IMAGE_TYPE_PEF) {
// PEF files contain a MakerNote data, which contains the data for ColorSpace tag.
@@ -3143,8 +3143,11 @@
// 2.1. Integers and byte order
in.setByteOrder(ByteOrder.BIG_ENDIAN);
+ int bytesRead = 0;
+
// Skip the signature bytes
in.seek(PNG_SIGNATURE.length);
+ bytesRead += PNG_SIGNATURE.length;
try {
while (true) {
@@ -3159,12 +3162,14 @@
// See PNG (Portable Network Graphics) Specification, Version 1.2,
// 3.2. Chunk layout
int length = in.readInt();
+ bytesRead += 4;
byte[] type = new byte[PNG_CHUNK_LENGTH_BYTE_LENGTH];
if (in.read(type) != type.length) {
throw new IOException("Encountered invalid length while parsing PNG chunk"
+ "type");
}
+ bytesRead += PNG_CHUNK_LENGTH_BYTE_LENGTH;
if (Arrays.equals(type, PNG_CHUNK_TYPE_IEND)) {
// IEND marks the end of the image.
@@ -3177,12 +3182,17 @@
+ "type: " + byteArrayToHexString(type));
}
readExifSegment(data, IFD_TYPE_PRIMARY);
+
+ validateImages();
break;
} else {
// Skip to next chunk
in.skipBytes(length + PNG_CHUNK_CRC_BYTE_LENGTH);
+ bytesRead += length + PNG_CHUNK_CRC_BYTE_LENGTH;
}
}
+ // Save offset values for handleThumbnailFromJfif() function
+ mExifOffset = bytesRead;
} catch (EOFException e) {
// Should not reach here. Will only reach here if the file is corrupted or
// does not follow the PNG specifications
@@ -3675,7 +3685,7 @@
int thumbnailLength = jpegInterchangeFormatLengthAttribute.getIntValue(mExifByteOrder);
if (mMimeType == IMAGE_TYPE_JPEG || mMimeType == IMAGE_TYPE_RAF
- || mMimeType == IMAGE_TYPE_RW2) {
+ || mMimeType == IMAGE_TYPE_RW2 || mMimeType == IMAGE_TYPE_PNG) {
thumbnailOffset += mExifOffset;
} else if (mMimeType == IMAGE_TYPE_ORF) {
// Update offset value since RAF files have IFD data preceding MakerNote data.
@@ -3819,12 +3829,13 @@
}
// Validate primary, preview, thumbnail image data by comparing image size
- private void validateImages(InputStream in) throws IOException {
+ private void validateImages() throws IOException {
// Swap images based on size (primary > preview > thumbnail)
swapBasedOnImageSize(IFD_TYPE_PRIMARY, IFD_TYPE_PREVIEW);
swapBasedOnImageSize(IFD_TYPE_PRIMARY, IFD_TYPE_THUMBNAIL);
swapBasedOnImageSize(IFD_TYPE_PREVIEW, IFD_TYPE_THUMBNAIL);
+ // TODO (b/142296453): Revise image width/height setting logic
// Check if image has PixelXDimension/PixelYDimension tags, which contain valid image
// sizes, excluding padding at the right end or bottom end of the image to make sure that
// the values are multiples of 64. See JEITA CP-3451C Table 5 and Section 4.8.1. B.
diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl
index f132cef..66764c7 100644
--- a/media/java/android/media/IMediaRoute2Provider.aidl
+++ b/media/java/android/media/IMediaRoute2Provider.aidl
@@ -27,4 +27,6 @@
void selectRoute(String packageName, String id);
void unselectRoute(String packageName, String id);
void notifyControlRequestSent(String id, in Intent request);
+ void requestSetVolume(String id, int volume);
+ void requestUpdateVolume(String id, int delta);
}
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 81213b9..7b7a34e 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -46,6 +46,8 @@
void registerClient2(IMediaRouter2Client client, String packageName);
void unregisterClient2(IMediaRouter2Client client);
void sendControlRequest(IMediaRouter2Client client, in MediaRoute2Info route, in Intent request);
+ void requestSetVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int volume);
+ void requestUpdateVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int direction);
/**
* Changes the selected route of the client.
*
@@ -66,4 +68,9 @@
*/
void selectClientRoute2(IMediaRouter2Manager manager, String packageName,
in @nullable MediaRoute2Info route);
+
+ void requestSetVolume2Manager(IMediaRouter2Manager manager,
+ in MediaRoute2Info route, int volume);
+ void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
+ in MediaRoute2Info route, int direction);
}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 510ee44..b7a9ffe 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -649,7 +649,7 @@
are not consumed by the Surface in a timely manner). Or it may be configured to not drop excessive
frames. In the latter mode if the Surface is not consuming output frames fast enough, it will
eventually block the decoder. Prior to {@link android.os.Build.VERSION_CODES#Q} the exact behavior
- was undefined, with the exception that View surfaces (SuerfaceView or TextureView) always dropped
+ was undefined, with the exception that View surfaces (SurfaceView or TextureView) always dropped
excessive frames. Since {@link android.os.Build.VERSION_CODES#Q} the default behavior is to drop
excessive frames. Applications can opt out of this behavior for non-View surfaces (such as
ImageReader or SurfaceTexture) by targeting SDK {@link android.os.Build.VERSION_CODES#Q} and
@@ -3513,6 +3513,19 @@
public static final String PARAMETER_KEY_HDR10_PLUS_INFO = MediaFormat.KEY_HDR10_PLUS_INFO;
/**
+ * Enable/disable low latency decoding mode.
+ * When enabled, the decoder doesn't hold input and output data more than
+ * required by the codec standards.
+ * The value is an Integer object containing the value 1 to enable
+ * or the value 0 to disable.
+ *
+ * @see #setParameters(Bundle)
+ * @see MediaFormat#KEY_LOW_LATENCY
+ */
+ public static final String PARAMETER_KEY_LOW_LATENCY =
+ MediaFormat.KEY_LOW_LATENCY;
+
+ /**
* Communicate additional parameter changes to the component instance.
* <b>Note:</b> Some of these parameter changes may silently fail to apply.
*
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index f304f7c..91d644b 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.os.Build;
@@ -559,6 +560,14 @@
public static final String FEATURE_IntraRefresh = "intra-refresh";
/**
+ * <b>decoder only</b>: codec supports low latency decoding.
+ * If supported, clients can enable the low latency mode for the decoder.
+ * When the mode is enabled, the decoder doesn't hold input and output data more than
+ * required by the codec standards.
+ */
+ public static final String FEATURE_LowLatency = "low-latency";
+
+ /**
* Query codec feature capabilities.
* <p>
* These features are supported to be used by the codec. These
@@ -587,6 +596,7 @@
new Feature(FEATURE_FrameParsing, (1 << 4), false),
new Feature(FEATURE_MultipleFrames, (1 << 5), false),
new Feature(FEATURE_DynamicTimestamp, (1 << 6), false),
+ new Feature(FEATURE_LowLatency, (1 << 7), true),
};
private static final Feature[] encoderFeatures = {
@@ -3741,8 +3751,11 @@
public static final int DolbyVisionProfileDvheStn = 0x20;
public static final int DolbyVisionProfileDvheDth = 0x40;
public static final int DolbyVisionProfileDvheDtb = 0x80;
- public static final int DolbyVisionProfileDvheSt = 0x100;
- public static final int DolbyVisionProfileDvavSe = 0x200;
+ public static final int DolbyVisionProfileDvheSt = 0x100;
+ public static final int DolbyVisionProfileDvavSe = 0x200;
+ /** Dolby Vision AV1 profile */
+ @SuppressLint("AllUpper")
+ public static final int DolbyVisionProfileDvav110 = 0x400;
public static final int DolbyVisionLevelHd24 = 0x1;
public static final int DolbyVisionLevelHd30 = 0x2;
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 8b667f7..8080f45 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -211,6 +211,15 @@
public static final String KEY_MIME = "mime";
/**
+ * An optional key describing the low latency decoding mode. This is an optional parameter
+ * that applies only to decoders. If enabled, the decoder doesn't hold input and output
+ * data more than required by the codec standards.
+ * The associated value is an integer (0 or 1): 1 when low-latency decoding is enabled,
+ * 0 otherwise. The default value is 0.
+ */
+ public static final String KEY_LOW_LATENCY = "low-latency";
+
+ /**
* A key describing the language of the content, using either ISO 639-1
* or 639-2/T codes. The associated value is a string.
*/
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 0346010..7ed431d 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -227,6 +227,44 @@
public native String extractMetadata(int keyCode);
/**
+ * This method is similar to {@link #getFrameAtTime(long, int, BitmapParams)}
+ * except that the device will choose the actual {@link Bitmap.Config} to use.
+ *
+ * @param timeUs The time position where the frame will be retrieved.
+ * When retrieving the frame at the given time position, there is no
+ * guarantee that the data source has a frame located at the position.
+ * When this happens, a frame nearby will be returned. If timeUs is
+ * negative, time position and option will ignored, and any frame
+ * that the implementation considers as representative may be returned.
+ *
+ * @param option a hint on how the frame is found. Use
+ * {@link #OPTION_PREVIOUS_SYNC} if one wants to retrieve a sync frame
+ * that has a timestamp earlier than or the same as timeUs. Use
+ * {@link #OPTION_NEXT_SYNC} if one wants to retrieve a sync frame
+ * that has a timestamp later than or the same as timeUs. Use
+ * {@link #OPTION_CLOSEST_SYNC} if one wants to retrieve a sync frame
+ * that has a timestamp closest to or the same as timeUs. Use
+ * {@link #OPTION_CLOSEST} if one wants to retrieve a frame that may
+ * or may not be a sync frame but is closest to or the same as timeUs.
+ * {@link #OPTION_CLOSEST} often has larger performance overhead compared
+ * to the other options if there is no sync frame located at timeUs.
+ *
+ * @return A Bitmap containing a representative video frame, which can be null,
+ * if such a frame cannot be retrieved. {@link Bitmap#getConfig()} can
+ * be used to query the actual {@link Bitmap.Config}.
+ *
+ * @see {@link #getFrameAtTime(long, int, BitmapParams)}
+ */
+ public Bitmap getFrameAtTime(long timeUs, @Option int option) {
+ if (option < OPTION_PREVIOUS_SYNC ||
+ option > OPTION_CLOSEST) {
+ throw new IllegalArgumentException("Unsupported option: " + option);
+ }
+
+ return _getFrameAtTime(timeUs, option, -1 /*dst_width*/, -1 /*dst_height*/, null);
+ }
+
+ /**
* Call this method after setDataSource(). This method finds a
* representative frame close to the given time position by considering
* the given option if possible, and returns it as a bitmap.
@@ -255,16 +293,60 @@
* {@link #OPTION_CLOSEST} often has larger performance overhead compared
* to the other options if there is no sync frame located at timeUs.
*
+ * @param params BitmapParams that controls the returned bitmap config
+ * (such as pixel formats).
+ *
* @return A Bitmap containing a representative video frame, which
* can be null, if such a frame cannot be retrieved.
+ *
+ * @see {@link #getFrameAtTime(long, int)}
*/
- public Bitmap getFrameAtTime(long timeUs, @Option int option) {
+ public Bitmap getFrameAtTime(
+ long timeUs, @Option int option, @NonNull BitmapParams params) {
if (option < OPTION_PREVIOUS_SYNC ||
option > OPTION_CLOSEST) {
throw new IllegalArgumentException("Unsupported option: " + option);
}
- return _getFrameAtTime(timeUs, option, -1 /*dst_width*/, -1 /*dst_height*/);
+ return _getFrameAtTime(timeUs, option, -1 /*dst_width*/, -1 /*dst_height*/, params);
+ }
+
+ /**
+ * This method is similar to {@link #getScaledFrameAtTime(long, int, int, int, BitmapParams)}
+ * except that the device will choose the actual {@link Bitmap.Config} to use.
+ *
+ * @param timeUs The time position in microseconds where the frame will be retrieved.
+ * When retrieving the frame at the given time position, there is no
+ * guarantee that the data source has a frame located at the position.
+ * When this happens, a frame nearby will be returned. If timeUs is
+ * negative, time position and option will ignored, and any frame
+ * that the implementation considers as representative may be returned.
+ *
+ * @param option a hint on how the frame is found. Use
+ * {@link #OPTION_PREVIOUS_SYNC} if one wants to retrieve a sync frame
+ * that has a timestamp earlier than or the same as timeUs. Use
+ * {@link #OPTION_NEXT_SYNC} if one wants to retrieve a sync frame
+ * that has a timestamp later than or the same as timeUs. Use
+ * {@link #OPTION_CLOSEST_SYNC} if one wants to retrieve a sync frame
+ * that has a timestamp closest to or the same as timeUs. Use
+ * {@link #OPTION_CLOSEST} if one wants to retrieve a frame that may
+ * or may not be a sync frame but is closest to or the same as timeUs.
+ * {@link #OPTION_CLOSEST} often has larger performance overhead compared
+ * to the other options if there is no sync frame located at timeUs.
+ *
+ * @param dstWidth expected output bitmap width
+ * @param dstHeight expected output bitmap height
+ * @return A Bitmap containing a representative video frame, which can be null,
+ * if such a frame cannot be retrieved. {@link Bitmap#getConfig()} can
+ * be used to query the actual {@link Bitmap.Config}.
+ * @throws IllegalArgumentException if passed in invalid option or width by height
+ * is less than or equal to 0.
+ * @see {@link #getScaledFrameAtTime(long, int, int, int, BitmapParams)}
+ */
+ public Bitmap getScaledFrameAtTime(
+ long timeUs, @Option int option, int dstWidth, int dstHeight) {
+ validate(option, dstWidth, dstHeight);
+ return _getFrameAtTime(timeUs, option, dstWidth, dstHeight, null);
}
/**
@@ -297,15 +379,23 @@
*
* @param dstWidth expected output bitmap width
* @param dstHeight expected output bitmap height
+ * @param params BitmapParams that controls the returned bitmap config
+ * (such as pixel formats).
+ *
* @return A Bitmap of size not larger than dstWidth by dstHeight containing a
* scaled video frame, which can be null, if such a frame cannot be retrieved.
* @throws IllegalArgumentException if passed in invalid option or width by height
* is less than or equal to 0.
+ * @see {@link #getScaledFrameAtTime(long, int, int, int)}
*/
- public Bitmap getScaledFrameAtTime(
- long timeUs, @Option int option, int dstWidth, int dstHeight) {
- if (option < OPTION_PREVIOUS_SYNC ||
- option > OPTION_CLOSEST) {
+ public Bitmap getScaledFrameAtTime(long timeUs, @Option int option,
+ int dstWidth, int dstHeight, @NonNull BitmapParams params) {
+ validate(option, dstWidth, dstHeight);
+ return _getFrameAtTime(timeUs, option, dstWidth, dstHeight, params);
+ }
+
+ private void validate(@Option int option, int dstWidth, int dstHeight) {
+ if (option < OPTION_PREVIOUS_SYNC || option > OPTION_CLOSEST) {
throw new IllegalArgumentException("Unsupported option: " + option);
}
if (dstWidth <= 0) {
@@ -314,8 +404,6 @@
if (dstHeight <= 0) {
throw new IllegalArgumentException("Invalid height: " + dstHeight);
}
-
- return _getFrameAtTime(timeUs, option, dstWidth, dstHeight);
}
/**
@@ -365,10 +453,12 @@
* @see #getFrameAtTime(long, int)
*/
public Bitmap getFrameAtTime() {
- return _getFrameAtTime(-1, OPTION_CLOSEST_SYNC, -1 /*dst_width*/, -1 /*dst_height*/);
+ return _getFrameAtTime(
+ -1, OPTION_CLOSEST_SYNC, -1 /*dst_width*/, -1 /*dst_height*/, null);
}
- private native Bitmap _getFrameAtTime(long timeUs, int option, int width, int height);
+ private native Bitmap _getFrameAtTime(
+ long timeUs, int option, int width, int height, @Nullable BitmapParams params);
public static final class BitmapParams {
private Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;
@@ -938,8 +1028,6 @@
* @see MediaFormat#COLOR_STANDARD_BT601_PAL
* @see MediaFormat#COLOR_STANDARD_BT601_NTSC
* @see MediaFormat#COLOR_STANDARD_BT2020
- *
- * @hide
*/
public static final int METADATA_KEY_COLOR_STANDARD = 35;
@@ -950,8 +1038,6 @@
* @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO
* @see MediaFormat#COLOR_TRANSFER_ST2084
* @see MediaFormat#COLOR_TRANSFER_HLG
- *
- * @hide
*/
public static final int METADATA_KEY_COLOR_TRANSFER = 36;
@@ -960,8 +1046,6 @@
*
* @see MediaFormat#COLOR_RANGE_LIMITED
* @see MediaFormat#COLOR_RANGE_FULL
- *
- * @hide
*/
public static final int METADATA_KEY_COLOR_RANGE = 37;
// Add more here...
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 5dcbb05..abd774d 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -46,6 +46,21 @@
}
};
+ /**
+ * Playback information indicating the playback volume is fixed, i.e. it cannot be
+ * controlled from this object. An example of fixed playback volume is a remote player,
+ * playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
+ * than attenuate at the source.
+ * @see #getVolumeHandling()
+ */
+ public static final int PLAYBACK_VOLUME_FIXED = 0;
+ /**
+ * Playback information indicating the playback volume is variable and can be controlled
+ * from this object.
+ * @see #getVolumeHandling()
+ */
+ public static final int PLAYBACK_VOLUME_VARIABLE = 1;
+
@NonNull
final String mId;
@Nullable
@@ -58,6 +73,9 @@
final String mClientPackageName;
@NonNull
final List<String> mSupportedCategories;
+ final int mVolume;
+ final int mVolumeMax;
+ final int mVolumeHandling;
@Nullable
final Bundle mExtras;
@@ -68,6 +86,9 @@
mDescription = builder.mDescription;
mClientPackageName = builder.mClientPackageName;
mSupportedCategories = builder.mSupportedCategories;
+ mVolume = builder.mVolume;
+ mVolumeMax = builder.mVolumeMax;
+ mVolumeHandling = builder.mVolumeHandling;
mExtras = builder.mExtras;
}
@@ -78,6 +99,9 @@
mDescription = in.readString();
mClientPackageName = in.readString();
mSupportedCategories = in.createStringArrayList();
+ mVolume = in.readInt();
+ mVolumeMax = in.readInt();
+ mVolumeHandling = in.readInt();
mExtras = in.readBundle();
}
@@ -111,6 +135,9 @@
&& Objects.equals(mDescription, other.mDescription)
&& Objects.equals(mClientPackageName, other.mClientPackageName)
&& Objects.equals(mSupportedCategories, other.mSupportedCategories)
+ && (mVolume == other.mVolume)
+ && (mVolumeMax == other.mVolumeMax)
+ && (mVolumeHandling == other.mVolumeHandling)
//TODO: This will be evaluated as false in most cases. Try not to.
&& Objects.equals(mExtras, other.mExtras);
}
@@ -162,6 +189,29 @@
return mSupportedCategories;
}
+ /**
+ * Gets the current volume of the route. This may be invalid if the route is not selected.
+ */
+ public int getVolume() {
+ return mVolume;
+ }
+
+ /**
+ * Gets the maximum volume of the route.
+ */
+ public int getVolumeMax() {
+ return mVolumeMax;
+ }
+
+ /**
+ * Gets information about how volume is handled on the route.
+ *
+ * @return {@link #PLAYBACK_VOLUME_FIXED} or {@link #PLAYBACK_VOLUME_VARIABLE}
+ */
+ public int getVolumeHandling() {
+ return mVolumeHandling;
+ }
+
@Nullable
public Bundle getExtras() {
return mExtras;
@@ -199,6 +249,9 @@
dest.writeString(mDescription);
dest.writeString(mClientPackageName);
dest.writeStringList(mSupportedCategories);
+ dest.writeInt(mVolume);
+ dest.writeInt(mVolumeMax);
+ dest.writeInt(mVolumeHandling);
dest.writeBundle(mExtras);
}
@@ -209,6 +262,9 @@
.append("id=").append(getId())
.append(", name=").append(getName())
.append(", description=").append(getDescription())
+ .append(", volume=").append(getVolume())
+ .append(", volumeMax=").append(getVolumeMax())
+ .append(", volumeHandling=").append(getVolumeHandling())
.append(", providerId=").append(getProviderId())
.append(" }");
return result.toString();
@@ -224,6 +280,9 @@
String mDescription;
String mClientPackageName;
List<String> mSupportedCategories;
+ int mVolume;
+ int mVolumeMax;
+ int mVolumeHandling;
Bundle mExtras;
public Builder(@NonNull String id, @NonNull String name) {
@@ -251,6 +310,9 @@
mDescription = routeInfo.mDescription;
setClientPackageName(routeInfo.mClientPackageName);
setSupportedCategories(routeInfo.mSupportedCategories);
+ setVolume(routeInfo.mVolume);
+ setVolumeMax(routeInfo.mVolumeMax);
+ setVolumeHandling(routeInfo.mVolumeHandling);
if (routeInfo.mExtras != null) {
mExtras = new Bundle(routeInfo.mExtras);
}
@@ -345,6 +407,32 @@
}
/**
+ * Sets the route's current volume, or 0 if unknown.
+ */
+ @NonNull
+ public Builder setVolume(int volume) {
+ mVolume = volume;
+ return this;
+ }
+
+ /**
+ * Sets the route's maximum volume, or 0 if unknown.
+ */
+ @NonNull
+ public Builder setVolumeMax(int volumeMax) {
+ mVolumeMax = volumeMax;
+ return this;
+ }
+
+ /**
+ * Sets the route's volume handling.
+ */
+ @NonNull
+ public Builder setVolumeHandling(int volumeHandling) {
+ mVolumeHandling = volumeHandling;
+ return this;
+ }
+ /**
* Sets a bundle of extras for the route.
*/
@NonNull
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index e8e0f82..58deff2 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -81,6 +81,20 @@
public abstract void onControlRequest(String routeId, Intent request);
/**
+ * Called when requestSetVolume is called on a route of the provider
+ * @param routeId the id of the route
+ * @param volume the target volume
+ */
+ public abstract void onSetVolume(String routeId, int volume);
+
+ /**
+ * Called when requestUpdateVolume is called on a route of the provider
+ * @param routeId id of the route
+ * @param delta the delta to add to the current volume
+ */
+ public abstract void onUpdateVolume(String routeId, int delta);
+
+ /**
* Updates provider info and publishes routes
*/
public final void setProviderInfo(MediaRoute2ProviderInfo info) {
@@ -130,5 +144,17 @@
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onControlRequest,
MediaRoute2ProviderService.this, id, request));
}
+
+ @Override
+ public void requestSetVolume(String id, int volume) {
+ mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSetVolume,
+ MediaRoute2ProviderService.this, id, volume));
+ }
+
+ @Override
+ public void requestUpdateVolume(String id, int delta) {
+ mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onUpdateVolume,
+ MediaRoute2ProviderService.this, id, delta));
+ }
}
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index ed35ef6..aca40d8 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -265,6 +265,54 @@
}
}
+ /**
+ * Requests a volume change for the route asynchronously.
+ * <p>
+ * It may have no effect if the route is currently not selected.
+ * </p>
+ *
+ * @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}.
+ */
+ public void requestSetVolume(@NonNull MediaRoute2Info route, int volume) {
+ Objects.requireNonNull(route, "route must not be null");
+
+ Client client;
+ synchronized (sLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ mMediaRouterService.requestSetVolume2(client, route, volume);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to send control request.", ex);
+ }
+ }
+ }
+
+ /**
+ * Requests an incremental volume update for the route asynchronously.
+ * <p>
+ * It may have no effect if the route is currently not selected.
+ * </p>
+ *
+ * @param delta The delta to add to the current volume.
+ */
+ public void requestUpdateVolume(@NonNull MediaRoute2Info route, int delta) {
+ Objects.requireNonNull(route, "route must not be null");
+
+ Client client;
+ synchronized (sLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ mMediaRouterService.requestUpdateVolume2(client, route, delta);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to send control request.", ex);
+ }
+ }
+ }
+
@GuardedBy("mCallbackRecords")
private int findCallbackRecordIndexLocked(Callback callback) {
final int count = mCallbackRecords.size();
@@ -310,6 +358,7 @@
List<MediaRoute2Info> outRoutes) {
if (provider == null || !provider.isValid()) {
Log.w(TAG, "Ignoring invalid provider : " + provider);
+ return;
}
final Collection<MediaRoute2Info> routes = provider.getRoutes();
@@ -321,10 +370,21 @@
if (!route.supportsControlCategory(controlCategories)) {
continue;
}
+ MediaRoute2Info preRoute = findRouteById(route.getId());
+ if (!route.equals(preRoute)) {
+ notifyRouteChanged(route);
+ }
outRoutes.add(route);
}
}
+ MediaRoute2Info findRouteById(String id) {
+ for (MediaRoute2Info route : mRoutes) {
+ if (route.getId().equals(id)) return route;
+ }
+ return null;
+ }
+
void notifyRouteListChanged(List<MediaRoute2Info> routes) {
for (CallbackRecord record: mCallbackRecords) {
record.mExecutor.execute(
@@ -332,10 +392,18 @@
}
}
+ void notifyRouteChanged(MediaRoute2Info route) {
+ for (CallbackRecord record: mCallbackRecords) {
+ record.mExecutor.execute(
+ () -> record.mCallback.onRouteChanged(route));
+ }
+ }
+
/**
* Interface for receiving events about media routing changes.
*/
public static class Callback {
+ //TODO: clean up these callbacks
/**
* Called when a route is added.
*/
@@ -369,7 +437,7 @@
void notifyRoutes() {
final List<MediaRoute2Info> routes = mRoutes;
// notify only when bound to media router service.
- //TODO: Correct the condition when control category, default rotue, .. are finalized.
+ //TODO: Correct the condition when control category, default route, .. are finalized.
if (routes.size() > 0) {
mExecutor.execute(() -> mCallback.onRoutesChanged(routes));
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 0b64569..4f2a295 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -234,6 +234,54 @@
}
}
+ /**
+ * Requests a volume change for the route asynchronously.
+ * <p>
+ * It may have no effect if the route is currently not selected.
+ * </p>
+ *
+ * @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}.
+ */
+ public void requestSetVolume(@NonNull MediaRoute2Info route, int volume) {
+ Objects.requireNonNull(route, "route must not be null");
+
+ Client client;
+ synchronized (sLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ mMediaRouterService.requestSetVolume2Manager(client, route, volume);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to send control request.", ex);
+ }
+ }
+ }
+
+ /**
+ * Requests an incremental volume update for the route asynchronously.
+ * <p>
+ * It may have no effect if the route is currently not selected.
+ * </p>
+ *
+ * @param delta The delta to add to the current volume.
+ */
+ public void requestUpdateVolume(@NonNull MediaRoute2Info route, int delta) {
+ Objects.requireNonNull(route, "route must not be null");
+
+ Client client;
+ synchronized (sLock) {
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ mMediaRouterService.requestUpdateVolume2Manager(client, route, delta);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to send control request.", ex);
+ }
+ }
+ }
+
int findProviderIndex(MediaRoute2ProviderInfo provider) {
final int count = mProviders.size();
for (int i = 0; i < count; i++) {
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 771628c..ca96c9a 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -17,106 +17,14 @@
package android.media;
import android.annotation.UnsupportedAppUsage;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.drm.DrmManagerClient;
-import android.graphics.BitmapFactory;
-import android.mtp.MtpConstants;
import android.net.Uri;
import android.os.Build;
-import android.os.Environment;
import android.os.RemoteException;
-import android.os.SystemProperties;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Audio;
-import android.provider.MediaStore.Audio.Playlists;
-import android.provider.MediaStore.Files;
-import android.provider.MediaStore.Files.FileColumns;
-import android.provider.MediaStore.Images;
-import android.provider.MediaStore.Video;
-import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
-import android.sax.Element;
-import android.sax.ElementListener;
-import android.sax.RootElement;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Xml;
-
-import dalvik.system.CloseGuard;
-
-import org.xml.sax.Attributes;
-import org.xml.sax.ContentHandler;
-import org.xml.sax.SAXException;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Locale;
-import java.util.TimeZone;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
- * Internal service helper that no-one should use directly.
- *
- * The way the scan currently works is:
- * - The Java MediaScannerService creates a MediaScanner (this class), and calls
- * MediaScanner.scanDirectories on it.
- * - scanDirectories() calls the native processDirectory() for each of the specified directories.
- * - the processDirectory() JNI method wraps the provided mediascanner client in a native
- * 'MyMediaScannerClient' class, then calls processDirectory() on the native MediaScanner
- * object (which got created when the Java MediaScanner was created).
- * - native MediaScanner.processDirectory() calls
- * doProcessDirectory(), which recurses over the folder, and calls
- * native MyMediaScannerClient.scanFile() for every file whose extension matches.
- * - native MyMediaScannerClient.scanFile() calls back on Java MediaScannerClient.scanFile,
- * which calls doScanFile, which after some setup calls back down to native code, calling
- * MediaScanner.processFile().
- * - MediaScanner.processFile() calls one of several methods, depending on the type of the
- * file: parseMP3, parseMP4, parseMidi, parseOgg or parseWMA.
- * - each of these methods gets metadata key/value pairs from the file, and repeatedly
- * calls native MyMediaScannerClient.handleStringTag, which calls back up to its Java
- * counterparts in this file.
- * - Java handleStringTag() gathers the key/value pairs that it's interested in.
- * - once processFile returns and we're back in Java code in doScanFile(), it calls
- * Java MyMediaScannerClient.endFile(), which takes all the data that's been
- * gathered and inserts an entry in to the database.
- *
- * In summary:
- * Java MediaScannerService calls
- * Java MediaScanner scanDirectories, which calls
- * Java MediaScanner processDirectory (native method), which calls
- * native MediaScanner processDirectory, which calls
- * native MyMediaScannerClient scanFile, which calls
- * Java MyMediaScannerClient scanFile, which calls
- * Java MediaScannerClient doScanFile, which calls
- * Java MediaScanner processFile (native method), which calls
- * native MediaScanner processFile, which calls
- * native parseMP3, parseMP4, parseMidi, parseOgg or parseWMA, which calls
- * native MyMediaScanner handleStringTag, which calls
- * Java MyMediaScanner handleStringTag.
- * Once MediaScanner processFile returns, an entry is inserted in to the database.
- *
- * The MediaScanner class is not thread-safe, so it should only be used in a single threaded manner.
- *
- * {@hide}
- *
+ * @hide
* @deprecated this media scanner has served faithfully for many years, but it's
* become tedious to test and maintain, mainly due to the way it
* weaves obscurely between managed and native code. It's been
@@ -125,1876 +33,182 @@
*/
@Deprecated
public class MediaScanner implements AutoCloseable {
- static {
- System.loadLibrary("media_jni");
- native_init();
- }
-
- private final static String TAG = "MediaScanner";
-
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private static final String[] FILES_PRESCAN_PROJECTION = new String[] {
- Files.FileColumns._ID, // 0
- Files.FileColumns.DATA, // 1
- Files.FileColumns.FORMAT, // 2
- Files.FileColumns.DATE_MODIFIED, // 3
- Files.FileColumns.MEDIA_TYPE, // 4
};
- private static final String[] ID_PROJECTION = new String[] {
- Files.FileColumns._ID,
- };
-
- private static final int FILES_PRESCAN_ID_COLUMN_INDEX = 0;
- private static final int FILES_PRESCAN_PATH_COLUMN_INDEX = 1;
- private static final int FILES_PRESCAN_FORMAT_COLUMN_INDEX = 2;
- private static final int FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX = 3;
- private static final int FILES_PRESCAN_MEDIA_TYPE_COLUMN_INDEX = 4;
-
- private static final String[] PLAYLIST_MEMBERS_PROJECTION = new String[] {
- Audio.Playlists.Members.PLAYLIST_ID, // 0
- };
-
- private static final int ID_PLAYLISTS_COLUMN_INDEX = 0;
- private static final int PATH_PLAYLISTS_COLUMN_INDEX = 1;
- private static final int DATE_MODIFIED_PLAYLISTS_COLUMN_INDEX = 2;
-
- private static final String RINGTONES_DIR = "/ringtones/";
- private static final String NOTIFICATIONS_DIR = "/notifications/";
- private static final String ALARMS_DIR = "/alarms/";
- private static final String MUSIC_DIR = "/music/";
- private static final String PODCASTS_DIR = "/podcasts/";
- private static final String AUDIOBOOKS_DIR = "/audiobooks/";
-
- public static final String SCANNED_BUILD_PREFS_NAME = "MediaScanBuild";
- public static final String LAST_INTERNAL_SCAN_FINGERPRINT = "lastScanFingerprint";
- private static final String SYSTEM_SOUNDS_DIR = Environment.getRootDirectory() + "/media/audio";
- private static final String OEM_SOUNDS_DIR = Environment.getOemDirectory() + "/media/audio";
- private static final String PRODUCT_SOUNDS_DIR = Environment.getProductDirectory() + "/media/audio";
- private static String sLastInternalScanFingerprint;
-
- private static final String[] ID3_GENRES = {
- // ID3v1 Genres
- "Blues",
- "Classic Rock",
- "Country",
- "Dance",
- "Disco",
- "Funk",
- "Grunge",
- "Hip-Hop",
- "Jazz",
- "Metal",
- "New Age",
- "Oldies",
- "Other",
- "Pop",
- "R&B",
- "Rap",
- "Reggae",
- "Rock",
- "Techno",
- "Industrial",
- "Alternative",
- "Ska",
- "Death Metal",
- "Pranks",
- "Soundtrack",
- "Euro-Techno",
- "Ambient",
- "Trip-Hop",
- "Vocal",
- "Jazz+Funk",
- "Fusion",
- "Trance",
- "Classical",
- "Instrumental",
- "Acid",
- "House",
- "Game",
- "Sound Clip",
- "Gospel",
- "Noise",
- "AlternRock",
- "Bass",
- "Soul",
- "Punk",
- "Space",
- "Meditative",
- "Instrumental Pop",
- "Instrumental Rock",
- "Ethnic",
- "Gothic",
- "Darkwave",
- "Techno-Industrial",
- "Electronic",
- "Pop-Folk",
- "Eurodance",
- "Dream",
- "Southern Rock",
- "Comedy",
- "Cult",
- "Gangsta",
- "Top 40",
- "Christian Rap",
- "Pop/Funk",
- "Jungle",
- "Native American",
- "Cabaret",
- "New Wave",
- "Psychadelic",
- "Rave",
- "Showtunes",
- "Trailer",
- "Lo-Fi",
- "Tribal",
- "Acid Punk",
- "Acid Jazz",
- "Polka",
- "Retro",
- "Musical",
- "Rock & Roll",
- "Hard Rock",
- // The following genres are Winamp extensions
- "Folk",
- "Folk-Rock",
- "National Folk",
- "Swing",
- "Fast Fusion",
- "Bebob",
- "Latin",
- "Revival",
- "Celtic",
- "Bluegrass",
- "Avantgarde",
- "Gothic Rock",
- "Progressive Rock",
- "Psychedelic Rock",
- "Symphonic Rock",
- "Slow Rock",
- "Big Band",
- "Chorus",
- "Easy Listening",
- "Acoustic",
- "Humour",
- "Speech",
- "Chanson",
- "Opera",
- "Chamber Music",
- "Sonata",
- "Symphony",
- "Booty Bass",
- "Primus",
- "Porn Groove",
- "Satire",
- "Slow Jam",
- "Club",
- "Tango",
- "Samba",
- "Folklore",
- "Ballad",
- "Power Ballad",
- "Rhythmic Soul",
- "Freestyle",
- "Duet",
- "Punk Rock",
- "Drum Solo",
- "A capella",
- "Euro-House",
- "Dance Hall",
- // The following ones seem to be fairly widely supported as well
- "Goa",
- "Drum & Bass",
- "Club-House",
- "Hardcore",
- "Terror",
- "Indie",
- "Britpop",
- null,
- "Polsk Punk",
- "Beat",
- "Christian Gangsta",
- "Heavy Metal",
- "Black Metal",
- "Crossover",
- "Contemporary Christian",
- "Christian Rock",
- "Merengue",
- "Salsa",
- "Thrash Metal",
- "Anime",
- "JPop",
- "Synthpop",
- // 148 and up don't seem to have been defined yet.
- };
-
- private long mNativeContext;
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private final Context mContext;
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private final String mPackageName;
- private final String mVolumeName;
- private final ContentProviderClient mMediaProvider;
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private final Uri mAudioUri;
- private final Uri mVideoUri;
- private final Uri mImagesUri;
- private final Uri mPlaylistsUri;
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private final Uri mFilesUri;
- private final Uri mFilesFullUri;
- private final boolean mProcessPlaylists;
- private final boolean mProcessGenres;
- private int mMtpObjectHandle;
- private final AtomicBoolean mClosed = new AtomicBoolean();
- private final CloseGuard mCloseGuard = CloseGuard.get();
-
- /** whether to use bulk inserts or individual inserts for each item */
- private static final boolean ENABLE_BULK_INSERTS = true;
-
- // used when scanning the image database so we know whether we have to prune
- // old thumbnail files
- private int mOriginalCount;
- /** Whether the scanner has set a default sound for the ringer ringtone. */
- private boolean mDefaultRingtoneSet;
- /** Whether the scanner has set a default sound for the notification ringtone. */
- private boolean mDefaultNotificationSet;
- /** Whether the scanner has set a default sound for the alarm ringtone. */
- private boolean mDefaultAlarmSet;
- /** The filename for the default sound for the ringer ringtone. */
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private String mDefaultRingtoneFilename;
- /** The filename for the default sound for the notification ringtone. */
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private String mDefaultNotificationFilename;
- /** The filename for the default sound for the alarm ringtone. */
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private String mDefaultAlarmAlertFilename;
- /**
- * The prefix for system properties that define the default sound for
- * ringtones. Concatenate the name of the setting from Settings
- * to get the full system property.
- */
- private static final String DEFAULT_RINGTONE_PROPERTY_PREFIX = "ro.config.";
-
- private final BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options();
private static class FileEntry {
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
long mRowId;
- String mPath;
- long mLastModified;
- int mFormat;
- int mMediaType;
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
boolean mLastModifiedChanged;
- /** @deprecated kept intact for lame apps using reflection */
@Deprecated
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
FileEntry(long rowId, String path, long lastModified, int format) {
- this(rowId, path, lastModified, format, FileColumns.MEDIA_TYPE_NONE);
- }
-
- FileEntry(long rowId, String path, long lastModified, int format, int mediaType) {
- mRowId = rowId;
- mPath = path;
- mLastModified = lastModified;
- mFormat = format;
- mMediaType = mediaType;
- mLastModifiedChanged = false;
- }
-
- @Override
- public String toString() {
- return mPath + " mRowId: " + mRowId;
+ throw new UnsupportedOperationException();
}
}
- private static class PlaylistEntry {
- String path;
- long bestmatchid;
- int bestmatchlevel;
- }
-
- private final ArrayList<PlaylistEntry> mPlaylistEntries = new ArrayList<>();
- private final ArrayList<FileEntry> mPlayLists = new ArrayList<>();
-
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private MediaInserter mMediaInserter;
- private DrmManagerClient mDrmManagerClient = null;
-
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
public MediaScanner(Context c, String volumeName) {
- native_setup();
- mContext = c;
- mPackageName = c.getPackageName();
- mVolumeName = volumeName;
-
- mBitmapOptions.inSampleSize = 1;
- mBitmapOptions.inJustDecodeBounds = true;
-
- setDefaultRingtoneFileNames();
-
- mMediaProvider = mContext.getContentResolver()
- .acquireContentProviderClient(MediaStore.AUTHORITY);
-
- if (sLastInternalScanFingerprint == null) {
- final SharedPreferences scanSettings =
- mContext.getSharedPreferences(SCANNED_BUILD_PREFS_NAME, Context.MODE_PRIVATE);
- sLastInternalScanFingerprint =
- scanSettings.getString(LAST_INTERNAL_SCAN_FINGERPRINT, new String());
- }
-
- mAudioUri = Audio.Media.getContentUri(volumeName);
- mVideoUri = Video.Media.getContentUri(volumeName);
- mImagesUri = Images.Media.getContentUri(volumeName);
- mFilesUri = Files.getContentUri(volumeName);
-
- Uri filesFullUri = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();
- filesFullUri = MediaStore.setIncludePending(filesFullUri);
- filesFullUri = MediaStore.setIncludeTrashed(filesFullUri);
- mFilesFullUri = filesFullUri;
-
- if (!volumeName.equals("internal")) {
- // we only support playlists on external media
- mProcessPlaylists = true;
- mProcessGenres = true;
- mPlaylistsUri = Playlists.getContentUri(volumeName);
- } else {
- mProcessPlaylists = false;
- mProcessGenres = false;
- mPlaylistsUri = null;
- }
-
- final Locale locale = mContext.getResources().getConfiguration().locale;
- if (locale != null) {
- String language = locale.getLanguage();
- String country = locale.getCountry();
- if (language != null) {
- if (country != null) {
- setLocale(language + "_" + country);
- } else {
- setLocale(language);
- }
- }
- }
-
- mCloseGuard.open("close");
+ throw new UnsupportedOperationException();
}
- private void setDefaultRingtoneFileNames() {
- mDefaultRingtoneFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
- + Settings.System.RINGTONE);
- mDefaultNotificationFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
- + Settings.System.NOTIFICATION_SOUND);
- mDefaultAlarmAlertFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
- + Settings.System.ALARM_ALERT);
- }
-
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private final MyMediaScannerClient mClient = new MyMediaScannerClient();
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private boolean isDrmEnabled() {
- String prop = SystemProperties.get("drm.service.enabled");
- return prop != null && prop.equals("true");
+ throw new UnsupportedOperationException();
}
private class MyMediaScannerClient implements MediaScannerClient {
-
- private final SimpleDateFormat mDateFormatter;
-
- private String mArtist;
- private String mAlbumArtist; // use this if mArtist is missing
- private String mAlbum;
- private String mTitle;
- private String mComposer;
- private String mGenre;
- @UnsupportedAppUsage
- private String mMimeType;
- /** @deprecated file types no longer exist */
@Deprecated
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
+ private String mMimeType;
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private int mFileType;
- private int mTrack;
- private int mYear;
- private int mDuration;
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private String mPath;
- private long mDate;
- private long mLastModified;
- private long mFileSize;
- private String mWriter;
- private int mCompilation;
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private boolean mIsDrm;
- @UnsupportedAppUsage
- private boolean mNoMedia; // flag to suppress file from appearing in media tables
- private boolean mScanSuccess;
- private int mWidth;
- private int mHeight;
- private int mColorStandard;
- private int mColorTransfer;
- private int mColorRange;
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
+ private boolean mNoMedia;
public MyMediaScannerClient() {
- mDateFormatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
- mDateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
+ throw new UnsupportedOperationException();
}
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
public FileEntry beginFile(String path, String mimeType, long lastModified,
long fileSize, boolean isDirectory, boolean noMedia) {
- mMimeType = mimeType;
- mFileSize = fileSize;
- mIsDrm = false;
- mScanSuccess = true;
-
- if (!isDirectory) {
- if (!noMedia && isNoMediaFile(path)) {
- noMedia = true;
- }
- mNoMedia = noMedia;
-
- // if mimeType was not specified, compute file type based on file extension.
- if (mMimeType == null) {
- mMimeType = MediaFile.getMimeTypeForFile(path);
- }
-
- if (isDrmEnabled() && MediaFile.isDrmMimeType(mMimeType)) {
- getMimeTypeFromDrm(path);
- }
- }
-
- FileEntry entry = makeEntryFor(path);
- // add some slack to avoid a rounding error
- long delta = (entry != null) ? (lastModified - entry.mLastModified) : 0;
- boolean wasModified = delta > 1 || delta < -1;
- if (entry == null || wasModified) {
- if (wasModified) {
- entry.mLastModified = lastModified;
- } else {
- entry = new FileEntry(0, path, lastModified,
- (isDirectory ? MtpConstants.FORMAT_ASSOCIATION : 0),
- FileColumns.MEDIA_TYPE_NONE);
- }
- entry.mLastModifiedChanged = true;
- }
-
- if (mProcessPlaylists && MediaFile.isPlayListMimeType(mMimeType)) {
- mPlayLists.add(entry);
- // we don't process playlists in the main scan, so return null
- return null;
- }
-
- // clear all the metadata
- mArtist = null;
- mAlbumArtist = null;
- mAlbum = null;
- mTitle = null;
- mComposer = null;
- mGenre = null;
- mTrack = 0;
- mYear = 0;
- mDuration = 0;
- mPath = path;
- mDate = 0;
- mLastModified = lastModified;
- mWriter = null;
- mCompilation = 0;
- mWidth = 0;
- mHeight = 0;
- mColorStandard = -1;
- mColorTransfer = -1;
- mColorRange = -1;
-
- return entry;
+ throw new UnsupportedOperationException();
}
- @Override
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
public void scanFile(String path, long lastModified, long fileSize,
boolean isDirectory, boolean noMedia) {
- // This is the callback funtion from native codes.
- // Log.v(TAG, "scanFile: "+path);
- doScanFile(path, null, lastModified, fileSize, isDirectory, false, noMedia);
+ throw new UnsupportedOperationException();
}
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
public Uri doScanFile(String path, String mimeType, long lastModified,
long fileSize, boolean isDirectory, boolean scanAlways, boolean noMedia) {
- Uri result = null;
-// long t1 = System.currentTimeMillis();
- try {
- FileEntry entry = beginFile(path, mimeType, lastModified,
- fileSize, isDirectory, noMedia);
-
- if (entry == null) {
- return null;
- }
-
- // if this file was just inserted via mtp, set the rowid to zero
- // (even though it already exists in the database), to trigger
- // the correct code path for updating its entry
- if (mMtpObjectHandle != 0) {
- entry.mRowId = 0;
- }
-
- if (entry.mPath != null) {
- if (((!mDefaultNotificationSet &&
- doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename))
- || (!mDefaultRingtoneSet &&
- doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename))
- || (!mDefaultAlarmSet &&
- doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename)))) {
- Log.w(TAG, "forcing rescan of " + entry.mPath +
- "since ringtone setting didn't finish");
- scanAlways = true;
- } else if (isSystemSoundWithMetadata(entry.mPath)
- && !Build.FINGERPRINT.equals(sLastInternalScanFingerprint)) {
- // file is located on the system partition where the date cannot be trusted:
- // rescan if the build fingerprint has changed since the last scan.
- Log.i(TAG, "forcing rescan of " + entry.mPath
- + " since build fingerprint changed");
- scanAlways = true;
- }
- }
-
- // rescan for metadata if file was modified since last scan
- if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
- if (noMedia) {
- result = endFile(entry, false, false, false, false, false, false);
- } else {
- boolean isaudio = MediaFile.isAudioMimeType(mMimeType);
- boolean isvideo = MediaFile.isVideoMimeType(mMimeType);
- boolean isimage = MediaFile.isImageMimeType(mMimeType);
-
- if (isaudio || isvideo || isimage) {
- path = Environment.maybeTranslateEmulatedPathToInternal(new File(path))
- .getAbsolutePath();
- }
-
- // we only extract metadata for audio and video files
- if (isaudio || isvideo) {
- mScanSuccess = processFile(path, mimeType, this);
- }
-
- if (isimage) {
- mScanSuccess = processImageFile(path);
- }
-
- String lowpath = path.toLowerCase(Locale.ROOT);
- boolean ringtones = mScanSuccess && (lowpath.indexOf(RINGTONES_DIR) > 0);
- boolean notifications = mScanSuccess &&
- (lowpath.indexOf(NOTIFICATIONS_DIR) > 0);
- boolean alarms = mScanSuccess && (lowpath.indexOf(ALARMS_DIR) > 0);
- boolean podcasts = mScanSuccess && (lowpath.indexOf(PODCASTS_DIR) > 0);
- boolean audiobooks = mScanSuccess && (lowpath.indexOf(AUDIOBOOKS_DIR) > 0);
- boolean music = mScanSuccess && ((lowpath.indexOf(MUSIC_DIR) > 0) ||
- (!ringtones && !notifications && !alarms && !podcasts && !audiobooks));
-
- result = endFile(entry, ringtones, notifications, alarms, podcasts,
- audiobooks, music);
- }
- }
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
- }
-// long t2 = System.currentTimeMillis();
-// Log.v(TAG, "scanFile: " + path + " took " + (t2-t1));
- return result;
+ throw new UnsupportedOperationException();
}
- private long parseDate(String date) {
- try {
- return mDateFormatter.parse(date).getTime();
- } catch (ParseException e) {
- return 0;
- }
- }
-
- private int parseSubstring(String s, int start, int defaultValue) {
- int length = s.length();
- if (start == length) return defaultValue;
-
- char ch = s.charAt(start++);
- // return defaultValue if we have no integer at all
- if (ch < '0' || ch > '9') return defaultValue;
-
- int result = ch - '0';
- while (start < length) {
- ch = s.charAt(start++);
- if (ch < '0' || ch > '9') return result;
- result = result * 10 + (ch - '0');
- }
-
- return result;
- }
-
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
public void handleStringTag(String name, String value) {
- if (name.equalsIgnoreCase("title") || name.startsWith("title;")) {
- // Don't trim() here, to preserve the special \001 character
- // used to force sorting. The media provider will trim() before
- // inserting the title in to the database.
- mTitle = value;
- } else if (name.equalsIgnoreCase("artist") || name.startsWith("artist;")) {
- mArtist = value.trim();
- } else if (name.equalsIgnoreCase("albumartist") || name.startsWith("albumartist;")
- || name.equalsIgnoreCase("band") || name.startsWith("band;")) {
- mAlbumArtist = value.trim();
- } else if (name.equalsIgnoreCase("album") || name.startsWith("album;")) {
- mAlbum = value.trim();
- } else if (name.equalsIgnoreCase("composer") || name.startsWith("composer;")) {
- mComposer = value.trim();
- } else if (mProcessGenres &&
- (name.equalsIgnoreCase("genre") || name.startsWith("genre;"))) {
- mGenre = getGenreName(value);
- } else if (name.equalsIgnoreCase("year") || name.startsWith("year;")) {
- mYear = parseSubstring(value, 0, 0);
- } else if (name.equalsIgnoreCase("tracknumber") || name.startsWith("tracknumber;")) {
- // track number might be of the form "2/12"
- // we just read the number before the slash
- int num = parseSubstring(value, 0, 0);
- mTrack = (mTrack / 1000) * 1000 + num;
- } else if (name.equalsIgnoreCase("discnumber") ||
- name.equals("set") || name.startsWith("set;")) {
- // set number might be of the form "1/3"
- // we just read the number before the slash
- int num = parseSubstring(value, 0, 0);
- mTrack = (num * 1000) + (mTrack % 1000);
- } else if (name.equalsIgnoreCase("duration")) {
- mDuration = parseSubstring(value, 0, 0);
- } else if (name.equalsIgnoreCase("writer") || name.startsWith("writer;")) {
- mWriter = value.trim();
- } else if (name.equalsIgnoreCase("compilation")) {
- mCompilation = parseSubstring(value, 0, 0);
- } else if (name.equalsIgnoreCase("isdrm")) {
- mIsDrm = (parseSubstring(value, 0, 0) == 1);
- } else if (name.equalsIgnoreCase("date")) {
- mDate = parseDate(value);
- } else if (name.equalsIgnoreCase("width")) {
- mWidth = parseSubstring(value, 0, 0);
- } else if (name.equalsIgnoreCase("height")) {
- mHeight = parseSubstring(value, 0, 0);
- } else if (name.equalsIgnoreCase("colorstandard")) {
- mColorStandard = parseSubstring(value, 0, -1);
- } else if (name.equalsIgnoreCase("colortransfer")) {
- mColorTransfer = parseSubstring(value, 0, -1);
- } else if (name.equalsIgnoreCase("colorrange")) {
- mColorRange = parseSubstring(value, 0, -1);
- } else {
- //Log.v(TAG, "unknown tag: " + name + " (" + mProcessGenres + ")");
- }
+ throw new UnsupportedOperationException();
}
- private boolean convertGenreCode(String input, String expected) {
- String output = getGenreName(input);
- if (output.equals(expected)) {
- return true;
- } else {
- Log.d(TAG, "'" + input + "' -> '" + output + "', expected '" + expected + "'");
- return false;
- }
- }
- private void testGenreNameConverter() {
- convertGenreCode("2", "Country");
- convertGenreCode("(2)", "Country");
- convertGenreCode("(2", "(2");
- convertGenreCode("2 Foo", "Country");
- convertGenreCode("(2) Foo", "Country");
- convertGenreCode("(2 Foo", "(2 Foo");
- convertGenreCode("2Foo", "2Foo");
- convertGenreCode("(2)Foo", "Country");
- convertGenreCode("200 Foo", "Foo");
- convertGenreCode("(200) Foo", "Foo");
- convertGenreCode("200Foo", "200Foo");
- convertGenreCode("(200)Foo", "Foo");
- convertGenreCode("200)Foo", "200)Foo");
- convertGenreCode("200) Foo", "200) Foo");
- }
-
- public String getGenreName(String genreTagValue) {
-
- if (genreTagValue == null) {
- return null;
- }
- final int length = genreTagValue.length();
-
- if (length > 0) {
- boolean parenthesized = false;
- StringBuffer number = new StringBuffer();
- int i = 0;
- for (; i < length; ++i) {
- char c = genreTagValue.charAt(i);
- if (i == 0 && c == '(') {
- parenthesized = true;
- } else if (Character.isDigit(c)) {
- number.append(c);
- } else {
- break;
- }
- }
- char charAfterNumber = i < length ? genreTagValue.charAt(i) : ' ';
- if ((parenthesized && charAfterNumber == ')')
- || !parenthesized && Character.isWhitespace(charAfterNumber)) {
- try {
- short genreIndex = Short.parseShort(number.toString());
- if (genreIndex >= 0) {
- if (genreIndex < ID3_GENRES.length && ID3_GENRES[genreIndex] != null) {
- return ID3_GENRES[genreIndex];
- } else if (genreIndex == 0xFF) {
- return null;
- } else if (genreIndex < 0xFF && (i + 1) < length) {
- // genre is valid but unknown,
- // if there is a string after the value we take it
- if (parenthesized && charAfterNumber == ')') {
- i++;
- }
- String ret = genreTagValue.substring(i).trim();
- if (ret.length() != 0) {
- return ret;
- }
- } else {
- // else return the number, without parentheses
- return number.toString();
- }
- }
- } catch (NumberFormatException e) {
- }
- }
- }
-
- return genreTagValue;
- }
-
- private boolean processImageFile(String path) {
- try {
- mBitmapOptions.outWidth = 0;
- mBitmapOptions.outHeight = 0;
- BitmapFactory.decodeFile(path, mBitmapOptions);
- mWidth = mBitmapOptions.outWidth;
- mHeight = mBitmapOptions.outHeight;
- return mWidth > 0 && mHeight > 0;
- } catch (Throwable th) {
- // ignore;
- }
- return false;
- }
-
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
public void setMimeType(String mimeType) {
- if ("audio/mp4".equals(mMimeType) &&
- mimeType.startsWith("video")) {
- // for feature parity with Donut, we force m4a files to keep the
- // audio/mp4 mimetype, even if they are really "enhanced podcasts"
- // with a video track
- return;
- }
- mMimeType = mimeType;
+ throw new UnsupportedOperationException();
}
- /**
- * Formats the data into a values array suitable for use with the Media
- * Content Provider.
- *
- * @return a map of values
- */
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private ContentValues toValues() {
- ContentValues map = new ContentValues();
-
- map.put(MediaStore.MediaColumns.DATA, mPath);
- map.put(MediaStore.MediaColumns.TITLE, mTitle);
- map.put(MediaStore.MediaColumns.DATE_MODIFIED, mLastModified);
- map.put(MediaStore.MediaColumns.SIZE, mFileSize);
- map.put(MediaStore.MediaColumns.MIME_TYPE, mMimeType);
- map.put(MediaStore.MediaColumns.IS_DRM, mIsDrm);
- map.putNull(MediaStore.MediaColumns.HASH);
-
- String resolution = null;
- if (mWidth > 0 && mHeight > 0) {
- map.put(MediaStore.MediaColumns.WIDTH, mWidth);
- map.put(MediaStore.MediaColumns.HEIGHT, mHeight);
- resolution = mWidth + "x" + mHeight;
- }
-
- if (!mNoMedia) {
- if (MediaFile.isVideoMimeType(mMimeType)) {
- map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0
- ? mArtist : MediaStore.UNKNOWN_STRING));
- map.put(Video.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0
- ? mAlbum : MediaStore.UNKNOWN_STRING));
- map.put(Video.Media.DURATION, mDuration);
- if (resolution != null) {
- map.put(Video.Media.RESOLUTION, resolution);
- }
- if (mColorStandard >= 0) {
- map.put(Video.Media.COLOR_STANDARD, mColorStandard);
- }
- if (mColorTransfer >= 0) {
- map.put(Video.Media.COLOR_TRANSFER, mColorTransfer);
- }
- if (mColorRange >= 0) {
- map.put(Video.Media.COLOR_RANGE, mColorRange);
- }
- if (mDate > 0) {
- map.put(Video.Media.DATE_TAKEN, mDate);
- }
- } else if (MediaFile.isImageMimeType(mMimeType)) {
- // FIXME - add DESCRIPTION
- } else if (MediaFile.isAudioMimeType(mMimeType)) {
- map.put(Audio.Media.ARTIST, (mArtist != null && mArtist.length() > 0) ?
- mArtist : MediaStore.UNKNOWN_STRING);
- map.put(Audio.Media.ALBUM_ARTIST, (mAlbumArtist != null &&
- mAlbumArtist.length() > 0) ? mAlbumArtist : null);
- map.put(Audio.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0) ?
- mAlbum : MediaStore.UNKNOWN_STRING);
- map.put(Audio.Media.COMPOSER, mComposer);
- map.put(Audio.Media.GENRE, mGenre);
- if (mYear != 0) {
- map.put(Audio.Media.YEAR, mYear);
- }
- map.put(Audio.Media.TRACK, mTrack);
- map.put(Audio.Media.DURATION, mDuration);
- map.put(Audio.Media.COMPILATION, mCompilation);
- }
- }
- return map;
+ throw new UnsupportedOperationException();
}
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private Uri endFile(FileEntry entry, boolean ringtones, boolean notifications,
boolean alarms, boolean podcasts, boolean audiobooks, boolean music)
throws RemoteException {
- // update database
-
- // use album artist if artist is missing
- if (mArtist == null || mArtist.length() == 0) {
- mArtist = mAlbumArtist;
- }
-
- ContentValues values = toValues();
- String title = values.getAsString(MediaStore.MediaColumns.TITLE);
- if (title == null || TextUtils.isEmpty(title.trim())) {
- title = MediaFile.getFileTitle(values.getAsString(MediaStore.MediaColumns.DATA));
- values.put(MediaStore.MediaColumns.TITLE, title);
- }
- String album = values.getAsString(Audio.Media.ALBUM);
- if (MediaStore.UNKNOWN_STRING.equals(album)) {
- album = values.getAsString(MediaStore.MediaColumns.DATA);
- // extract last path segment before file name
- int lastSlash = album.lastIndexOf('/');
- if (lastSlash >= 0) {
- int previousSlash = 0;
- while (true) {
- int idx = album.indexOf('/', previousSlash + 1);
- if (idx < 0 || idx >= lastSlash) {
- break;
- }
- previousSlash = idx;
- }
- if (previousSlash != 0) {
- album = album.substring(previousSlash + 1, lastSlash);
- values.put(Audio.Media.ALBUM, album);
- }
- }
- }
- long rowId = entry.mRowId;
- if (MediaFile.isAudioMimeType(mMimeType) && (rowId == 0 || mMtpObjectHandle != 0)) {
- // Only set these for new entries. For existing entries, they
- // may have been modified later, and we want to keep the current
- // values so that custom ringtones still show up in the ringtone
- // picker.
- values.put(Audio.Media.IS_RINGTONE, ringtones);
- values.put(Audio.Media.IS_NOTIFICATION, notifications);
- values.put(Audio.Media.IS_ALARM, alarms);
- values.put(Audio.Media.IS_MUSIC, music);
- values.put(Audio.Media.IS_PODCAST, podcasts);
- values.put(Audio.Media.IS_AUDIOBOOK, audiobooks);
- } else if (MediaFile.isExifMimeType(mMimeType) && !mNoMedia) {
- ExifInterface exif = null;
- try {
- exif = new ExifInterface(entry.mPath);
- } catch (Exception ex) {
- // exif is null
- }
- if (exif != null) {
- long time = exif.getGpsDateTime();
- if (time != -1) {
- values.put(Images.Media.DATE_TAKEN, time);
- } else {
- // If no time zone information is available, we should consider using
- // EXIF local time as taken time if the difference between file time
- // and EXIF local time is not less than 1 Day, otherwise MediaProvider
- // will use file time as taken time.
- time = exif.getDateTime();
- if (time != -1 && Math.abs(mLastModified * 1000 - time) >= 86400000) {
- values.put(Images.Media.DATE_TAKEN, time);
- }
- }
-
- int orientation = exif.getAttributeInt(
- ExifInterface.TAG_ORIENTATION, -1);
- if (orientation != -1) {
- // We only recognize a subset of orientation tag values.
- int degree;
- switch(orientation) {
- case ExifInterface.ORIENTATION_ROTATE_90:
- degree = 90;
- break;
- case ExifInterface.ORIENTATION_ROTATE_180:
- degree = 180;
- break;
- case ExifInterface.ORIENTATION_ROTATE_270:
- degree = 270;
- break;
- default:
- degree = 0;
- break;
- }
- values.put(Images.Media.ORIENTATION, degree);
- }
- }
- }
-
- Uri tableUri = mFilesUri;
- int mediaType = FileColumns.MEDIA_TYPE_NONE;
- MediaInserter inserter = mMediaInserter;
- if (!mNoMedia) {
- if (MediaFile.isVideoMimeType(mMimeType)) {
- tableUri = mVideoUri;
- mediaType = FileColumns.MEDIA_TYPE_VIDEO;
- } else if (MediaFile.isImageMimeType(mMimeType)) {
- tableUri = mImagesUri;
- mediaType = FileColumns.MEDIA_TYPE_IMAGE;
- } else if (MediaFile.isAudioMimeType(mMimeType)) {
- tableUri = mAudioUri;
- mediaType = FileColumns.MEDIA_TYPE_AUDIO;
- } else if (MediaFile.isPlayListMimeType(mMimeType)) {
- tableUri = mPlaylistsUri;
- mediaType = FileColumns.MEDIA_TYPE_PLAYLIST;
- }
- }
- Uri result = null;
- boolean needToSetSettings = false;
- // Setting a flag in order not to use bulk insert for the file related with
- // notifications, ringtones, and alarms, because the rowId of the inserted file is
- // needed.
- if (notifications && !mDefaultNotificationSet) {
- if (TextUtils.isEmpty(mDefaultNotificationFilename) ||
- doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename)) {
- needToSetSettings = true;
- }
- } else if (ringtones && !mDefaultRingtoneSet) {
- if (TextUtils.isEmpty(mDefaultRingtoneFilename) ||
- doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename)) {
- needToSetSettings = true;
- }
- } else if (alarms && !mDefaultAlarmSet) {
- if (TextUtils.isEmpty(mDefaultAlarmAlertFilename) ||
- doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename)) {
- needToSetSettings = true;
- }
- }
-
- if (rowId == 0) {
- if (mMtpObjectHandle != 0) {
- values.put(MediaStore.MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, mMtpObjectHandle);
- }
- if (tableUri == mFilesUri) {
- int format = entry.mFormat;
- if (format == 0) {
- format = MediaFile.getFormatCode(entry.mPath, mMimeType);
- }
- values.put(Files.FileColumns.FORMAT, format);
- }
- // New file, insert it.
- // Directories need to be inserted before the files they contain, so they
- // get priority when bulk inserting.
- // If the rowId of the inserted file is needed, it gets inserted immediately,
- // bypassing the bulk inserter.
- if (inserter == null || needToSetSettings) {
- if (inserter != null) {
- inserter.flushAll();
- }
- result = mMediaProvider.insert(tableUri, values);
- } else if (entry.mFormat == MtpConstants.FORMAT_ASSOCIATION) {
- inserter.insertwithPriority(tableUri, values);
- } else {
- inserter.insert(tableUri, values);
- }
-
- if (result != null) {
- rowId = ContentUris.parseId(result);
- entry.mRowId = rowId;
- }
- } else {
- // updated file
- result = ContentUris.withAppendedId(tableUri, rowId);
- // path should never change, and we want to avoid replacing mixed cased paths
- // with squashed lower case paths
- values.remove(MediaStore.MediaColumns.DATA);
-
- if (!mNoMedia) {
- // Changing media type must be done as separate update
- if (mediaType != entry.mMediaType) {
- final ContentValues mediaTypeValues = new ContentValues();
- mediaTypeValues.put(FileColumns.MEDIA_TYPE, mediaType);
- mMediaProvider.update(ContentUris.withAppendedId(mFilesUri, rowId),
- mediaTypeValues, null, null);
- }
- }
-
- mMediaProvider.update(result, values, null, null);
- }
-
- if(needToSetSettings) {
- if (notifications) {
- setRingtoneIfNotSet(Settings.System.NOTIFICATION_SOUND, tableUri, rowId);
- mDefaultNotificationSet = true;
- } else if (ringtones) {
- setRingtoneIfNotSet(Settings.System.RINGTONE, tableUri, rowId);
- mDefaultRingtoneSet = true;
- } else if (alarms) {
- setRingtoneIfNotSet(Settings.System.ALARM_ALERT, tableUri, rowId);
- mDefaultAlarmSet = true;
- }
- }
-
- return result;
+ throw new UnsupportedOperationException();
}
- private boolean doesPathHaveFilename(String path, String filename) {
- int pathFilenameStart = path.lastIndexOf(File.separatorChar) + 1;
- int filenameLength = filename.length();
- return path.regionMatches(pathFilenameStart, filename, 0, filenameLength) &&
- pathFilenameStart + filenameLength == path.length();
- }
-
- private void setRingtoneIfNotSet(String settingName, Uri uri, long rowId) {
- if (wasRingtoneAlreadySet(settingName)) {
- return;
- }
-
- ContentResolver cr = mContext.getContentResolver();
- String existingSettingValue = Settings.System.getString(cr, settingName);
- if (TextUtils.isEmpty(existingSettingValue)) {
- final Uri settingUri = Settings.System.getUriFor(settingName);
- final Uri ringtoneUri = ContentUris.withAppendedId(uri, rowId);
- RingtoneManager.setActualDefaultRingtoneUri(mContext,
- RingtoneManager.getDefaultType(settingUri), ringtoneUri);
- }
- Settings.System.putInt(cr, settingSetIndicatorName(settingName), 1);
- }
-
- /** @deprecated file types no longer exist */
@Deprecated
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private int getFileTypeFromDrm(String path) {
- return 0;
- }
-
- private void getMimeTypeFromDrm(String path) {
- mMimeType = null;
-
- if (mDrmManagerClient == null) {
- mDrmManagerClient = new DrmManagerClient(mContext);
- }
-
- if (mDrmManagerClient.canHandle(path, null)) {
- mIsDrm = true;
- mMimeType = mDrmManagerClient.getOriginalMimeType(path);
- }
-
- if (mMimeType == null) {
- mMimeType = ContentResolver.MIME_TYPE_DEFAULT;
- }
- }
-
- }; // end of anonymous MediaScannerClient instance
-
- private static boolean isSystemSoundWithMetadata(String path) {
- if (path.startsWith(SYSTEM_SOUNDS_DIR + ALARMS_DIR)
- || path.startsWith(SYSTEM_SOUNDS_DIR + RINGTONES_DIR)
- || path.startsWith(SYSTEM_SOUNDS_DIR + NOTIFICATIONS_DIR)
- || path.startsWith(OEM_SOUNDS_DIR + ALARMS_DIR)
- || path.startsWith(OEM_SOUNDS_DIR + RINGTONES_DIR)
- || path.startsWith(OEM_SOUNDS_DIR + NOTIFICATIONS_DIR)
- || path.startsWith(PRODUCT_SOUNDS_DIR + ALARMS_DIR)
- || path.startsWith(PRODUCT_SOUNDS_DIR + RINGTONES_DIR)
- || path.startsWith(PRODUCT_SOUNDS_DIR + NOTIFICATIONS_DIR)) {
- return true;
- }
- return false;
- }
-
- private String settingSetIndicatorName(String base) {
- return base + "_set";
- }
-
- private boolean wasRingtoneAlreadySet(String name) {
- ContentResolver cr = mContext.getContentResolver();
- String indicatorName = settingSetIndicatorName(name);
- try {
- return Settings.System.getInt(cr, indicatorName) != 0;
- } catch (SettingNotFoundException e) {
- return false;
+ throw new UnsupportedOperationException();
}
}
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private void prescan(String filePath, boolean prescanFiles) throws RemoteException {
- Cursor c = null;
- String where = null;
- String[] selectionArgs = null;
-
- mPlayLists.clear();
-
- if (filePath != null) {
- // query for only one file
- where = MediaStore.Files.FileColumns._ID + ">?" +
- " AND " + Files.FileColumns.DATA + "=?";
- selectionArgs = new String[] { "", filePath };
- } else {
- where = MediaStore.Files.FileColumns._ID + ">?";
- selectionArgs = new String[] { "" };
- }
-
- mDefaultRingtoneSet = wasRingtoneAlreadySet(Settings.System.RINGTONE);
- mDefaultNotificationSet = wasRingtoneAlreadySet(Settings.System.NOTIFICATION_SOUND);
- mDefaultAlarmSet = wasRingtoneAlreadySet(Settings.System.ALARM_ALERT);
-
- // Tell the provider to not delete the file.
- // If the file is truly gone the delete is unnecessary, and we want to avoid
- // accidentally deleting files that are really there (this may happen if the
- // filesystem is mounted and unmounted while the scanner is running).
- Uri.Builder builder = mFilesUri.buildUpon();
- builder.appendQueryParameter(MediaStore.PARAM_DELETE_DATA, "false");
- MediaBulkDeleter deleter = new MediaBulkDeleter(mMediaProvider, builder.build());
-
- // Build the list of files from the content provider
- try {
- if (prescanFiles) {
- // First read existing files from the files table.
- // Because we'll be deleting entries for missing files as we go,
- // we need to query the database in small batches, to avoid problems
- // with CursorWindow positioning.
- long lastId = Long.MIN_VALUE;
- Uri limitUri = mFilesUri.buildUpon()
- .appendQueryParameter(MediaStore.PARAM_LIMIT, "1000").build();
-
- while (true) {
- selectionArgs[0] = "" + lastId;
- if (c != null) {
- c.close();
- c = null;
- }
- c = mMediaProvider.query(limitUri, FILES_PRESCAN_PROJECTION,
- where, selectionArgs, MediaStore.Files.FileColumns._ID, null);
- if (c == null) {
- break;
- }
-
- int num = c.getCount();
-
- if (num == 0) {
- break;
- }
- while (c.moveToNext()) {
- long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
- String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
- int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
- long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
- lastId = rowId;
-
- // Only consider entries with absolute path names.
- // This allows storing URIs in the database without the
- // media scanner removing them.
- if (path != null && path.startsWith("/")) {
- boolean exists = false;
- try {
- exists = Os.access(path, android.system.OsConstants.F_OK);
- } catch (ErrnoException e1) {
- }
- if (!exists && !MtpConstants.isAbstractObject(format)) {
- // do not delete missing playlists, since they may have been
- // modified by the user.
- // The user can delete them in the media player instead.
- // instead, clear the path and lastModified fields in the row
- String mimeType = MediaFile.getMimeTypeForFile(path);
- if (!MediaFile.isPlayListMimeType(mimeType)) {
- deleter.delete(rowId);
- if (path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
- deleter.flush();
- String parent = new File(path).getParent();
- mMediaProvider.call(MediaStore.UNHIDE_CALL, parent, null);
- }
- }
- }
- }
- }
- }
- }
- }
- finally {
- if (c != null) {
- c.close();
- }
- deleter.flush();
- }
-
- // compute original size of images
- mOriginalCount = 0;
- c = mMediaProvider.query(mImagesUri, ID_PROJECTION, null, null, null, null);
- if (c != null) {
- mOriginalCount = c.getCount();
- c.close();
- }
+ throw new UnsupportedOperationException();
}
- static class MediaBulkDeleter {
- StringBuilder whereClause = new StringBuilder();
- ArrayList<String> whereArgs = new ArrayList<String>(100);
- final ContentProviderClient mProvider;
- final Uri mBaseUri;
-
- public MediaBulkDeleter(ContentProviderClient provider, Uri baseUri) {
- mProvider = provider;
- mBaseUri = baseUri;
- }
-
- public void delete(long id) throws RemoteException {
- if (whereClause.length() != 0) {
- whereClause.append(",");
- }
- whereClause.append("?");
- whereArgs.add("" + id);
- if (whereArgs.size() > 100) {
- flush();
- }
- }
- public void flush() throws RemoteException {
- int size = whereArgs.size();
- if (size > 0) {
- String [] foo = new String [size];
- foo = whereArgs.toArray(foo);
- int numrows = mProvider.delete(mBaseUri,
- MediaStore.MediaColumns._ID + " IN (" +
- whereClause.toString() + ")", foo);
- //Log.i("@@@@@@@@@", "rows deleted: " + numrows);
- whereClause.setLength(0);
- whereArgs.clear();
- }
- }
- }
-
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
private void postscan(final String[] directories) throws RemoteException {
-
- // handle playlists last, after we know what media files are on the storage.
- if (mProcessPlaylists) {
- processPlayLists();
- }
-
- // allow GC to clean up
- mPlayLists.clear();
+ throw new UnsupportedOperationException();
}
- private void releaseResources() {
- // release the DrmManagerClient resources
- if (mDrmManagerClient != null) {
- mDrmManagerClient.close();
- mDrmManagerClient = null;
- }
- }
-
- public void scanDirectories(String[] directories) {
- try {
- long start = System.currentTimeMillis();
- prescan(null, true);
- long prescan = System.currentTimeMillis();
-
- if (ENABLE_BULK_INSERTS) {
- // create MediaInserter for bulk inserts
- mMediaInserter = new MediaInserter(mMediaProvider, 500);
- }
-
- for (int i = 0; i < directories.length; i++) {
- processDirectory(directories[i], mClient);
- }
-
- if (ENABLE_BULK_INSERTS) {
- // flush remaining inserts
- mMediaInserter.flushAll();
- mMediaInserter = null;
- }
-
- long scan = System.currentTimeMillis();
- postscan(directories);
- long end = System.currentTimeMillis();
-
- if (false) {
- Log.d(TAG, " prescan time: " + (prescan - start) + "ms\n");
- Log.d(TAG, " scan time: " + (scan - prescan) + "ms\n");
- Log.d(TAG, "postscan time: " + (end - scan) + "ms\n");
- Log.d(TAG, " total time: " + (end - start) + "ms\n");
- }
- } catch (SQLException e) {
- // this might happen if the SD card is removed while the media scanner is running
- Log.e(TAG, "SQLException in MediaScanner.scan()", e);
- } catch (UnsupportedOperationException e) {
- // this might happen if the SD card is removed while the media scanner is running
- Log.e(TAG, "UnsupportedOperationException in MediaScanner.scan()", e);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in MediaScanner.scan()", e);
- } finally {
- releaseResources();
- }
- }
-
- // this function is used to scan a single file
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
public Uri scanSingleFile(String path, String mimeType) {
- try {
- prescan(path, true);
-
- File file = new File(path);
- if (!file.exists() || !file.canRead()) {
- return null;
- }
-
- // lastModified is in milliseconds on Files.
- long lastModifiedSeconds = file.lastModified() / 1000;
-
- // always scan the file, so we can return the content://media Uri for existing files
- return mClient.doScanFile(path, mimeType, lastModifiedSeconds, file.length(),
- false, true, MediaScanner.isNoMediaPath(path));
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
- return null;
- } finally {
- releaseResources();
- }
+ throw new UnsupportedOperationException();
}
- private static boolean isNoMediaFile(String path) {
- File file = new File(path);
- if (file.isDirectory()) return false;
-
- // special case certain file names
- // I use regionMatches() instead of substring() below
- // to avoid memory allocation
- int lastSlash = path.lastIndexOf('/');
- if (lastSlash >= 0 && lastSlash + 2 < path.length()) {
- // ignore those ._* files created by MacOS
- if (path.regionMatches(lastSlash + 1, "._", 0, 2)) {
- return true;
- }
-
- // ignore album art files created by Windows Media Player:
- // Folder.jpg, AlbumArtSmall.jpg, AlbumArt_{...}_Large.jpg
- // and AlbumArt_{...}_Small.jpg
- if (path.regionMatches(true, path.length() - 4, ".jpg", 0, 4)) {
- if (path.regionMatches(true, lastSlash + 1, "AlbumArt_{", 0, 10) ||
- path.regionMatches(true, lastSlash + 1, "AlbumArt.", 0, 9)) {
- return true;
- }
- int length = path.length() - lastSlash - 1;
- if ((length == 17 && path.regionMatches(
- true, lastSlash + 1, "AlbumArtSmall", 0, 13)) ||
- (length == 10
- && path.regionMatches(true, lastSlash + 1, "Folder", 0, 6))) {
- return true;
- }
- }
- }
- return false;
- }
-
- private static HashMap<String,String> mNoMediaPaths = new HashMap<String,String>();
- private static HashMap<String,String> mMediaPaths = new HashMap<String,String>();
-
- /* MediaProvider calls this when a .nomedia file is added or removed */
- public static void clearMediaPathCache(boolean clearMediaPaths, boolean clearNoMediaPaths) {
- synchronized (MediaScanner.class) {
- if (clearMediaPaths) {
- mMediaPaths.clear();
- }
- if (clearNoMediaPaths) {
- mNoMediaPaths.clear();
- }
- }
- }
-
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
public static boolean isNoMediaPath(String path) {
- if (path == null) {
- return false;
- }
- // return true if file or any parent directory has name starting with a dot
- if (path.indexOf("/.") >= 0) {
- return true;
- }
-
- int firstSlash = path.lastIndexOf('/');
- if (firstSlash <= 0) {
- return false;
- }
- String parent = path.substring(0, firstSlash);
-
- synchronized (MediaScanner.class) {
- if (mNoMediaPaths.containsKey(parent)) {
- return true;
- } else if (!mMediaPaths.containsKey(parent)) {
- // check to see if any parent directories have a ".nomedia" file
- // start from 1 so we don't bother checking in the root directory
- int offset = 1;
- while (offset >= 0) {
- int slashIndex = path.indexOf('/', offset);
- if (slashIndex > offset) {
- slashIndex++; // move past slash
- File file = new File(path.substring(0, slashIndex) + ".nomedia");
- if (file.exists()) {
- // we have a .nomedia in one of the parent directories
- mNoMediaPaths.put(parent, "");
- return true;
- }
- }
- offset = slashIndex;
- }
- mMediaPaths.put(parent, "");
- }
- }
-
- return isNoMediaFile(path);
+ throw new UnsupportedOperationException();
}
- public void scanMtpFile(String path, int objectHandle, int format) {
- String mimeType = MediaFile.getMimeType(path, format);
- File file = new File(path);
- long lastModifiedSeconds = file.lastModified() / 1000;
-
- if (!MediaFile.isAudioMimeType(mimeType) && !MediaFile.isVideoMimeType(mimeType) &&
- !MediaFile.isImageMimeType(mimeType) && !MediaFile.isPlayListMimeType(mimeType) &&
- !MediaFile.isDrmMimeType(mimeType)) {
-
- // no need to use the media scanner, but we need to update last modified and file size
- ContentValues values = new ContentValues();
- values.put(Files.FileColumns.SIZE, file.length());
- values.put(Files.FileColumns.DATE_MODIFIED, lastModifiedSeconds);
- try {
- String[] whereArgs = new String[] { Integer.toString(objectHandle) };
- mMediaProvider.update(Files.getMtpObjectsUri(mVolumeName), values,
- "_id=?", whereArgs);
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in scanMtpFile", e);
- }
- return;
- }
-
- mMtpObjectHandle = objectHandle;
- Cursor fileList = null;
- try {
- if (MediaFile.isPlayListMimeType(mimeType)) {
- // build file cache so we can look up tracks in the playlist
- prescan(null, true);
-
- FileEntry entry = makeEntryFor(path);
- if (entry != null) {
- fileList = mMediaProvider.query(mFilesUri,
- FILES_PRESCAN_PROJECTION, null, null, null, null);
- processPlayList(entry, fileList);
- }
- } else {
- // MTP will create a file entry for us so we don't want to do it in prescan
- prescan(path, false);
-
- // always scan the file, so we can return the content://media Uri for existing files
- mClient.doScanFile(path, mimeType, lastModifiedSeconds, file.length(),
- (format == MtpConstants.FORMAT_ASSOCIATION), true, isNoMediaPath(path));
- }
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
- } finally {
- mMtpObjectHandle = 0;
- if (fileList != null) {
- fileList.close();
- }
- releaseResources();
- }
- }
-
- @UnsupportedAppUsage
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
FileEntry makeEntryFor(String path) {
- String where;
- String[] selectionArgs;
-
- Cursor c = null;
- try {
- where = Files.FileColumns.DATA + "=?";
- selectionArgs = new String[] { path };
- c = mMediaProvider.query(mFilesFullUri, FILES_PRESCAN_PROJECTION,
- where, selectionArgs, null, null);
- if (c != null && c.moveToFirst()) {
- long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
- long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);
- int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);
- int mediaType = c.getInt(FILES_PRESCAN_MEDIA_TYPE_COLUMN_INDEX);
- return new FileEntry(rowId, path, lastModified, format, mediaType);
- }
- } catch (RemoteException e) {
- } finally {
- if (c != null) {
- c.close();
- }
- }
- return null;
+ throw new UnsupportedOperationException();
}
- // returns the number of matching file/directory names, starting from the right
- private int matchPaths(String path1, String path2) {
- int result = 0;
- int end1 = path1.length();
- int end2 = path2.length();
-
- while (end1 > 0 && end2 > 0) {
- int slash1 = path1.lastIndexOf('/', end1 - 1);
- int slash2 = path2.lastIndexOf('/', end2 - 1);
- int backSlash1 = path1.lastIndexOf('\\', end1 - 1);
- int backSlash2 = path2.lastIndexOf('\\', end2 - 1);
- int start1 = (slash1 > backSlash1 ? slash1 : backSlash1);
- int start2 = (slash2 > backSlash2 ? slash2 : backSlash2);
- if (start1 < 0) start1 = 0; else start1++;
- if (start2 < 0) start2 = 0; else start2++;
- int length = end1 - start1;
- if (end2 - start2 != length) break;
- if (path1.regionMatches(true, start1, path2, start2, length)) {
- result++;
- end1 = start1 - 1;
- end2 = start2 - 1;
- } else break;
- }
-
- return result;
+ @Deprecated
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}")
+ private void setLocale(String locale) {
+ throw new UnsupportedOperationException();
}
- private boolean matchEntries(long rowId, String data) {
-
- int len = mPlaylistEntries.size();
- boolean done = true;
- for (int i = 0; i < len; i++) {
- PlaylistEntry entry = mPlaylistEntries.get(i);
- if (entry.bestmatchlevel == Integer.MAX_VALUE) {
- continue; // this entry has been matched already
- }
- done = false;
- if (data.equalsIgnoreCase(entry.path)) {
- entry.bestmatchid = rowId;
- entry.bestmatchlevel = Integer.MAX_VALUE;
- continue; // no need for path matching
- }
-
- int matchLength = matchPaths(data, entry.path);
- if (matchLength > entry.bestmatchlevel) {
- entry.bestmatchid = rowId;
- entry.bestmatchlevel = matchLength;
- }
- }
- return done;
- }
-
- private void cachePlaylistEntry(String line, String playListDirectory) {
- PlaylistEntry entry = new PlaylistEntry();
- // watch for trailing whitespace
- int entryLength = line.length();
- while (entryLength > 0 && Character.isWhitespace(line.charAt(entryLength - 1))) entryLength--;
- // path should be longer than 3 characters.
- // avoid index out of bounds errors below by returning here.
- if (entryLength < 3) return;
- if (entryLength < line.length()) line = line.substring(0, entryLength);
-
- // does entry appear to be an absolute path?
- // look for Unix or DOS absolute paths
- char ch1 = line.charAt(0);
- boolean fullPath = (ch1 == '/' ||
- (Character.isLetter(ch1) && line.charAt(1) == ':' && line.charAt(2) == '\\'));
- // if we have a relative path, combine entry with playListDirectory
- if (!fullPath)
- line = playListDirectory + line;
- entry.path = line;
- //FIXME - should we look for "../" within the path?
-
- mPlaylistEntries.add(entry);
- }
-
- private void processCachedPlaylist(Cursor fileList, ContentValues values, Uri playlistUri) {
- fileList.moveToPosition(-1);
- while (fileList.moveToNext()) {
- long rowId = fileList.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);
- String data = fileList.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);
- if (matchEntries(rowId, data)) {
- break;
- }
- }
-
- int len = mPlaylistEntries.size();
- int index = 0;
- for (int i = 0; i < len; i++) {
- PlaylistEntry entry = mPlaylistEntries.get(i);
- if (entry.bestmatchlevel > 0) {
- try {
- values.clear();
- values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, Integer.valueOf(index));
- values.put(MediaStore.Audio.Playlists.Members.AUDIO_ID, Long.valueOf(entry.bestmatchid));
- mMediaProvider.insert(playlistUri, values);
- index++;
- } catch (RemoteException e) {
- Log.e(TAG, "RemoteException in MediaScanner.processCachedPlaylist()", e);
- return;
- }
- }
- }
- mPlaylistEntries.clear();
- }
-
- private void processM3uPlayList(String path, String playListDirectory, Uri uri,
- ContentValues values, Cursor fileList) {
- BufferedReader reader = null;
- try {
- File f = new File(path);
- if (f.exists()) {
- reader = new BufferedReader(
- new InputStreamReader(new FileInputStream(f)), 8192);
- String line = reader.readLine();
- mPlaylistEntries.clear();
- while (line != null) {
- // ignore comment lines, which begin with '#'
- if (line.length() > 0 && line.charAt(0) != '#') {
- cachePlaylistEntry(line, playListDirectory);
- }
- line = reader.readLine();
- }
-
- processCachedPlaylist(fileList, values, uri);
- }
- } catch (IOException e) {
- Log.e(TAG, "IOException in MediaScanner.processM3uPlayList()", e);
- } finally {
- try {
- if (reader != null)
- reader.close();
- } catch (IOException e) {
- Log.e(TAG, "IOException in MediaScanner.processM3uPlayList()", e);
- }
- }
- }
-
- private void processPlsPlayList(String path, String playListDirectory, Uri uri,
- ContentValues values, Cursor fileList) {
- BufferedReader reader = null;
- try {
- File f = new File(path);
- if (f.exists()) {
- reader = new BufferedReader(
- new InputStreamReader(new FileInputStream(f)), 8192);
- String line = reader.readLine();
- mPlaylistEntries.clear();
- while (line != null) {
- // ignore comment lines, which begin with '#'
- if (line.startsWith("File")) {
- int equals = line.indexOf('=');
- if (equals > 0) {
- cachePlaylistEntry(line.substring(equals + 1), playListDirectory);
- }
- }
- line = reader.readLine();
- }
-
- processCachedPlaylist(fileList, values, uri);
- }
- } catch (IOException e) {
- Log.e(TAG, "IOException in MediaScanner.processPlsPlayList()", e);
- } finally {
- try {
- if (reader != null)
- reader.close();
- } catch (IOException e) {
- Log.e(TAG, "IOException in MediaScanner.processPlsPlayList()", e);
- }
- }
- }
-
- class WplHandler implements ElementListener {
-
- final ContentHandler handler;
- String playListDirectory;
-
- public WplHandler(String playListDirectory, Uri uri, Cursor fileList) {
- this.playListDirectory = playListDirectory;
-
- RootElement root = new RootElement("smil");
- Element body = root.getChild("body");
- Element seq = body.getChild("seq");
- Element media = seq.getChild("media");
- media.setElementListener(this);
-
- this.handler = root.getContentHandler();
- }
-
- @Override
- public void start(Attributes attributes) {
- String path = attributes.getValue("", "src");
- if (path != null) {
- cachePlaylistEntry(path, playListDirectory);
- }
- }
-
- @Override
- public void end() {
- }
-
- ContentHandler getContentHandler() {
- return handler;
- }
- }
-
- private void processWplPlayList(String path, String playListDirectory, Uri uri,
- ContentValues values, Cursor fileList) {
- FileInputStream fis = null;
- try {
- File f = new File(path);
- if (f.exists()) {
- fis = new FileInputStream(f);
-
- mPlaylistEntries.clear();
- Xml.parse(fis, Xml.findEncodingByName("UTF-8"),
- new WplHandler(playListDirectory, uri, fileList).getContentHandler());
-
- processCachedPlaylist(fileList, values, uri);
- }
- } catch (SAXException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- try {
- if (fis != null)
- fis.close();
- } catch (IOException e) {
- Log.e(TAG, "IOException in MediaScanner.processWplPlayList()", e);
- }
- }
- }
-
- private void processPlayList(FileEntry entry, Cursor fileList) throws RemoteException {
- String path = entry.mPath;
- ContentValues values = new ContentValues();
- int lastSlash = path.lastIndexOf('/');
- if (lastSlash < 0) throw new IllegalArgumentException("bad path " + path);
- Uri uri, membersUri;
- long rowId = entry.mRowId;
-
- // make sure we have a name
- String name = values.getAsString(MediaStore.Audio.Playlists.NAME);
- if (name == null) {
- name = values.getAsString(MediaStore.MediaColumns.TITLE);
- if (name == null) {
- // extract name from file name
- int lastDot = path.lastIndexOf('.');
- name = (lastDot < 0 ? path.substring(lastSlash + 1)
- : path.substring(lastSlash + 1, lastDot));
- }
- }
-
- values.put(MediaStore.Audio.Playlists.NAME, name);
- values.put(MediaStore.Audio.Playlists.DATE_MODIFIED, entry.mLastModified);
-
- if (rowId == 0) {
- values.put(MediaStore.Audio.Playlists.DATA, path);
- uri = mMediaProvider.insert(mPlaylistsUri, values);
- rowId = ContentUris.parseId(uri);
- membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
- } else {
- uri = ContentUris.withAppendedId(mPlaylistsUri, rowId);
- mMediaProvider.update(uri, values, null, null);
-
- // delete members of existing playlist
- membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
- mMediaProvider.delete(membersUri, null, null);
- }
-
- String playListDirectory = path.substring(0, lastSlash + 1);
- String mimeType = MediaFile.getMimeTypeForFile(path);
- switch (mimeType) {
- case "application/vnd.ms-wpl":
- processWplPlayList(path, playListDirectory, membersUri, values, fileList);
- break;
- case "audio/x-mpegurl":
- processM3uPlayList(path, playListDirectory, membersUri, values, fileList);
- break;
- case "audio/x-scpls":
- processPlsPlayList(path, playListDirectory, membersUri, values, fileList);
- break;
- }
- }
-
- private void processPlayLists() throws RemoteException {
- Iterator<FileEntry> iterator = mPlayLists.iterator();
- Cursor fileList = null;
- try {
- // use the files uri and projection because we need the format column,
- // but restrict the query to just audio files
- fileList = mMediaProvider.query(mFilesUri, FILES_PRESCAN_PROJECTION,
- "media_type=2", null, null, null);
- while (iterator.hasNext()) {
- FileEntry entry = iterator.next();
- // only process playlist files if they are new or have been modified since the last scan
- if (entry.mLastModifiedChanged) {
- processPlayList(entry, fileList);
- }
- }
- } catch (RemoteException e1) {
- } finally {
- if (fileList != null) {
- fileList.close();
- }
- }
- }
-
- private native void processDirectory(String path, MediaScannerClient client);
- private native boolean processFile(String path, String mimeType, MediaScannerClient client);
- @UnsupportedAppUsage
- private native void setLocale(String locale);
-
- public native byte[] extractAlbumArt(FileDescriptor fd);
-
- private static native final void native_init();
- private native final void native_setup();
- private native final void native_finalize();
-
@Override
public void close() {
- mCloseGuard.close();
- if (mClosed.compareAndSet(false, true)) {
- mMediaProvider.close();
- native_finalize();
- }
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- if (mCloseGuard != null) {
- mCloseGuard.warnIfOpen();
- }
-
- close();
- } finally {
- super.finalize();
- }
+ throw new UnsupportedOperationException();
}
}
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 435d8d7..ff40442 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -846,7 +846,7 @@
* Adds an audio file to the list of ringtones.
*
* After making sure the given file is an audio file, copies the file to the ringtone storage,
- * and asks the {@link android.media.MediaScanner} to scan that file. This call will block until
+ * and asks the system to scan that file. This call will block until
* the scan is completed.
*
* The directory where the copied file is stored is the directory that matches the ringtone's
diff --git a/media/java/android/media/ThumbnailUtils.java b/media/java/android/media/ThumbnailUtils.java
index fb581b5..a315c1e 100644
--- a/media/java/android/media/ThumbnailUtils.java
+++ b/media/java/android/media/ThumbnailUtils.java
@@ -139,6 +139,12 @@
/**
* Create a thumbnail for given audio file.
+ * <p>
+ * This method should only be used for files that you have direct access to;
+ * if you'd like to work with media hosted outside your app, consider using
+ * {@link ContentResolver#loadThumbnail(Uri, Size, CancellationSignal)}
+ * which enables remote providers to efficiently cache and invalidate
+ * thumbnails.
*
* @param file The audio file.
* @param size The desired thumbnail size.
@@ -231,6 +237,12 @@
/**
* Create a thumbnail for given image file.
+ * <p>
+ * This method should only be used for files that you have direct access to;
+ * if you'd like to work with media hosted outside your app, consider using
+ * {@link ContentResolver#loadThumbnail(Uri, Size, CancellationSignal)}
+ * which enables remote providers to efficiently cache and invalidate
+ * thumbnails.
*
* @param file The audio file.
* @param size The desired thumbnail size.
@@ -334,6 +346,12 @@
/**
* Create a thumbnail for given video file.
+ * <p>
+ * This method should only be used for files that you have direct access to;
+ * if you'd like to work with media hosted outside your app, consider using
+ * {@link ContentResolver#loadThumbnail(Uri, Size, CancellationSignal)}
+ * which enables remote providers to efficiently cache and invalidate
+ * thumbnails.
*
* @param file The video file.
* @param size The desired thumbnail size.
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 45ee210..bd9ea13 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -17,7 +17,6 @@
"android_media_MediaPlayer.cpp",
"android_media_MediaProfiles.cpp",
"android_media_MediaRecorder.cpp",
- "android_media_MediaScanner.cpp",
"android_media_MediaSync.cpp",
"android_media_ResampleInputStream.cpp",
"android_media_Streams.cpp",
diff --git a/media/jni/android_media_MediaMetadataRetriever.cpp b/media/jni/android_media_MediaMetadataRetriever.cpp
index 18fd1a0..bc4bceb 100644
--- a/media/jni/android_media_MediaMetadataRetriever.cpp
+++ b/media/jni/android_media_MediaMetadataRetriever.cpp
@@ -350,9 +350,10 @@
return jBitmap;
}
-static int getColorFormat(JNIEnv *env, jobject options) {
+static int getColorFormat(JNIEnv *env, jobject options,
+ int defaultPreferred = HAL_PIXEL_FORMAT_RGBA_8888) {
if (options == NULL) {
- return HAL_PIXEL_FORMAT_RGBA_8888;
+ return defaultPreferred;
}
ScopedLocalRef<jobject> inConfig(env, env->GetObjectField(options, fields.inPreferredConfig));
@@ -383,7 +384,8 @@
}
static jobject android_media_MediaMetadataRetriever_getFrameAtTime(
- JNIEnv *env, jobject thiz, jlong timeUs, jint option, jint dst_width, jint dst_height)
+ JNIEnv *env, jobject thiz, jlong timeUs, jint option,
+ jint dst_width, jint dst_height, jobject params)
{
ALOGV("getFrameAtTime: %lld us option: %d dst width: %d heigh: %d",
(long long)timeUs, option, dst_width, dst_height);
@@ -392,10 +394,13 @@
jniThrowException(env, "java/lang/IllegalStateException", "No retriever available");
return NULL;
}
+ // For getFrameAtTime family of calls, default to HAL_PIXEL_FORMAT_RGB_565
+ // to keep the behavior consistent with older releases
+ int colorFormat = getColorFormat(env, params, HAL_PIXEL_FORMAT_RGB_565);
// Call native method to retrieve a video frame
VideoFrame *videoFrame = NULL;
- sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option);
+ sp<IMemory> frameMemory = retriever->getFrameAtTime(timeUs, option, colorFormat);
// TODO: Using unsecurePointer() has some associated security pitfalls
// (see declaration for details).
// Either document why it is safe in this case or address the
@@ -408,7 +413,9 @@
return NULL;
}
- return getBitmapFromVideoFrame(env, videoFrame, dst_width, dst_height, kRGB_565_SkColorType);
+ SkColorType outColorType = setOutColorType(env, colorFormat, params);
+
+ return getBitmapFromVideoFrame(env, videoFrame, dst_width, dst_height, outColorType);
}
static jobject android_media_MediaMetadataRetriever_getImageAtIndex(
@@ -739,7 +746,7 @@
(void *)android_media_MediaMetadataRetriever_setDataSourceFD},
{"_setDataSource", "(Landroid/media/MediaDataSource;)V",
(void *)android_media_MediaMetadataRetriever_setDataSourceCallback},
- {"_getFrameAtTime", "(JIII)Landroid/graphics/Bitmap;",
+ {"_getFrameAtTime", "(JIIILandroid/media/MediaMetadataRetriever$BitmapParams;)Landroid/graphics/Bitmap;",
(void *)android_media_MediaMetadataRetriever_getFrameAtTime},
{
"_getImageAtIndex",
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index d24edc7..94299bc 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1446,7 +1446,6 @@
extern int register_android_media_MediaMetadataRetriever(JNIEnv *env);
extern int register_android_media_MediaMuxer(JNIEnv *env);
extern int register_android_media_MediaRecorder(JNIEnv *env);
-extern int register_android_media_MediaScanner(JNIEnv *env);
extern int register_android_media_MediaSync(JNIEnv *env);
extern int register_android_media_ResampleInputStream(JNIEnv *env);
extern int register_android_media_MediaProfiles(JNIEnv *env);
@@ -1485,11 +1484,6 @@
goto bail;
}
- if (register_android_media_MediaScanner(env) < 0) {
- ALOGE("ERROR: MediaScanner native registration failed\n");
- goto bail;
- }
-
if (register_android_media_MediaMetadataRetriever(env) < 0) {
ALOGE("ERROR: MediaMetadataRetriever native registration failed\n");
goto bail;
diff --git a/media/jni/android_media_MediaScanner.cpp b/media/jni/android_media_MediaScanner.cpp
deleted file mode 100644
index 58044c0..0000000
--- a/media/jni/android_media_MediaScanner.cpp
+++ /dev/null
@@ -1,468 +0,0 @@
-/*
-**
-** Copyright 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.
-*/
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "MediaScannerJNI"
-#include <utils/Log.h>
-#include <utils/threads.h>
-#include <media/mediascanner.h>
-#include <media/stagefright/StagefrightMediaScanner.h>
-#include <private/media/VideoFrame.h>
-
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include "android_runtime/AndroidRuntime.h"
-#include "android_runtime/Log.h"
-#include <android-base/macros.h> // for FALLTHROUGH_INTENDED
-
-using namespace android;
-
-
-static const char* const kClassMediaScannerClient =
- "android/media/MediaScannerClient";
-
-static const char* const kClassMediaScanner =
- "android/media/MediaScanner";
-
-static const char* const kRunTimeException =
- "java/lang/RuntimeException";
-
-static const char* const kIllegalArgumentException =
- "java/lang/IllegalArgumentException";
-
-struct fields_t {
- jfieldID context;
-};
-static fields_t fields;
-
-static status_t checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
- if (env->ExceptionCheck()) {
- ALOGE("An exception was thrown by callback '%s'.", methodName);
- LOGE_EX(env);
- env->ExceptionClear();
- return UNKNOWN_ERROR;
- }
- return OK;
-}
-
-// stolen from dalvik/vm/checkJni.cpp
-static bool isValidUtf8(const char* bytes) {
- while (*bytes != '\0') {
- unsigned char utf8 = *(bytes++);
- // Switch on the high four bits.
- switch (utf8 >> 4) {
- case 0x00:
- case 0x01:
- case 0x02:
- case 0x03:
- case 0x04:
- case 0x05:
- case 0x06:
- case 0x07:
- // Bit pattern 0xxx. No need for any extra bytes.
- break;
- case 0x08:
- case 0x09:
- case 0x0a:
- case 0x0b:
- case 0x0f:
- /*
- * Bit pattern 10xx or 1111, which are illegal start bytes.
- * Note: 1111 is valid for normal UTF-8, but not the
- * modified UTF-8 used here.
- */
- return false;
- case 0x0e:
- // Bit pattern 1110, so there are two additional bytes.
- utf8 = *(bytes++);
- if ((utf8 & 0xc0) != 0x80) {
- return false;
- }
- // Fall through to take care of the final byte.
- FALLTHROUGH_INTENDED;
- case 0x0c:
- case 0x0d:
- // Bit pattern 110x, so there is one additional byte.
- utf8 = *(bytes++);
- if ((utf8 & 0xc0) != 0x80) {
- return false;
- }
- break;
- }
- }
- return true;
-}
-
-class MyMediaScannerClient : public MediaScannerClient
-{
-public:
- MyMediaScannerClient(JNIEnv *env, jobject client)
- : mEnv(env),
- mClient(env->NewGlobalRef(client)),
- mScanFileMethodID(0),
- mHandleStringTagMethodID(0),
- mSetMimeTypeMethodID(0)
- {
- ALOGV("MyMediaScannerClient constructor");
- jclass mediaScannerClientInterface =
- env->FindClass(kClassMediaScannerClient);
-
- if (mediaScannerClientInterface == NULL) {
- ALOGE("Class %s not found", kClassMediaScannerClient);
- } else {
- mScanFileMethodID = env->GetMethodID(
- mediaScannerClientInterface,
- "scanFile",
- "(Ljava/lang/String;JJZZ)V");
-
- mHandleStringTagMethodID = env->GetMethodID(
- mediaScannerClientInterface,
- "handleStringTag",
- "(Ljava/lang/String;Ljava/lang/String;)V");
-
- mSetMimeTypeMethodID = env->GetMethodID(
- mediaScannerClientInterface,
- "setMimeType",
- "(Ljava/lang/String;)V");
- }
- }
-
- virtual ~MyMediaScannerClient()
- {
- ALOGV("MyMediaScannerClient destructor");
- mEnv->DeleteGlobalRef(mClient);
- }
-
- virtual status_t scanFile(const char* path, long long lastModified,
- long long fileSize, bool isDirectory, bool noMedia)
- {
- ALOGV("scanFile: path(%s), time(%lld), size(%lld) and isDir(%d)",
- path, lastModified, fileSize, isDirectory);
-
- jstring pathStr;
- if ((pathStr = mEnv->NewStringUTF(path)) == NULL) {
- mEnv->ExceptionClear();
- return NO_MEMORY;
- }
-
- mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,
- fileSize, isDirectory, noMedia);
-
- mEnv->DeleteLocalRef(pathStr);
- return checkAndClearExceptionFromCallback(mEnv, "scanFile");
- }
-
- virtual status_t handleStringTag(const char* name, const char* value)
- {
- ALOGV("handleStringTag: name(%s) and value(%s)", name, value);
- jstring nameStr, valueStr;
- if ((nameStr = mEnv->NewStringUTF(name)) == NULL) {
- mEnv->ExceptionClear();
- return NO_MEMORY;
- }
- char *cleaned = NULL;
- if (!isValidUtf8(value)) {
- cleaned = strdup(value);
- char *chp = cleaned;
- char ch;
- while ((ch = *chp)) {
- if (ch & 0x80) {
- *chp = '?';
- }
- chp++;
- }
- value = cleaned;
- }
- valueStr = mEnv->NewStringUTF(value);
- free(cleaned);
- if (valueStr == NULL) {
- mEnv->DeleteLocalRef(nameStr);
- mEnv->ExceptionClear();
- return NO_MEMORY;
- }
-
- mEnv->CallVoidMethod(
- mClient, mHandleStringTagMethodID, nameStr, valueStr);
-
- mEnv->DeleteLocalRef(nameStr);
- mEnv->DeleteLocalRef(valueStr);
- return checkAndClearExceptionFromCallback(mEnv, "handleStringTag");
- }
-
- virtual status_t setMimeType(const char* mimeType)
- {
- ALOGV("setMimeType: %s", mimeType);
- jstring mimeTypeStr;
- if ((mimeTypeStr = mEnv->NewStringUTF(mimeType)) == NULL) {
- mEnv->ExceptionClear();
- return NO_MEMORY;
- }
-
- mEnv->CallVoidMethod(mClient, mSetMimeTypeMethodID, mimeTypeStr);
-
- mEnv->DeleteLocalRef(mimeTypeStr);
- return checkAndClearExceptionFromCallback(mEnv, "setMimeType");
- }
-
-private:
- JNIEnv *mEnv;
- jobject mClient;
- jmethodID mScanFileMethodID;
- jmethodID mHandleStringTagMethodID;
- jmethodID mSetMimeTypeMethodID;
-};
-
-
-static MediaScanner *getNativeScanner_l(JNIEnv* env, jobject thiz)
-{
- return (MediaScanner *) env->GetLongField(thiz, fields.context);
-}
-
-static void setNativeScanner_l(JNIEnv* env, jobject thiz, MediaScanner *s)
-{
- env->SetLongField(thiz, fields.context, (jlong)s);
-}
-
-static void
-android_media_MediaScanner_processDirectory(
- JNIEnv *env, jobject thiz, jstring path, jobject client)
-{
- ALOGV("processDirectory");
- MediaScanner *mp = getNativeScanner_l(env, thiz);
- if (mp == NULL) {
- jniThrowException(env, kRunTimeException, "No scanner available");
- return;
- }
-
- if (path == NULL) {
- jniThrowException(env, kIllegalArgumentException, NULL);
- return;
- }
-
- const char *pathStr = env->GetStringUTFChars(path, NULL);
- if (pathStr == NULL) { // Out of memory
- return;
- }
-
- MyMediaScannerClient myClient(env, client);
- MediaScanResult result = mp->processDirectory(pathStr, myClient);
- if (result == MEDIA_SCAN_RESULT_ERROR) {
- ALOGE("An error occurred while scanning directory '%s'.", pathStr);
- }
- env->ReleaseStringUTFChars(path, pathStr);
-}
-
-static jboolean
-android_media_MediaScanner_processFile(
- JNIEnv *env, jobject thiz, jstring path,
- jstring mimeType, jobject client)
-{
- ALOGV("processFile");
-
- // Lock already hold by processDirectory
- MediaScanner *mp = getNativeScanner_l(env, thiz);
- if (mp == NULL) {
- jniThrowException(env, kRunTimeException, "No scanner available");
- return false;
- }
-
- if (path == NULL) {
- jniThrowException(env, kIllegalArgumentException, NULL);
- return false;
- }
-
- const char *pathStr = env->GetStringUTFChars(path, NULL);
- if (pathStr == NULL) { // Out of memory
- return false;
- }
-
- const char *mimeTypeStr =
- (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL);
- if (mimeType && mimeTypeStr == NULL) { // Out of memory
- // ReleaseStringUTFChars can be called with an exception pending.
- env->ReleaseStringUTFChars(path, pathStr);
- return false;
- }
-
- MyMediaScannerClient myClient(env, client);
- MediaScanResult result = mp->processFile(pathStr, mimeTypeStr, myClient);
- if (result == MEDIA_SCAN_RESULT_ERROR) {
- ALOGE("An error occurred while scanning file '%s'.", pathStr);
- }
- env->ReleaseStringUTFChars(path, pathStr);
- if (mimeType) {
- env->ReleaseStringUTFChars(mimeType, mimeTypeStr);
- }
- return result != MEDIA_SCAN_RESULT_ERROR;
-}
-
-static void
-android_media_MediaScanner_setLocale(
- JNIEnv *env, jobject thiz, jstring locale)
-{
- ALOGV("setLocale");
- MediaScanner *mp = getNativeScanner_l(env, thiz);
- if (mp == NULL) {
- jniThrowException(env, kRunTimeException, "No scanner available");
- return;
- }
-
- if (locale == NULL) {
- jniThrowException(env, kIllegalArgumentException, NULL);
- return;
- }
- const char *localeStr = env->GetStringUTFChars(locale, NULL);
- if (localeStr == NULL) { // Out of memory
- return;
- }
- mp->setLocale(localeStr);
-
- env->ReleaseStringUTFChars(locale, localeStr);
-}
-
-static jbyteArray
-android_media_MediaScanner_extractAlbumArt(
- JNIEnv *env, jobject thiz, jobject fileDescriptor)
-{
- ALOGV("extractAlbumArt");
- MediaScanner *mp = getNativeScanner_l(env, thiz);
- if (mp == NULL) {
- jniThrowException(env, kRunTimeException, "No scanner available");
- return NULL;
- }
-
- if (fileDescriptor == NULL) {
- jniThrowException(env, kIllegalArgumentException, NULL);
- return NULL;
- }
-
- int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
- MediaAlbumArt* mediaAlbumArt = mp->extractAlbumArt(fd);
- if (mediaAlbumArt == NULL) {
- return NULL;
- }
-
- jbyteArray array = env->NewByteArray(mediaAlbumArt->size());
- if (array != NULL) {
- const jbyte* data =
- reinterpret_cast<const jbyte*>(mediaAlbumArt->data());
- env->SetByteArrayRegion(array, 0, mediaAlbumArt->size(), data);
- }
-
- free(mediaAlbumArt);
- // if NewByteArray() returned NULL, an out-of-memory
- // exception will have been raised. I just want to
- // return null in that case.
- env->ExceptionClear();
- return array;
-}
-
-// This function gets a field ID, which in turn causes class initialization.
-// It is called from a static block in MediaScanner, which won't run until the
-// first time an instance of this class is used.
-static void
-android_media_MediaScanner_native_init(JNIEnv *env)
-{
- ALOGV("native_init");
- jclass clazz = env->FindClass(kClassMediaScanner);
- if (clazz == NULL) {
- return;
- }
-
- fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
- if (fields.context == NULL) {
- return;
- }
-}
-
-static void
-android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz)
-{
- ALOGV("native_setup");
- MediaScanner *mp = new StagefrightMediaScanner;
-
- if (mp == NULL) {
- jniThrowException(env, kRunTimeException, "Out of memory");
- return;
- }
-
- env->SetLongField(thiz, fields.context, (jlong)mp);
-}
-
-static void
-android_media_MediaScanner_native_finalize(JNIEnv *env, jobject thiz)
-{
- ALOGV("native_finalize");
- MediaScanner *mp = getNativeScanner_l(env, thiz);
- if (mp == 0) {
- return;
- }
- delete mp;
- setNativeScanner_l(env, thiz, 0);
-}
-
-static const JNINativeMethod gMethods[] = {
- {
- "processDirectory",
- "(Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
- (void *)android_media_MediaScanner_processDirectory
- },
-
- {
- "processFile",
- "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)Z",
- (void *)android_media_MediaScanner_processFile
- },
-
- {
- "setLocale",
- "(Ljava/lang/String;)V",
- (void *)android_media_MediaScanner_setLocale
- },
-
- {
- "extractAlbumArt",
- "(Ljava/io/FileDescriptor;)[B",
- (void *)android_media_MediaScanner_extractAlbumArt
- },
-
- {
- "native_init",
- "()V",
- (void *)android_media_MediaScanner_native_init
- },
-
- {
- "native_setup",
- "()V",
- (void *)android_media_MediaScanner_native_setup
- },
-
- {
- "native_finalize",
- "()V",
- (void *)android_media_MediaScanner_native_finalize
- },
-};
-
-// This function only registers the native methods, and is called from
-// JNI_OnLoad in android_media_MediaPlayer.cpp
-int register_android_media_MediaScanner(JNIEnv *env)
-{
- return AndroidRuntime::registerNativeMethods(env,
- kClassMediaScanner, gMethods, NELEM(gMethods));
-}
diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
index 1267aa8..680c879 100644
--- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
@@ -36,6 +36,12 @@
public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category";
public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route";
+ public static final int VOLUME_MAX = 100;
+ public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume";
+ public static final String ROUTE_NAME_FIXED_VOLUME = "Fixed Volume Route";
+ public static final String ROUTE_ID_VARIABLE_VOLUME = "route_variable_volume";
+ public static final String ROUTE_NAME_VARIABLE_VOLUME = "Variable Volume Route";
+
public static final String ACTION_REMOVE_ROUTE =
"com.android.mediarouteprovider.action_remove_route";
@@ -58,9 +64,23 @@
.addSupportedCategory(CATEGORY_SAMPLE)
.addSupportedCategory(CATEGORY_SPECIAL)
.build();
+ MediaRoute2Info fixedVolumeRoute =
+ new MediaRoute2Info.Builder(ROUTE_ID_FIXED_VOLUME, ROUTE_NAME_FIXED_VOLUME)
+ .addSupportedCategory(CATEGORY_SAMPLE)
+ .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_FIXED)
+ .build();
+ MediaRoute2Info variableVolumeRoute =
+ new MediaRoute2Info.Builder(ROUTE_ID_VARIABLE_VOLUME, ROUTE_NAME_VARIABLE_VOLUME)
+ .addSupportedCategory(CATEGORY_SAMPLE)
+ .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
+ .setVolumeMax(VOLUME_MAX)
+ .build();
+
mRoutes.put(route1.getId(), route1);
mRoutes.put(route2.getId(), route2);
mRoutes.put(routeSpecial.getId(), routeSpecial);
+ mRoutes.put(fixedVolumeRoute.getId(), fixedVolumeRoute);
+ mRoutes.put(variableVolumeRoute.getId(), variableVolumeRoute);
}
@Override
@@ -110,6 +130,33 @@
}
}
+ @Override
+ public void onSetVolume(String routeId, int volume) {
+ MediaRoute2Info route = mRoutes.get(routeId);
+ if (route == null) {
+ return;
+ }
+ volume = Math.min(volume, Math.max(0, route.getVolumeMax()));
+ mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
+ .setVolume(volume)
+ .build());
+ publishRoutes();
+ }
+
+ @Override
+ public void onUpdateVolume(String routeId, int delta) {
+ MediaRoute2Info route = mRoutes.get(routeId);
+ if (route == null) {
+ return;
+ }
+ int volume = route.getVolume() + delta;
+ volume = Math.min(volume, Math.max(0, route.getVolumeMax()));
+ mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
+ .setVolume(volume)
+ .build());
+ publishRoutes();
+ }
+
void publishRoutes() {
MediaRoute2ProviderInfo info = new MediaRoute2ProviderInfo.Builder()
.addRoutes(mRoutes.values())
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index a3ed07a..ca43d04 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -16,6 +16,9 @@
package com.android.mediaroutertest;
+import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
+import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
@@ -44,8 +47,10 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -61,6 +66,12 @@
public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category";
public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route";
+ public static final int VOLUME_MAX = 100;
+ public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume";
+ public static final String ROUTE_NAME_FIXED_VOLUME = "Fixed Volume Route";
+ public static final String ROUTE_ID_VARIABLE_VOLUME = "route_variable_volume";
+ public static final String ROUTE_NAME_VARIABLE_VOLUME = "Variable Volume Route";
+
public static final String ACTION_REMOVE_ROUTE =
"com.android.mediarouteprovider.action_remove_route";
@@ -98,7 +109,7 @@
mPackageName = mContext.getPackageName();
}
- //TODO: Move to a seperate file
+ //TODO: Move to a separate file
@Test
public void testMediaRoute2Info() {
MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder("id", "name")
@@ -181,17 +192,8 @@
MediaRouter2Manager.Callback mockCallback = mock(MediaRouter2Manager.Callback.class);
mManager.registerCallback(mExecutor, mockCallback);
- MediaRouter2.Callback mockRouterCallback = mock(MediaRouter2.Callback.class);
-
- mRouter2.setControlCategories(CONTROL_CATEGORIES_SPECIAL);
- mRouter2.registerCallback(mExecutor, mockRouterCallback);
- mRouter2.unregisterCallback(mockRouterCallback);
-
- verify(mockCallback, timeout(TIMEOUT_MS))
- .onRoutesChanged(argThat(routes -> routes.size() > 0));
-
Map<String, MediaRoute2Info> routes =
- createRouteMap(mManager.getAvailableRoutes(mPackageName));
+ waitAndGetRoutesWithManager(CONTROL_CATEGORIES_SPECIAL);
Assert.assertEquals(1, routes.size());
Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
@@ -205,12 +207,10 @@
@Test
public void testGetRoutes() throws Exception {
MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);
-
- mRouter2.setControlCategories(CONTROL_CATEGORIES_SPECIAL);
mRouter2.registerCallback(mExecutor, mockCallback);
- verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
- .onRoutesChanged(argThat(routes -> routes.size() > 0));
- Map<String, MediaRoute2Info> routes = createRouteMap(mRouter2.getRoutes());
+
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CONTROL_CATEGORIES_SPECIAL);
+
Assert.assertEquals(1, routes.size());
Assert.assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
@@ -219,51 +219,40 @@
@Test
public void testOnRouteSelected() throws Exception {
- MediaRouter2.Callback mockRouterCallback = mock(MediaRouter2.Callback.class);
+ MediaRouter2.Callback routerCallback = new MediaRouter2.Callback();
MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class);
mManager.registerCallback(mExecutor, managerCallback);
- mRouter2.setControlCategories(CONTROL_CATEGORIES_ALL);
- mRouter2.registerCallback(mExecutor, mockRouterCallback);
+ mRouter2.registerCallback(mExecutor, routerCallback);
- verify(managerCallback, timeout(TIMEOUT_MS))
- .onRoutesChanged(argThat(routes -> routes.size() > 0));
-
- Map<String, MediaRoute2Info> routes =
- createRouteMap(mManager.getAvailableRoutes(mPackageName));
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CONTROL_CATEGORIES_ALL);
MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
- mManager.selectRoute(mPackageName, routeToSelect);
-
assertNotNull(routeToSelect);
+
+ mManager.selectRoute(mPackageName, routeToSelect);
verify(managerCallback, timeout(TIMEOUT_MS))
.onRouteAdded(argThat(route -> route.equals(routeToSelect)));
+ mRouter2.unregisterCallback(routerCallback);
mManager.unregisterCallback(managerCallback);
- mRouter2.unregisterCallback(mockRouterCallback);
}
/**
* Tests selecting and unselecting routes of a single provider.
*/
@Test
- public void testSingleProviderSelect() {
+ public void testSingleProviderSelect() throws Exception {
MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class);
MediaRouter2.Callback routerCallback = mock(MediaRouter2.Callback.class);
mManager.registerCallback(mExecutor, managerCallback);
- mRouter2.setControlCategories(CONTROL_CATEGORIES_ALL);
mRouter2.registerCallback(mExecutor, routerCallback);
- verify(managerCallback, timeout(TIMEOUT_MS))
- .onRoutesChanged(argThat(routes -> routes.size() > 0));
-
- Map<String, MediaRoute2Info> routes =
- createRouteMap(mManager.getAvailableRoutes(mPackageName));
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CONTROL_CATEGORIES_ALL);
mManager.selectRoute(mPackageName, routes.get(ROUTE_ID1));
- verify(managerCallback, timeout(TIMEOUT_MS)
- )
+ verify(managerCallback, timeout(TIMEOUT_MS))
.onRouteChanged(argThat(routeInfo -> TextUtils.equals(ROUTE_ID1, routeInfo.getId())
&& TextUtils.equals(routeInfo.getClientPackageName(), mPackageName)));
@@ -281,6 +270,121 @@
mManager.unregisterCallback(managerCallback);
}
+ @Test
+ public void testControlVolumeWithRouter() throws Exception {
+ MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);
+
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CONTROL_CATEGORIES_ALL);
+ mRouter2.registerCallback(mExecutor, mockCallback);
+
+ MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
+ int originalVolume = volRoute.getVolume();
+ int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
+ int targetVolume = originalVolume + deltaVolume;
+
+ mRouter2.requestSetVolume(volRoute, targetVolume);
+ verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
+ .onRouteChanged(argThat(route ->
+ route.getId().equals(volRoute.getId())
+ && route.getVolume() == targetVolume));
+
+ mRouter2.requestUpdateVolume(volRoute, -deltaVolume);
+ verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
+ .onRouteChanged(argThat(route ->
+ route.getId().equals(volRoute.getId())
+ && route.getVolume() == originalVolume));
+
+ mRouter2.unregisterCallback(mockCallback);
+ }
+
+ @Test
+ public void testControlVolumeWithManager() throws Exception {
+ MediaRouter2Manager.Callback managerCallback = mock(MediaRouter2Manager.Callback.class);
+ MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);
+
+ mManager.registerCallback(mExecutor, managerCallback);
+ mRouter2.registerCallback(mExecutor, mockCallback);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CONTROL_CATEGORIES_ALL);
+
+ MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
+ int originalVolume = volRoute.getVolume();
+ int deltaVolume = (originalVolume == volRoute.getVolumeMax() ? -1 : 1);
+ int targetVolume = originalVolume + deltaVolume;
+
+ mManager.requestSetVolume(volRoute, targetVolume);
+ verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
+ .onRouteChanged(argThat(route ->
+ route.getId().equals(volRoute.getId())
+ && route.getVolume() == targetVolume));
+
+ mManager.requestUpdateVolume(volRoute, -deltaVolume);
+ verify(mockCallback, timeout(TIMEOUT_MS).atLeastOnce())
+ .onRouteChanged(argThat(route ->
+ route.getId().equals(volRoute.getId())
+ && route.getVolume() == originalVolume));
+
+ mRouter2.unregisterCallback(mockCallback);
+ mManager.unregisterCallback(managerCallback);
+ }
+
+ @Test
+ public void testVolumeHandling() throws Exception {
+ MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class);
+ mRouter2.registerCallback(mExecutor, mockCallback);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CONTROL_CATEGORIES_ALL);
+
+ MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME);
+ MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
+
+ assertEquals(PLAYBACK_VOLUME_FIXED, fixedVolumeRoute.getVolumeHandling());
+ assertEquals(PLAYBACK_VOLUME_VARIABLE, variableVolumeRoute.getVolumeHandling());
+ assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax());
+
+ mRouter2.unregisterCallback(mockCallback);
+ }
+
+ Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> controlCategories) throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+ MediaRouter2.Callback callback = new MediaRouter2.Callback() {
+ @Override
+ public void onRoutesChanged(List<MediaRoute2Info> routes) {
+ if (routes.size() > 0) latch.countDown();
+ }
+ };
+ mRouter2.setControlCategories(controlCategories);
+ mRouter2.registerCallback(mExecutor, callback);
+ try {
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ return createRouteMap(mRouter2.getRoutes());
+ } finally {
+ mRouter2.unregisterCallback(callback);
+ }
+ }
+
+ Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> controlCategories)
+ throws Exception {
+ CountDownLatch latch = new CountDownLatch(1);
+
+ // Dummy callback is required to send control category info.
+ MediaRouter2.Callback routerCallback = new MediaRouter2.Callback();
+ MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
+ @Override
+ public void onRoutesChanged(List<MediaRoute2Info> routes) {
+ if (routes.size() > 0) latch.countDown();
+ }
+ };
+ mManager.registerCallback(mExecutor, managerCallback);
+ mRouter2.setControlCategories(controlCategories);
+ mRouter2.registerCallback(mExecutor, routerCallback);
+ try {
+ latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ return createRouteMap(mManager.getAvailableRoutes(mPackageName));
+ } finally {
+ mRouter2.unregisterCallback(routerCallback);
+ mManager.unregisterCallback(managerCallback);
+ }
+ }
+
// Helper for getting routes easily
static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
Map<String, MediaRoute2Info> routeMap = new HashMap<>();
diff --git a/mime/java-res/vendor.mime.types b/mime/java-res/vendor.mime.types
index 06939c5..afb8f9e 100644
--- a/mime/java-res/vendor.mime.types
+++ b/mime/java-res/vendor.mime.types
@@ -39,6 +39,3 @@
#
# Add your custom mappings below this line (with no "#" at the start of the line):
-test/mimeA extA extB extX
-?test/mimeC ?extX ?extY ?extZ
-test/mimeB extC ?extD extF
diff --git a/packages/BackupEncryption/Android.bp b/packages/BackupEncryption/Android.bp
index 342d796..68e937c 100644
--- a/packages/BackupEncryption/Android.bp
+++ b/packages/BackupEncryption/Android.bp
@@ -17,8 +17,7 @@
android_app {
name: "BackupEncryption",
srcs: ["src/**/*.java"],
- libs: ["backup-encryption-protos"],
- static_libs: ["backuplib"],
+ static_libs: ["backup-encryption-protos", "backuplib"],
optimize: { enabled: false },
platform_apis: true,
certificate: "platform",
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/EncryptionKeyHelper.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/EncryptionKeyHelper.java
new file mode 100644
index 0000000..2035b66
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/EncryptionKeyHelper.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption;
+
+import android.content.Context;
+import android.security.keystore.recovery.InternalRecoveryServiceException;
+import android.security.keystore.recovery.RecoveryController;
+
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager;
+import com.android.server.backup.encryption.keys.TertiaryKeyManager;
+import com.android.server.backup.encryption.keys.TertiaryKeyRotationScheduler;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+class EncryptionKeyHelper {
+ private static SecureRandom sSecureRandom = new SecureRandom();
+
+ private final Context mContext;
+ private final RecoverableKeyStoreSecondaryKeyManager
+ .RecoverableKeyStoreSecondaryKeyManagerProvider
+ mSecondaryKeyManagerProvider;
+
+ EncryptionKeyHelper(Context context) {
+ mContext = context;
+ mSecondaryKeyManagerProvider =
+ () ->
+ new RecoverableKeyStoreSecondaryKeyManager(
+ RecoveryController.getInstance(mContext), sSecureRandom);
+ }
+
+ RecoverableKeyStoreSecondaryKeyManager
+ .RecoverableKeyStoreSecondaryKeyManagerProvider getKeyManagerProvider() {
+ return mSecondaryKeyManagerProvider;
+ }
+
+ RecoverableKeyStoreSecondaryKey getActiveSecondaryKey()
+ throws UnrecoverableKeyException, InternalRecoveryServiceException {
+ String keyAlias = CryptoSettings.getInstance(mContext).getActiveSecondaryKeyAlias().get();
+ return mSecondaryKeyManagerProvider.get().get(keyAlias).get();
+ }
+
+ SecretKey getTertiaryKey(
+ String packageName,
+ RecoverableKeyStoreSecondaryKey secondaryKey)
+ throws IllegalBlockSizeException, InvalidAlgorithmParameterException,
+ NoSuchAlgorithmException, IOException, NoSuchPaddingException,
+ InvalidKeyException {
+ TertiaryKeyManager tertiaryKeyManager =
+ new TertiaryKeyManager(
+ mContext,
+ sSecureRandom,
+ TertiaryKeyRotationScheduler.getInstance(mContext),
+ secondaryKey,
+ packageName);
+ return tertiaryKeyManager.getKey();
+ }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java
new file mode 100644
index 0000000..1d841b4
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption;
+
+import static com.android.server.backup.encryption.BackupEncryptionService.TAG;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import com.android.server.backup.encryption.client.CryptoBackupServer;
+import com.android.server.backup.encryption.keys.KeyWrapUtils;
+import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+import com.android.server.backup.encryption.tasks.EncryptedKvBackupTask;
+import com.android.server.backup.encryption.tasks.EncryptedKvRestoreTask;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.Map;
+
+public class KeyValueEncrypter {
+ private final Context mContext;
+ private final EncryptionKeyHelper mKeyHelper;
+
+ public KeyValueEncrypter(Context context) {
+ mContext = context;
+ mKeyHelper = new EncryptionKeyHelper(mContext);
+ }
+
+ public void encryptKeyValueData(
+ String packageName, ParcelFileDescriptor inputFd, OutputStream outputStream)
+ throws Exception {
+ EncryptedKvBackupTask.EncryptedKvBackupTaskFactory backupTaskFactory =
+ new EncryptedKvBackupTask.EncryptedKvBackupTaskFactory();
+ EncryptedKvBackupTask backupTask =
+ backupTaskFactory.newInstance(
+ mContext,
+ new SecureRandom(),
+ new FileBackupServer(outputStream),
+ CryptoSettings.getInstance(mContext),
+ mKeyHelper.getKeyManagerProvider(),
+ inputFd,
+ packageName);
+ backupTask.performBackup(/* incremental */ false);
+ }
+
+ public void decryptKeyValueData(String packageName,
+ InputStream encryptedInputStream, ParcelFileDescriptor outputFd) throws Exception {
+ RecoverableKeyStoreSecondaryKey secondaryKey = mKeyHelper.getActiveSecondaryKey();
+
+ EncryptedKvRestoreTask.EncryptedKvRestoreTaskFactory restoreTaskFactory =
+ new EncryptedKvRestoreTask.EncryptedKvRestoreTaskFactory();
+ EncryptedKvRestoreTask restoreTask =
+ restoreTaskFactory.newInstance(
+ mContext,
+ mKeyHelper.getKeyManagerProvider(),
+ new InputStreamFullRestoreDownloader(encryptedInputStream),
+ secondaryKey.getAlias(),
+ KeyWrapUtils.wrap(
+ secondaryKey.getSecretKey(),
+ mKeyHelper.getTertiaryKey(packageName, secondaryKey)));
+
+ restoreTask.getRestoreData(outputFd);
+ }
+
+ // TODO(b/142455725): Extract into a commong class.
+ private static class FileBackupServer implements CryptoBackupServer {
+ private static final String EMPTY_DOC_ID = "";
+
+ private final OutputStream mOutputStream;
+
+ FileBackupServer(OutputStream outputStream) {
+ mOutputStream = outputStream;
+ }
+
+ @Override
+ public String uploadIncrementalBackup(
+ String packageName,
+ String oldDocId,
+ byte[] diffScript,
+ WrappedKeyProto.WrappedKey tertiaryKey) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String uploadNonIncrementalBackup(
+ String packageName, byte[] data, WrappedKeyProto.WrappedKey tertiaryKey) {
+ try {
+ mOutputStream.write(data);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to write encrypted data to file: ", e);
+ }
+
+ return EMPTY_DOC_ID;
+ }
+
+ @Override
+ public void setActiveSecondaryKeyAlias(
+ String keyAlias, Map<String, WrappedKeyProto.WrappedKey> tertiaryKeys) {
+ // Do nothing.
+ }
+ }
+
+ // TODO(b/142455725): Extract into a commong class.
+ private static class InputStreamFullRestoreDownloader extends FullRestoreDownloader {
+ private final InputStream mInputStream;
+
+ InputStreamFullRestoreDownloader(InputStream inputStream) {
+ mInputStream = inputStream;
+ }
+
+ @Override
+ public int readNextChunk(byte[] buffer) throws IOException {
+ return mInputStream.read(buffer);
+ }
+
+ @Override
+ public void finish(FinishType finishType) {
+ try {
+ mInputStream.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Error while reading restore data");
+ }
+ }
+ }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/ClearCryptoStateTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/ClearCryptoStateTask.java
new file mode 100644
index 0000000..8f35db6
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/ClearCryptoStateTask.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+import android.content.Context;
+import android.util.Slog;
+
+import com.android.server.backup.encryption.CryptoSettings;
+import com.android.server.backup.encryption.chunking.ProtoStore;
+import com.android.server.backup.encryption.storage.BackupEncryptionDb;
+import com.android.server.backup.encryption.storage.EncryptionDbException;
+
+import java.io.IOException;
+
+/**
+ * Task to clear local crypto state.
+ *
+ * <p>Needs to run whenever the user changes their backup account.
+ */
+public class ClearCryptoStateTask {
+ private static final String TAG = "ClearCryptoStateTask";
+
+ private final Context mContext;
+ private final CryptoSettings mCryptoSettings;
+
+ /**
+ * A new instance.
+ *
+ * @param context for finding local storage.
+ * @param cryptoSettings to clear
+ */
+ public ClearCryptoStateTask(Context context, CryptoSettings cryptoSettings) {
+ mContext = context;
+ mCryptoSettings = cryptoSettings;
+ }
+
+ /** Deletes all local state for backup (not restore). */
+ public void run() {
+ Slog.d(TAG, "Clearing local crypto state.");
+ try {
+ BackupEncryptionDb.newInstance(mContext).clear();
+ } catch (EncryptionDbException e) {
+ Slog.e(TAG, "Error clearing encryption database", e);
+ }
+ mCryptoSettings.clearAllSettingsForBackup();
+ try {
+ ProtoStore.createChunkListingStore(mContext).deleteAllProtos();
+ } catch (IOException e) {
+ Slog.e(TAG, "Error clearing chunk listing store", e);
+ }
+ try {
+ ProtoStore.createKeyValueListingStore(mContext).deleteAllProtos();
+ } catch (IOException e) {
+ Slog.e(TAG, "Error clearing key-value store", e);
+ }
+ }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java
index 1d0224d..c3cb335 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java
@@ -18,27 +18,58 @@
import static com.android.server.backup.encryption.BackupEncryptionService.TAG;
+import android.app.backup.BackupTransport;
+import android.app.backup.RestoreDescription;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.encryption.KeyValueEncrypter;
import com.android.server.backup.transport.DelegatingTransport;
import com.android.server.backup.transport.TransportClient;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.atomic.AtomicReference;
+
/**
* This is an implementation of {@link IBackupTransport} that encrypts (or decrypts) the data when
* sending it (or receiving it) from the {@link IBackupTransport} returned by {@link
* TransportClient.connect(String)}.
*/
public class IntermediateEncryptingTransport extends DelegatingTransport {
+ private static final String BACKUP_TEMP_DIR = "backup";
+ private static final String RESTORE_TEMP_DIR = "restore";
+
private final TransportClient mTransportClient;
private final Object mConnectLock = new Object();
+ private final Context mContext;
private volatile IBackupTransport mRealTransport;
+ private AtomicReference<String> mNextRestorePackage = new AtomicReference<>();
+ private final KeyValueEncrypter mKeyValueEncrypter;
+ private final boolean mShouldEncrypt;
+
+ IntermediateEncryptingTransport(
+ TransportClient transportClient, Context context, boolean shouldEncrypt) {
+ this(transportClient, context, new KeyValueEncrypter(context), shouldEncrypt);
+ }
@VisibleForTesting
- IntermediateEncryptingTransport(TransportClient transportClient) {
+ IntermediateEncryptingTransport(
+ TransportClient transportClient, Context context, KeyValueEncrypter keyValueEncrypter,
+ boolean shouldEncrypt) {
mTransportClient = transportClient;
+ mContext = context;
+ mKeyValueEncrypter = keyValueEncrypter;
+ mShouldEncrypt = shouldEncrypt;
}
@Override
@@ -46,9 +77,116 @@
if (mRealTransport == null) {
connect();
}
+ Log.d(TAG, "real transport = " + mRealTransport.name());
return mRealTransport;
}
+ @Override
+ public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags)
+ throws RemoteException {
+ if (!mShouldEncrypt) {
+ return super.performBackup(packageInfo, inFd, flags);
+ }
+
+ File encryptedStorageFile = getBackupTempStorage(packageInfo.packageName);
+ if (encryptedStorageFile == null) {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ // Encrypt the backup data and write it into a temp file.
+ try (OutputStream encryptedOutput = new FileOutputStream(encryptedStorageFile)) {
+ mKeyValueEncrypter.encryptKeyValueData(packageInfo.packageName, inFd,
+ encryptedOutput);
+ } catch (Throwable e) {
+ Log.e(TAG, "Failed to encrypt backup data: ", e);
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ // Pass the temp file to the real transport for backup.
+ try (FileInputStream encryptedInput = new FileInputStream(encryptedStorageFile)) {
+ return super.performBackup(
+ packageInfo, ParcelFileDescriptor.dup(encryptedInput.getFD()), flags);
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to read encrypted data from temp storage: ", e);
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+ }
+
+ @Override
+ public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException {
+ if (!mShouldEncrypt) {
+ return super.getRestoreData(outFd);
+ }
+
+ String nextRestorePackage = mNextRestorePackage.get();
+ if (nextRestorePackage == null) {
+ Log.e(TAG, "No next restore package set");
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ File encryptedStorageFile = getRestoreTempStorage(nextRestorePackage);
+ if (encryptedStorageFile == null) {
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ // Get encrypted restore data from the real transport and write it into a temp file.
+ try (FileOutputStream outputStream = new FileOutputStream(encryptedStorageFile)) {
+ int status = super.getRestoreData(ParcelFileDescriptor.dup(outputStream.getFD()));
+ if (status != BackupTransport.TRANSPORT_OK) {
+ Log.e(TAG, "Failed to read restore data from transport, status = " + status);
+ return status;
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to write encrypted data to temp storage: ", e);
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ // Decrypt the data and write it into the fd given by the real transport.
+ try (InputStream inputStream = new FileInputStream(encryptedStorageFile)) {
+ mKeyValueEncrypter.decryptKeyValueData(nextRestorePackage, inputStream, outFd);
+ encryptedStorageFile.delete();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to decrypt restored data: ", e);
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ return BackupTransport.TRANSPORT_OK;
+ }
+
+ @Override
+ public RestoreDescription nextRestorePackage() throws RemoteException {
+ if (!mShouldEncrypt) {
+ return super.nextRestorePackage();
+ }
+
+ RestoreDescription restoreDescription = super.nextRestorePackage();
+ mNextRestorePackage.set(restoreDescription.getPackageName());
+
+ return restoreDescription;
+ }
+
+ @VisibleForTesting
+ protected File getBackupTempStorage(String packageName) {
+ return getTempStorage(packageName, BACKUP_TEMP_DIR);
+ }
+
+ @VisibleForTesting
+ protected File getRestoreTempStorage(String packageName) {
+ return getTempStorage(packageName, RESTORE_TEMP_DIR);
+ }
+
+ private File getTempStorage(String packageName, String operationType) {
+ File encryptedDir = new File(mContext.getFilesDir(), operationType);
+ encryptedDir.mkdir();
+ File encryptedFile = new File(encryptedDir, packageName);
+ try {
+ encryptedFile.createNewFile();
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to create temp file for encrypted data: ", e);
+ }
+ return encryptedFile;
+ }
+
private void connect() throws RemoteException {
Log.i(TAG, "connecting " + mTransportClient);
synchronized (mConnectLock) {
@@ -65,4 +203,9 @@
TransportClient getClient() {
return mTransportClient;
}
+
+ @VisibleForTesting
+ void setNextRestorePackage(String nextRestorePackage) {
+ mNextRestorePackage.set(nextRestorePackage);
+ }
}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java
index 6e6d571..7c4082c 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java
@@ -26,20 +26,20 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
+import com.android.internal.widget.LockPatternUtils;
import com.android.server.backup.transport.TransportClientManager;
import com.android.server.backup.transport.TransportStats;
import java.util.HashMap;
import java.util.Map;
-/**
- * Handles creation and cleanup of {@link IntermediateEncryptingTransport} instances.
- */
+/** Handles creation and cleanup of {@link IntermediateEncryptingTransport} instances. */
public class IntermediateEncryptingTransportManager {
private static final String CALLER = "IntermediateEncryptingTransportManager";
private final TransportClientManager mTransportClientManager;
private final Object mTransportsLock = new Object();
private final Map<ComponentName, IntermediateEncryptingTransport> mTransports = new HashMap<>();
+ private Context mContext;
@VisibleForTesting
IntermediateEncryptingTransportManager(TransportClientManager transportClientManager) {
@@ -48,6 +48,7 @@
public IntermediateEncryptingTransportManager(Context context) {
this(new TransportClientManager(UserHandle.myUserId(), context, new TransportStats()));
+ mContext = context;
}
/**
@@ -55,31 +56,42 @@
* provide a {@link IntermediateEncryptingTransport} which is an implementation of {@link
* IBackupTransport} that encrypts (or decrypts) the data when sending it (or receiving it) from
* the real {@link IBackupTransport}.
+ *
* @param intent {@link Intent} created with a call to {@link
- * TransportClientManager.getEncryptingTransportIntent(ComponentName)}.
+ * TransportClientManager.getEncryptingTransportIntent(ComponentName)}.
* @return
*/
public IntermediateEncryptingTransport get(Intent intent) {
Intent transportIntent = TransportClientManager.getRealTransportIntent(intent);
Log.i(TAG, "get: intent:" + intent + " transportIntent:" + transportIntent);
synchronized (mTransportsLock) {
- return mTransports.computeIfAbsent(transportIntent.getComponent(),
- c -> create(transportIntent));
+ return mTransports.computeIfAbsent(
+ transportIntent.getComponent(), c -> create(transportIntent));
}
}
- /**
- * Create an instance of {@link IntermediateEncryptingTransport}.
- */
+ /** Create an instance of {@link IntermediateEncryptingTransport}. */
private IntermediateEncryptingTransport create(Intent realTransportIntent) {
Log.d(TAG, "create: intent:" + realTransportIntent);
- return new IntermediateEncryptingTransport(mTransportClientManager.getTransportClient(
- realTransportIntent.getComponent(), realTransportIntent.getExtras(), CALLER));
+
+ LockPatternUtils patternUtils = new LockPatternUtils(mContext);
+ boolean shouldEncrypt =
+ realTransportIntent.getComponent().getClassName().contains("EncryptedLocalTransport")
+ && (patternUtils.isLockPatternEnabled(UserHandle.myUserId())
+ || patternUtils.isLockPasswordEnabled(UserHandle.myUserId()));
+
+ return new IntermediateEncryptingTransport(
+ mTransportClientManager.getTransportClient(
+ realTransportIntent.getComponent(),
+ realTransportIntent.getExtras(),
+ CALLER),
+ mContext,
+ shouldEncrypt);
}
/**
- * Cleanup the {@link IntermediateEncryptingTransport} which was created by a call to
- * {@link #get(Intent)} with this {@link Intent}.
+ * Cleanup the {@link IntermediateEncryptingTransport} which was created by a call to {@link
+ * #get(Intent)} with this {@link Intent}.
*/
public void cleanup(Intent intent) {
Intent transportIntent = TransportClientManager.getRealTransportIntent(intent);
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/ClearCryptoStateTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/ClearCryptoStateTaskTest.java
new file mode 100644
index 0000000..81bfce1
--- /dev/null
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/ClearCryptoStateTaskTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.server.backup.encryption.CryptoSettings;
+import com.android.server.backup.encryption.chunking.ProtoStore;
+import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunkListing;
+import com.android.server.backup.encryption.protos.nano.KeyValueListingProto.KeyValueListing;
+import com.android.server.backup.encryption.storage.BackupEncryptionDb;
+import com.android.server.backup.encryption.storage.TertiaryKey;
+import com.android.server.backup.encryption.storage.TertiaryKeysTable;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class ClearCryptoStateTaskTest {
+ private static final String TEST_PACKAGE_NAME = "com.android.example";
+
+ private ClearCryptoStateTask mClearCryptoStateTask;
+ private CryptoSettings mCryptoSettings;
+ private Context mApplication;
+
+ @Before
+ public void setUp() {
+ mApplication = ApplicationProvider.getApplicationContext();
+ mCryptoSettings = spy(CryptoSettings.getInstanceForTesting(mApplication));
+ mClearCryptoStateTask = new ClearCryptoStateTask(mApplication, mCryptoSettings);
+ }
+
+ @Test
+ public void run_clearsChunkListingProtoState() throws Exception {
+ String packageName = TEST_PACKAGE_NAME;
+ ChunkListing chunkListing = new ChunkListing();
+ ProtoStore.createChunkListingStore(mApplication).saveProto(packageName, chunkListing);
+
+ mClearCryptoStateTask.run();
+
+ assertThat(
+ ProtoStore.createChunkListingStore(mApplication)
+ .loadProto(packageName)
+ .isPresent())
+ .isFalse();
+ }
+
+ @Test
+ public void run_clearsKeyValueProtoState() throws Exception {
+ String packageName = TEST_PACKAGE_NAME;
+ KeyValueListing keyValueListing = new KeyValueListing();
+ ProtoStore.createKeyValueListingStore(mApplication).saveProto(packageName, keyValueListing);
+
+ mClearCryptoStateTask.run();
+
+ assertThat(
+ ProtoStore.createKeyValueListingStore(mApplication)
+ .loadProto(packageName)
+ .isPresent())
+ .isFalse();
+ }
+
+ @Test
+ public void run_clearsTertiaryKeysTable() throws Exception {
+ String secondaryKeyAlias = "bob";
+ TertiaryKeysTable tertiaryKeysTable =
+ BackupEncryptionDb.newInstance(mApplication).getTertiaryKeysTable();
+ tertiaryKeysTable.addKey(
+ new TertiaryKey(
+ secondaryKeyAlias, "packageName", /*wrappedKeyBytes=*/ new byte[0]));
+
+ mClearCryptoStateTask.run();
+
+ assertThat(tertiaryKeysTable.getAllKeys(secondaryKeyAlias)).isEmpty();
+ }
+
+ @Test
+ public void run_clearsSettings() {
+ mCryptoSettings.setSecondaryLastRotated(100001);
+
+ mClearCryptoStateTask.run();
+
+ assertThat(mCryptoSettings.getSecondaryLastRotated().isPresent()).isFalse();
+ }
+}
diff --git a/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java b/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java
index cc4b0ab..a85b2e4 100644
--- a/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java
+++ b/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java
@@ -18,43 +18,71 @@
import static junit.framework.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
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 android.app.backup.BackupTransport;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.encryption.KeyValueEncrypter;
import com.android.server.backup.transport.TransportClient;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.File;
+
@Presubmit
@RunWith(AndroidJUnit4.class)
public class IntermediateEncryptingTransportTest {
- @Mock private IBackupTransport mRealTransport;
- @Mock private TransportClient mTransportClient;
+ private static final String TEST_PACKAGE_NAME = "test_package";
private IntermediateEncryptingTransport mIntermediateEncryptingTransport;
+ private final PackageInfo mTestPackage = new PackageInfo();
+
+ @Mock private IBackupTransport mRealTransport;
+ @Mock private TransportClient mTransportClient;
+ @Mock private ParcelFileDescriptor mParcelFileDescriptor;
+ @Mock private KeyValueEncrypter mKeyValueEncrypter;
+ @Mock private Context mContext;
+
+ @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+ private File mTempFile;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mIntermediateEncryptingTransport = new IntermediateEncryptingTransport(mTransportClient);
+
+ mIntermediateEncryptingTransport =
+ new IntermediateEncryptingTransport(
+ mTransportClient, mContext, mKeyValueEncrypter, true);
+ mTestPackage.packageName = TEST_PACKAGE_NAME;
+ mTempFile = mTemporaryFolder.newFile();
+
+ when(mTransportClient.connect(anyString())).thenReturn(mRealTransport);
+ when(mRealTransport.getRestoreData(any())).thenReturn(BackupTransport.TRANSPORT_OK);
}
@Test
public void testGetDelegate_callsConnect() throws Exception {
- when(mTransportClient.connect(anyString())).thenReturn(mRealTransport);
-
IBackupTransport ret = mIntermediateEncryptingTransport.getDelegate();
assertEquals(mRealTransport, ret);
@@ -74,4 +102,79 @@
verify(mTransportClient, times(1)).connect(anyString());
verifyNoMoreInteractions(mTransportClient);
}
+
+ @Test
+ public void testPerformBackup_shouldEncryptTrue_encryptsDataAndPassesToDelegate()
+ throws Exception {
+ mIntermediateEncryptingTransport =
+ new TestIntermediateTransport(mTransportClient, mContext, mKeyValueEncrypter, true);
+
+ mIntermediateEncryptingTransport.performBackup(mTestPackage, mParcelFileDescriptor, 0);
+
+ verify(mKeyValueEncrypter, times(1))
+ .encryptKeyValueData(eq(TEST_PACKAGE_NAME), eq(mParcelFileDescriptor), any());
+ verify(mRealTransport, times(1)).performBackup(eq(mTestPackage), any(), eq(0));
+ }
+
+ @Test
+ public void testPerformBackup_shouldEncryptFalse_doesntEncryptDataAndPassedToDelegate()
+ throws Exception {
+ mIntermediateEncryptingTransport =
+ new TestIntermediateTransport(
+ mTransportClient, mContext, mKeyValueEncrypter, false);
+
+ mIntermediateEncryptingTransport.performBackup(mTestPackage, mParcelFileDescriptor, 0);
+
+ verifyZeroInteractions(mKeyValueEncrypter);
+ verify(mRealTransport, times(1))
+ .performBackup(eq(mTestPackage), eq(mParcelFileDescriptor), eq(0));
+ }
+
+ @Test
+ public void testGetRestoreData_shouldEncryptTrue_decryptsDataAndPassesToDelegate()
+ throws Exception {
+ mIntermediateEncryptingTransport =
+ new TestIntermediateTransport(mTransportClient, mContext, mKeyValueEncrypter, true);
+ mIntermediateEncryptingTransport.setNextRestorePackage(TEST_PACKAGE_NAME);
+
+ mIntermediateEncryptingTransport.getRestoreData(mParcelFileDescriptor);
+
+ verify(mKeyValueEncrypter, times(1))
+ .decryptKeyValueData(eq(TEST_PACKAGE_NAME), any(), eq(mParcelFileDescriptor));
+ verify(mRealTransport, times(1)).getRestoreData(any());
+ }
+
+ @Test
+ public void testGetRestoreData_shouldEncryptFalse_doesntDecryptDataAndPassesToDelegate()
+ throws Exception {
+ mIntermediateEncryptingTransport =
+ new TestIntermediateTransport(
+ mTransportClient, mContext, mKeyValueEncrypter, false);
+ mIntermediateEncryptingTransport.setNextRestorePackage(TEST_PACKAGE_NAME);
+
+ mIntermediateEncryptingTransport.getRestoreData(mParcelFileDescriptor);
+
+ verifyZeroInteractions(mKeyValueEncrypter);
+ verify(mRealTransport, times(1)).getRestoreData(eq(mParcelFileDescriptor));
+ }
+
+ private final class TestIntermediateTransport extends IntermediateEncryptingTransport {
+ TestIntermediateTransport(
+ TransportClient transportClient,
+ Context context,
+ KeyValueEncrypter keyValueEncrypter,
+ boolean shouldEncrypt) {
+ super(transportClient, context, keyValueEncrypter, shouldEncrypt);
+ }
+
+ @Override
+ protected File getBackupTempStorage(String packageName) {
+ return mTempFile;
+ }
+
+ @Override
+ protected File getRestoreTempStorage(String packageName) {
+ return mTempFile;
+ }
+ }
}
diff --git a/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml
index 72ec8d8..94f5b96 100644
--- a/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_left_navigation_bar.xml
@@ -48,6 +48,18 @@
/>
<com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/grid"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ systemui:intent="intent:#Intent;component=com.android.car.home/.AppGridActivity;end"
+ systemui:longIntent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ android:src="@drawable/car_ic_apps"
+ android:background="?android:attr/selectableItemBackground"
+ android:paddingTop="30dp"
+ android:paddingBottom="30dp"
+ />
+
+ <com.android.systemui.statusbar.car.CarNavigationButton
android:id="@+id/hvac"
android:layout_height="wrap_content"
android:layout_width="match_parent"
@@ -58,6 +70,7 @@
android:paddingTop="30dp"
android:paddingBottom="30dp"
/>
+
</LinearLayout>
<LinearLayout
@@ -78,6 +91,7 @@
android:alpha="0.7"
/>
+
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
index 897976f..18ae582 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -22,6 +22,8 @@
android:layout_height="match_parent"
android:background="@drawable/system_bar_background"
android:orientation="vertical">
+ <!--The 20dp padding is the difference between the background selected icon size and the ripple
+ that was chosen, thus it's a hack to make it look pretty and not an official margin value-->
<LinearLayout
android:id="@id/nav_buttons"
android:layout_width="match_parent"
@@ -37,7 +39,6 @@
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
systemui:icon="@drawable/car_ic_overview"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
- systemui:longIntent="intent:#Intent;action=com.google.android.demandspace.START;end"
systemui:selectedIcon="@drawable/car_ic_overview_selected"
systemui:useMoreIcon="false"
/>
@@ -108,13 +109,11 @@
android:layout_height="match_parent"
android:layout_weight="1"/>
- <!-- Click handling will be initialized in CarNavigationBarView because its
- id = notifications which is treated special for the opening of the notification panel
- -->
<com.android.systemui.statusbar.car.CarNavigationButton
android:id="@+id/notifications"
style="@style/NavigationBarButton"
- android:src="@drawable/car_ic_notification"
+ systemui:icon="@drawable/car_ic_notification"
+ systemui:longIntent="intent:#Intent;component=com.android.car.bugreport/.BugReportActivity;end"
systemui:selectedIcon="@drawable/car_ic_notification_selected"
systemui:useMoreIcon="false"
/>
@@ -124,13 +123,13 @@
android:layout_height="match_parent"
android:layout_weight="1"/>
- <com.android.systemui.statusbar.car.CarFacetButton
+ <com.android.systemui.statusbar.car.AssitantButton
android:id="@+id/assist"
style="@style/NavigationBarButton"
systemui:icon="@drawable/ic_mic_white"
- systemui:intent="intent:#Intent;action=com.google.android.demandspace.START;end"
systemui:useMoreIcon="false"
/>
+
</LinearLayout>
<LinearLayout
@@ -138,10 +137,11 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:paddingStart="@*android:dimen/car_keyline_1"
- android:paddingEnd="@*android:dimen/car_keyline_1"
+ android:paddingStart="@dimen/car_keyline_1"
+ android:paddingEnd="@dimen/car_keyline_1"
android:gravity="center"
android:visibility="gone">
+
</LinearLayout>
-</com.android.systemui.statusbar.car.CarNavigationBarView>
+</com.android.systemui.statusbar.car.CarNavigationBarView>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml
index 72ec8d8..d36d1d6 100644
--- a/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_right_navigation_bar.xml
@@ -25,6 +25,9 @@
android:orientation="vertical"
android:background="@drawable/system_bar_background">
+ <!-- phone.NavigationBarView has rot0 and rot90 but we expect the car head unit to have a fixed
+ rotation so skip this level of the hierarchy.
+ -->
<LinearLayout
android:layout_height="match_parent"
android:layout_width="match_parent"
@@ -48,6 +51,18 @@
/>
<com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/grid"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ systemui:intent="intent:#Intent;component=com.android.car.home/.AppGridActivity;launchFlags=0x14000000;end"
+ systemui:longIntent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ android:src="@drawable/car_ic_apps"
+ android:background="?android:attr/selectableItemBackground"
+ android:paddingTop="30dp"
+ android:paddingBottom="30dp"
+ />
+
+ <com.android.systemui.statusbar.car.CarNavigationButton
android:id="@+id/hvac"
android:layout_height="wrap_content"
android:layout_width="match_parent"
@@ -58,6 +73,7 @@
android:paddingTop="30dp"
android:paddingBottom="30dp"
/>
+
</LinearLayout>
<LinearLayout
@@ -78,6 +94,7 @@
android:alpha="0.7"
/>
+
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:textAppearance="@style/TextAppearance.StatusBar.Clock"
diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml
new file mode 100644
index 0000000..8247211
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<com.android.systemui.statusbar.car.CarNavigationBarView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/car_top_bar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/system_bar_background"
+ android:orientation="vertical">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1">
+
+ <FrameLayout
+ android:id="@+id/left_hvac_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentStart="true"
+ >
+
+ <com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/hvacleft"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null"
+ systemui:broadcast="true"
+ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+ />
+
+ <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+ android:id="@+id/lefttext"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="@*android:dimen/car_padding_4"
+ android:paddingEnd="16dp"
+ android:gravity="center_vertical|start"
+ android:minEms="4"
+ android:textAppearance="@style/TextAppearance.CarStatus"
+ systemui:hvacAreaId="49"
+ systemui:hvacMaxText="@string/hvac_max_text"
+ systemui:hvacMaxValue="@dimen/hvac_max_value"
+ systemui:hvacMinText="@string/hvac_min_text"
+ systemui:hvacMinValue="@dimen/hvac_min_value"
+ systemui:hvacPivotOffset="60dp"
+ systemui:hvacPropertyId="358614275"
+ systemui:hvacTempFormat="%.0f\u00B0"
+ />
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/clock_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_centerInParent="true">
+ <com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/qs"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null"/>
+ <com.android.systemui.statusbar.policy.Clock
+ android:id="@+id/clock"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:elevation="5dp"
+ android:singleLine="true"
+ android:textAppearance="@style/TextAppearance.StatusBar.Clock"/>
+ </FrameLayout>
+
+ <LinearLayout
+ android:id="@+id/system_icon_area"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:layout_toEndOf="@+id/clock_container"
+ android:paddingStart="@*android:dimen/car_padding_1"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ >
+
+ <include
+ layout="@layout/system_icons"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingStart="4dp"
+ android:gravity="center_vertical"
+ />
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/right_hvac_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_alignParentEnd="true"
+ >
+
+ <com.android.systemui.statusbar.car.CarNavigationButton
+ android:id="@+id/hvacright"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null"
+ systemui:broadcast="true"
+ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+ />
+
+ <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+ android:id="@+id/righttext"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="16dp"
+ android:paddingEnd="@*android:dimen/car_padding_4"
+ android:gravity="center_vertical|end"
+ android:minEms="4"
+ android:textAppearance="@style/TextAppearance.CarStatus"
+ systemui:hvacAreaId="68"
+ systemui:hvacMaxText="@string/hvac_max_text"
+ systemui:hvacMaxValue="@dimen/hvac_max_value"
+ systemui:hvacMinText="@string/hvac_min_text"
+ systemui:hvacMinValue="@dimen/hvac_min_value"
+ systemui:hvacPivotOffset="60dp"
+ systemui:hvacPropertyId="358614275"
+ systemui:hvacTempFormat="%.0f\u00B0"
+ />
+ </FrameLayout>
+ </RelativeLayout>
+
+</com.android.systemui.statusbar.car.CarNavigationBarView>
diff --git a/packages/CarSystemUI/res/layout/super_status_bar.xml b/packages/CarSystemUI/res/layout/super_status_bar.xml
index 08d48bf..37cd1d4 100644
--- a/packages/CarSystemUI/res/layout/super_status_bar.xml
+++ b/packages/CarSystemUI/res/layout/super_status_bar.xml
@@ -69,10 +69,10 @@
android:visibility="gone"
/>
- <include layout="@layout/car_top_navigation_bar"
+ <FrameLayout
+ android:id="@+id/car_top_navigation_bar_container"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- />
+ android:layout_height="wrap_content"/>
</LinearLayout>
<include layout="@layout/brightness_mirror"/>
diff --git a/packages/CarSystemUI/res/values/dimens.xml b/packages/CarSystemUI/res/values/dimens.xml
index fb422af..e2da91b 100644
--- a/packages/CarSystemUI/res/values/dimens.xml
+++ b/packages/CarSystemUI/res/values/dimens.xml
@@ -120,4 +120,73 @@
<dimen name="notification_shade_handle_bar_margin_bottom">10dp</dimen>
<dimen name="notification_shade_list_padding_bottom">50dp</dimen>
+ <!-- The alpha for the scrim behind the notification shade. This value is 1 so that the
+ scrim has no transparency. -->
+ <item name="scrim_behind_alpha" format="float" type="dimen">1.0</item>
+
+ <!-- The width of panel holding the notification card. -->
+ <dimen name="notification_panel_width">522dp</dimen>
+
+ <!-- The width of the quick settings panel. -1 for match_parent. -->
+ <dimen name="qs_panel_width">-1px</dimen>
+
+ <!-- Height of a small notification in the status bar-->
+ <dimen name="notification_min_height">192dp</dimen>
+
+ <!-- Height of a small notification in the status bar which was used before android N -->
+ <dimen name="notification_min_height_legacy">192dp</dimen>
+
+ <!-- Height of a large notification in the status bar -->
+ <dimen name="notification_max_height">400dp</dimen>
+
+ <!-- Height of a heads up notification in the status bar for legacy custom views -->
+ <dimen name="notification_max_heads_up_height_legacy">400dp</dimen>
+
+ <!-- Height of a heads up notification in the status bar -->
+ <dimen name="notification_max_heads_up_height">400dp</dimen>
+
+ <!-- Height of the status bar header bar -->
+ <dimen name="status_bar_header_height">54dp</dimen>
+
+ <!-- The height of the divider between the individual notifications. -->
+ <dimen name="notification_divider_height">16dp</dimen>
+
+ <!-- The height of the divider between the individual notifications when the notification
+ wants it to be increased. This value is the same as notification_divider_height so that
+ the spacing between all notifications will always be the same. -->
+ <dimen name="notification_divider_height_increased">@dimen/notification_divider_height</dimen>
+
+ <!-- The alpha of the dividing line between child notifications of a notification group. -->
+ <item name="notification_divider_alpha" format="float" type="dimen">1.0</item>
+
+ <!-- The width of each individual notification card. -->
+ <dimen name="notification_child_width">522dp</dimen>
+
+ <!-- The top margin of the notification panel. -->
+ <dimen name="notification_panel_margin_top">32dp</dimen>
+
+ <!-- The bottom margin of the panel that holds the list of notifications. -->
+ <dimen name="notification_panel_margin_bottom">@dimen/notification_divider_height</dimen>
+
+ <!-- The corner radius of the shadow behind the notification. -->
+ <dimen name="notification_shadow_radius">16dp</dimen>
+
+ <!-- The amount of space below the notification list. This value is 0 so the list scrolls
+ all the way to the bottom. -->
+ <dimen name="close_handle_underlap">0dp</dimen>
+
+ <!-- The height of the divider between the individual notifications in a notification group. -->
+ <dimen name="notification_children_container_divider_height">1dp</dimen>
+
+ <!-- The height of the header for a container containing child notifications. -->
+ <dimen name="notification_children_container_header_height">76dp</dimen>
+
+ <!-- The top margin for the notification children container in its non-expanded form. This
+ value is smaller than notification_children_container_header_height to bring the first
+ child closer so there is less wasted space. -->
+ <dimen name="notification_children_container_margin_top">68dp</dimen>
+
+ <!-- The height of the quick settings footer that holds the user switcher, settings icon,
+ etc. in the car setting.-->
+ <dimen name="qs_footer_height">74dp</dimen>
</resources>
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
index c7654e8..be2d542 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIFactory.java
@@ -23,8 +23,6 @@
import com.android.systemui.statusbar.car.CarFacetButtonController;
import com.android.systemui.statusbar.car.CarStatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.volume.CarVolumeDialogComponent;
-import com.android.systemui.volume.VolumeDialogComponent;
import javax.inject.Singleton;
@@ -57,10 +55,6 @@
return new CarStatusBarKeyguardViewManager(context, viewMediatorCallback, lockPatternUtils);
}
- public VolumeDialogComponent createVolumeDialogComponent(SystemUI systemUi, Context context) {
- return new CarVolumeDialogComponent(systemUi, context);
- }
-
@Singleton
@Component(modules = ContextHolder.class)
public interface CarDependencyComponent {
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
index 5ec1bae..b1067f8 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIModule.java
@@ -36,6 +36,8 @@
import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.volume.CarVolumeDialogComponent;
+import com.android.systemui.volume.VolumeDialogComponent;
import javax.inject.Named;
import javax.inject.Singleton;
@@ -102,4 +104,8 @@
@IntoMap
@ClassKey(StatusBar.class)
public abstract SystemUI providesStatusBar(CarStatusBar statusBar);
+
+ @Binds
+ abstract VolumeDialogComponent bindVolumeDialogComponent(
+ CarVolumeDialogComponent carVolumeDialogComponent);
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
index c205bb4..53a88a9 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
@@ -20,6 +20,7 @@
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationData;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -34,8 +35,8 @@
public class CarNotificationEntryManager extends NotificationEntryManager {
@Inject
- public CarNotificationEntryManager(NotificationData notificationData) {
- super(notificationData);
+ public CarNotificationEntryManager(NotificationData notificationData, NotifLog notifLog) {
+ super(notificationData, notifLog);
}
@Override
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index cc6e842..90aba2f 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -108,10 +108,12 @@
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.LightBarController;
@@ -161,9 +163,11 @@
private BatteryMeterView mBatteryMeterView;
private Drawable mNotificationPanelBackground;
+ private ViewGroup mTopNavigationBarContainer;
private ViewGroup mNavigationBarWindow;
private ViewGroup mLeftNavigationBarWindow;
private ViewGroup mRightNavigationBarWindow;
+ private CarNavigationBarView mTopNavigationBarView;
private CarNavigationBarView mNavigationBarView;
private CarNavigationBarView mLeftNavigationBarView;
private CarNavigationBarView mRightNavigationBarView;
@@ -175,7 +179,7 @@
private CarFacetButtonController mCarFacetButtonController;
private ActivityManagerWrapper mActivityManagerWrapper;
private DeviceProvisionedController mDeviceProvisionedController;
- private boolean mDeviceIsProvisioned = true;
+ private boolean mDeviceIsSetUpForUser = true;
private HvacController mHvacController;
private DrivingStateHelper mDrivingStateHelper;
private PowerManagerHelper mPowerManagerHelper;
@@ -196,6 +200,9 @@
private boolean mNotificationListAtBottom;
// Was the notification list at the bottom when the user first touched the screen
private boolean mNotificationListAtBottomAtTimeOfTouch;
+ // To be attached to the top navigation bar (i.e. status bar) to pull down the notification
+ // panel.
+ private View.OnTouchListener mTopNavBarNotificationTouchListener;
// To be attached to the navigation bars such that they can close the notification panel if
// it's open.
private View.OnTouchListener mNavBarNotificationTouchListener;
@@ -244,6 +251,7 @@
@Inject
public CarStatusBar(
+ Context context,
LightBarController lightBarController,
AutoHideController autoHideController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -296,8 +304,11 @@
NotificationListener notificationListener,
ConfigurationController configurationController,
StatusBarWindowController statusBarWindowController,
- StatusBarWindowViewController.Builder statusBarWindowViewControllerBuild) {
+ StatusBarWindowViewController.Builder statusBarWindowViewControllerBuild,
+ NotifLog notifLog,
+ DozeParameters dozeParameters) {
super(
+ context,
lightBarController,
autoHideController,
keyguardUpdateMonitor,
@@ -350,7 +361,9 @@
notificationListener,
configurationController,
statusBarWindowController,
- statusBarWindowViewControllerBuild);
+ statusBarWindowViewControllerBuild,
+ notifLog,
+ dozeParameters);
mNavigationBarController = navigationBarController;
}
@@ -359,7 +372,7 @@
// get the provisioned state before calling the parent class since it's that flow that
// builds the nav bar
mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
- mDeviceIsProvisioned = mDeviceProvisionedController.isDeviceProvisioned();
+ mDeviceIsSetUpForUser = mDeviceProvisionedController.isCurrentUserSetup();
// Keyboard related setup, before nav bars are created.
mHideNavBarForKeyboard = mContext.getResources().getBoolean(
@@ -371,6 +384,10 @@
mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
mScreenLifecycle.addObserver(mScreenObserver);
+ // Need to initialize HVAC controller before calling super.start - before system bars are
+ // created.
+ mHvacController = new HvacController(mContext);
+
super.start();
mTaskStackListener = new TaskStackListenerImpl();
mActivityManagerWrapper = ActivityManagerWrapper.getInstance();
@@ -389,25 +406,18 @@
mHvacController.connectToCarService();
- CarSystemUIFactory factory = SystemUIFactory.getInstance();
- if (!mDeviceIsProvisioned) {
- mDeviceProvisionedController.addCallback(
- new DeviceProvisionedController.DeviceProvisionedListener() {
- @Override
- public void onDeviceProvisionedChanged() {
- mHandler.post(() -> {
- // on initial boot we are getting a call even though the value
- // is the same so we are confirming the reset is needed
- boolean deviceProvisioned =
- mDeviceProvisionedController.isDeviceProvisioned();
- if (mDeviceIsProvisioned != deviceProvisioned) {
- mDeviceIsProvisioned = deviceProvisioned;
- restartNavBars();
- }
- });
- }
- });
- }
+ mDeviceProvisionedController.addCallback(
+ new DeviceProvisionedController.DeviceProvisionedListener() {
+ @Override
+ public void onUserSetupChanged() {
+ mHandler.post(() -> restartNavBarsIfNecessary());
+ }
+
+ @Override
+ public void onUserSwitched() {
+ mHandler.post(() -> restartNavBarsIfNecessary());
+ }
+ });
// Register a listener for driving state changes.
mDrivingStateHelper = new DrivingStateHelper(mContext, this::onDrivingStateChanged);
@@ -419,6 +429,14 @@
mSwitchToGuestTimer = new SwitchToGuestTimer(mContext);
}
+ private void restartNavBarsIfNecessary() {
+ boolean currentUserSetup = mDeviceProvisionedController.isCurrentUserSetup();
+ if (mDeviceIsSetUpForUser != currentUserSetup) {
+ mDeviceIsSetUpForUser = currentUserSetup;
+ restartNavBars();
+ }
+ }
+
/**
* Remove all content from navbars and rebuild them. Used to allow for different nav bars
* before and after the device is provisioned. . Also for change of density and font size.
@@ -427,8 +445,8 @@
// remove and reattach all hvac components such that we don't keep a reference to unused
// ui elements
mHvacController.removeAllComponents();
- addTemperatureViewToController(mStatusBarWindow);
mCarFacetButtonController.removeAll();
+
if (mNavigationBarWindow != null) {
mNavigationBarWindow.removeAllViews();
mNavigationBarView = null;
@@ -522,7 +540,6 @@
@Override
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
super.makeStatusBarView(result);
- mHvacController = new HvacController(mContext);
CarSystemUIFactory factory = SystemUIFactory.getInstance();
mCarFacetButtonController = factory.getCarDependencyComponent()
@@ -547,7 +564,8 @@
* touch listeners needed for opening and closing the notification panel
*/
private void connectNotificationsUI() {
- // Attached to the status bar to detect pull down of the notification shade.
+ // Attached to the top navigation bar (i.e. status bar) to detect pull down of the
+ // notification shade.
GestureDetector openGestureDetector = new GestureDetector(mContext,
new OpenNotificationGestureListener() {
@Override
@@ -580,6 +598,18 @@
GestureDetector handleBarCloseNotificationGestureDetector = new GestureDetector(mContext,
new HandleBarCloseNotificationGestureListener());
+ mTopNavBarNotificationTouchListener = (v, event) -> {
+ if (!mDeviceIsSetUpForUser) {
+ return true;
+ }
+ boolean consumed = openGestureDetector.onTouchEvent(event);
+ if (consumed) {
+ return true;
+ }
+ maybeCompleteAnimation(event);
+ return true;
+ };
+
mNavBarNotificationTouchListener =
(v, event) -> {
boolean consumed = navBarCloseNotificationGestureDetector.onTouchEvent(event);
@@ -590,21 +620,6 @@
return true;
};
- // The following are the ui elements that the user would call the status bar.
- // This will set the status bar so it they can make call backs.
- CarNavigationBarView topBar = mStatusBarWindow.findViewById(R.id.car_top_bar);
- topBar.setStatusBar(this);
- topBar.setStatusBarWindowTouchListener((v1, event1) -> {
-
- boolean consumed = openGestureDetector.onTouchEvent(event1);
- if (consumed) {
- return true;
- }
- maybeCompleteAnimation(event1);
- return true;
- }
- );
-
mNotificationClickHandlerFactory = new NotificationClickHandlerFactory(mBarService);
mNotificationClickHandlerFactory.registerClickListener((launchResult, alertEntry) -> {
if (launchResult == ActivityManager.START_TASK_TO_FRONT
@@ -911,23 +926,30 @@
}
private void buildNavBarContent() {
+ // Always build top bar.
+ buildTopBar((mDeviceIsSetUpForUser) ? R.layout.car_top_navigation_bar :
+ R.layout.car_top_navigation_bar_unprovisioned);
+
if (mShowBottom) {
- buildBottomBar((mDeviceIsProvisioned) ? R.layout.car_navigation_bar :
+ buildBottomBar((mDeviceIsSetUpForUser) ? R.layout.car_navigation_bar :
R.layout.car_navigation_bar_unprovisioned);
}
if (mShowLeft) {
- buildLeft((mDeviceIsProvisioned) ? R.layout.car_left_navigation_bar :
+ buildLeft((mDeviceIsSetUpForUser) ? R.layout.car_left_navigation_bar :
R.layout.car_left_navigation_bar_unprovisioned);
}
if (mShowRight) {
- buildRight((mDeviceIsProvisioned) ? R.layout.car_right_navigation_bar :
+ buildRight((mDeviceIsSetUpForUser) ? R.layout.car_right_navigation_bar :
R.layout.car_right_navigation_bar_unprovisioned);
}
}
private void buildNavBarWindows() {
+ mTopNavigationBarContainer = mStatusBarWindow
+ .findViewById(R.id.car_top_navigation_bar_container);
+
if (mShowBottom) {
mNavigationBarWindow = (ViewGroup) View.inflate(mContext,
R.layout.navigation_bar_window, null);
@@ -960,15 +982,25 @@
}
boolean isKeyboardVisible = (vis & InputMethodService.IME_VISIBLE) != 0;
- if (!isKeyboardVisible) {
- attachBottomNavBarWindow();
- } else {
- detachBottomNavBarWindow();
- }
+ showBottomNavBarWindow(isKeyboardVisible);
}
private void attachNavBarWindows() {
- attachBottomNavBarWindow();
+ if (mShowBottom && !mBottomNavBarVisible) {
+ mBottomNavBarVisible = true;
+
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+ PixelFormat.TRANSLUCENT);
+ lp.setTitle("CarNavigationBar");
+ lp.windowAnimations = 0;
+ mWindowManager.addView(mNavigationBarWindow, lp);
+ }
if (mShowLeft) {
int width = mContext.getResources().getDimensionPixelSize(
@@ -1006,47 +1038,32 @@
}
}
- /**
- * Attaches the bottom nav bar window. Can be extended to modify the specific behavior of
- * attaching the bottom nav bar.
- */
- protected void attachBottomNavBarWindow() {
+ private void showBottomNavBarWindow(boolean isKeyboardVisible) {
if (!mShowBottom) {
return;
}
- if (mBottomNavBarVisible) {
+ // If keyboard is visible and bottom nav bar not visible, this is the correct state, so do
+ // nothing. Same with if keyboard is not visible and bottom nav bar is visible.
+ if (isKeyboardVisible ^ mBottomNavBarVisible) {
return;
}
- mBottomNavBarVisible = true;
- WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
- PixelFormat.TRANSLUCENT);
- lp.setTitle("CarNavigationBar");
- lp.windowAnimations = 0;
- mWindowManager.addView(mNavigationBarWindow, lp);
+ mNavigationBarWindow.setVisibility(isKeyboardVisible ? View.GONE : View.VISIBLE);
+ mBottomNavBarVisible = !isKeyboardVisible;
}
- /**
- * Detaches the bottom nav bar window. Can be extended to modify the specific behavior of
- * detaching the bottom nav bar.
- */
- protected void detachBottomNavBarWindow() {
- if (!mShowBottom) {
- return;
+ private void buildTopBar(int layout) {
+ mTopNavigationBarContainer.removeAllViews();
+ View.inflate(mContext, layout, mTopNavigationBarContainer);
+ mTopNavigationBarView = (CarNavigationBarView) mTopNavigationBarContainer.getChildAt(0);
+ if (mTopNavigationBarView == null) {
+ Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_top_navigation_bar");
+ throw new RuntimeException("Unable to build top nav bar due to missing layout");
}
-
- if (!mBottomNavBarVisible) {
- return;
- }
- mBottomNavBarVisible = false;
- mWindowManager.removeView(mNavigationBarWindow);
+ mTopNavigationBarView.setStatusBar(this);
+ addTemperatureViewToController(mTopNavigationBarView);
+ mTopNavigationBarView.setStatusBarWindowTouchListener(mTopNavBarNotificationTouchListener);
}
private void buildBottomBar(int layout) {
@@ -1057,7 +1074,7 @@
mNavigationBarView = (CarNavigationBarView) mNavigationBarWindow.getChildAt(0);
if (mNavigationBarView == null) {
Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
- throw new RuntimeException("Unable to build botom nav bar due to missing layout");
+ throw new RuntimeException("Unable to build bottom nav bar due to missing layout");
}
mNavigationBarView.setStatusBar(this);
addTemperatureViewToController(mNavigationBarView);
@@ -1068,7 +1085,7 @@
View.inflate(mContext, layout, mLeftNavigationBarWindow);
mLeftNavigationBarView = (CarNavigationBarView) mLeftNavigationBarWindow.getChildAt(0);
if (mLeftNavigationBarView == null) {
- Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
+ Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_left_navigation_bar");
throw new RuntimeException("Unable to build left nav bar due to missing layout");
}
mLeftNavigationBarView.setStatusBar(this);
@@ -1081,7 +1098,7 @@
View.inflate(mContext, layout, mRightNavigationBarWindow);
mRightNavigationBarView = (CarNavigationBarView) mRightNavigationBarWindow.getChildAt(0);
if (mRightNavigationBarView == null) {
- Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_navigation_bar");
+ Log.e(TAG, "CarStatusBar failed inflate for R.layout.car_right_navigation_bar");
throw new RuntimeException("Unable to build right nav bar due to missing layout");
}
mRightNavigationBarView.setStatusBar(this);
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
index 743ab47..f7e5d01 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
@@ -34,6 +34,7 @@
import android.graphics.Rect;
import android.os.AsyncTask;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -52,6 +53,8 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Displays a GridLayout with icons for the users in the system to allow switching between users.
@@ -61,6 +64,7 @@
private UserSelectionListener mUserSelectionListener;
private UserAdapter mAdapter;
private CarUserManagerHelper mCarUserManagerHelper;
+ private UserManager mUserManager;
private Context mContext;
private final BroadcastReceiver mUserUpdateReceiver = new BroadcastReceiver() {
@@ -74,6 +78,7 @@
super(context, attrs);
mContext = context;
mCarUserManagerHelper = new CarUserManagerHelper(mContext);
+ mUserManager = UserManager.get(mContext);
addItemDecoration(new ItemSpacingDecoration(context.getResources().getDimensionPixelSize(
R.dimen.car_user_switcher_vertical_spacing_between_users)));
@@ -103,12 +108,23 @@
* @return the adapter
*/
public void buildAdapter() {
- List<UserRecord> userRecords = createUserRecords(mCarUserManagerHelper
- .getAllUsers());
+ List<UserRecord> userRecords = createUserRecords(getAllUsers());
mAdapter = new UserAdapter(mContext, userRecords);
super.setAdapter(mAdapter);
}
+ private List<UserInfo> getAllUsers() {
+ Stream<UserInfo> userListStream =
+ mUserManager.getUsers(/* excludeDying= */ true).stream();
+
+ if (UserManager.isHeadlessSystemUserMode()) {
+ userListStream =
+ userListStream.filter(userInfo -> userInfo.id != UserHandle.USER_SYSTEM);
+ }
+ userListStream = userListStream.filter(userInfo -> userInfo.supportsSwitchToByUser());
+ return userListStream.collect(Collectors.toList());
+ }
+
private List<UserRecord> createUserRecords(List<UserInfo> userInfoList) {
List<UserRecord> userRecords = new ArrayList<>();
@@ -173,7 +189,7 @@
private void onUsersUpdate() {
mAdapter.clearUsers();
- mAdapter.updateUsers(createUserRecords(mCarUserManagerHelper.getAllUsers()));
+ mAdapter.updateUsers(createUserRecords(getAllUsers()));
mAdapter.notifyDataSetChanged();
}
@@ -183,11 +199,9 @@
filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_INFO_CHANGED);
filter.addAction(Intent.ACTION_USER_SWITCHED);
- filter.addAction(Intent.ACTION_USER_STOPPED);
- filter.addAction(Intent.ACTION_USER_UNLOCKED);
mContext.registerReceiverAsUser(
mUserUpdateReceiver,
- UserHandle.ALL,
+ UserHandle.ALL, // Necessary because CarSystemUi lives in User 0
filter,
/* broadcastPermission= */ null,
/* scheduler= */ null);
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java
index ead1de2..88d641e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/OngoingPrivacyChip.java
@@ -47,6 +47,7 @@
public class OngoingPrivacyChip extends LinearLayout implements View.OnClickListener {
private Context mContext;
+ private Handler mHandler;
private LinearLayout mIconsContainer;
private List<PrivacyItem> mPrivacyItems;
@@ -88,6 +89,7 @@
private void init(Context context) {
mContext = context;
+ mHandler = new Handler(Looper.getMainLooper());
mPrivacyItems = new ArrayList<>();
sAppOpsController = Dependency.get(AppOpsController.class);
mUserManager = mContext.getSystemService(UserManager.class);
@@ -131,8 +133,7 @@
@Override
public void onClick(View v) {
updatePrivacyList();
- Handler mUiHandler = new Handler(Looper.getMainLooper());
- mUiHandler.post(() -> {
+ mHandler.post(() -> {
mActivityStarter.postStartActivityDismissingKeyguard(
new Intent(Intent.ACTION_REVIEW_ONGOING_PERMISSION_USAGE), 0);
});
@@ -152,21 +153,17 @@
}
private void updatePrivacyList() {
- mPrivacyItems = mCurrentUserIds.stream()
+ List<PrivacyItem> privacyItems = mCurrentUserIds.stream()
.flatMap(item -> sAppOpsController.getActiveAppOpsForUser(item).stream())
.filter(Objects::nonNull)
.map(item -> toPrivacyItem(item))
.filter(Objects::nonNull)
.collect(Collectors.toList());
- mPrivacyDialogBuilder = new PrivacyDialogBuilder(mContext, mPrivacyItems);
-
- Handler refresh = new Handler(Looper.getMainLooper());
- refresh.post(new Runnable() {
- @Override
- public void run() {
- updateView();
- }
- });
+ if (!privacyItems.equals(mPrivacyItems)) {
+ mPrivacyItems = privacyItems;
+ mPrivacyDialogBuilder = new PrivacyDialogBuilder(mContext, mPrivacyItems);
+ mHandler.post(this::updateView);
+ }
}
private PrivacyItem toPrivacyItem(AppOpItem appOpItem) {
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java
index 5ec7a77..820bea9 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyApplication.java
@@ -23,16 +23,20 @@
import android.graphics.drawable.Drawable;
import android.util.Log;
+import java.util.Objects;
+
/**
* Class to hold the data for the applications that are using the AppOps permissions.
*/
public class PrivacyApplication {
private static final String TAG = "PrivacyApplication";
+ private String mPackageName;
private Drawable mIcon;
private String mApplicationName;
public PrivacyApplication(String packageName, Context context) {
+ mPackageName = packageName;
try {
CarUserManagerHelper carUserManagerHelper = new CarUserManagerHelper(context);
ApplicationInfo app = context.getPackageManager()
@@ -59,4 +63,17 @@
public String getApplicationName() {
return mApplicationName;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PrivacyApplication that = (PrivacyApplication) o;
+ return mPackageName.equals(that.mPackageName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPackageName);
+ }
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java
index fca1373..d3e123e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/privacy/PrivacyItem.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.car.privacy;
+import java.util.Objects;
+
/**
* Class for holding the data of each privacy item displayed in {@link OngoingPrivacyDialog}
*/
@@ -43,4 +45,18 @@
public PrivacyType getPrivacyType() {
return mPrivacyType;
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ PrivacyItem that = (PrivacyItem) o;
+ return mPrivacyType == that.mPrivacyType
+ && mPrivacyApplication.equals(that.mPrivacyApplication);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPrivacyType, mPrivacyApplication);
+ }
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java
index 71cc19b..4d6af95 100644
--- a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java
@@ -19,15 +19,21 @@
import android.content.Context;
import com.android.systemui.SystemUI;
+import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.VolumeDialog;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
/**
* Allows for adding car specific dialog when the volume dialog is created.
*/
+@Singleton
public class CarVolumeDialogComponent extends VolumeDialogComponent {
- public CarVolumeDialogComponent(SystemUI sysui, Context context) {
- super(sysui, context);
+ @Inject
+ public CarVolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator) {
+ super(context, keyguardViewMediator);
}
protected VolumeDialog createDefault() {
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
index 81c5bcd..642dc82 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
@@ -35,7 +35,6 @@
import android.net.http.SslError;
import android.os.Bundle;
import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
import android.telephony.SubscriptionManager;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -477,11 +476,11 @@
}
private static void logd(String s) {
- Rlog.d(TAG, s);
+ Log.d(TAG, s);
}
private static void loge(String s) {
- Rlog.d(TAG, s);
+ Log.d(TAG, s);
}
}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
index 02c61d7..46b1d5f 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
@@ -19,7 +19,6 @@
import android.content.Intent;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
-import android.telephony.Rlog;
import android.text.TextUtils;
import android.util.Log;
@@ -27,7 +26,6 @@
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
/**
@@ -44,7 +42,7 @@
private static final String INTER_GROUP_DELIMITER = "\\s*:\\s*";
private static final String TAG = CustomConfigLoader.class.getSimpleName();
- private static final boolean VDBG = Rlog.isLoggable(TAG, Log.VERBOSE);
+ private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
/**
* loads and parses the carrier config, return a list of carrier action for the given signal
@@ -70,7 +68,7 @@
// return an empty list if no match found
List<Integer> actionList = new ArrayList<>();
if (carrierConfigManager == null) {
- Rlog.e(TAG, "load carrier config failure with carrier config manager uninitialized");
+ Log.e(TAG, "load carrier config failure with carrier config manager uninitialized");
return actionList;
}
PersistableBundle b = carrierConfigManager.getConfig();
@@ -101,8 +99,8 @@
.EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY, false));
break;
default:
- Rlog.e(TAG, "load carrier config failure with un-configured key: " +
- intent.getAction());
+ Log.e(TAG, "load carrier config failure with un-configured key: "
+ + intent.getAction());
break;
}
if (!ArrayUtils.isEmpty(configs)) {
@@ -111,12 +109,12 @@
matchConfig(config, arg1, arg2, actionList);
if (!actionList.isEmpty()) {
// return the first match
- if (VDBG) Rlog.d(TAG, "found match action list: " + actionList.toString());
+ if (VDBG) Log.d(TAG, "found match action list: " + actionList.toString());
return actionList;
}
}
}
- Rlog.d(TAG, "no matching entry for signal: " + intent.getAction() + "arg1: " + arg1
+ Log.d(TAG, "no matching entry for signal: " + intent.getAction() + "arg1: " + arg1
+ "arg2: " + arg2);
}
return actionList;
@@ -166,7 +164,7 @@
try {
actionList.add(Integer.parseInt(idx));
} catch (NumberFormatException e) {
- Rlog.e(TAG, "NumberFormatException(string: " + idx + " config:" + config + "): "
+ Log.e(TAG, "NumberFormatException(string: " + idx + " config:" + config + "): "
+ e);
}
}
diff --git a/packages/EncryptedLocalTransport/Android.bp b/packages/EncryptedLocalTransport/Android.bp
new file mode 100644
index 0000000..dd30ad1
--- /dev/null
+++ b/packages/EncryptedLocalTransport/Android.bp
@@ -0,0 +1,27 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_app {
+ name: "EncryptedLocalTransport",
+ srcs: ["src/**/*.java"],
+ optimize: {
+ proguard_flags_files: ["proguard.flags"],
+ },
+ static_libs: ["LocalTransport"],
+ platform_apis: true,
+ certificate: "platform",
+ privileged: true,
+}
diff --git a/packages/EncryptedLocalTransport/AndroidManifest.xml b/packages/EncryptedLocalTransport/AndroidManifest.xml
new file mode 100644
index 0000000..dc3617f
--- /dev/null
+++ b/packages/EncryptedLocalTransport/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2019 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.encryptedlocaltransport"
+ android:sharedUserId="android.uid.system" >
+
+
+ <application android:allowBackup="false" >
+ <!-- This service does not need to be exported because it shares uid with the system server
+ which is the only client. -->
+ <service android:name=".EncryptedLocalTransportService"
+ android:permission="android.permission.CONFIRM_FULL_BACKUP"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.backup.TRANSPORT_HOST" />
+ </intent-filter>
+ </service>
+
+ </application>
+</manifest>
diff --git a/packages/EncryptedLocalTransport/proguard.flags b/packages/EncryptedLocalTransport/proguard.flags
new file mode 100644
index 0000000..e4ce3c5
--- /dev/null
+++ b/packages/EncryptedLocalTransport/proguard.flags
@@ -0,0 +1,2 @@
+-keep class com.android.localTransport.EncryptedLocalTransport
+-keep class com.android.localTransport.EncryptedLocalTransportService
diff --git a/packages/EncryptedLocalTransport/src/com/android/encryptedlocaltransport/EncryptedLocalTransport.java b/packages/EncryptedLocalTransport/src/com/android/encryptedlocaltransport/EncryptedLocalTransport.java
new file mode 100644
index 0000000..3dd453e
--- /dev/null
+++ b/packages/EncryptedLocalTransport/src/com/android/encryptedlocaltransport/EncryptedLocalTransport.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.encryptedlocaltransport;
+
+import android.app.backup.BackupTransport;
+import android.app.backup.RestoreDescription;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.os.ParcelFileDescriptor;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructStat;
+import android.util.Log;
+
+import com.android.localtransport.LocalTransport;
+import com.android.localtransport.LocalTransportParameters;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+
+public class EncryptedLocalTransport extends LocalTransport {
+ private static final String TAG = "EncryptedLocalTransport";
+ private static final int BACKUP_BUFFER_SIZE = 32 * 1024; // 32 KB.
+
+ public EncryptedLocalTransport(Context context,
+ LocalTransportParameters parameters) {
+ super(context, parameters);
+ }
+
+ @Override
+ public int performBackup(
+ PackageInfo packageInfo, ParcelFileDescriptor data, int flags) {
+ File packageFile;
+ try {
+ StructStat stat = Os.fstat(data.getFileDescriptor());
+ if (stat.st_size > KEY_VALUE_BACKUP_SIZE_QUOTA) {
+ Log.w(TAG, "New datastore size " + stat.st_size
+ + " exceeds quota " + KEY_VALUE_BACKUP_SIZE_QUOTA);
+ return TRANSPORT_QUOTA_EXCEEDED;
+ }
+ } catch (ErrnoException e) {
+ Log.w(TAG, "Failed to stat the backup input file: ", e);
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ clearBackupData(packageInfo);
+
+ try (InputStream in = new FileInputStream(data.getFileDescriptor())) {
+ packageFile = new File(mCurrentSetIncrementalDir, packageInfo.packageName);
+ Files.copy(in, packageFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to save backup data to file: ", e);
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ return TRANSPORT_OK;
+ }
+
+ @Override
+ public int getRestoreData(ParcelFileDescriptor outFd) {
+ if (mRestorePackages == null) {
+ throw new IllegalStateException("startRestore not called");
+ }
+ if (mRestorePackage < 0) {
+ throw new IllegalStateException("nextRestorePackage not called");
+ }
+ if (mRestoreType != RestoreDescription.TYPE_KEY_VALUE) {
+ throw new IllegalStateException("getRestoreData(fd) for non-key/value dataset");
+ }
+
+ try(OutputStream out = new FileOutputStream(outFd.getFileDescriptor())) {
+ File packageFile = new File(mRestoreSetIncrementalDir,
+ mRestorePackages[mRestorePackage].packageName);
+ Files.copy(packageFile.toPath(), out);
+ } catch (IOException e) {
+ Log.d(TAG, "Failed to transfer restore data: " + e);
+ return BackupTransport.TRANSPORT_ERROR;
+ }
+
+ return BackupTransport.TRANSPORT_OK;
+ }
+
+ @Override
+ protected boolean hasRestoreDataForPackage(String packageName) {
+ File contents = (new File(mRestoreSetIncrementalDir, packageName));
+ return contents.exists() && contents.length() != 0;
+
+ }
+}
diff --git a/packages/EncryptedLocalTransport/src/com/android/encryptedlocaltransport/EncryptedLocalTransportService.java b/packages/EncryptedLocalTransport/src/com/android/encryptedlocaltransport/EncryptedLocalTransportService.java
new file mode 100644
index 0000000..952f90d
--- /dev/null
+++ b/packages/EncryptedLocalTransport/src/com/android/encryptedlocaltransport/EncryptedLocalTransportService.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.encryptedlocaltransport;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+import com.android.localtransport.LocalTransportParameters;
+
+public class EncryptedLocalTransportService extends Service {
+ private static EncryptedLocalTransport sTransport = null;
+
+ @Override
+ public void onCreate() {
+ if (sTransport == null) {
+ LocalTransportParameters parameters =
+ new LocalTransportParameters(getMainThreadHandler(), getContentResolver());
+ sTransport = new EncryptedLocalTransport(this, parameters);
+ }
+ sTransport.getParameters().start();
+ }
+
+ @Override
+ public void onDestroy() {
+ sTransport.getParameters().stop();
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return sTransport.getBinder();
+ }
+}
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 1b27b52..48d34ae 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -16,6 +16,7 @@
package com.android.externalstorage;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.usage.StorageStatsManager;
import android.content.ContentResolver;
@@ -298,6 +299,53 @@
return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
}
+ /**
+ * Check that the directory is the root of storage or blocked file from tree.
+ *
+ * @param docId the docId of the directory to be checked
+ * @return true, should be blocked from tree. Otherwise, false.
+ */
+ @Override
+ protected boolean shouldBlockFromTree(@NonNull String docId) {
+ try {
+ final File dir = getFileForDocId(docId, true /* visible */).getCanonicalFile();
+ if (!dir.isDirectory()) {
+ return false;
+ }
+
+ final String path = dir.getAbsolutePath();
+
+ // Block Download folder from tree
+ if (MediaStore.Downloads.isDownloadDir(path)) {
+ return true;
+ }
+
+ final ArrayMap<String, RootInfo> roots = new ArrayMap<>();
+
+ synchronized (mRootsLock) {
+ roots.putAll(mRoots);
+ }
+
+ // block root of storage
+ for (int i = 0; i < roots.size(); i++) {
+ RootInfo rootInfo = roots.valueAt(i);
+ // skip home root
+ if (TextUtils.equals(rootInfo.rootId, ROOT_ID_HOME)) {
+ continue;
+ }
+
+ // block the root of storage
+ if (TextUtils.equals(path, rootInfo.visiblePath.getAbsolutePath())) {
+ return true;
+ }
+ }
+ return false;
+ } catch (IOException e) {
+ throw new IllegalArgumentException(
+ "Failed to determine if " + docId + " should block from tree " + ": " + e);
+ }
+ }
+
@Override
protected String getDocIdForFile(File file) throws FileNotFoundException {
return getDocIdForFileMaybeCreate(file, false);
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
index 4408ef5..50f858e 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransport.java
@@ -16,6 +16,7 @@
package com.android.localtransport;
+import android.annotation.Nullable;
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
@@ -71,19 +72,19 @@
// Size quotas at reasonable values, similar to the current cloud-storage limits
private static final long FULL_BACKUP_SIZE_QUOTA = 25 * 1024 * 1024;
- private static final long KEY_VALUE_BACKUP_SIZE_QUOTA = 5 * 1024 * 1024;
+ protected static final long KEY_VALUE_BACKUP_SIZE_QUOTA = 5 * 1024 * 1024;
private Context mContext;
private File mDataDir;
private File mCurrentSetDir;
- private File mCurrentSetIncrementalDir;
+ protected File mCurrentSetIncrementalDir;
private File mCurrentSetFullDir;
- private PackageInfo[] mRestorePackages = null;
- private int mRestorePackage = -1; // Index into mRestorePackages
- private int mRestoreType;
+ protected PackageInfo[] mRestorePackages = null;
+ protected int mRestorePackage = -1; // Index into mRestorePackages
+ protected int mRestoreType;
private File mRestoreSetDir;
- private File mRestoreSetIncrementalDir;
+ protected File mRestoreSetIncrementalDir;
private File mRestoreSetFullDir;
// Additional bookkeeping for full backup
@@ -115,7 +116,7 @@
makeDataDirs();
}
- LocalTransportParameters getParameters() {
+ public LocalTransportParameters getParameters() {
return mParameters;
}
@@ -142,11 +143,18 @@
return null;
}
+ /** @removed Replaced with dataManagementIntentLabel in the API */
public String dataManagementLabel() {
return TRANSPORT_DATA_MANAGEMENT_LABEL;
}
@Override
+ @Nullable
+ public CharSequence dataManagementIntentLabel() {
+ return TRANSPORT_DATA_MANAGEMENT_LABEL;
+ }
+
+ @Override
public String transportDirName() {
return TRANSPORT_DIR_NAME;
}
@@ -537,14 +545,14 @@
int bytesLeft = numBytes;
while (bytesLeft > 0) {
try {
- int nRead = mSocketInputStream.read(mFullBackupBuffer, 0, bytesLeft);
- if (nRead < 0) {
- // Something went wrong if we expect data but saw EOD
- Log.w(TAG, "Unexpected EOD; failing backup");
- return TRANSPORT_ERROR;
- }
- mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead);
- bytesLeft -= nRead;
+ int nRead = mSocketInputStream.read(mFullBackupBuffer, 0, bytesLeft);
+ if (nRead < 0) {
+ // Something went wrong if we expect data but saw EOD
+ Log.w(TAG, "Unexpected EOD; failing backup");
+ return TRANSPORT_ERROR;
+ }
+ mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead);
+ bytesLeft -= nRead;
} catch (IOException e) {
Log.e(TAG, "Error handling backup data for " + mFullTargetPackage);
return TRANSPORT_ERROR;
@@ -620,20 +628,15 @@
}
if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
- boolean found = false;
+ boolean found;
while (++mRestorePackage < mRestorePackages.length) {
String name = mRestorePackages[mRestorePackage].packageName;
// If we have key/value data for this package, deliver that
// skip packages where we have a data dir but no actual contents
- String[] contents = (new File(mRestoreSetIncrementalDir, name)).list();
- if (contents != null && contents.length > 0) {
- if (DEBUG) {
- Log.v(TAG, " nextRestorePackage(TYPE_KEY_VALUE) @ "
- + mRestorePackage + " = " + name);
- }
+ found = hasRestoreDataForPackage(name);
+ if (found) {
mRestoreType = RestoreDescription.TYPE_KEY_VALUE;
- found = true;
}
if (!found) {
@@ -664,6 +667,18 @@
return RestoreDescription.NO_MORE_PACKAGES;
}
+ protected boolean hasRestoreDataForPackage(String packageName) {
+ String[] contents = (new File(mRestoreSetIncrementalDir, packageName)).list();
+ if (contents != null && contents.length > 0) {
+ if (DEBUG) {
+ Log.v(TAG, " nextRestorePackage(TYPE_KEY_VALUE) @ "
+ + mRestorePackage + " = " + packageName);
+ }
+ return true;
+ }
+ return false;
+ }
+
@Override
public int getRestoreData(ParcelFileDescriptor outFd) {
if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
diff --git a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
index 784be22..8b4db92 100644
--- a/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
+++ b/packages/LocalTransport/src/com/android/localtransport/LocalTransportParameters.java
@@ -22,7 +22,7 @@
import android.provider.Settings;
import android.util.KeyValueListParser;
-class LocalTransportParameters extends KeyValueSettingObserver {
+public class LocalTransportParameters extends KeyValueSettingObserver {
private static final String TAG = "LocalTransportParams";
private static final String SETTING = Settings.Secure.BACKUP_LOCAL_TRANSPORT_PARAMETERS;
private static final String KEY_FAKE_ENCRYPTION_FLAG = "fake_encryption_flag";
@@ -31,7 +31,7 @@
private boolean mFakeEncryptionFlag;
private boolean mIsNonIncrementalOnly;
- LocalTransportParameters(Handler handler, ContentResolver resolver) {
+ public LocalTransportParameters(Handler handler, ContentResolver resolver) {
super(handler, resolver, Settings.Secure.getUriFor(SETTING));
}
diff --git a/packages/PackageInstaller/res/values-television/themes.xml b/packages/PackageInstaller/res/values-television/themes.xml
new file mode 100644
index 0000000..5ae4957
--- /dev/null
+++ b/packages/PackageInstaller/res/values-television/themes.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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>
+
+ <style name="Theme.AlertDialogActivity.NoAnimation">
+ <item name="android:windowAnimationStyle">@null</item>
+ </style>
+
+ <style name="Theme.AlertDialogActivity"
+ parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
+
+ <style name="Theme.AlertDialogActivity.NoActionBar"
+ parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
+ </style>
+
+</resources>
diff --git a/packages/PrintSpooler/TEST_MAPPING b/packages/PrintSpooler/TEST_MAPPING
new file mode 100644
index 0000000..4fa8822
--- /dev/null
+++ b/packages/PrintSpooler/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsPrintTestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ }
+ ]
+ }
+ ]
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/OWNERS b/packages/SettingsProvider/src/android/provider/settings/OWNERS
new file mode 100644
index 0000000..541dd878
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/OWNERS
@@ -0,0 +1,5 @@
+# Please reach out to Android B&R when making Settings backup changes
+alsutton@google.com
+nathch@google.com
+rthakohov@google.com
+
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/DeviceSpecificSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/DeviceSpecificSettings.java
new file mode 100644
index 0000000..e425790
--- /dev/null
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/DeviceSpecificSettings.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.settings.backup;
+
+import android.provider.Settings;
+
+/** Device specific settings list */
+public class DeviceSpecificSettings {
+ /**
+ * The settings values which should only be restored if the target device is the
+ * same as the source device
+ *
+ * NOTE: Settings are backed up and restored in the order they appear
+ * in this array. If you have one setting depending on another,
+ * make sure that they are ordered appropriately.
+ *
+ * @hide
+ */
+ public static final String[] DEVICE_SPECIFIC_SETTINGS_TO_BACKUP = {
+ Settings.Secure.DISPLAY_DENSITY_FORCED,
+ };
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 67062b7..8c97057 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -107,9 +107,6 @@
Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD,
Settings.Secure.FACE_UNLOCK_APP_ENABLED,
Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
- Settings.Secure.ASSIST_GESTURE_ENABLED,
- Settings.Secure.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED,
- Settings.Secure.ASSIST_GESTURE_WAKE_ENABLED,
Settings.Secure.VR_DISPLAY_MODE,
Settings.Secure.NOTIFICATION_BADGING,
Settings.Secure.NOTIFICATION_DISMISS_RTL,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 976f336..ef67bbd 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -41,8 +41,8 @@
public class SecureSettingsValidators {
/**
* All settings in {@link Secure.SETTINGS_TO_BACKUP} and {@link
- * Secure.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP} array *must* have a non-null validator, otherwise
- * they won't be restored.
+ * DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP} array *must* have a non-null
+ * validator, otherwise they won't be restored.
*/
public static final Map<String, Validator> VALIDATORS = new ArrayMap<>();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 5e2b7c8..17c621e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -51,6 +51,7 @@
import com.android.internal.util.XmlUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockscreenCredential;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -1992,8 +1993,11 @@
try {
LockPatternUtils lpu = new LockPatternUtils(mContext);
List<LockPatternView.Cell> cellPattern =
- LockPatternUtils.stringToPattern(lockPattern);
- lpu.saveLockPattern(cellPattern, null, UserHandle.USER_SYSTEM);
+ LockPatternUtils.byteArrayToPattern(lockPattern.getBytes());
+ lpu.setLockCredential(
+ LockscreenCredential.createPattern(cellPattern),
+ LockscreenCredential.createNone(),
+ UserHandle.USER_SYSTEM);
} catch (IllegalArgumentException e) {
// Don't want corrupted lock pattern to hang the reboot process
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index d0ffe7a..8fb879d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -16,7 +16,6 @@
package com.android.providers.settings;
-import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.ActivityManager;
import android.content.IContentProvider;
@@ -195,8 +194,15 @@
: "Failed to delete " + key + " from " + namespace);
break;
case LIST:
- for (String line : list(iprovider, namespace)) {
- pout.println(line);
+ if (namespace != null) {
+ DeviceConfig.Properties properties = DeviceConfig.getProperties(namespace);
+ for (String name : properties.getKeyset()) {
+ pout.println(name + "=" + properties.getString(name, null));
+ }
+ } else {
+ for (String line : listAll(iprovider)) {
+ pout.println(line);
+ }
}
break;
case RESET:
@@ -251,16 +257,13 @@
return success;
}
- private List<String> list(IContentProvider provider, @Nullable String namespace) {
+ private List<String> listAll(IContentProvider provider) {
final ArrayList<String> lines = new ArrayList<>();
try {
Bundle args = new Bundle();
args.putInt(Settings.CALL_METHOD_USER_KEY,
ActivityManager.getService().getCurrentUser().id);
- if (namespace != null) {
- args.putString(Settings.CALL_METHOD_PREFIX_KEY, namespace);
- }
Bundle b = provider.call(resolveCallingPackage(), Settings.AUTHORITY,
Settings.CALL_METHOD_LIST_CONFIG, null, args);
if (b != null) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index f545fa6..7e60452 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -34,6 +34,7 @@
import android.os.ParcelFileDescriptor;
import android.os.UserHandle;
import android.provider.Settings;
+import android.provider.settings.backup.DeviceSpecificSettings;
import android.provider.settings.backup.GlobalSettings;
import android.provider.settings.backup.SecureSettings;
import android.provider.settings.backup.SystemSettings;
@@ -641,7 +642,7 @@
if (contentUri.equals(Settings.Secure.CONTENT_URI)) {
whitelist = ArrayUtils.concatElements(String.class, SecureSettings.SETTINGS_TO_BACKUP,
Settings.Secure.LEGACY_RESTORE_SETTINGS,
- Settings.Secure.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
+ DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
validators = SecureSettingsValidators.VALIDATORS;
} else if (contentUri.equals(Settings.System.CONTENT_URI)) {
whitelist = ArrayUtils.concatElements(String.class, SystemSettings.SETTINGS_TO_BACKUP,
@@ -1000,7 +1001,7 @@
getContentResolver()
.query(Settings.Secure.CONTENT_URI, PROJECTION, null, null, null)) {
return extractRelevantValues(
- cursor, Settings.Secure.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
+ cursor, DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 16c96e6..a9c466e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -439,10 +439,8 @@
case Settings.CALL_METHOD_LIST_CONFIG: {
String prefix = getSettingPrefix(args);
- Bundle result = new Bundle();
- result.putSerializable(
- Settings.NameValueTable.VALUE, (HashMap) getAllConfigFlags(prefix));
- return result;
+ return packageValuesForCallResult(getAllConfigFlags(prefix),
+ isTrackingGeneration(args));
}
case Settings.CALL_METHOD_LIST_GLOBAL: {
@@ -1076,7 +1074,7 @@
return false;
}
- private Map<String, String> getAllConfigFlags(@Nullable String prefix) {
+ private HashMap<String, String> getAllConfigFlags(@Nullable String prefix) {
if (DEBUG) {
Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
}
@@ -1085,12 +1083,11 @@
// Get the settings.
SettingsState settingsState = mSettingsRegistry.getSettingsLocked(
SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM);
-
List<String> names = getSettingsNamesLocked(SETTINGS_TYPE_CONFIG,
UserHandle.USER_SYSTEM);
final int nameCount = names.size();
- Map<String, String> flagsToValues = new HashMap<>(names.size());
+ HashMap<String, String> flagsToValues = new HashMap<>(names.size());
for (int i = 0; i < nameCount; i++) {
String name = names.get(i);
@@ -2057,8 +2054,7 @@
"get/set setting for user", null);
}
- private Bundle packageValueForCallResult(Setting setting,
- boolean trackingGeneration) {
+ private Bundle packageValueForCallResult(Setting setting, boolean trackingGeneration) {
if (!trackingGeneration) {
if (setting == null || setting.isNull()) {
return NULL_SETTING_BUNDLE;
@@ -2073,6 +2069,21 @@
return result;
}
+ private Bundle packageValuesForCallResult(HashMap<String, String> keyValues,
+ boolean trackingGeneration) {
+ Bundle result = new Bundle();
+ result.putSerializable(Settings.NameValueTable.VALUE, keyValues);
+ if (trackingGeneration) {
+ synchronized (mLock) {
+ mSettingsRegistry.mGenerationRegistry.addGenerationData(result,
+ mSettingsRegistry.getSettingsLocked(
+ SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM).mKey);
+ }
+ }
+
+ return result;
+ }
+
private static int getRequestingUserId(Bundle args) {
final int callingUserId = UserHandle.getCallingUserId();
return (args != null) ? args.getInt(Settings.CALL_METHOD_USER_KEY, callingUserId)
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index c05c4cd..de6a3a8 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -24,7 +24,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.os.Binder;
import android.os.Build;
@@ -49,7 +48,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
-import com.android.server.LocalServices;
import libcore.io.IoUtils;
@@ -1175,9 +1173,8 @@
}
// If SetupWizard, done.
- PackageManagerInternal packageManagerInternal = LocalServices.getService(
- PackageManagerInternal.class);
- if (packageName.equals(packageManagerInternal.getSetupWizardPackageName())) {
+ String setupWizPackage = context.getPackageManager().getSetupWizardPackageName();
+ if (packageName.equals(setupWizPackage)) {
sSystemUids.put(uid, uid);
return true;
}
diff --git a/packages/SettingsProvider/test/src/android/provider/OWNERS b/packages/SettingsProvider/test/src/android/provider/OWNERS
new file mode 100644
index 0000000..f3241ea
--- /dev/null
+++ b/packages/SettingsProvider/test/src/android/provider/OWNERS
@@ -0,0 +1,4 @@
+per-file * = *
+
+# Please reach out to the Android B&R team for settings backup changes
+per-file SettingsBackupTest.java = alsutton@google.com, nathch@google.com, rthakohov@google.com
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index ebb9e86..62827bc 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -16,6 +16,8 @@
package android.provider;
+import static android.provider.settings.backup.DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP;
+
import static com.google.android.collect.Sets.newHashSet;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -594,7 +596,10 @@
Settings.Secure.ANR_SHOW_BACKGROUND,
Settings.Secure.ASSISTANT,
Settings.Secure.ASSIST_DISCLOSURE_ENABLED,
+ Settings.Secure.ASSIST_GESTURE_ENABLED,
Settings.Secure.ASSIST_GESTURE_SENSITIVITY,
+ Settings.Secure.ASSIST_GESTURE_WAKE_ENABLED,
+ Settings.Secure.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED,
Settings.Secure.ASSIST_GESTURE_SETUP_COMPLETE,
Settings.Secure.ASSIST_SCREENSHOT_ENABLED,
Settings.Secure.ASSIST_STRUCTURE_ENABLED,
@@ -747,7 +752,7 @@
public void secureSettingsBackedUpOrBlacklisted() {
HashSet<String> keys = new HashSet<String>();
Collections.addAll(keys, SecureSettings.SETTINGS_TO_BACKUP);
- Collections.addAll(keys, Settings.Secure.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
+ Collections.addAll(keys, DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
checkSettingsBackedUpOrBlacklisted(
getCandidateSettings(Settings.Secure.class),
keys,
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index a097249..665bde3 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -476,10 +476,10 @@
}
private static void addScreenshotToIntent(Intent intent, BugreportInfo info) {
- final String screenshotFileName = info.name + ".png";
- final File screenshotFile = new File(BUGREPORT_DIR, screenshotFileName);
- final String screenshotFilePath = screenshotFile.getAbsolutePath();
- if (screenshotFile.length() > 0) {
+ final File screenshotFile = info.screenshotFiles.isEmpty()
+ ? null : info.screenshotFiles.get(0);
+ if (screenshotFile != null && screenshotFile.length() > 0) {
+ final String screenshotFilePath = screenshotFile.getAbsolutePath();
intent.putExtra(EXTRA_SCREENSHOT, screenshotFilePath);
}
return;
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 83acfa0..656827a 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -46,3 +46,6 @@
#Android Auto
stenning@google.com
+#Android TV
+rgl@google.com
+
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/HomeControlsPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/HomeControlsPlugin.java
index cac673f..c1d4b03 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/HomeControlsPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/HomeControlsPlugin.java
@@ -33,4 +33,9 @@
* will add home controls to this space.
*/
void sendParentGroup(ViewGroup group);
+
+ /**
+ * When visible, will poll for updates.
+ */
+ void setVisible(boolean visible);
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index db026ca..6518924 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -186,6 +186,8 @@
return toStringBuilder().toString();
}
+ // Used in dumps to determine current state of a tile.
+ // This string may be used for CTS testing of tiles, so removing elements is discouraged.
protected StringBuilder toStringBuilder() {
final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('[');
sb.append(",icon=").append(icon);
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 22b0ab7..3e74970 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -35,3 +35,7 @@
*;
}
-keep class androidx.core.app.CoreComponentFactory
+
+-keep public class * extends com.android.systemui.SystemUI {
+ public <init>(android.content.Context);
+}
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_lightbulb_outline_gm2_24px.xml b/packages/SystemUI/res/drawable/ic_lightbulb_outline_gm2_24px.xml
new file mode 100644
index 0000000..87684a3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_lightbulb_outline_gm2_24px.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M9,21c0,0.55 0.45,1 1,1h4c0.55,0 1,-0.45 1,-1v-1L9,20v1zM12,2C8.14,2 5,5.14 5,9c0,2.38 1.19,4.47 3,5.74L8,17c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1v-2.26c1.81,-1.27 3,-3.36 3,-5.74 0,-3.86 -3.14,-7 -7,-7zM14.85,13.1l-0.85,0.6L14,16h-4v-2.3l-0.85,-0.6C7.8,12.16 7,10.63 7,9c0,-2.76 2.24,-5 5,-5s5,2.24 5,5c0,1.63 -0.8,3.16 -2.15,4.1z"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/home_controls.xml b/packages/SystemUI/res/layout/home_controls.xml
new file mode 100644
index 0000000..bb971c2
--- /dev/null
+++ b/packages/SystemUI/res/layout/home_controls.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/home_controls_layout"
+ android:layout_width="match_parent"
+ android:layout_height="125dp"
+ android:layout_gravity="@integer/notification_panel_layout_gravity"
+ android:visibility="gone"
+ android:padding="8dp"
+ android:layout_margin="5dp"
+ android:background="?android:attr/colorBackgroundFloating"
+ android:orientation="vertical">
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 0e59a41..4869be1 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -51,17 +51,7 @@
systemui:viewType="com.android.systemui.plugins.qs.QS" />
<!-- Temporary area to test out home controls -->
- <LinearLayout
- android:id="@+id/home_controls_layout"
- android:layout_width="match_parent"
- android:layout_height="125dp"
- android:layout_gravity="@integer/notification_panel_layout_gravity"
- android:visibility="gone"
- android:padding="8dp"
- android:layout_margin="5dp"
- android:background="?android:attr/colorBackgroundFloating"
- android:orientation="vertical">
- </LinearLayout>
+ <include layout="@layout/home_controls" />
<com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
android:id="@+id/notification_stack_scroller"
@@ -107,4 +97,4 @@
android:background="@drawable/qs_navbar_scrim" />
<include layout="@layout/status_bar_expanded_plugin_frame"/>
-</merge>
\ No newline at end of file
+</merge>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 105b27e..efcc2c4 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -117,7 +117,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
- wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night
+ wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,controls
</string>
<!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8335c11..3cc683a 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -129,6 +129,9 @@
<!-- Prompt for the USB device confirm dialog [CHAR LIMIT=80] -->
<string name="usb_device_confirm_prompt">Open <xliff:g id="application">%1$s</xliff:g> to handle <xliff:g id="usb_device">%2$s</xliff:g>?</string>
+ <!-- Prompt for the USB device confirm dialog with warning text for USB device dialogs. [CHAR LIMIT=200] -->
+ <string name="usb_device_confirm_prompt_warn">Open <xliff:g id="application" example= "Usb Mega Player">%1$s</xliff:g> to handle <xliff:g id="usb_device" example="USB Headphones">%2$s</xliff:g>?\nThis app has not been granted record permission but could capture audio through this USB device.</string>
+
<!-- Prompt for the USB accessory confirm dialog [CHAR LIMIT=80] -->
<string name="usb_accessory_confirm_prompt">Open <xliff:g id="application">%1$s</xliff:g> to handle <xliff:g id="usb_accessory">%2$s</xliff:g>?</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
index 2ef0422..748f356 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
@@ -115,4 +115,15 @@
Log.e(TAG, "Failed to clean up screenshot of recents animation", e);
}
}
+
+ /**
+ * @see {{@link IRecentsAnimationController#setWillFinishToHome(boolean)}}.
+ */
+ public void setWillFinishToHome(boolean willFinishToHome) {
+ try {
+ mAnimationController.setWillFinishToHome(willFinishToHome);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to set overview reached state", e);
+ }
+ }
}
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
index ad182fe..22d1675c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -53,7 +53,6 @@
public static final int TRANSIT_WALLPAPER_INTRA_CLOSE =
WindowManager.TRANSIT_WALLPAPER_INTRA_CLOSE;
public static final int TRANSIT_TASK_OPEN_BEHIND = WindowManager.TRANSIT_TASK_OPEN_BEHIND;
- public static final int TRANSIT_TASK_IN_PLACE = WindowManager.TRANSIT_TASK_IN_PLACE;
public static final int TRANSIT_ACTIVITY_RELAUNCH = WindowManager.TRANSIT_ACTIVITY_RELAUNCH;
public static final int TRANSIT_DOCK_TASK_FROM_RECENTS =
WindowManager.TRANSIT_DOCK_TASK_FROM_RECENTS;
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
index ef9538d..46b4c6b 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java
@@ -170,7 +170,7 @@
mSeparator = separator;
mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
mSimSlotsNumber = ((TelephonyManager) context.getSystemService(
- Context.TELEPHONY_SERVICE)).getMaxPhoneCount();
+ Context.TELEPHONY_SERVICE)).getSupportedModemCount();
mSimErrorState = new boolean[mSimSlotsNumber];
mMainHandler = Dependency.get(Dependency.MAIN_HANDLER);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index d45603f..ebac3d9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -33,10 +33,9 @@
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
import com.android.systemui.R;
-import java.util.Arrays;
-
/**
* Base class for PIN and password unlock screens.
*/
@@ -132,19 +131,20 @@
protected void verifyPasswordAndUnlock() {
if (mDismissing) return; // already verified but haven't been dismissed; don't do it again.
- final byte[] entry = getPasswordText();
+ final LockscreenCredential password =
+ LockscreenCredential.createPassword(getPasswordText());
setPasswordEntryInputEnabled(false);
if (mPendingLockCheck != null) {
mPendingLockCheck.cancel(false);
}
final int userId = KeyguardUpdateMonitor.getCurrentUser();
- if (entry.length <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
+ if (password.size() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
// to avoid accidental lockout, only count attempts that are long enough to be a
// real password. This may require some tweaking.
setPasswordEntryInputEnabled(true);
onPasswordChecked(userId, false /* matched */, 0, false /* not valid - too short */);
- Arrays.fill(entry, (byte) 0);
+ password.zeroize();
return;
}
@@ -152,9 +152,9 @@
LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
}
- mPendingLockCheck = LockPatternChecker.checkPassword(
+ mPendingLockCheck = LockPatternChecker.checkCredential(
mLockPatternUtils,
- entry,
+ password,
userId,
new LockPatternChecker.OnCheckCallback() {
@@ -166,7 +166,7 @@
}
onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */,
true /* isValidPassword */);
- Arrays.fill(entry, (byte) 0);
+ password.zeroize();
}
@Override
@@ -181,7 +181,7 @@
onPasswordChecked(userId, false /* matched */, timeoutMs,
true /* isValidPassword */);
}
- Arrays.fill(entry, (byte) 0);
+ password.zeroize();
}
@Override
@@ -192,7 +192,7 @@
LatencyTracker.getInstance(mContext).onActionEnd(
ACTION_CHECK_CREDENTIAL_UNLOCKED);
}
- Arrays.fill(entry, (byte) 0);
+ password.zeroize();
}
});
}
@@ -223,7 +223,7 @@
}
protected abstract void resetPasswordText(boolean animate, boolean announce);
- protected abstract byte[] getPasswordText();
+ protected abstract CharSequence getPasswordText();
protected abstract void setPasswordEntryEnabled(boolean enabled);
protected abstract void setPasswordEntryInputEnabled(boolean enabled);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index e3ac0f6..12c9fc9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -243,8 +243,8 @@
}
@Override
- protected byte[] getPasswordText() {
- return charSequenceToByteArray(mPasswordEntry.getText());
+ protected CharSequence getPasswordText() {
+ return mPasswordEntry.getText();
}
@Override
@@ -379,18 +379,4 @@
return getContext().getString(
com.android.internal.R.string.keyguard_accessibility_password_unlock);
}
-
- /*
- * This method avoids creating a new string when getting a byte array from EditView#getText().
- */
- private static byte[] charSequenceToByteArray(CharSequence chars) {
- if (chars == null) {
- return null;
- }
- byte[] bytes = new byte[chars.length()];
- for (int i = 0; i < chars.length(); i++) {
- bytes[i] = (byte) chars.charAt(i);
- }
- return bytes;
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 297052f..9eb168a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -39,6 +39,7 @@
import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockscreenCredential;
import com.android.settingslib.animation.AppearAnimationCreator;
import com.android.settingslib.animation.AppearAnimationUtils;
import com.android.settingslib.animation.DisappearAnimationUtils;
@@ -297,9 +298,9 @@
LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
}
- mPendingLockCheck = LockPatternChecker.checkPattern(
+ mPendingLockCheck = LockPatternChecker.checkCredential(
mLockPatternUtils,
- pattern,
+ LockscreenCredential.createPattern(pattern),
userId,
new LockPatternChecker.OnCheckCallback() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 274f739..8e9df55 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -167,8 +167,8 @@
}
@Override
- protected byte[] getPasswordText() {
- return charSequenceToByteArray(mPasswordEntry.getText());
+ protected CharSequence getPasswordText() {
+ return mPasswordEntry.getText();
}
@Override
@@ -266,18 +266,4 @@
return getContext().getString(
com.android.internal.R.string.keyguard_accessibility_pin_unlock);
}
-
- /*
- * This method avoids creating a new string when getting a byte array from EditView#getText().
- */
- private static byte[] charSequenceToByteArray(CharSequence chars) {
- if (chars == null) {
- return null;
- }
- byte[] bytes = new byte[chars.length()];
- for (int i = 0; i < chars.length(); i++) {
- bytes[i] = (byte) chars.charAt(i);
- }
- return bytes;
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index af4e61b..5d35169 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -31,6 +31,7 @@
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
+import android.graphics.text.LineBreaker;
import android.net.Uri;
import android.os.Trace;
import android.provider.Settings;
@@ -152,6 +153,7 @@
mRowWithHeaderTextSize = mContext.getResources().getDimensionPixelSize(
R.dimen.header_row_font_size);
mTitle.setOnClickListener(this);
+ mTitle.setBreakStrategy(LineBreaker.BREAK_STRATEGY_BALANCED);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9bcccbc..ac5e255 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -99,6 +99,7 @@
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.widget.LockPatternUtils;
import com.android.settingslib.WirelessUtils;
+import com.android.systemui.DejankUtils;
import com.android.systemui.R;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -1437,6 +1438,8 @@
}
private void handleScreenTurnedOff() {
+ final String tag = "KeyguardUpdateMonitor#handleScreenTurnedOff";
+ DejankUtils.startDetectingBlockingIpcs(tag);
checkIsHandlerThread();
mHardwareFingerprintUnavailableRetryCount = 0;
mHardwareFaceUnavailableRetryCount = 0;
@@ -1446,6 +1449,7 @@
cb.onScreenTurnedOff();
}
}
+ DejankUtils.stopDetectingBlockingIpcs(tag);
}
private void handleDreamingStateChanged(int dreamStart) {
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 07bfa71..ff8a932 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -17,6 +17,7 @@
import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.INotificationManager;
+import android.app.IWallpaperManager;
import android.content.res.Configuration;
import android.hardware.SensorPrivacyManager;
import android.hardware.display.NightDisplayListener;
@@ -77,6 +78,7 @@
import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.AutoHideController;
+import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
@@ -318,6 +320,8 @@
@Inject Lazy<SysUiState> mSysUiStateFlagsContainer;
@Inject Lazy<AlarmManager> mAlarmManager;
@Inject Lazy<KeyguardSecurityModel> mKeyguardSecurityModel;
+ @Inject Lazy<DozeParameters> mDozeParameters;
+ @Inject Lazy<IWallpaperManager> mWallpaperManager;
@Inject
public Dependency() {
@@ -504,6 +508,8 @@
mProviders.put(SysUiState.class, mSysUiStateFlagsContainer::get);
mProviders.put(AlarmManager.class, mAlarmManager::get);
mProviders.put(KeyguardSecurityModel.class, mKeyguardSecurityModel::get);
+ mProviders.put(DozeParameters.class, mDozeParameters::get);
+ mProviders.put(IWallpaperManager.class, mWallpaperManager::get);
// TODO(b/118592525): to support multi-display , we start to add something which is
// per-display, while others may be global. I think it's time to add
diff --git a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
index 0d24321..9192eed 100644
--- a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java
@@ -22,26 +22,36 @@
import static com.android.systemui.Dependency.MAIN_LOOPER_NAME;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.AlarmManager;
+import android.app.IActivityManager;
import android.app.INotificationManager;
+import android.app.IWallpaperManager;
import android.content.Context;
+import android.content.res.Resources;
import android.hardware.SensorPrivacyManager;
+import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.display.NightDisplayListener;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.PowerManager;
import android.os.Process;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.DisplayMetrics;
import android.view.IWindowManager;
+import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.widget.LockPatternUtils;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.plugins.PluginInitializerImpl;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.plugins.PluginManagerImpl;
@@ -49,6 +59,7 @@
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.statusbar.NavigationBarController;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -58,7 +69,11 @@
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.util.leak.LeakDetector;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
import javax.inject.Named;
+import javax.inject.Qualifier;
import javax.inject.Singleton;
import dagger.Module;
@@ -70,6 +85,12 @@
*/
@Module
public class DependencyProvider {
+ @Qualifier
+ @Documented
+ @Retention(RUNTIME)
+ public @interface MainResources {
+ // TODO: use attribute to get other, non-main resources?
+ }
@Singleton
@Provides
@@ -204,8 +225,11 @@
@Singleton
@Provides
public AutoHideController provideAutoHideController(Context context,
- @Named(MAIN_HANDLER_NAME) Handler mainHandler) {
- return new AutoHideController(context, mainHandler);
+ @Named(MAIN_HANDLER_NAME) Handler mainHandler,
+ NotificationRemoteInputManager notificationRemoteInputManager,
+ IWindowManager iWindowManager) {
+ return new AutoHideController(context, mainHandler, notificationRemoteInputManager,
+ iWindowManager);
}
@Singleton
@@ -245,4 +269,48 @@
public LockPatternUtils provideLockPatternUtils(Context context) {
return new LockPatternUtils(context);
}
+
+ /** */
+ @Provides
+ public AmbientDisplayConfiguration provideAmbientDispalyConfiguration(Context context) {
+ return new AmbientDisplayConfiguration(context);
+ }
+
+ /** */
+ @Provides
+ public AlwaysOnDisplayPolicy provideAlwaysOnDisplayPolicy(Context context) {
+ return new AlwaysOnDisplayPolicy(context);
+ }
+
+ /** */
+ @Provides
+ public PowerManager providePowerManager(Context context) {
+ return context.getSystemService(PowerManager.class);
+ }
+
+ /** */
+ @Provides
+ @MainResources
+ public Resources provideResources(Context context) {
+ return context.getResources();
+ }
+
+ /** */
+ @Provides
+ public IWallpaperManager provideWallPaperManager() {
+ return IWallpaperManager.Stub.asInterface(
+ ServiceManager.getService(Context.WALLPAPER_SERVICE));
+ }
+
+ /** */
+ @Provides
+ public WindowManager providesWindowManager(Context context) {
+ return context.getSystemService(WindowManager.class);
+ }
+
+ /** */
+ @Provides
+ public IActivityManager providesIActivityManager() {
+ return ActivityManager.getService();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 1c0e0b3..29a7167 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -38,6 +38,8 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import javax.inject.Inject;
+
/**
* Default built-in wallpaper that simply shows a static image.
*/
@@ -50,8 +52,15 @@
private static final int INTERVAL_WAIT_FOR_RENDERING = 100;
private static final int PATIENCE_WAIT_FOR_RENDERING = 10;
private static final boolean DEBUG = true;
+ private final DozeParameters mDozeParameters;
private HandlerThread mWorker;
+ @Inject
+ public ImageWallpaper(DozeParameters dozeParameters) {
+ super();
+ mDozeParameters = dozeParameters;
+ }
+
@Override
public void onCreate() {
super.onCreate();
@@ -61,7 +70,7 @@
@Override
public Engine onCreateEngine() {
- return new GLEngine(this);
+ return new GLEngine(this, mDozeParameters);
}
@Override
@@ -89,9 +98,9 @@
// This variable can only be accessed in synchronized block.
private boolean mWaitingForRendering;
- GLEngine(Context context) {
+ GLEngine(Context context, DozeParameters dozeParameters) {
mNeedTransition = ActivityManager.isHighEndGfx()
- && !DozeParameters.getInstance(context).getDisplayNeedsBlanking();
+ && !dozeParameters.getDisplayNeedsBlanking();
// We will preserve EGL context when we are in lock screen or aod
// to avoid janking in following transition, we need to release when back to home.
@@ -339,9 +348,9 @@
boolean isHighEndGfx = ActivityManager.isHighEndGfx();
out.print(prefix); out.print("isHighEndGfx="); out.println(isHighEndGfx);
- DozeParameters dozeParameters = DozeParameters.getInstance(getApplicationContext());
out.print(prefix); out.print("displayNeedsBlanking=");
- out.println(dozeParameters != null ? dozeParameters.getDisplayNeedsBlanking() : "null");
+ out.println(
+ mDozeParameters != null ? mDozeParameters.getDisplayNeedsBlanking() : "null");
out.print(prefix); out.print("mNeedTransition="); out.println(mNeedTransition);
out.print(prefix); out.print("StatusBarState=");
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index 50f1b44..ddbabee 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -41,6 +41,10 @@
private static final String ACTION_TURN_ON_SCREEN =
"com.android.systemui.latency.ACTION_TURN_ON_SCREEN";
+ public LatencyTester(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
if (!Build.IS_DEBUGGABLE) {
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 3e068b0..ad20986 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -132,12 +132,15 @@
return result;
}
+ public ScreenDecorations(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
mHandler = startHandlerThread();
mHandler.post(this::startOnScreenDecorationsThread);
setupStatusBarPaddingIfNeeded();
- putComponent(ScreenDecorations.class, this);
}
@VisibleForTesting
@@ -457,7 +460,7 @@
| WindowManager.LayoutParams.FLAG_SLIPPERY
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSLUCENT);
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS
| WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS) {
diff --git a/packages/SystemUI/src/com/android/systemui/ServiceBinder.java b/packages/SystemUI/src/com/android/systemui/ServiceBinder.java
index e761a2b..c11236e 100644
--- a/packages/SystemUI/src/com/android/systemui/ServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/ServiceBinder.java
@@ -19,6 +19,7 @@
import android.app.Service;
import com.android.systemui.doze.DozeService;
+import com.android.systemui.keyguard.KeyguardService;
import dagger.Binds;
import dagger.Module;
@@ -30,8 +31,21 @@
*/
@Module
public abstract class ServiceBinder {
+ /** */
@Binds
@IntoMap
@ClassKey(DozeService.class)
public abstract Service bindDozeService(DozeService service);
+
+ /** */
+ @Binds
+ @IntoMap
+ @ClassKey(ImageWallpaper.class)
+ public abstract Service bindImageWallpaper(ImageWallpaper service);
+
+ /** */
+ @Binds
+ @IntoMap
+ @ClassKey(KeyguardService.class)
+ public abstract Service bindKeyguardService(KeyguardService service);
}
diff --git a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
index c54f6306..10009f5 100644
--- a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java
@@ -59,12 +59,13 @@
/** Only show once automatically in the process life. */
private boolean mHasShownHint;
- public SizeCompatModeActivityController() {
- this(ActivityManagerWrapper.getInstance());
+ public SizeCompatModeActivityController(Context context) {
+ this(context, ActivityManagerWrapper.getInstance());
}
@VisibleForTesting
- SizeCompatModeActivityController(ActivityManagerWrapper am) {
+ SizeCompatModeActivityController(Context context, ActivityManagerWrapper am) {
+ super(context);
am.registerTaskStackListener(new TaskStackChangeListener() {
@Override
public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) {
@@ -202,7 +203,7 @@
mWinParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
mWinParams.format = PixelFormat.TRANSLUCENT;
- mWinParams.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ mWinParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
mWinParams.setTitle(SizeCompatModeActivityController.class.getSimpleName()
+ context.getDisplayId());
}
diff --git a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
index b3fc69e..92fbd25 100644
--- a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java
@@ -39,6 +39,10 @@
private final ArrayMap<Uri, BroadcastRelay> mRelays = new ArrayMap<>();
+ public SliceBroadcastRelayHandler(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
if (DEBUG) Log.d(TAG, "Start");
diff --git a/packages/SystemUI/src/com/android/systemui/SysUIToast.java b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
index 8bcf057..0f7f1be 100644
--- a/packages/SystemUI/src/com/android/systemui/SysUIToast.java
+++ b/packages/SystemUI/src/com/android/systemui/SysUIToast.java
@@ -31,7 +31,7 @@
public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
Toast toast = Toast.makeText(context, text, duration);
toast.getWindowParams().privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
return toast;
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUI.java b/packages/SystemUI/src/com/android/systemui/SystemUI.java
index 30fbef6..7570037 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUI.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUI.java
@@ -26,9 +26,13 @@
import java.util.Map;
public abstract class SystemUI implements SysUiServiceProvider {
- public Context mContext;
+ protected final Context mContext;
public Map<Class<?>, Object> mComponents;
+ public SystemUI(Context context) {
+ mContext = context;
+ }
+
public abstract void start();
protected void onConfigurationChanged(Configuration newConfig) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 56b5d08..91776a3 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -42,6 +42,8 @@
import com.android.systemui.statusbar.phone.StatusBarWindowController;
import com.android.systemui.util.NotificationChannels;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
@@ -193,18 +195,18 @@
try {
SystemUI obj = mComponentHelper.resolveSystemUI(clsName);
if (obj == null) {
- obj = (SystemUI) Class.forName(clsName).newInstance();
+ Constructor constructor = Class.forName(clsName).getConstructor(Context.class);
+ obj = (SystemUI) constructor.newInstance(this);
}
mServices[i] = obj;
- } catch (ClassNotFoundException ex) {
- throw new RuntimeException(ex);
- } catch (IllegalAccessException ex) {
- throw new RuntimeException(ex);
- } catch (InstantiationException ex) {
+ } catch (ClassNotFoundException
+ | NoSuchMethodException
+ | IllegalAccessException
+ | InstantiationException
+ | InvocationTargetException ex) {
throw new RuntimeException(ex);
}
- mServices[i].mContext = this;
mServices[i].mComponents = mComponents;
if (DEBUG) Log.d(TAG, "running: " + mServices[i]);
mServices[i].start();
@@ -235,7 +237,7 @@
if (statusBar != null) {
plugin.setup(statusBar.getStatusBarWindow(),
statusBar.getNavigationBarView(), new Callback(plugin),
- DozeParameters.getInstance(getBaseContext()));
+ Dependency.get(DozeParameters.class));
}
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
index 785038f..a5a5598 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIBinder.java
@@ -17,10 +17,12 @@
package com.android.systemui;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.pip.PipUI;
import com.android.systemui.power.PowerUI;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsModule;
import com.android.systemui.util.leak.GarbageMonitor;
+import com.android.systemui.volume.VolumeUI;
import dagger.Binds;
import dagger.Module;
@@ -32,12 +34,25 @@
*/
@Module(includes = {RecentsModule.class})
public abstract class SystemUIBinder {
+
+ /** Inject into GarbageMonitor.Service. */
+ @Binds
+ @IntoMap
+ @ClassKey(GarbageMonitor.Service.class)
+ public abstract SystemUI bindGarbageMonitorService(GarbageMonitor.Service service);
+
/** Inject into KeyguardViewMediator. */
@Binds
@IntoMap
@ClassKey(KeyguardViewMediator.class)
public abstract SystemUI bindKeyguardViewMediator(KeyguardViewMediator sysui);
+ /** Inject into PipUI. */
+ @Binds
+ @IntoMap
+ @ClassKey(PipUI.class)
+ public abstract SystemUI bindPipUI(PipUI sysui);
+
/** Inject into PowerUI. */
@Binds
@IntoMap
@@ -50,9 +65,10 @@
@ClassKey(Recents.class)
public abstract SystemUI bindRecents(Recents sysui);
- /** Inject into GarbageMonitor.Service. */
+ /** Inject into VolumeUI. */
@Binds
@IntoMap
- @ClassKey(GarbageMonitor.Service.class)
- public abstract SystemUI bindGarbageMonitorService(GarbageMonitor.Service service);
+ @ClassKey(VolumeUI.class)
+ public abstract SystemUI bindVolumeUI(VolumeUI sysui);
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 530dcdc..ef7526b 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -48,7 +48,6 @@
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.volume.VolumeDialogComponent;
import java.util.function.Consumer;
@@ -161,7 +160,8 @@
StatusBarStateController statusBarStateController) {
return new NotificationIconAreaController(context, statusBar, statusBarStateController,
wakeUpCoordinator, keyguardBypassController,
- Dependency.get(NotificationMediaManager.class));
+ Dependency.get(NotificationMediaManager.class),
+ Dependency.get(DozeParameters.class));
}
public KeyguardIndicationController createKeyguardIndicationController(Context context,
@@ -169,10 +169,6 @@
return new KeyguardIndicationController(context, indicationArea, lockIcon);
}
- public VolumeDialogComponent createVolumeDialogComponent(SystemUI systemUi, Context context) {
- return new VolumeDialogComponent(systemUi, context);
- }
-
@Module
public static class ContextHolder {
private Context mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/VendorServices.java b/packages/SystemUI/src/com/android/systemui/VendorServices.java
index 0be6b12..13d847b 100644
--- a/packages/SystemUI/src/com/android/systemui/VendorServices.java
+++ b/packages/SystemUI/src/com/android/systemui/VendorServices.java
@@ -16,11 +16,17 @@
package com.android.systemui;
+import android.content.Context;
+
/**
* Placeholder for any vendor-specific services.
*/
public class VendorServices extends SystemUI {
+ public VendorServices(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
// no-op
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
index 739eade..6f5a17d 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistModule.java
@@ -21,6 +21,7 @@
import android.os.HandlerThread;
import android.os.SystemClock;
+import androidx.annotation.Nullable;
import androidx.slice.Clock;
import com.android.internal.app.AssistUtils;
@@ -68,6 +69,7 @@
}
@Provides
+ @Nullable
static AssistHandleViewController provideAssistHandleViewController(
NavigationBarController navigationBarController) {
return navigationBarController.getAssistHandlerViewController();
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
index 9958124..4cb1708 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -164,8 +164,11 @@
}
private void updateAssistHandleVisibility() {
- AssistHandleViewController controller = Dependency.get(NavigationBarController.class)
- .getAssistHandlerViewController();
+ NavigationBarController navigationBarController =
+ Dependency.get(NavigationBarController.class);
+ AssistHandleViewController controller =
+ navigationBarController == null
+ ? null : navigationBarController.getAssistHandlerViewController();
if (controller != null) {
controller.setAssistHintBlocked(mInvocationInProgress);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index a9359d4..f1abdb3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -580,7 +580,7 @@
WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
PixelFormat.TRANSLUCENT);
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setTitle("BiometricPrompt");
lp.token = windowToken;
return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 4c2afb0..cdc2623 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -172,12 +172,13 @@
}
}
- public AuthController() {
- this(new Injector());
+ public AuthController(Context context) {
+ this(context, new Injector());
}
@VisibleForTesting
- AuthController(Injector injector) {
+ AuthController(Context context, Injector injector) {
+ super(context);
mInjector = injector;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
index 8df072e..bebaa4b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPasswordView.java
@@ -26,7 +26,7 @@
import android.widget.TextView;
import com.android.internal.widget.LockPatternChecker;
-import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
import com.android.systemui.R;
/**
@@ -96,13 +96,16 @@
}
private void checkPasswordAndUnlock() {
- final byte[] password = LockPatternUtils.charSequenceToByteArray(mPasswordField.getText());
- if (password == null || password.length == 0) {
- return;
- }
+ try (LockscreenCredential password = mCredentialType == Utils.CREDENTIAL_PIN
+ ? LockscreenCredential.createPinOrNone(mPasswordField.getText())
+ : LockscreenCredential.createPasswordOrNone(mPasswordField.getText())) {
+ if (password.isNone()) {
+ return;
+ }
- mPendingLockCheck = LockPatternChecker.checkPassword(mLockPatternUtils,
- password, mUserId, this::onCredentialChecked);
+ mPendingLockCheck = LockPatternChecker.checkCredential(mLockPatternUtils,
+ password, mUserId, this::onCredentialChecked);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
index 6c36f82..14414a4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthCredentialPatternView.java
@@ -22,6 +22,7 @@
import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockscreenCredential;
import com.android.systemui.R;
import java.util.List;
@@ -64,11 +65,13 @@
return;
}
- mPendingLockCheck = LockPatternChecker.checkPattern(
- mLockPatternUtils,
- pattern,
- mUserId,
- this::onPatternChecked);
+ try (LockscreenCredential credential = LockscreenCredential.createPattern(pattern)) {
+ mPendingLockCheck = LockPatternChecker.checkCredential(
+ mLockPatternUtils,
+ credential,
+ mUserId,
+ this::onPatternChecked);
+ }
}
private void onPatternChecked(boolean matched, int timeoutMs) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 8240345..9568a18 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -18,7 +18,6 @@
import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
import static android.app.Notification.FLAG_BUBBLE;
-import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
@@ -569,7 +568,8 @@
if (mStackView != null) {
mStackView.updateDotVisibility(entry.key);
}
- mNotificationEntryManager.updateNotifications();
+ mNotificationEntryManager.updateNotifications(
+ "BubbleController.onNotificationRemoveRequested");
return true;
} else if (!userRemovedNotif && entry != null) {
// This wasn't a user removal so we should remove the bubble as well
@@ -609,7 +609,8 @@
mBubbleData.addSummaryToSuppress(summary.notification.getGroupKey(),
summary.key);
// Tell shade to update for the suppression
- mNotificationEntryManager.updateNotifications();
+ mNotificationEntryManager.updateNotifications(
+ "BubbleController.handleSummaryRemovalInterception");
}
return !isAutogroupSummary;
} else {
@@ -760,7 +761,8 @@
mStackView.setExpanded(true);
}
- mNotificationEntryManager.updateNotifications();
+ mNotificationEntryManager.updateNotifications(
+ "BubbleData.Listener.applyUpdate");
updateStack();
if (DEBUG_BUBBLE_CONTROLLER) {
@@ -962,16 +964,6 @@
+ intent);
return false;
}
- if (info.documentLaunchMode != DOCUMENT_LAUNCH_ALWAYS) {
- Log.w(TAG, "Unable to send as bubble -- activity is not documentLaunchMode=always "
- + "for intent: " + intent);
- return false;
- }
- if ((info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) == 0) {
- Log.w(TAG, "Unable to send as bubble -- activity is not embeddable for intent: "
- + intent);
- return false;
- }
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 521ebde..6f953d5 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -16,6 +16,8 @@
package com.android.systemui.bubbles;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.view.Display.INVALID_DISPLAY;
import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW;
@@ -128,8 +130,12 @@
Log.d(TAG, "onActivityViewReady: calling startActivity, "
+ "bubble=" + getBubbleKey());
}
+ Intent fillInIntent = new Intent();
+ // Apply flags to make behaviour match documentLaunchMode=always.
+ fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
try {
- mActivityView.startActivity(mBubbleIntent, options);
+ mActivityView.startActivity(mBubbleIntent, fillInIntent, options);
} catch (RuntimeException e) {
// If there's a runtime exception here then there's something
// wrong with the intent, we can't really recover / try to populate
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java
index 85a4d23..b726c3e 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/brightline/PointerCountClassifier.java
@@ -16,6 +16,9 @@
package com.android.systemui.classifier.brightline;
+import static com.android.systemui.classifier.Classifier.NOTIFICATION_DRAG_DOWN;
+import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
+
import android.view.MotionEvent;
import java.util.Locale;
@@ -29,6 +32,7 @@
class PointerCountClassifier extends FalsingClassifier {
private static final int MAX_ALLOWED_POINTERS = 1;
+ private static final int MAX_ALLOWED_POINTERS_SWIPE_DOWN = 2;
private int mMaxPointerCount;
PointerCountClassifier(FalsingDataProvider dataProvider) {
@@ -50,6 +54,10 @@
@Override
public boolean isFalseTouch() {
+ int interactionType = getInteractionType();
+ if (interactionType == QUICK_SETTINGS || interactionType == NOTIFICATION_DRAG_DOWN) {
+ return mMaxPointerCount > MAX_ALLOWED_POINTERS_SWIPE_DOWN;
+ }
return mMaxPointerCount > MAX_ALLOWED_POINTERS;
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index ca4ec6d..3f0505f 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -18,6 +18,7 @@
import android.app.AlarmManager;
import android.app.Application;
+import android.app.IWallpaperManager;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorManager;
@@ -25,7 +26,6 @@
import android.os.Handler;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.dock.DockManager;
@@ -39,47 +39,71 @@
import com.android.systemui.util.wakelock.DelayedWakeLock;
import com.android.systemui.util.wakelock.WakeLock;
+import javax.inject.Inject;
+
public class DozeFactory {
- public DozeFactory() {
+ private final FalsingManager mFalsingManager;
+ private final DozeLog mDozeLog;
+ private final DozeParameters mDozeParameters;
+ private final BatteryController mBatteryController;
+ private final AsyncSensorManager mAsyncSensorManager;
+ private final AlarmManager mAlarmManager;
+ private final WakefulnessLifecycle mWakefulnessLifecycle;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final DockManager mDockManager;
+ private final IWallpaperManager mWallpaperManager;
+
+ @Inject
+ public DozeFactory(FalsingManager falsingManager, DozeLog dozeLog,
+ DozeParameters dozeParameters, BatteryController batteryController,
+ AsyncSensorManager asyncSensorManager, AlarmManager alarmManager,
+ WakefulnessLifecycle wakefulnessLifecycle, KeyguardUpdateMonitor keyguardUpdateMonitor,
+ DockManager dockManager, IWallpaperManager wallpaperManager) {
+ mFalsingManager = falsingManager;
+ mDozeLog = dozeLog;
+ mDozeParameters = dozeParameters;
+ mBatteryController = batteryController;
+ mAsyncSensorManager = asyncSensorManager;
+ mAlarmManager = alarmManager;
+ mWakefulnessLifecycle = wakefulnessLifecycle;
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mDockManager = dockManager;
+ mWallpaperManager = wallpaperManager;
}
/** Creates a DozeMachine with its parts for {@code dozeService}. */
- public DozeMachine assembleMachine(DozeService dozeService, FalsingManager falsingManager,
- DozeLog dozeLog) {
- Context context = dozeService;
- AsyncSensorManager sensorManager = Dependency.get(AsyncSensorManager.class);
- AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
- DockManager dockManager = Dependency.get(DockManager.class);
- WakefulnessLifecycle wakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
-
+ public DozeMachine assembleMachine(DozeService dozeService) {
DozeHost host = getHost(dozeService);
- AmbientDisplayConfiguration config = new AmbientDisplayConfiguration(context);
- DozeParameters params = DozeParameters.getInstance(context);
+ AmbientDisplayConfiguration config = new AmbientDisplayConfiguration(dozeService);
Handler handler = new Handler();
WakeLock wakeLock = new DelayedWakeLock(handler,
- WakeLock.createPartial(context, "Doze"));
+ WakeLock.createPartial(dozeService, "Doze"));
DozeMachine.Service wrappedService = dozeService;
wrappedService = new DozeBrightnessHostForwarder(wrappedService, host);
- wrappedService = DozeScreenStatePreventingAdapter.wrapIfNeeded(wrappedService, params);
- wrappedService = DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(wrappedService,
- params);
+ wrappedService = DozeScreenStatePreventingAdapter.wrapIfNeeded(
+ wrappedService, mDozeParameters);
+ wrappedService = DozeSuspendScreenStatePreventingAdapter.wrapIfNeeded(
+ wrappedService, mDozeParameters);
DozeMachine machine = new DozeMachine(wrappedService, config, wakeLock,
- wakefulnessLifecycle, Dependency.get(BatteryController.class), dozeLog);
+ mWakefulnessLifecycle, mBatteryController, mDozeLog);
machine.setParts(new DozeMachine.Part[]{
- new DozePauser(handler, machine, alarmManager, params.getPolicy()),
- new DozeFalsingManagerAdapter(falsingManager),
- createDozeTriggers(context, sensorManager, host, alarmManager, config, params,
- handler, wakeLock, machine, dockManager, dozeLog),
- createDozeUi(context, host, wakeLock, machine, handler, alarmManager, params,
- dozeLog),
- new DozeScreenState(wrappedService, handler, params, wakeLock),
- createDozeScreenBrightness(context, wrappedService, sensorManager, host, params,
- handler),
- new DozeWallpaperState(context, getBiometricUnlockController(dozeService)),
- new DozeDockHandler(context, machine, host, config, handler, dockManager),
+ new DozePauser(handler, machine, mAlarmManager, mDozeParameters.getPolicy()),
+ new DozeFalsingManagerAdapter(mFalsingManager),
+ createDozeTriggers(dozeService, mAsyncSensorManager, host, mAlarmManager, config,
+ mDozeParameters, handler, wakeLock, machine, mDockManager, mDozeLog),
+ createDozeUi(dozeService, host, wakeLock, machine, handler, mAlarmManager,
+ mDozeParameters, mDozeLog),
+ new DozeScreenState(wrappedService, handler, host, mDozeParameters, wakeLock),
+ createDozeScreenBrightness(dozeService, wrappedService, mAsyncSensorManager, host,
+ mDozeParameters, handler),
+ new DozeWallpaperState(
+ mWallpaperManager,
+ getBiometricUnlockController(dozeService),
+ mDozeParameters),
+ new DozeDockHandler(dozeService, machine, host, config, handler, mDockManager),
new DozeAuthRemover(dozeService)
});
@@ -110,7 +134,7 @@
DozeMachine machine, Handler handler, AlarmManager alarmManager,
DozeParameters params, DozeLog dozeLog) {
return new DozeUi(context, alarmManager, machine, wakeLock, host, handler, params,
- Dependency.get(KeyguardUpdateMonitor.class), dozeLog);
+ mKeyguardUpdateMonitor, dozeLog);
}
public static DozeHost getHost(DozeService service) {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 07dd2cd..1a6bd60 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -63,9 +63,10 @@
void setDozeScreenBrightness(int value);
/**
- * Makes scrims black and changes animation durations.
+ * Fade out screen before switching off the display power mode.
+ * @param onDisplayOffCallback Executed when the display is black.
*/
- default void prepareForGentleWakeUp() {}
+ void prepareForGentleSleep(Runnable onDisplayOffCallback);
void onIgnoreTouchWhilePulsing(boolean ignore);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index 38ee2fe..95c42fc 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -16,6 +16,12 @@
package com.android.systemui.doze;
+import static com.android.systemui.doze.DozeMachine.State.DOZE;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
+
import android.os.Handler;
import android.util.Log;
import android.view.Display;
@@ -48,21 +54,24 @@
private final Handler mHandler;
private final Runnable mApplyPendingScreenState = this::applyPendingScreenState;
private final DozeParameters mParameters;
+ private final DozeHost mDozeHost;
private int mPendingScreenState = Display.STATE_UNKNOWN;
private SettableWakeLock mWakeLock;
- public DozeScreenState(DozeMachine.Service service, Handler handler,
+ public DozeScreenState(DozeMachine.Service service, Handler handler, DozeHost host,
DozeParameters parameters, WakeLock wakeLock) {
mDozeService = service;
mHandler = handler;
mParameters = parameters;
+ mDozeHost = host;
mWakeLock = new SettableWakeLock(wakeLock, TAG);
}
@Override
public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
int screenState = newState.screenState(mParameters);
+ mDozeHost.prepareForGentleSleep(null);
if (newState == DozeMachine.State.FINISH) {
// Make sure not to apply the screen state after DozeService was destroyed.
@@ -79,12 +88,13 @@
return;
}
- boolean messagePending = mHandler.hasCallbacks(mApplyPendingScreenState);
- boolean pulseEnding = oldState == DozeMachine.State.DOZE_PULSE_DONE
- && newState == DozeMachine.State.DOZE_AOD;
- boolean turningOn = (oldState == DozeMachine.State.DOZE_AOD_PAUSED
- || oldState == DozeMachine.State.DOZE) && newState == DozeMachine.State.DOZE_AOD;
- boolean justInitialized = oldState == DozeMachine.State.INITIALIZED;
+ final boolean messagePending = mHandler.hasCallbacks(mApplyPendingScreenState);
+ final boolean pulseEnding = oldState == DOZE_PULSE_DONE && newState == DOZE_AOD;
+ final boolean turningOn = (oldState == DOZE_AOD_PAUSED
+ || oldState == DOZE) && newState == DOZE_AOD;
+ final boolean turningOff = (oldState == DOZE_AOD && newState == DOZE)
+ || (oldState == DOZE_AOD_PAUSING && newState == DOZE_AOD_PAUSED);
+ final boolean justInitialized = oldState == DozeMachine.State.INITIALIZED;
if (messagePending || justInitialized || pulseEnding || turningOn) {
// During initialization, we hide the navigation bar. That is however only applied after
// a traversal; setting the screen state here is immediate however, so it can happen
@@ -93,7 +103,7 @@
mPendingScreenState = screenState;
// Delay screen state transitions even longer while animations are running.
- boolean shouldDelayTransition = newState == DozeMachine.State.DOZE_AOD
+ boolean shouldDelayTransition = newState == DOZE_AOD
&& mParameters.shouldControlScreenOff() && !turningOn;
if (shouldDelayTransition) {
@@ -114,6 +124,8 @@
} else if (DEBUG) {
Log.d(TAG, "Pending display state change to " + screenState);
}
+ } else if (turningOff) {
+ mDozeHost.prepareForGentleSleep(() -> applyScreenState(screenState));
} else {
applyScreenState(screenState);
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 17559c9..08734d2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -22,10 +22,8 @@
import android.service.dreams.DreamService;
import android.util.Log;
-import com.android.systemui.Dependency;
import com.android.systemui.plugins.DozeServicePlugin;
import com.android.systemui.plugins.DozeServicePlugin.RequestDoze;
-import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.shared.plugins.PluginManager;
@@ -38,18 +36,17 @@
implements DozeMachine.Service, RequestDoze, PluginListener<DozeServicePlugin> {
private static final String TAG = "DozeService";
static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private final FalsingManager mFalsingManager;
- private final DozeLog mDozeLog;
+ private final DozeFactory mDozeFactory;
private DozeMachine mDozeMachine;
private DozeServicePlugin mDozePlugin;
private PluginManager mPluginManager;
@Inject
- public DozeService(FalsingManager falsingManager, DozeLog dozeLog) {
+ public DozeService(DozeFactory dozeFactory, PluginManager pluginManager) {
setDebug(DEBUG);
- mFalsingManager = falsingManager;
- mDozeLog = dozeLog;
+ mDozeFactory = dozeFactory;
+ mPluginManager = pluginManager;
}
@Override
@@ -62,9 +59,8 @@
finish();
return;
}
- mPluginManager = Dependency.get(PluginManager.class);
mPluginManager.addPluginListener(this, DozeServicePlugin.class, false /* allowMultiple */);
- mDozeMachine = new DozeFactory().assembleMachine(this, mFalsingManager, mDozeLog);
+ mDozeMachine = mDozeFactory.assembleMachine(this);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 2c0ccd21..f155783 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -135,7 +135,6 @@
break;
case DOZE:
case DOZE_AOD_PAUSED:
- mHost.prepareForGentleWakeUp();
unscheduleTimeTick();
break;
case DOZE_REQUEST_PULSE:
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
index 35c8b74..9457dc9 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
@@ -17,12 +17,9 @@
package com.android.systemui.doze;
import android.app.IWallpaperManager;
-import android.content.Context;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -42,17 +39,10 @@
private final BiometricUnlockController mBiometricUnlockController;
private boolean mIsAmbientMode;
- public DozeWallpaperState(Context context,
- BiometricUnlockController biometricUnlockController) {
- this(IWallpaperManager.Stub.asInterface(
- ServiceManager.getService(Context.WALLPAPER_SERVICE)),
- biometricUnlockController,
- DozeParameters.getInstance(context));
- }
-
- @VisibleForTesting
- DozeWallpaperState(IWallpaperManager wallpaperManagerService,
- BiometricUnlockController biometricUnlockController, DozeParameters parameters) {
+ public DozeWallpaperState(
+ IWallpaperManager wallpaperManagerService,
+ BiometricUnlockController biometricUnlockController,
+ DozeParameters parameters) {
mWallpaperManagerService = wallpaperManagerService;
mBiometricUnlockController = biometricUnlockController;
mDozeParameters = parameters;
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
index e8ef454..c11127d 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -35,6 +35,10 @@
private Extension<GlobalActions> mExtension;
private IStatusBarService mBarService;
+ public GlobalActionsComponent(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
mBarService = IStatusBarService.Stub.asInterface(
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index 7d52a9a..42f455a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -117,6 +117,10 @@
private int mState;
+ public KeyboardUI(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
mContext = super.mContext;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
index b3481c5..4a33590 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardLifecyclesDispatcher.java
@@ -19,9 +19,13 @@
import android.os.Handler;
import android.os.Message;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
/**
* Dispatches the lifecycles keyguard gets from WindowManager on the main thread.
*/
+@Singleton
public class KeyguardLifecyclesDispatcher {
static final int SCREEN_TURNING_ON = 0;
@@ -37,6 +41,7 @@
private final ScreenLifecycle mScreenLifecycle;
private final WakefulnessLifecycle mWakefulnessLifecycle;
+ @Inject
public KeyguardLifecyclesDispatcher(ScreenLifecycle screenLifecycle,
WakefulnessLifecycle wakefulnessLifecycle) {
mScreenLifecycle = screenLifecycle;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 81247cd..9f4056f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -33,25 +33,30 @@
import com.android.internal.policy.IKeyguardExitCallback;
import com.android.internal.policy.IKeyguardService;
import com.android.internal.policy.IKeyguardStateCallback;
-import com.android.systemui.Dependency;
import com.android.systemui.SystemUIApplication;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+@Singleton
public class KeyguardService extends Service {
static final String TAG = "KeyguardService";
static final String PERMISSION = android.Manifest.permission.CONTROL_KEYGUARD;
- private KeyguardViewMediator mKeyguardViewMediator;
- private KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
+ private final KeyguardViewMediator mKeyguardViewMediator;
+ private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
+
+ @Inject
+ public KeyguardService(KeyguardViewMediator keyguardViewMediator,
+ KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher) {
+ super();
+ mKeyguardViewMediator = keyguardViewMediator;
+ mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
+ }
@Override
public void onCreate() {
((SystemUIApplication) getApplication()).startServicesIfNeeded();
- mKeyguardViewMediator =
- ((SystemUIApplication) getApplication()).getComponent(KeyguardViewMediator.class);
- mKeyguardLifecyclesDispatcher = new KeyguardLifecyclesDispatcher(
- Dependency.get(ScreenLifecycle.class),
- Dependency.get(WakefulnessLifecycle.class));
-
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index a8027c0..e0270de 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -685,9 +685,8 @@
Context context,
FalsingManager falsingManager,
LockPatternUtils lockPatternUtils) {
- super();
+ super(context);
- mContext = context;
mFalsingManager = falsingManager;
mLockPatternUtils = lockPatternUtils;
@@ -795,7 +794,6 @@
synchronized (this) {
setupLocked();
}
- putComponent(KeyguardViewMediator.class, this);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
index a9fe54b..4d061e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
@@ -49,6 +49,12 @@
private static final String TAG = "WorkLockActivity";
/**
+ * Add additional extra {@link com.android.settings.password.ConfirmDeviceCredentialActivity} to
+ * enable device policy management enforcement from systemui.
+ */
+ public static final String EXTRA_FROM_WORK_LOCK_ACTIVITY = "from_work_lock_activity";
+
+ /**
* Contains a {@link TaskDescription} for the activity being covered.
*/
static final String EXTRA_TASK_DESCRIPTION =
@@ -151,6 +157,7 @@
if (target != null) {
credential.putExtra(Intent.EXTRA_INTENT, target.getIntentSender());
+ credential.putExtra(EXTRA_FROM_WORK_LOCK_ACTIVITY, true);
}
startActivityForResult(credential, REQUEST_CODE_CONFIRM_CREDENTIALS);
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index aebadf9..4c96de2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -59,6 +59,10 @@
private final NotificationPlayer mAsyncPlayer = new NotificationPlayer(TAG);
private final HashMap<IBinder, Client> mClients = new HashMap<IBinder, Client>();
+ public RingtonePlayer(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
mAsyncPlayer.setUsesWakeLock(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
index 682c76c..f1e801b 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipUI.java
@@ -19,6 +19,7 @@
import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.UserHandle;
@@ -30,15 +31,24 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
/**
* Controls the picture-in-picture window.
*/
+@Singleton
public class PipUI extends SystemUI implements CommandQueue.Callbacks {
private BasePipManager mPipManager;
private boolean mSupportsPip;
+ @Inject
+ public PipUI(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
PackageManager pm = mContext.getPackageManager();
@@ -59,7 +69,6 @@
mPipManager.initialize(mContext);
getComponent(CommandQueue.class).addCallback(this);
- putComponent(PipUI.class, this);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
index 8224365..3f15966 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipDismissViewController.java
@@ -107,7 +107,7 @@
| LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
lp.setTitle("pip-dismiss-overlay");
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
mWindowManager.addView(mDismissView, lp);
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index a258f35..98f0b2a 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -103,7 +103,8 @@
private final BroadcastDispatcher mBroadcastDispatcher;
@Inject
- public PowerUI(BroadcastDispatcher broadcastDispatcher) {
+ public PowerUI(Context context, BroadcastDispatcher broadcastDispatcher) {
+ super(context);
mBroadcastDispatcher = broadcastDispatcher;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index ae83567..2e24403 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -28,10 +28,12 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.provider.Settings;
import android.service.quicksettings.Tile;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
+import android.widget.FrameLayout;
import android.widget.LinearLayout;
import com.android.internal.logging.MetricsLogger;
@@ -49,6 +51,8 @@
import com.android.systemui.qs.external.CustomTile;
import com.android.systemui.settings.BrightnessController;
import com.android.systemui.settings.ToggleSliderView;
+import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.phone.NPVPluginManager;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController.BrightnessMirrorListener;
import com.android.systemui.tuner.TunerService;
@@ -98,6 +102,10 @@
private BrightnessMirrorController mBrightnessMirrorController;
private View mDivider;
+ private FrameLayout mPluginFrame;
+ private final PluginManager mPluginManager;
+ private NPVPluginManager mNPVPluginManager;
+
public QSPanel(Context context) {
this(context, null);
}
@@ -106,9 +114,13 @@
this(context, attrs, null);
}
+ public QSPanel(Context context, AttributeSet attrs, DumpController dumpController) {
+ this(context, attrs, dumpController, null);
+ }
+
@Inject
public QSPanel(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
- DumpController dumpController) {
+ DumpController dumpController, PluginManager pluginManager) {
super(context, attrs);
mContext = context;
@@ -136,6 +148,15 @@
mBrightnessController = new BrightnessController(getContext(),
findViewById(R.id.brightness_slider));
mDumpController = dumpController;
+ mPluginManager = pluginManager;
+ if (mPluginManager != null && Settings.System.getInt(
+ mContext.getContentResolver(), "npv_plugin_flag", 0) == 2) {
+ mPluginFrame = (FrameLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.status_bar_expanded_plugin_frame, this, false);
+ addView(mPluginFrame);
+ mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager);
+ }
+
}
protected void addDivider() {
@@ -377,6 +398,7 @@
if (mListening) {
refreshAllTiles();
}
+ if (mNPVPluginManager != null) mNPVPluginManager.setListening(listening);
}
public void setListening(boolean listening, boolean expanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
index 14addb9..37743ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/TileLayout.java
@@ -2,6 +2,7 @@
import android.content.Context;
import android.content.res.Resources;
+import android.provider.Settings;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
@@ -31,6 +32,9 @@
private boolean mListening;
protected int mMaxAllowedRows = 3;
+ // Prototyping with less rows
+ private final boolean mLessRows;
+
public TileLayout(Context context) {
this(context, null);
}
@@ -38,7 +42,9 @@
public TileLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setFocusableInTouchMode(true);
+ mLessRows = Settings.System.getInt(context.getContentResolver(), "qs_less_rows", 0) != 0;
updateResources();
+
}
@Override
@@ -89,6 +95,7 @@
mCellMarginTop = res.getDimensionPixelSize(R.dimen.qs_tile_margin_top);
mSidePadding = res.getDimensionPixelOffset(R.dimen.qs_tile_layout_margin_side);
mMaxAllowedRows = Math.max(1, getResources().getInteger(R.integer.quick_settings_max_rows));
+ if (mLessRows) mMaxAllowedRows = Math.max(1, mMaxAllowedRows - 1);
if (mColumns != columns) {
mColumns = columns;
requestLayout();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
index 466c808..411980b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java
@@ -39,6 +39,7 @@
import android.util.Log;
import android.view.IWindowManager;
import android.view.WindowManagerGlobal;
+import android.widget.Switch;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Dependency;
@@ -82,6 +83,11 @@
mTile = new Tile();
updateDefaultTileAndIcon();
mServiceManager = host.getTileServices().getTileWrapper(this);
+ if (mServiceManager.isBooleanTile()) {
+ // Replace states with BooleanState
+ resetStates();
+ }
+
mService = mServiceManager.getTileService();
mServiceManager.setTileChangeListener(this);
mUser = ActivityManager.getCurrentUser();
@@ -246,8 +252,10 @@
@Override
public State newTileState() {
- State state = new State();
- return state;
+ if (mServiceManager != null && mServiceManager.isBooleanTile()) {
+ return new BooleanState();
+ }
+ return new State();
}
@Override
@@ -336,6 +344,12 @@
} else {
state.contentDescription = state.label;
}
+
+ if (state instanceof BooleanState) {
+ state.expandedAccessibilityClassName = Switch.class.getName();
+ ((BooleanState) state).value = (state.state == Tile.STATE_ACTIVE);
+ }
+
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index effea6a..f59e0c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -131,6 +131,24 @@
}
/**
+ * Determines whether the associated TileService is a Boolean Tile.
+ *
+ * @return true if {@link TileService#META_DATA_BOOLEAN_TILE} is set to {@code true} for this
+ * tile
+ * @see TileService#META_DATA_BOOLEAN_TILE
+ */
+ public boolean isBooleanTile() {
+ try {
+ ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
+ PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
+ return info.metaData != null
+ && info.metaData.getBoolean(TileService.META_DATA_BOOLEAN_TILE, false);
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
+ /**
* Binds just long enough to send any queued messages, then unbinds.
*/
public void flushMessagesAndUnbind() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index 2a7e55f..0b4e648 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -123,6 +123,10 @@
return mStateManager.isActiveTile();
}
+ public boolean isBooleanTile() {
+ return mStateManager.isBooleanTile();
+ }
+
public void setShowingDialog(boolean dialog) {
mShowingDialog = dialog;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index daaee4c..1c8e451 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -16,6 +16,7 @@
import android.content.Context;
import android.os.Build;
+import android.provider.Settings;
import android.util.Log;
import android.view.ContextThemeWrapper;
@@ -32,6 +33,7 @@
import com.android.systemui.qs.tiles.CastTile;
import com.android.systemui.qs.tiles.CellularTile;
import com.android.systemui.qs.tiles.ColorInversionTile;
+import com.android.systemui.qs.tiles.ControlsTile;
import com.android.systemui.qs.tiles.DataSaverTile;
import com.android.systemui.qs.tiles.DndTile;
import com.android.systemui.qs.tiles.FlashlightTile;
@@ -58,6 +60,7 @@
private final Provider<WifiTile> mWifiTileProvider;
private final Provider<BluetoothTile> mBluetoothTileProvider;
+ private final Provider<ControlsTile> mControlsTileProvider;
private final Provider<CellularTile> mCellularTileProvider;
private final Provider<DndTile> mDndTileProvider;
private final Provider<ColorInversionTile> mColorInversionTileProvider;
@@ -81,6 +84,7 @@
@Inject
public QSFactoryImpl(Provider<WifiTile> wifiTileProvider,
Provider<BluetoothTile> bluetoothTileProvider,
+ Provider<ControlsTile> controlsTileProvider,
Provider<CellularTile> cellularTileProvider,
Provider<DndTile> dndTileProvider,
Provider<ColorInversionTile> colorInversionTileProvider,
@@ -100,6 +104,7 @@
Provider<UiModeNightTile> uiModeNightTileProvider) {
mWifiTileProvider = wifiTileProvider;
mBluetoothTileProvider = bluetoothTileProvider;
+ mControlsTileProvider = controlsTileProvider;
mCellularTileProvider = cellularTileProvider;
mDndTileProvider = dndTileProvider;
mColorInversionTileProvider = colorInversionTileProvider;
@@ -138,6 +143,11 @@
return mWifiTileProvider.get();
case "bt":
return mBluetoothTileProvider.get();
+ case "controls":
+ if (Settings.System.getInt(mHost.getContext().getContentResolver(),
+ "qs_controls_tile_enabled", 0) == 1) {
+ return mControlsTileProvider.get();
+ } else return null;
case "cell":
return mCellularTileProvider.get();
case "dnd":
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
index 681de37..e0f26cd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java
@@ -139,6 +139,11 @@
mQSSettingsPanelOption = QSSettingsControllerKt.getQSSettingsPanelOption();
}
+ protected final void resetStates() {
+ mState = newTileState();
+ mTmpState = newTileState();
+ }
+
@NonNull
@Override
public Lifecycle getLifecycle() {
@@ -629,6 +634,11 @@
}
}
+ /**
+ * Dumps the state of this tile along with its name.
+ *
+ * This may be used for CTS testing of tiles.
+ */
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println(this.getClass().getSimpleName() + ":");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ControlsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ControlsTile.java
new file mode 100644
index 0000000..0a59618
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ControlsTile.java
@@ -0,0 +1,197 @@
+/*
+ * 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 com.android.systemui.qs.tiles;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.HomeControlsPlugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.qs.DetailAdapter;
+import com.android.systemui.plugins.qs.QSTile.BooleanState;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.shared.plugins.PluginManager;
+
+import javax.inject.Inject;
+
+
+/**
+ * Temporary control test for prototyping
+ */
+public class ControlsTile extends QSTileImpl<BooleanState> {
+ private ControlsDetailAdapter mDetailAdapter;
+ private final ActivityStarter mActivityStarter;
+ private PluginManager mPluginManager;
+ private HomeControlsPlugin mPlugin;
+ private Intent mHomeAppIntent;
+
+ @Inject
+ public ControlsTile(QSHost host,
+ ActivityStarter activityStarter,
+ PluginManager pluginManager) {
+ super(host);
+ mActivityStarter = activityStarter;
+ mPluginManager = pluginManager;
+ mDetailAdapter = (ControlsDetailAdapter) createDetailAdapter();
+
+ mHomeAppIntent = new Intent(Intent.ACTION_VIEW);
+ mHomeAppIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mHomeAppIntent.setComponent(new ComponentName("com.google.android.apps.chromecast.app",
+ "com.google.android.apps.chromecast.app.DiscoveryActivity"));
+ }
+
+ @Override
+ public DetailAdapter getDetailAdapter() {
+ return mDetailAdapter;
+ }
+
+ @Override
+ public BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ public void handleSetListening(boolean listening) {
+
+ }
+
+ @Override
+ public void setDetailListening(boolean listening) {
+ if (mPlugin == null) return;
+
+ mPlugin.setVisible(listening);
+ }
+
+ @Override
+ protected void handleClick() {
+ showDetail(true);
+ }
+
+ @Override
+ public Intent getLongClickIntent() {
+ return mHomeAppIntent;
+ }
+
+ @Override
+ protected void handleSecondaryClick() {
+ showDetail(true);
+ }
+
+ @Override
+ public CharSequence getTileLabel() {
+ return "Controls";
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ state.icon = ResourceIcon.get(R.drawable.ic_lightbulb_outline_gm2_24px);
+ state.label = "Controls";
+ }
+
+ @Override
+ public boolean supportsDetailView() {
+ return getDetailAdapter() != null && mQSSettingsPanelOption == QSSettingsPanel.OPEN_CLICK;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return -1;
+ }
+
+ @Override
+ protected String composeChangeAnnouncement() {
+ if (mState.value) {
+ return "On";
+ } else {
+ return "Off";
+ }
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return true;
+ }
+
+ @Override
+ protected DetailAdapter createDetailAdapter() {
+ mDetailAdapter = new ControlsDetailAdapter();
+ return mDetailAdapter;
+ }
+
+ private class ControlsDetailAdapter implements DetailAdapter {
+ private View mDetailView;
+ protected LinearLayout mHomeControlsLayout;
+
+ public CharSequence getTitle() {
+ return "Controls";
+ }
+
+ public Boolean getToggleState() {
+ return null;
+ }
+
+ public boolean getToggleEnabled() {
+ return false;
+ }
+
+ public View createDetailView(Context context, View convertView, final ViewGroup parent) {
+ mHomeControlsLayout = (LinearLayout) LayoutInflater.from(context).inflate(
+ R.layout.home_controls, parent, false);
+ mHomeControlsLayout.setVisibility(View.VISIBLE);
+ mPluginManager.addPluginListener(
+ new PluginListener<HomeControlsPlugin>() {
+ @Override
+ public void onPluginConnected(HomeControlsPlugin plugin,
+ Context pluginContext) {
+ mPlugin = plugin;
+ mPlugin.sendParentGroup(mHomeControlsLayout);
+ mPlugin.setVisible(true);
+ }
+
+ @Override
+ public void onPluginDisconnected(HomeControlsPlugin plugin) {
+
+ }
+ }, HomeControlsPlugin.class, false);
+ return mHomeControlsLayout;
+ }
+
+ public Intent getSettingsIntent() {
+ return mHomeAppIntent;
+ }
+
+ public void setToggleState(boolean state) {
+
+ }
+
+ public int getMetricsCategory() {
+ return -1;
+ }
+
+ public boolean hasHeader() {
+ return false;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index e0ae8ed..3fc1398 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -101,6 +101,7 @@
private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
private final Context mContext;
+ private final PipUI mPipUI;
private SysUiState mSysUiState;
private final Handler mHandler;
private final NavigationBarController mNavBarController;
@@ -361,8 +362,7 @@
}
long token = Binder.clearCallingIdentity();
try {
- final PipUI component = SysUiServiceProvider.getComponent(mContext, PipUI.class);
- component.setShelfHeight(visible, shelfHeight);
+ mPipUI.setShelfHeight(visible, shelfHeight);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -479,8 +479,9 @@
public OverviewProxyService(Context context, DeviceProvisionedController provisionController,
NavigationBarController navBarController, NavigationModeController navModeController,
StatusBarWindowController statusBarWinController,
- SysUiState sysUiState) {
+ SysUiState sysUiState, PipUI pipUI) {
mContext = context;
+ mPipUI = pipUI;
mHandler = new Handler();
mNavBarController = navBarController;
mStatusBarWinController = statusBarWinController;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index a1b4a93..0a8264b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -17,6 +17,7 @@
package com.android.systemui.recents;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.provider.Settings;
@@ -37,7 +38,8 @@
private final RecentsImplementation mImpl;
@Inject
- public Recents(RecentsImplementation impl) {
+ public Recents(Context context, RecentsImplementation impl) {
+ super(context);
mImpl = impl;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
index c1ce163..aa64449 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsOnboarding.java
@@ -497,7 +497,7 @@
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
flags,
PixelFormat.TRANSLUCENT);
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setTitle("RecentsOnboarding");
lp.gravity = gravity;
return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 0f277ca..2d1c087 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -128,7 +128,7 @@
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT);
lp.token = new Binder();
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setTitle("ScreenPinningConfirmation");
lp.gravity = Gravity.FILL;
return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 07675e2..df9791d 100644
--- a/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/packages/SystemUI/src/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -19,6 +19,7 @@
import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import android.content.Context;
import android.content.res.Configuration;
import android.os.RemoteException;
import android.util.Log;
@@ -52,6 +53,10 @@
protected final long SC_DOCK_LEFT = META_MASK | KeyEvent.KEYCODE_LEFT_BRACKET;
protected final long SC_DOCK_RIGHT = META_MASK | KeyEvent.KEYCODE_RIGHT_BRACKET;
+ public ShortcutKeyDispatcher(Context context) {
+ super(context);
+ }
+
/**
* Registers a shortcut key to window manager.
* @param shortcutCode packed representation of shortcut key code and meta information
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index cd2074f..c8b2b6a 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -19,6 +19,7 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import android.content.Context;
import android.content.res.Configuration;
import android.os.RemoteException;
import android.util.Log;
@@ -50,6 +51,10 @@
private boolean mHomeStackResizable = false;
private ForcedResizableInfoActivityController mForcedResizableController;
+ public Divider(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
mWindowManager = new DividerWindowManager(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 36e04fe..d6a8f90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -1101,6 +1101,10 @@
// Need this class since CommandQueue already extends IStatusBar.Stub, so CommandQueueStart
// is needed so it can extend SystemUI.
public static class CommandQueueStart extends SystemUI {
+ public CommandQueueStart(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
putComponent(CommandQueue.class, new CommandQueue(mContext));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
index 16cdfaa..5144a95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
@@ -152,7 +152,9 @@
// Dependency problem.
AutoHideController autoHideController = isOnDefaultDisplay
? Dependency.get(AutoHideController.class)
- : new AutoHideController(context, mHandler);
+ : new AutoHideController(context, mHandler,
+ Dependency.get(NotificationRemoteInputManager.class),
+ Dependency.get(IWindowManager.class));
navBar.setAutoHideController(autoHideController);
navBar.restoreSystemUiVisibilityState();
mNavigationBars.append(displayId, navBar);
@@ -231,11 +233,13 @@
}
/** @return {@link NavigationBarFragment} on the default display. */
+ @Nullable
public NavigationBarFragment getDefaultNavigationBarFragment() {
return mNavigationBars.get(DEFAULT_DISPLAY);
}
/** @return {@link AssistHandleViewController} (only on the default display). */
+ @Nullable
public AssistHandleViewController getAssistHandlerViewController() {
NavigationBarFragment navBar = getDefaultNavigationBarFragment();
return navBar == null ? null : navBar.getAssistHandlerViewController();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
index 6c0f90a..c4de2d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener.java
@@ -127,7 +127,7 @@
mEntryManager.removeNotification(key, rankingMap, UNDEFINED_DISMISS_REASON);
} else {
mEntryManager.getNotificationData()
- .updateRanking(rankingMap);
+ .updateRanking(rankingMap, "onNotificationPosted");
}
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 4ba1114..6ffea79 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -106,7 +106,7 @@
isCurrentProfile(getSendingUserId())) {
mUsersAllowingPrivateNotifications.clear();
updateLockscreenNotificationSetting();
- getEntryManager().updateNotifications();
+ getEntryManager().updateNotifications("ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED");
}
}
};
@@ -124,7 +124,7 @@
updatePublicMode();
// The filtering needs to happen before the update call below in order to make sure
// the presenter has the updated notifications from the new user
- getEntryManager().getNotificationData().filterAndSort();
+ getEntryManager().getNotificationData().filterAndSort("user switched");
mPresenter.onUserSwitched(mCurrentUserId);
for (UserChangedListener listener : mListeners) {
@@ -205,7 +205,8 @@
mUsersAllowingNotifications.clear();
// ... and refresh all the notifications
updateLockscreenNotificationSetting();
- getEntryManager().updateNotifications();
+ getEntryManager().updateNotifications("LOCK_SCREEN_SHOW_NOTIFICATIONS,"
+ + " or LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS change");
}
};
@@ -214,7 +215,8 @@
public void onChange(boolean selfChange) {
updateLockscreenNotificationSetting();
if (mDeviceProvisionedController.isDeviceProvisioned()) {
- getEntryManager().updateNotifications();
+ getEntryManager().updateNotifications("LOCK_SCREEN_ALLOW_REMOTE_INPUT"
+ + " or ZEN_MODE change");
}
}
};
@@ -532,7 +534,7 @@
setLockscreenPublicMode(isProfilePublic, userId);
mUsersWithSeperateWorkChallenge.put(userId, needsSeparateChallenge);
}
- getEntryManager().updateNotifications();
+ getEntryManager().updateNotifications("NotificationLockscreenUserManager.updatePublicMode");
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index c50fb3d..3616b54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -361,7 +361,7 @@
}
if (metaDataChanged) {
- mEntryManager.updateNotifications();
+ mEntryManager.updateNotifications("NotificationMediaManager - metaDataChanged");
}
dispatchUpdateMediaMetaData(metaDataChanged, true /* allowEnterAnimation */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
index a5b7fa7..f284f73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
@@ -31,6 +31,7 @@
import android.app.PendingIntent;
import android.app.SynchronousUserSwitchObserver;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
@@ -60,7 +61,7 @@
import java.util.List;
-/** The clsss to show notification(s) of instant apps. This may show multiple notifications on
+/** The class to show notification(s) of instant apps. This may show multiple notifications on
* splitted screen.
*/
public class InstantAppNotifier extends SystemUI
@@ -74,7 +75,9 @@
private boolean mDockedStackExists;
private KeyguardStateController mKeyguardStateController;
- public InstantAppNotifier() {}
+ public InstantAppNotifier(Context context) {
+ super(context);
+ }
@Override
public void start() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 148a1a8..01c79b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -40,6 +40,8 @@
import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.logging.NotifEvent;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
@@ -92,6 +94,7 @@
private NotificationListenerService.RankingMap mLatestRankingMap;
@VisibleForTesting
protected NotificationData mNotificationData;
+ private NotifLog mNotifLog;
@VisibleForTesting
final ArrayList<NotificationLifetimeExtender> mNotificationLifetimeExtenders
@@ -123,8 +126,9 @@
}
@Inject
- public NotificationEntryManager(NotificationData notificationData) {
+ public NotificationEntryManager(NotificationData notificationData, NotifLog notifLog) {
mNotificationData = notificationData;
+ mNotifLog = notifLog;
}
/** Adds a {@link NotificationEntryListener}. */
@@ -178,7 +182,7 @@
@Override
public void onReorderingAllowed() {
- updateNotifications();
+ updateNotifications("reordering is now allowed");
}
/**
@@ -203,15 +207,19 @@
return NotificationVisibility.obtain(key, rank, count, true, location);
}
- private void abortExistingInflation(String key) {
+ private void abortExistingInflation(String key, String reason) {
if (mPendingNotifications.containsKey(key)) {
NotificationEntry entry = mPendingNotifications.get(key);
entry.abortTask();
mPendingNotifications.remove(key);
+ mNotifLog.log(NotifEvent.INFLATION_ABORTED, entry.sbn(), null,
+ "PendingNotification aborted. " + reason);
}
NotificationEntry addedEntry = mNotificationData.get(key);
if (addedEntry != null) {
addedEntry.abortTask();
+ mNotifLog.log(NotifEvent.INFLATION_ABORTED, addedEntry.sbn(),
+ null, reason);
}
}
@@ -247,7 +255,7 @@
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onBeforeNotificationAdded(entry);
}
- updateNotifications();
+ updateNotifications("onAsyncInflationFinished");
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onNotificationAdded(entry);
}
@@ -276,7 +284,8 @@
if (mRemoveInterceptor != null
&& mRemoveInterceptor.onNotificationRemoveRequested(key, reason)) {
- // Remove intercepted; skip
+ // Remove intercepted; log and skip
+ mNotifLog.log(NotifEvent.REMOVE_INTERCEPTED);
return;
}
@@ -291,13 +300,17 @@
if (extender.shouldExtendLifetimeForPendingNotification(pendingEntry)) {
extendLifetime(pendingEntry, extender);
lifetimeExtended = true;
+ mNotifLog.log(
+ NotifEvent.LIFETIME_EXTENDED,
+ pendingEntry.sbn(),
+ "pendingEntry extendedBy=" + extender.toString());
}
}
}
}
if (!lifetimeExtended) {
- abortExistingInflation(key);
+ abortExistingInflation(key, "removeNotification");
}
if (entry != null) {
@@ -310,6 +323,10 @@
mLatestRankingMap = ranking;
extendLifetime(entry, extender);
lifetimeExtended = true;
+ mNotifLog.log(
+ NotifEvent.LIFETIME_EXTENDED,
+ entry.sbn(),
+ "entry extendedBy=" + extender.toString());
break;
}
}
@@ -329,10 +346,12 @@
handleGroupSummaryRemoved(key);
mNotificationData.remove(key, ranking);
- updateNotifications();
+ updateNotifications("removeNotificationInternal");
Dependency.get(LeakDetector.class).trackGarbage(entry);
removedByUser |= entryDismissed;
+ mNotifLog.log(NotifEvent.NOTIF_REMOVED, entry.sbn(),
+ "removedByUser=" + removedByUser);
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onEntryRemoved(entry, visibility, removedByUser);
}
@@ -389,7 +408,7 @@
Log.d(TAG, "addNotification key=" + key);
}
- mNotificationData.updateRanking(rankingMap);
+ mNotificationData.updateRanking(rankingMap, "addNotificationInternal");
Ranking ranking = new Ranking();
rankingMap.getRanking(key, ranking);
@@ -400,9 +419,9 @@
requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
REASON_CANCEL));
- abortExistingInflation(key);
-
+ abortExistingInflation(key, "addNotification");
mPendingNotifications.put(key, entry);
+ mNotifLog.log(NotifEvent.NOTIF_ADDED, entry.sbn());
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onPendingEntryAdded(entry);
}
@@ -423,7 +442,7 @@
if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
final String key = notification.getKey();
- abortExistingInflation(key);
+ abortExistingInflation(key, "updateNotification");
NotificationEntry entry = mNotificationData.get(key);
if (entry == null) {
return;
@@ -433,15 +452,15 @@
// to keep its lifetime extended.
cancelLifetimeExtension(entry);
- mNotificationData.update(entry, ranking, notification);
-
+ mNotificationData.update(entry, ranking, notification, "updateNotificationInternal");
+ mNotifLog.log(NotifEvent.NOTIF_UPDATED, entry.sbn(), entry.ranking());
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onPreEntryUpdated(entry);
}
requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
REASON_CANCEL));
- updateNotifications();
+ updateNotifications("updateNotificationInternal");
if (DEBUG) {
// Is this for you?
@@ -465,8 +484,12 @@
}
}
- public void updateNotifications() {
- mNotificationData.filterAndSort();
+ /**
+ * Update the notifications
+ * @param reason why the notifications are updating
+ */
+ public void updateNotifications(String reason) {
+ mNotificationData.filterAndSort(reason);
if (mPresenter != null) {
mPresenter.updateNotificationViews();
}
@@ -489,7 +512,7 @@
}
// Populate notification entries from the new rankings.
- mNotificationData.updateRanking(rankingMap);
+ mNotificationData.updateRanking(rankingMap, "updateNotificationRanking");
updateRankingOfPendingNotifications(rankingMap);
// By comparing the old and new UI adjustments, reinflate the view accordingly.
@@ -501,7 +524,7 @@
NotificationUiAdjustment.extractFromNotificationEntry(entry));
}
- updateNotifications();
+ updateNotifications("updateNotificationRanking");
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onNotificationRankingUpdated(rankingMap);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java
index 769cbb7..970cbf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationListController.java
@@ -81,7 +81,7 @@
new DeviceProvisionedListener() {
@Override
public void onDeviceProvisionedChanged() {
- mEntryManager.updateNotifications();
+ mEntryManager.updateNotifications("device provisioned changed");
}
};
@@ -106,7 +106,7 @@
if (foregroundKey != null) {
mEntryManager
.getNotificationData().updateAppOp(appOp, uid, pkg, foregroundKey, showIcon);
- mEntryManager.updateNotifications();
+ mEntryManager.updateNotifications("app opp changed pkg=" + pkg);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
index 2f67f90..8a23e37 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt
@@ -37,10 +37,10 @@
@Singleton
class NotificationWakeUpCoordinator @Inject constructor(
- private val mContext: Context,
private val mHeadsUpManagerPhone: HeadsUpManagerPhone,
private val statusBarStateController: StatusBarStateController,
- private val bypassController: KeyguardBypassController)
+ private val bypassController: KeyguardBypassController,
+ private val dozeParameters: DozeParameters)
: OnHeadsUpChangedListener, StatusBarStateController.StateListener,
PanelExpansionListener {
@@ -67,7 +67,6 @@
private var mVisibilityAmount = 0.0f
private var mLinearVisibilityAmount = 0.0f
private val mEntrySetToClearWhenFinished = mutableSetOf<NotificationEntry>()
- private val mDozeParameters: DozeParameters
private var pulseExpanding: Boolean = false
private val wakeUpListeners = arrayListOf<WakeUpListener>()
private var state: Int = StatusBarState.KEYGUARD
@@ -146,7 +145,6 @@
init {
mHeadsUpManagerPhone.addListener(this)
statusBarStateController.addCallback(this)
- mDozeParameters = DozeParameters.getInstance(mContext)
addListener(object : WakeUpListener {
override fun onFullyHiddenChanged(isFullyHidden: Boolean) {
if (isFullyHidden && mNotificationsVisibleForExpansion) {
@@ -377,7 +375,7 @@
}
private fun shouldAnimateVisibility() =
- mDozeParameters.getAlwaysOn() && !mDozeParameters.getDisplayNeedsBlanking()
+ dozeParameters.getAlwaysOn() && !dozeParameters.getDisplayNeedsBlanking()
interface WakeUpListener {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
index cf0fbbb..a98fa66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationData.java
@@ -35,6 +35,8 @@
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
+import com.android.systemui.statusbar.notification.logging.NotifEvent;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -73,10 +75,13 @@
private RankingMap mRankingMap;
private final Ranking mTmpRanking = new Ranking();
private final boolean mUsePeopleFiltering;
+ private final NotifLog mNotifLog;
@Inject
- public NotificationData(NotificationSectionsFeatureManager sectionsFeatureManager) {
+ public NotificationData(NotificationSectionsFeatureManager sectionsFeatureManager,
+ NotifLog notifLog) {
mUsePeopleFiltering = sectionsFeatureManager.isFilteringEnabled();
+ mNotifLog = notifLog;
}
public void setHeadsUpManager(HeadsUpManager headsUpManager) {
@@ -179,7 +184,7 @@
}
mGroupManager.onEntryAdded(entry);
- updateRankingAndSort(mRankingMap);
+ updateRankingAndSort(mRankingMap, "addEntry=" + entry.sbn());
}
public NotificationEntry remove(String key, RankingMap ranking) {
@@ -189,7 +194,7 @@
}
if (removed == null) return null;
mGroupManager.onEntryRemoved(removed);
- updateRankingAndSort(ranking);
+ updateRankingAndSort(ranking, "removeEntry=" + removed.sbn());
return removed;
}
@@ -197,15 +202,19 @@
public void update(
NotificationEntry entry,
RankingMap ranking,
- StatusBarNotification notification) {
- updateRanking(ranking);
+ StatusBarNotification notification,
+ String reason) {
+ updateRanking(ranking, reason);
final StatusBarNotification oldNotification = entry.notification;
entry.setNotification(notification);
mGroupManager.onEntryUpdated(entry, oldNotification);
}
- public void updateRanking(RankingMap ranking) {
- updateRankingAndSort(ranking);
+ /**
+ * Update ranking and trigger a re-sort
+ */
+ public void updateRanking(RankingMap ranking, String reason) {
+ updateRankingAndSort(ranking, reason);
}
public void updateAppOp(int appOp, int uid, String pkg, String key, boolean showIcon) {
@@ -352,7 +361,7 @@
return false;
}
- private void updateRankingAndSort(RankingMap rankingMap) {
+ private void updateRankingAndSort(RankingMap rankingMap, String reason) {
if (rankingMap != null) {
mRankingMap = rankingMap;
synchronized (mEntries) {
@@ -375,7 +384,7 @@
}
}
}
- filterAndSort();
+ filterAndSort(reason);
}
/**
@@ -393,7 +402,11 @@
// TODO: This should not be public. Instead the Environment should notify this class when
// anything changed, and this class should call back the UI so it updates itself.
- public void filterAndSort() {
+ /**
+ * Filters and sorts the list of notification entries
+ */
+ public void filterAndSort(String reason) {
+ mNotifLog.log(NotifEvent.FILTER_AND_SORT, reason);
mSortedAndFiltered.clear();
synchronized (mEntries) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
index 60cf995..e5571b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
@@ -41,6 +41,8 @@
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.NotificationClicker;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.logging.NotifEvent;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
@@ -72,6 +74,7 @@
private final boolean mAllowLongPress;
private final KeyguardBypassController mKeyguardBypassController;
private final StatusBarStateController mStatusBarStateController;
+ private final NotifLog mNotifLog;
private NotificationRemoteInputManager mRemoteInputManager;
private NotificationPresenter mPresenter;
@@ -85,12 +88,14 @@
public NotificationRowBinderImpl(Context context, boolean allowLongPress,
KeyguardBypassController keyguardBypassController,
- StatusBarStateController statusBarStateController) {
+ StatusBarStateController statusBarStateController,
+ NotifLog notifLog) {
mContext = context;
mMessagingUtil = new NotificationMessagingUtil(context);
mAllowLongPress = allowLongPress;
mKeyguardBypassController = keyguardBypassController;
mStatusBarStateController = statusBarStateController;
+ mNotifLog = notifLog;
}
private NotificationRemoteInputManager getRemoteInputManager() {
@@ -143,6 +148,7 @@
row -> {
bindRow(entry, pmUser, sbn, row, onDismissRunnable);
updateNotification(entry, pmUser, sbn, row);
+ mNotifLog.log(NotifEvent.INFLATED, sbn);
});
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
index 2396d28..7703cbd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
@@ -30,9 +30,9 @@
* and triaging purposes.
*/
public class NotifEvent extends RichEvent {
- public static final int TOTAL_EVENT_TYPES = 8;
- private StatusBarNotification mSbn;
- private Ranking mRanking;
+ public static final int TOTAL_EVENT_TYPES = 11;
+ private final StatusBarNotification mSbn;
+ private final Ranking mRanking;
/**
* Creates a NotifEvent with an event type that matches with an index in the array
@@ -44,9 +44,20 @@
public NotifEvent(int logLevel, int type, String reason, StatusBarNotification sbn,
Ranking ranking) {
super(logLevel, type, reason);
- mSbn = sbn.clone();
- mRanking = new Ranking();
- mRanking.populate(ranking);
+
+ if (sbn != null) {
+ mSbn = sbn.cloneLight();
+ } else {
+ mSbn = null;
+ }
+
+ if (ranking != null) {
+ mRanking = new Ranking();
+ mRanking.populate(ranking);
+ } else {
+ mRanking = null;
+ }
+
mMessage += getExtraInfo();
}
@@ -76,11 +87,14 @@
"NotifAdded",
"NotifRemoved",
"NotifUpdated",
- "HeadsUpStarted",
- "HeadsUpEnded",
"Filter",
"Sort",
+ "FilterAndSort",
"NotifVisibilityChanged",
+ "LifetimeExtended",
+ "RemoveIntercepted",
+ "InflationAborted",
+ "Inflated"
};
if (events.length != TOTAL_EVENT_TYPES) {
@@ -135,8 +149,19 @@
}
}
- @IntDef({NOTIF_ADDED, NOTIF_REMOVED, NOTIF_UPDATED, HEADS_UP_STARTED, HEADS_UP_ENDED, FILTER,
- SORT, NOTIF_VISIBILITY_CHANGED})
+ @IntDef({NOTIF_ADDED,
+ NOTIF_REMOVED,
+ NOTIF_UPDATED,
+ FILTER,
+ SORT,
+ FILTER_AND_SORT,
+ NOTIF_VISIBILITY_CHANGED,
+ LIFETIME_EXTENDED,
+ REMOVE_INTERCEPTED,
+ INFLATION_ABORTED,
+ INFLATED
+ })
+
/**
* Types of NotifEvents
*/
@@ -145,9 +170,13 @@
public static final int NOTIF_ADDED = 0;
public static final int NOTIF_REMOVED = 1;
public static final int NOTIF_UPDATED = 2;
- public static final int HEADS_UP_STARTED = 3;
- public static final int HEADS_UP_ENDED = 4;
- public static final int FILTER = 5;
- public static final int SORT = 6;
- public static final int NOTIF_VISIBILITY_CHANGED = 7;
+ public static final int FILTER = 3;
+ public static final int SORT = 4;
+ public static final int FILTER_AND_SORT = 5;
+ public static final int NOTIF_VISIBILITY_CHANGED = 6;
+ public static final int LIFETIME_EXTENDED = 7;
+ // unable to remove notif - removal intercepted by {@link NotificationRemoveInterceptor}
+ public static final int REMOVE_INTERCEPTED = 8;
+ public static final int INFLATION_ABORTED = 9;
+ public static final int INFLATED = 10;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java
index d42cd82..8466d2e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifLog.java
@@ -82,6 +82,14 @@
}
/**
+ * Logs a {@link NotifEvent} with a notification
+ * @return true if successfully logged, else false
+ */
+ public boolean log(@NotifEvent.EventType int eventType, StatusBarNotification sbn, String msg) {
+ return log(eventType, sbn, null, msg);
+ }
+
+ /**
* Logs a {@link NotifEvent} with a ranking
* @return true if successfully logged, else false
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 9f4b026..924a347 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -1447,6 +1447,7 @@
}
setDismissed(fromAccessibility);
if (mEntry.isClearable()) {
+ // TODO: beverlyt, log dismissal
// TODO: track dismiss sentiment
if (mOnDismissRunnable != null) {
mOnDismissRunnable.run();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
index 73093c6..37f63c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManager.java
@@ -139,7 +139,8 @@
mBlockingHelperRow.setBlockingHelperShowing(false);
if (mBlockingHelperRow.isAttachedToWindow()) {
- Dependency.get(NotificationEntryManager.class).updateNotifications();
+ Dependency.get(NotificationEntryManager.class).updateNotifications(
+ "dismissCurrentBlockingHelper");
}
mBlockingHelperRow = null;
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index f5705c5..9817825 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -473,8 +473,7 @@
private int mHeadsUpInset;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private NotificationIconAreaController mIconAreaController;
- private final NotificationLockscreenUserManager mLockscreenUserManager =
- Dependency.get(NotificationLockscreenUserManager.class);
+ private final NotificationLockscreenUserManager mLockscreenUserManager;
private final Rect mTmpRect = new Rect();
private final NotificationEntryManager mEntryManager =
Dependency.get(NotificationEntryManager.class);
@@ -497,8 +496,7 @@
private NotificationPanelView mNotificationPanel;
private final ShadeController mShadeController = Dependency.get(ShadeController.class);
- private final NotificationGutsManager
- mNotificationGutsManager = Dependency.get(NotificationGutsManager.class);
+ private final NotificationGutsManager mNotificationGutsManager;
private final NotificationSectionsManager mSectionsManager;
private boolean mAnimateBottomOnLayout;
private float mLastSentAppear;
@@ -518,6 +516,8 @@
HeadsUpManagerPhone headsUpManager,
KeyguardBypassController keyguardBypassController,
FalsingManager falsingManager,
+ NotificationLockscreenUserManager notificationLockscreenUserManager,
+ NotificationGutsManager notificationGutsManager,
NotificationSectionsFeatureManager sectionsFeatureManager) {
super(context, attrs, 0, 0);
Resources res = getResources();
@@ -526,6 +526,8 @@
mRoundnessManager = notificationRoundnessManager;
+ mLockscreenUserManager = notificationLockscreenUserManager;
+ mNotificationGutsManager = notificationGutsManager;
mHeadsUpManager = headsUpManager;
mHeadsUpManager.addListener(mRoundnessManager);
mHeadsUpManager.setAnimationStateHandler(this::setHeadsUpGoingAwayAnimationsAllowed);
@@ -5333,7 +5335,7 @@
requestChildrenUpdate();
onUpdateRowStates();
- mEntryManager.updateNotifications();
+ mEntryManager.updateNotifications("StatusBar state changed");
updateVisibility();
}
@@ -6492,12 +6494,12 @@
@Override
public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
- mStatusBar.requestNotificationUpdate();
+ mStatusBar.requestNotificationUpdate("onGroupCreatedFromChildren");
}
@Override
public void onGroupsChanged() {
- mStatusBar.requestNotificationUpdate();
+ mStatusBar.requestNotificationUpdate("onGroupsChanged");
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
index 5912cd7..175d072 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
@@ -29,7 +29,6 @@
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
import com.android.systemui.SysUiServiceProvider;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -68,12 +67,14 @@
};
@Inject
- public AutoHideController(Context context, @Named(MAIN_HANDLER_NAME) Handler handler) {
+ public AutoHideController(Context context, @Named(MAIN_HANDLER_NAME) Handler handler,
+ NotificationRemoteInputManager notificationRemoteInputManager,
+ IWindowManager iWindowManager) {
mCommandQueue = SysUiServiceProvider.getComponent(context, CommandQueue.class);
mCommandQueue.addCallback(this);
mHandler = handler;
- mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
- mWindowManagerService = Dependency.get(IWindowManager.class);
+ mRemoteInputManager = notificationRemoteInputManager;
+ mWindowManagerService = iWindowManager;
mDisplayId = context.getDisplayId();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 7cbdfb0..548afd5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -128,6 +128,7 @@
private final KeyguardBypassController mKeyguardBypassController;
private PowerManager.WakeLock mWakeLock;
private final KeyguardUpdateMonitor mUpdateMonitor;
+ private final DozeParameters mDozeParameters;
private final KeyguardStateController mKeyguardStateController;
private final StatusBarWindowController mStatusBarWindowController;
private final Context mContext;
@@ -146,31 +147,33 @@
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
- public BiometricUnlockController(Context context,
+ public BiometricUnlockController(
+ Context context,
DozeScrimController dozeScrimController,
KeyguardViewMediator keyguardViewMediator,
ScrimController scrimController,
StatusBar statusBar,
KeyguardStateController keyguardStateController, Handler handler,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- KeyguardBypassController keyguardBypassController) {
+ KeyguardBypassController keyguardBypassController,
+ DozeParameters dozeParameters) {
this(context, dozeScrimController, keyguardViewMediator, scrimController, statusBar,
keyguardStateController, handler, keyguardUpdateMonitor,
context.getResources()
.getInteger(com.android.internal.R.integer.config_wakeUpDelayDoze),
- keyguardBypassController);
+ keyguardBypassController, dozeParameters);
}
@VisibleForTesting
- protected BiometricUnlockController(Context context,
- DozeScrimController dozeScrimController, KeyguardViewMediator keyguardViewMediator,
- ScrimController scrimController, StatusBar statusBar,
- KeyguardStateController keyguardStateController, Handler handler,
+ protected BiometricUnlockController(Context context, DozeScrimController dozeScrimController,
+ KeyguardViewMediator keyguardViewMediator, ScrimController scrimController,
+ StatusBar statusBar, KeyguardStateController keyguardStateController, Handler handler,
KeyguardUpdateMonitor keyguardUpdateMonitor, int wakeUpDelay,
- KeyguardBypassController keyguardBypassController) {
+ KeyguardBypassController keyguardBypassController, DozeParameters dozeParameters) {
mContext = context;
mPowerManager = context.getSystemService(PowerManager.class);
mUpdateMonitor = keyguardUpdateMonitor;
+ mDozeParameters = dozeParameters;
mUpdateMonitor.registerCallback(this);
mMediaManager = Dependency.get(NotificationMediaManager.class);
Dependency.get(WakefulnessLifecycle.class).addObserver(mWakefulnessObserver);
@@ -284,7 +287,7 @@
}
// During wake and unlock, we need to draw black before waking up to avoid abrupt
// brightness changes due to display state transitions.
- boolean alwaysOnEnabled = DozeParameters.getInstance(mContext).getAlwaysOn();
+ boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
boolean delayWakeUp = mode == MODE_WAKE_AND_UNLOCK && alwaysOnEnabled && mWakeUpDelay > 0;
Runnable wakeUp = ()-> {
if (!wasDeviceInteractive) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
index 0d62703..78ea5c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DoubleTapHelper.java
@@ -100,6 +100,7 @@
event.getY() - mActivationY);
}
if (withinDoubleTapSlop) {
+ makeInactive();
if (!mDoubleTapListener.onDoubleTap()) {
return false;
}
@@ -134,6 +135,7 @@
if (mActivated) {
mActivated = false;
mActivationListener.onActiveChanged(false);
+ mView.removeCallbacks(mTapTimeoutRunnable);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index bb6a38e..28dac87 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -16,7 +16,7 @@
package com.android.systemui.statusbar.phone;
-import android.content.Context;
+import android.content.res.Resources;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.PowerManager;
import android.os.SystemProperties;
@@ -25,7 +25,7 @@
import android.util.MathUtils;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
+import com.android.systemui.DependencyProvider;
import com.android.systemui.R;
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.doze.DozeScreenState;
@@ -33,9 +33,13 @@
import java.io.PrintWriter;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
/**
* Retrieve doze information
*/
+@Singleton
public class DozeParameters implements TunerService.Tunable,
com.android.systemui.plugins.statusbar.DozeParameters {
private static final int MAX_DURATION = 60 * 1000;
@@ -44,35 +48,33 @@
public static final boolean FORCE_BLANKING =
SystemProperties.getBoolean("debug.force_blanking", false);
- private static DozeParameters sInstance;
-
- private final Context mContext;
private final AmbientDisplayConfiguration mAmbientDisplayConfiguration;
private final PowerManager mPowerManager;
private final AlwaysOnDisplayPolicy mAlwaysOnPolicy;
+ private final Resources mResources;
private boolean mDozeAlwaysOn;
private boolean mControlScreenOffAnimation;
- public static DozeParameters getInstance(Context context) {
- if (sInstance == null) {
- sInstance = new DozeParameters(context);
- }
- return sInstance;
- }
-
- @VisibleForTesting
- protected DozeParameters(Context context) {
- mContext = context.getApplicationContext();
- mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext);
- mAlwaysOnPolicy = new AlwaysOnDisplayPolicy(mContext);
+ @Inject
+ protected DozeParameters(
+ @DependencyProvider.MainResources Resources resources,
+ AmbientDisplayConfiguration ambientDisplayConfiguration,
+ AlwaysOnDisplayPolicy alwaysOnDisplayPolicy,
+ PowerManager powerManager,
+ TunerService tunerService) {
+ mResources = resources;
+ mAmbientDisplayConfiguration = ambientDisplayConfiguration;
+ mAlwaysOnPolicy = alwaysOnDisplayPolicy;
mControlScreenOffAnimation = !getDisplayNeedsBlanking();
- mPowerManager = mContext.getSystemService(PowerManager.class);
+ mPowerManager = powerManager;
mPowerManager.setDozeAfterScreenOff(!mControlScreenOffAnimation);
- Dependency.get(TunerService.class).addTunable(this, Settings.Secure.DOZE_ALWAYS_ON,
+ tunerService.addTunable(
+ this,
+ Settings.Secure.DOZE_ALWAYS_ON,
Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
}
@@ -95,7 +97,7 @@
}
public boolean getDozeSuspendDisplayStateSupported() {
- return mContext.getResources().getBoolean(R.bool.doze_suspend_display_state_supported);
+ return mResources.getBoolean(R.bool.doze_suspend_display_state_supported);
}
public int getPulseDuration() {
@@ -103,7 +105,7 @@
}
public float getScreenBrightnessDoze() {
- return mContext.getResources().getInteger(
+ return mResources.getInteger(
com.android.internal.R.integer.config_screenBrightnessDoze) / 255f;
}
@@ -173,7 +175,7 @@
* @return {@code true} if screen needs to be completely black before a power transition.
*/
public boolean getDisplayNeedsBlanking() {
- return FORCE_BLANKING || !FORCE_NO_BLANKING && mContext.getResources().getBoolean(
+ return FORCE_BLANKING || !FORCE_NO_BLANKING && mResources.getBoolean(
com.android.internal.R.bool.config_displayBlanksAfterDoze);
}
@@ -195,24 +197,20 @@
}
private boolean getBoolean(String propName, int resId) {
- return SystemProperties.getBoolean(propName, mContext.getResources().getBoolean(resId));
+ return SystemProperties.getBoolean(propName, mResources.getBoolean(resId));
}
private int getInt(String propName, int resId) {
- int value = SystemProperties.getInt(propName, mContext.getResources().getInteger(resId));
+ int value = SystemProperties.getInt(propName, mResources.getInteger(resId));
return MathUtils.constrain(value, 0, MAX_DURATION);
}
- private String getString(String propName, int resId) {
- return SystemProperties.get(propName, mContext.getString(resId));
- }
-
public int getPulseVisibleDurationExtended() {
return 2 * getPulseVisibleDuration();
}
public boolean doubleTapReportsTouchCoordinates() {
- return mContext.getResources().getBoolean(R.bool.doze_double_tap_reports_touch_coordinates);
+ return mResources.getBoolean(R.bool.doze_double_tap_reports_touch_coordinates);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index 4cd3ad2..442c089 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
import static android.view.Display.INVALID_DISPLAY;
+import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+import static android.view.View.NAVIGATION_BAR_TRANSIENT;
import android.content.Context;
import android.content.res.Resources;
@@ -131,6 +133,7 @@
private boolean mIsAttached;
private boolean mIsGesturalModeEnabled;
private boolean mIsEnabled;
+ private boolean mIsInTransientImmersiveStickyState;
private InputMonitor mInputMonitor;
private InputEventReceiver mInputEventReceiver;
@@ -195,6 +198,12 @@
updateCurrentUserResources(currentUserContext.getResources());
}
+ public void onSystemUiVisibilityChanged(int systemUiVisibility) {
+ mIsInTransientImmersiveStickyState =
+ (systemUiVisibility & SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0
+ && (systemUiVisibility & NAVIGATION_BAR_TRANSIENT) != 0;
+ }
+
private void disposeInputChannel() {
if (mInputEventReceiver != null) {
mInputEventReceiver.dispose();
@@ -267,7 +276,7 @@
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
PixelFormat.TRANSLUCENT);
mEdgePanelLp.privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
mEdgePanelLp.setTitle(TAG + mDisplayId);
mEdgePanelLp.accessibilityTitle = mContext.getString(R.string.nav_bar_edge_panel);
mEdgePanelLp.windowAnimations = 0;
@@ -296,13 +305,21 @@
}
private boolean isWithinTouchRegion(int x, int y) {
+ // Disallow if over the IME
if (y > (mDisplaySize.y - Math.max(mImeHeight, mNavBarHeight))) {
return false;
}
+ // Disallow if too far from the edge
if (x > mEdgeWidth + mLeftInset && x < (mDisplaySize.x - mEdgeWidth - mRightInset)) {
return false;
}
+
+ // Always allow if the user is in a transient sticky immersive state
+ if (mIsInTransientImmersiveStickyState) {
+ return true;
+ }
+
boolean isInExcludedRegion = mExcludeRegion.contains(x, y);
if (isInExcludedRegion) {
mOverviewProxyService.notifyBackAction(false /* completed */, -1, -1,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
index deb314b..a784984 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FloatingRotationButton.java
@@ -82,7 +82,7 @@
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(mDiameter, mDiameter,
mMargin, mMargin, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, flags,
PixelFormat.TRANSLUCENT);
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setTitle("FloatingRotationButton");
switch (mWindowManager.getDefaultDisplay().getRotation()) {
case Surface.ROTATION_0:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index da62d9b..ce96005 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -98,9 +98,10 @@
View statusbarView,
SysuiStatusBarStateController statusBarStateController,
KeyguardBypassController keyguardBypassController,
+ KeyguardStateController keyguardStateController,
NotificationWakeUpCoordinator wakeUpCoordinator) {
this(notificationIconAreaController, headsUpManager, statusBarStateController,
- keyguardBypassController, wakeUpCoordinator,
+ keyguardBypassController, wakeUpCoordinator, keyguardStateController,
statusbarView.findViewById(R.id.heads_up_status_bar_view),
statusbarView.findViewById(R.id.notification_stack_scroller),
statusbarView.findViewById(R.id.notification_panel),
@@ -116,6 +117,7 @@
StatusBarStateController stateController,
KeyguardBypassController bypassController,
NotificationWakeUpCoordinator wakeUpCoordinator,
+ KeyguardStateController keyguardStateController,
HeadsUpStatusBarView headsUpStatusBarView,
NotificationStackScrollLayout stackScroller,
NotificationPanelView panelView,
@@ -160,7 +162,7 @@
mWakeUpCoordinator = wakeUpCoordinator;
wakeUpCoordinator.addListener(this);
mCommandQueue = getComponent(headsUpStatusBarView.getContext(), CommandQueue.class);
- mKeyguardStateController = Dependency.get(KeyguardStateController.class);
+ mKeyguardStateController = keyguardStateController;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index a7e7f08..c6d051d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -29,6 +29,7 @@
import android.view.Gravity;
import android.view.View;
import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
import androidx.collection.ArraySet;
@@ -390,7 +391,12 @@
}
private void updateRegionForNotch(Region region) {
- DisplayCutout cutout = mStatusBarWindowView.getRootWindowInsets().getDisplayCutout();
+ WindowInsets windowInsets = mStatusBarWindowView.getRootWindowInsets();
+ if (windowInsets == null) {
+ Log.w(TAG, "StatusBarWindowView is not attached.");
+ return;
+ }
+ DisplayCutout cutout = windowInsets.getDisplayCutout();
if (cutout == null) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index cac3304..d95d2b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -132,7 +132,6 @@
private ActivityStarter mActivityStarter;
private KeyguardStateController mKeyguardStateController;
- private LockPatternUtils mLockPatternUtils;
private FlashlightController mFlashlightController;
private PreviewInflater mPreviewInflater;
private AccessibilityController mAccessibilityController;
@@ -231,7 +230,6 @@
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mLockPatternUtils = new LockPatternUtils(mContext);
mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext),
new ActivityIntentHelper(mContext));
mPreviewContainer = findViewById(R.id.preview_container);
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 6e61d7c..38dc5ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -560,6 +560,9 @@
}
mAutoHideController.touchAutoHide();
}
+ if (mNavigationBarView != null) {
+ mNavigationBarView.onSystemUiVisibilityChanged(mSystemUiVisibility);
+ }
}
mLightBarController.onNavigationVisibilityChanged(
vis, mask, nbModeChanged, mNavigationBarMode, navbarColorManagedByIme);
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 2aae5b1..ae18833 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -366,6 +366,10 @@
return super.onTouchEvent(event);
}
+ void onSystemUiVisibilityChanged(int systemUiVisibility) {
+ mEdgeBackGestureHandler.onSystemUiVisibilityChanged(systemUiVisibility);
+ }
+
void onBarTransition(int newMode) {
if (newMode == MODE_OPAQUE) {
// If the nav bar background is opaque, stop auto tinting since we know the icons are
@@ -1208,8 +1212,11 @@
setClipChildren(shouldClip);
setClipToPadding(shouldClip);
- AssistHandleViewController controller = Dependency.get(NavigationBarController.class)
- .getAssistHandlerViewController();
+ NavigationBarController navigationBarController =
+ Dependency.get(NavigationBarController.class);
+ AssistHandleViewController controller =
+ navigationBarController == null
+ ? null : navigationBarController.getAssistHandlerViewController();
if (controller != null) {
controller.setBottomOffset(insets.getSystemWindowInsetBottom());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 1a3560e..1a37520 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -80,11 +80,14 @@
private boolean mAodIconsVisible;
private boolean mIsPulsing;
- public NotificationIconAreaController(Context context, StatusBar statusBar,
+ public NotificationIconAreaController(
+ Context context,
+ StatusBar statusBar,
StatusBarStateController statusBarStateController,
NotificationWakeUpCoordinator wakeUpCoordinator,
KeyguardBypassController keyguardBypassController,
- NotificationMediaManager notificationMediaManager) {
+ NotificationMediaManager notificationMediaManager,
+ DozeParameters dozeParameters) {
mStatusBar = statusBar;
mContrastColorUtil = ContrastColorUtil.getInstance(context);
mContext = context;
@@ -92,7 +95,7 @@
mStatusBarStateController = statusBarStateController;
mStatusBarStateController.addCallback(this);
mMediaManager = notificationMediaManager;
- mDozeParameters = DozeParameters.getInstance(mContext);
+ mDozeParameters = dozeParameters;
mWakeUpCoordinator = wakeUpCoordinator;
wakeUpCoordinator.addListener(this);
mBypassController = keyguardBypassController;
@@ -533,8 +536,7 @@
}
public void appearAodIcons() {
- DozeParameters dozeParameters = DozeParameters.getInstance(mContext);
- if (dozeParameters.shouldControlScreenOff()) {
+ if (mDozeParameters.shouldControlScreenOff()) {
mAodIcons.setTranslationY(-mAodIconAppearTranslation);
mAodIcons.setAlpha(0);
animateInAodIconTranslation();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 00736b7..89051cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -43,6 +43,7 @@
import android.os.PowerManager;
import android.os.SystemClock;
import android.provider.DeviceConfig;
+import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Log;
import android.util.MathUtils;
@@ -89,6 +90,7 @@
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
@@ -144,6 +146,7 @@
* Fling until QS is completely hidden.
*/
public static final int FLING_HIDE = 2;
+ private final DozeParameters mDozeParameters;
private double mQqsSplitFraction;
@@ -452,17 +455,17 @@
@Inject
public NotificationPanelView(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
InjectionInflationController injectionInflationController,
- NotificationWakeUpCoordinator coordinator,
- PulseExpansionHandler pulseExpansionHandler,
+ NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler,
DynamicPrivacyController dynamicPrivacyController,
- KeyguardBypassController bypassController,
- FalsingManager falsingManager,
- PluginManager pluginManager,
- ShadeController shadeController,
+ KeyguardBypassController bypassController, FalsingManager falsingManager,
+ PluginManager pluginManager, ShadeController shadeController,
NotificationLockscreenUserManager notificationLockscreenUserManager,
NotificationEntryManager notificationEntryManager,
- DozeLog dozeLog) {
- super(context, attrs, falsingManager, dozeLog);
+ KeyguardStateController keyguardStateController,
+ StatusBarStateController statusBarStateController, DozeLog dozeLog,
+ DozeParameters dozeParameters) {
+ super(context, attrs, falsingManager, dozeLog, keyguardStateController,
+ (SysuiStatusBarStateController) statusBarStateController);
setWillNotDraw(!DEBUG);
mInjectionInflationController = injectionInflationController;
mFalsingManager = falsingManager;
@@ -475,6 +478,7 @@
mCommandQueue = getComponent(context, CommandQueue.class);
mDisplayId = context.getDisplayId();
mPulseExpansionHandler = pulseExpansionHandler;
+ mDozeParameters = dozeParameters;
pulseExpansionHandler.setPulseExpandAbortListener(() -> {
if (mQs != null) {
mQs.animateHeaderSlidingOut();
@@ -537,7 +541,10 @@
mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
mLastOrientation = getResources().getConfiguration().orientation;
mPluginFrame = findViewById(R.id.plugin_frame);
- mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager);
+ if (Settings.System.getInt(
+ mContext.getContentResolver(), "npv_plugin_flag", 0) == 1) {
+ mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager);
+ }
initBottomArea();
@@ -774,7 +781,7 @@
mPluginFrame.setLayoutParams(lp);
}
- mNPVPluginManager.replaceFrameLayout(mPluginFrame);
+ if (mNPVPluginManager != null) mNPVPluginManager.replaceFrameLayout(mPluginFrame);
}
private void initBottomArea() {
@@ -804,8 +811,10 @@
int oldMaxHeight = mQsMaxExpansionHeight;
if (mQs != null) {
mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight();
- mNPVPluginManager.setYOffset(mQsMinExpansionHeight);
- mQsMinExpansionHeight += mNPVPluginManager.getHeight();
+ if (mNPVPluginManager != null) {
+ mNPVPluginManager.setYOffset(mQsMinExpansionHeight);
+ mQsMinExpansionHeight += mNPVPluginManager.getHeight();
+ }
mQsMaxExpansionHeight = mQs.getDesiredHeight();
mNotificationStackScroller.setMaxTopPadding(
mQsMaxExpansionHeight + mQsNotificationTopPadding);
@@ -1911,9 +1920,11 @@
mBarState != StatusBarState.KEYGUARD && (!mQsExpanded
|| mQsExpansionFromOverscroll));
updateEmptyShadeView();
- mNPVPluginManager.changeVisibility((mBarState != StatusBarState.KEYGUARD)
- ? View.VISIBLE
- : View.INVISIBLE);
+ if (mNPVPluginManager != null) {
+ mNPVPluginManager.changeVisibility((mBarState != StatusBarState.KEYGUARD)
+ ? View.VISIBLE
+ : View.INVISIBLE);
+ }
mQsNavbarScrim.setVisibility(mBarState == StatusBarState.SHADE && mQsExpanded
&& !mStackScrollerOverscrolling && mQsScrimEnabled
? View.VISIBLE
@@ -1971,7 +1982,9 @@
float qsExpansionFraction = getQsExpansionFraction();
mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
int heightDiff = mQs.getDesiredHeight() - mQs.getQsMinExpansionHeight();
- mNPVPluginManager.setExpansion(qsExpansionFraction, getHeaderTranslation(), heightDiff);
+ if (mNPVPluginManager != null) {
+ mNPVPluginManager.setExpansion(qsExpansionFraction, getHeaderTranslation(), heightDiff);
+ }
mNotificationStackScroller.setQsExpansionFraction(qsExpansionFraction);
}
@@ -2392,7 +2405,7 @@
appearAmount = mNotificationStackScroller.calculateAppearFractionBypass();
}
startHeight = -mQs.getQsMinExpansionHeight();
- startHeight -= mNPVPluginManager.getHeight();
+ if (mNPVPluginManager != null) startHeight -= mNPVPluginManager.getHeight();
}
float translation = MathUtils.lerp(startHeight, 0,
Math.min(1.0f, appearAmount))
@@ -2536,7 +2549,7 @@
mKeyguardStatusBar.setListening(listening);
if (mQs == null) return;
mQs.setListening(listening);
- mNPVPluginManager.setListening(listening);
+ if (mNPVPluginManager != null) mNPVPluginManager.setListening(listening);
}
@Override
@@ -3421,9 +3434,8 @@
public void setPulsing(boolean pulsing) {
mPulsing = pulsing;
- DozeParameters dozeParameters = DozeParameters.getInstance(mContext);
- final boolean animatePulse = !dozeParameters.getDisplayNeedsBlanking()
- && dozeParameters.getAlwaysOn();
+ final boolean animatePulse = !mDozeParameters.getDisplayNeedsBlanking()
+ && mDozeParameters.getAlwaysOn();
if (animatePulse) {
mAnimateNextPositionUpdate = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 432d636..e8e5e1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -44,7 +44,6 @@
import com.android.systemui.R;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -144,10 +143,8 @@
private boolean mGestureWaitForTouchSlop;
private boolean mIgnoreXTouchSlop;
private boolean mExpandLatencyTracking;
- protected final KeyguardStateController mKeyguardStateController = Dependency.get(
- KeyguardStateController.class);
- protected final SysuiStatusBarStateController mStatusBarStateController =
- (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class);
+ protected final KeyguardStateController mKeyguardStateController;
+ protected final SysuiStatusBarStateController mStatusBarStateController;
protected void onExpandingFinished() {
mBar.onExpandingFinished();
@@ -206,8 +203,11 @@
}
public PanelView(Context context, AttributeSet attrs, FalsingManager falsingManager,
- DozeLog dozeLog) {
+ DozeLog dozeLog, KeyguardStateController keyguardStateController,
+ SysuiStatusBarStateController statusBarStateController) {
super(context, attrs);
+ mKeyguardStateController = keyguardStateController;
+ mStatusBarStateController = statusBarStateController;
mFlingAnimationUtils = new FlingAnimationUtils(context, 0.6f /* maxLengthSeconds */,
0.6f /* speedUpFactor */);
mFlingAnimationUtilsClosing = new FlingAnimationUtils(context, 0.5f /* maxLengthSeconds */,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 5ba19cc..e7d896c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -258,7 +258,7 @@
final ScrimState oldState = mState;
mState = state;
- Trace.traceCounter(Trace.TRACE_TAG_APP, "scrim_state", mState.getIndex());
+ Trace.traceCounter(Trace.TRACE_TAG_APP, "scrim_state", mState.ordinal());
if (mCallback != null) {
mCallback.onCancelled();
@@ -519,22 +519,6 @@
}
/**
- * Set front scrim to black, cancelling animations, in order to prepare to fade them
- * away once the display turns on.
- */
- public void prepareForGentleWakeUp() {
- if (mState == ScrimState.AOD && mDozeParameters.getAlwaysOn()) {
- mInFrontAlpha = 1f;
- mInFrontTint = Color.BLACK;
- mBehindTint = Color.BLACK;
- mAnimateChange = false;
- updateScrims();
- mAnimateChange = true;
- mAnimationDuration = ANIMATION_DURATION_LONG;
- }
- }
-
- /**
* If the lock screen sensor is active.
*/
public void setWakeLockScreenSensorActive(boolean active) {
@@ -595,6 +579,8 @@
setScrimAlpha(mScrimInFront, mInFrontAlpha);
setScrimAlpha(mScrimBehind, mBehindAlpha);
setScrimAlpha(mScrimForBubble, mBubbleAlpha);
+ // The animation could have all already finished, let's call onFinished just in case
+ onFinished();
dispatchScrimsVisible();
}
@@ -693,9 +679,9 @@
@Override
public void onAnimationEnd(Animator animation) {
+ scrim.setTag(TAG_KEY_ANIM, null);
onFinished(lastCallback);
- scrim.setTag(TAG_KEY_ANIM, null);
dispatchScrimsVisible();
if (!mDeferFinishedListener && mOnAnimationFinished != null) {
@@ -759,9 +745,9 @@
}
private void onFinished(Callback callback) {
- if (!hasReachedFinalState(mScrimBehind)
- || !hasReachedFinalState(mScrimInFront)
- || !hasReachedFinalState(mScrimForBubble)) {
+ if (isAnimating(mScrimBehind)
+ || isAnimating(mScrimInFront)
+ || isAnimating(mScrimForBubble)) {
if (callback != null && callback != mCallback) {
// Since we only notify the callback that we're finished once everything has
// finished, we need to make sure that any changing callbacks are also invoked
@@ -794,11 +780,6 @@
}
}
- private boolean hasReachedFinalState(ScrimView scrim) {
- return scrim.getViewAlpha() == getCurrentScrimAlpha(scrim)
- && scrim.getTint() == getCurrentScrimTint(scrim);
- }
-
private boolean isAnimating(View scrim) {
return scrim.getTag(TAG_KEY_ANIM) != null;
}
@@ -854,10 +835,7 @@
} else {
// update the alpha directly
updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
- onFinished();
}
- } else {
- onFinished();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 7463c7c..e0597159 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -30,12 +30,30 @@
/**
* Initial state.
*/
- UNINITIALIZED(-1),
+ UNINITIALIZED,
+
+ /**
+ * When turned off by sensors (prox, presence.)
+ */
+ OFF {
+ @Override
+ public void prepare(ScrimState previousState) {
+ mFrontTint = Color.BLACK;
+ mBehindTint = previousState.mBehindTint;
+ mBubbleTint = previousState.mBubbleTint;
+
+ mFrontAlpha = 1f;
+ mBehindAlpha = previousState.mBehindAlpha;
+ mBubbleAlpha = previousState.mBubbleAlpha;
+
+ mAnimationDuration = ScrimController.ANIMATION_DURATION_LONG;
+ }
+ },
/**
* On the lock screen.
*/
- KEYGUARD(0) {
+ KEYGUARD {
@Override
public void prepare(ScrimState previousState) {
mBlankScreen = false;
@@ -65,7 +83,7 @@
/**
* Showing password challenge on the keyguard.
*/
- BOUNCER(1) {
+ BOUNCER {
@Override
public void prepare(ScrimState previousState) {
mBehindAlpha = ScrimController.GRADIENT_SCRIM_ALPHA_BUSY;
@@ -77,7 +95,7 @@
/**
* Showing password challenge on top of a FLAG_SHOW_WHEN_LOCKED activity.
*/
- BOUNCER_SCRIMMED(2) {
+ BOUNCER_SCRIMMED {
@Override
public void prepare(ScrimState previousState) {
mBehindAlpha = 0;
@@ -89,7 +107,7 @@
/**
* Changing screen brightness from quick settings.
*/
- BRIGHTNESS_MIRROR(3) {
+ BRIGHTNESS_MIRROR {
@Override
public void prepare(ScrimState previousState) {
mBehindAlpha = 0;
@@ -101,7 +119,7 @@
/**
* Always on display or screen off.
*/
- AOD(4) {
+ AOD {
@Override
public void prepare(ScrimState previousState) {
final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
@@ -136,7 +154,7 @@
/**
* When phone wakes up because you received a notification.
*/
- PULSING(5) {
+ PULSING {
@Override
public void prepare(ScrimState previousState) {
mFrontAlpha = mAodFrontScrimAlpha;
@@ -164,7 +182,7 @@
/**
* Unlocked on top of an app (launcher or any other activity.)
*/
- UNLOCKED(6) {
+ UNLOCKED {
@Override
public void prepare(ScrimState previousState) {
// State that UI will sync to.
@@ -201,7 +219,7 @@
/**
* Unlocked with a bubble expanded.
*/
- BUBBLE_EXPANDED(7) {
+ BUBBLE_EXPANDED {
@Override
public void prepare(ScrimState previousState) {
mFrontTint = Color.TRANSPARENT;
@@ -237,17 +255,12 @@
DozeParameters mDozeParameters;
boolean mDisplayRequiresBlanking;
boolean mWallpaperSupportsAmbientMode;
- int mIndex;
boolean mHasBackdrop;
boolean mLaunchingAffordanceWithPreview;
boolean mWakeLockScreenSensorActive;
boolean mKeyguardFadingAway;
long mKeyguardFadingAwayDuration;
- ScrimState(int index) {
- mIndex = index;
- }
-
public void init(ScrimView scrimInFront, ScrimView scrimBehind, ScrimView scrimForBubble,
DozeParameters dozeParameters) {
mScrimInFront = scrimInFront;
@@ -262,10 +275,6 @@
public void prepare(ScrimState previousState) {
}
- public int getIndex() {
- return mIndex;
- }
-
public float getFrontAlpha() {
return mFrontAlpha;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 6ce6dfa..c092f9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -210,6 +210,7 @@
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -385,6 +386,8 @@
private final BroadcastDispatcher mBroadcastDispatcher;
private final ConfigurationController mConfigurationController;
private final StatusBarWindowViewController.Builder mStatusBarWindowViewControllerBuilder;
+ private final NotifLog mNotifLog;
+ private final DozeParameters mDozeParameters;
// expanded notifications
protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
@@ -495,8 +498,7 @@
WallpaperInfo info = wallpaperManager.getWallpaperInfo(UserHandle.USER_CURRENT);
final boolean deviceSupportsAodWallpaper = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_dozeSupportsAodWallpaper);
- final boolean imageWallpaperInAmbient =
- !DozeParameters.getInstance(mContext).getDisplayNeedsBlanking();
+ final boolean imageWallpaperInAmbient = !mDozeParameters.getDisplayNeedsBlanking();
// If WallpaperInfo is null, it must be ImageWallpaper.
final boolean supportsAmbientMode = deviceSupportsAodWallpaper
&& ((info == null && imageWallpaperInAmbient)
@@ -585,7 +587,7 @@
@Override
public void onStrongAuthStateChanged(int userId) {
super.onStrongAuthStateChanged(userId);
- mEntryManager.updateNotifications();
+ mEntryManager.updateNotifications("onStrongAuthStateChanged");
}
};
private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
@@ -599,6 +601,7 @@
private boolean mPulsing;
private final BubbleController mBubbleController;
private final BubbleController.BubbleExpandListener mBubbleExpandListener;
+
private ActivityIntentHelper mActivityIntentHelper;
@Override
@@ -617,6 +620,7 @@
@Inject
public StatusBar(
+ Context context,
LightBarController lightBarController,
AutoHideController autoHideController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -669,7 +673,10 @@
NotificationListener notificationListener,
ConfigurationController configurationController,
StatusBarWindowController statusBarWindowController,
- StatusBarWindowViewController.Builder statusBarWindowViewControllerBuilder) {
+ StatusBarWindowViewController.Builder statusBarWindowViewControllerBuilder,
+ NotifLog notifLog,
+ DozeParameters dozeParameters) {
+ super(context);
mLightBarController = lightBarController;
mAutoHideController = autoHideController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
@@ -723,10 +730,12 @@
mConfigurationController = configurationController;
mStatusBarWindowController = statusBarWindowController;
mStatusBarWindowViewControllerBuilder = statusBarWindowViewControllerBuilder;
+ mNotifLog = notifLog;
+ mDozeParameters = dozeParameters;
mBubbleExpandListener =
(isExpanding, key) -> {
- mEntryManager.updateNotifications();
+ mEntryManager.updateNotifications("onBubbleExpandChanged");
updateScrimController();
};
}
@@ -743,7 +752,7 @@
KeyguardSliceProvider sliceProvider = KeyguardSliceProvider.getAttachedInstance();
if (sliceProvider != null) {
sliceProvider.initDependencies(mMediaManager, mStatusBarStateController,
- mKeyguardBypassController, DozeParameters.getInstance(mContext));
+ mKeyguardBypassController, mDozeParameters);
} else {
Log.w(TAG, "Cannot init KeyguardSliceProvider dependencies");
}
@@ -954,7 +963,7 @@
mHeadsUpAppearanceController = new HeadsUpAppearanceController(
mNotificationIconAreaController, mHeadsUpManager, mStatusBarWindow,
mStatusBarStateController, mKeyguardBypassController,
- mWakeUpCoordinator);
+ mKeyguardStateController, mWakeUpCoordinator);
mHeadsUpAppearanceController.readFrom(oldController);
mStatusBarWindowViewController.setStatusBarView(mStatusBarView);
updateAreThereNotifications();
@@ -1025,13 +1034,12 @@
if (mStatusBarWindow != null) {
mStatusBarWindowViewController.onScrimVisibilityChanged(scrimsVisible);
}
- }, DozeParameters.getInstance(mContext),
+ }, mDozeParameters,
mContext.getSystemService(AlarmManager.class),
mKeyguardStateController);
mNotificationPanel.initDependencies(this, mGroupManager, mNotificationShelf,
mHeadsUpManager, mNotificationIconAreaController, mScrimController);
- mDozeScrimController = new DozeScrimController(DozeParameters.getInstance(context),
- mDozeLog);
+ mDozeScrimController = new DozeScrimController(mDozeParameters, mDozeLog);
BackDropView backdrop = mStatusBarWindow.findViewById(R.id.backdrop);
mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),
@@ -1160,12 +1168,13 @@
mContext,
mAllowNotificationLongPress,
mKeyguardBypassController,
- mStatusBarStateController);
+ mStatusBarStateController,
+ mNotifLog);
mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanel,
mHeadsUpManager, mStatusBarWindow, mStackScroller, mDozeScrimController,
mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController,
- mNotificationAlertingManager, rowBinder);
+ mNotificationAlertingManager, rowBinder, mKeyguardStateController);
mNotificationListController =
new NotificationListController(
@@ -1335,7 +1344,7 @@
mBiometricUnlockController = new BiometricUnlockController(mContext,
mDozeScrimController, mKeyguardViewMediator,
mScrimController, this, mKeyguardStateController, new Handler(),
- mKeyguardUpdateMonitor, mKeyguardBypassController);
+ mKeyguardUpdateMonitor, mKeyguardBypassController, mDozeParameters);
putComponent(BiometricUnlockController.class, mBiometricUnlockController);
mStatusBarKeyguardViewManager = mKeyguardViewMediator.registerStatusBar(this,
getBouncerContainer(), mNotificationPanel, mBiometricUnlockController,
@@ -1443,8 +1452,12 @@
return mZenController.areNotificationsHiddenInShade();
}
- public void requestNotificationUpdate() {
- mEntryManager.updateNotifications();
+ /**
+ * Request a notification update
+ * @param reason why we're requesting a notification update
+ */
+ public void requestNotificationUpdate(String reason) {
+ mEntryManager.updateNotifications(reason);
}
/**
@@ -1685,7 +1698,7 @@
@Override
public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
- mEntryManager.updateNotifications();
+ mEntryManager.updateNotifications("onHeadsUpStateChanged");
if (isDozing() && isHeadsUp) {
entry.setPulseSuppressed(false);
mDozeServiceHost.fireNotificationPulse(entry);
@@ -3559,14 +3572,13 @@
mDozing = isDozing;
// Collapse the notification panel if open
- boolean dozingAnimated = mDozingRequested
- && DozeParameters.getInstance(mContext).shouldControlScreenOff();
+ boolean dozingAnimated = mDozingRequested && mDozeParameters.shouldControlScreenOff();
mNotificationPanel.resetViews(dozingAnimated);
updateQsExpansionEnabled();
mKeyguardViewMediator.setDozing(mDozing);
- mEntryManager.updateNotifications();
+ mEntryManager.updateNotifications("onDozingChanged");
updateDozingState();
updateScrimController();
updateReportRejectedTouchVisibility();
@@ -3590,7 +3602,6 @@
private void updateKeyguardState() {
mKeyguardStateController.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
- mKeyguardStateController.isMethodSecure(),
mStatusBarKeyguardViewManager.isOccluded());
}
@@ -3817,7 +3828,7 @@
*/
private void updateNotificationPanelTouchState() {
boolean goingToSleepWithoutAnimation = isGoingToSleep()
- && !DozeParameters.getInstance(mContext).shouldControlScreenOff();
+ && !mDozeParameters.shouldControlScreenOff();
boolean disabled = (!mDeviceInteractive && !mPulsing) || goingToSleepWithoutAnimation;
mNotificationPanel.setTouchAndAnimationDisabled(disabled);
mNotificationIconAreaController.setAnimationsEnabled(!disabled);
@@ -4007,6 +4018,13 @@
} else if (isPulsing()) {
mScrimController.transitionTo(ScrimState.PULSING,
mDozeScrimController.getScrimCallback());
+ } else if (mDozeServiceHost.hasPendingScreenOffCallback()) {
+ mScrimController.transitionTo(ScrimState.OFF, new ScrimController.Callback() {
+ @Override
+ public void onFinished() {
+ mDozeServiceHost.executePendingScreenOffCallback();
+ }
+ });
} else if (mDozing && !unlocking) {
mScrimController.transitionTo(ScrimState.AOD);
} else if (mIsKeyguard && !unlocking) {
@@ -4033,6 +4051,7 @@
private boolean mAnimateWakeup;
private boolean mAnimateScreenOff;
private boolean mIgnoreTouchWhilePulsing;
+ private Runnable mPendingScreenOffCallback;
@VisibleForTesting
boolean mWakeLockScreenPerformsAuth = SystemProperties.getBoolean(
"persist.sysui.wake_performs_auth", true);
@@ -4255,8 +4274,33 @@
}
@Override
- public void prepareForGentleWakeUp() {
- mScrimController.prepareForGentleWakeUp();
+ public void prepareForGentleSleep(Runnable onDisplayOffCallback) {
+ if (onDisplayOffCallback != null) {
+ Log.w(TAG, "Overlapping onDisplayOffCallback. Ignoring previous one.");
+ }
+ mPendingScreenOffCallback = onDisplayOffCallback;
+ updateScrimController();
+ }
+
+ /**
+ * When the dozing host is waiting for scrims to fade out to change the display state.
+ */
+ boolean hasPendingScreenOffCallback() {
+ return mPendingScreenOffCallback != null;
+ }
+
+ /**
+ * Executes an nullifies the pending display state callback.
+ *
+ * @see #hasPendingScreenOffCallback()
+ * @see #prepareForGentleSleep(Runnable)
+ */
+ void executePendingScreenOffCallback() {
+ if (mPendingScreenOffCallback == null) {
+ return;
+ }
+ mPendingScreenOffCallback.run();
+ mPendingScreenOffCallback = null;
}
private void dispatchTap(View view, float x, float y) {
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 75b0cdc..8683586 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -33,6 +33,8 @@
import android.view.ViewRootImpl;
import android.view.WindowManagerGlobal;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -61,8 +63,6 @@
import java.io.PrintWriter;
import java.util.ArrayList;
-import androidx.annotation.VisibleForTesting;
-
/**
* Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
* via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done,
@@ -301,8 +301,7 @@
public void show(Bundle options) {
mShowing = true;
mStatusBarWindowController.setKeyguardShowing(true);
- mKeyguardStateController.notifyKeyguardState(
- mShowing, mKeyguardStateController.isMethodSecure(),
+ mKeyguardStateController.notifyKeyguardState(mShowing,
mKeyguardStateController.isOccluded());
reset(true /* hideBouncerWhenShowing */);
StatsLog.write(StatsLog.KEYGUARD_STATE_CHANGED,
@@ -545,7 +544,7 @@
public void hide(long startTime, long fadeoutDuration) {
mShowing = false;
mKeyguardStateController.notifyKeyguardState(mShowing,
- mKeyguardStateController.isMethodSecure(), mKeyguardStateController.isOccluded());
+ mKeyguardStateController.isOccluded());
launchPendingWakeupAction();
if (Dependency.get(KeyguardUpdateMonitor.class).needsSlowUnlockTransition()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 3e0c268..f4a26ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -89,8 +89,7 @@
private final ShadeController mShadeController = Dependency.get(ShadeController.class);
private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
- private final KeyguardStateController mKeyguardStateController = Dependency.get(
- KeyguardStateController.class);
+ private final KeyguardStateController mKeyguardStateController;
private final NotificationViewHierarchyManager mViewHierarchyManager =
Dependency.get(NotificationViewHierarchyManager.class);
private final NotificationLockscreenUserManager mLockscreenUserManager =
@@ -139,8 +138,10 @@
ActivityLaunchAnimator activityLaunchAnimator,
DynamicPrivacyController dynamicPrivacyController,
NotificationAlertingManager notificationAlertingManager,
- NotificationRowBinderImpl notificationRowBinder) {
+ NotificationRowBinderImpl notificationRowBinder,
+ KeyguardStateController keyguardStateController) {
mContext = context;
+ mKeyguardStateController = keyguardStateController;
mNotificationPanel = panel;
mHeadsUpManager = headsUp;
mDynamicPrivacyController = dynamicPrivacyController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 9a281ce..1def89b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -35,7 +35,6 @@
import android.view.ViewParent;
import com.android.systemui.ActivityIntentHelper;
-import com.android.systemui.Dependency;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
@@ -58,19 +57,16 @@
public class StatusBarRemoteInputCallback implements Callback, Callbacks,
StatusBarStateController.StateListener {
- private final KeyguardStateController mKeyguardStateController = Dependency.get(
- KeyguardStateController.class);
- private final SysuiStatusBarStateController mStatusBarStateController =
- (SysuiStatusBarStateController) Dependency.get(StatusBarStateController.class);
- private final NotificationLockscreenUserManager mLockscreenUserManager =
- Dependency.get(NotificationLockscreenUserManager.class);
- private final ActivityStarter mActivityStarter = Dependency.get(ActivityStarter.class);
+ private final KeyguardStateController mKeyguardStateController;
+ private final SysuiStatusBarStateController mStatusBarStateController;
+ private final NotificationLockscreenUserManager mLockscreenUserManager;
+ private final ActivityStarter mActivityStarter;
+ private final ShadeController mShadeController;
private final Context mContext;
private final ActivityIntentHelper mActivityIntentHelper;
private final NotificationGroupManager mGroupManager;
private View mPendingWorkRemoteInputView;
private View mPendingRemoteInputView;
- private final ShadeController mShadeController = Dependency.get(ShadeController.class);
private KeyguardManager mKeyguardManager;
private final CommandQueue mCommandQueue;
private int mDisabled2;
@@ -80,10 +76,19 @@
/**
*/
@Inject
- public StatusBarRemoteInputCallback(Context context, NotificationGroupManager groupManager) {
+ public StatusBarRemoteInputCallback(Context context, NotificationGroupManager groupManager,
+ NotificationLockscreenUserManager notificationLockscreenUserManager,
+ KeyguardStateController keyguardStateController,
+ StatusBarStateController statusBarStateController,
+ ActivityStarter activityStarter, ShadeController shadeController) {
mContext = context;
mContext.registerReceiverAsUser(mChallengeReceiver, UserHandle.ALL,
new IntentFilter(ACTION_DEVICE_LOCKED_CHANGED), null, null);
+ mLockscreenUserManager = notificationLockscreenUserManager;
+ mKeyguardStateController = keyguardStateController;
+ mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
+ mShadeController = shadeController;
+ mActivityStarter = activityStarter;
mStatusBarStateController.addCallback(this);
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mCommandQueue = getComponent(context, CommandQueue.class);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
index 724b462..ca7a936 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java
@@ -21,7 +21,6 @@
import static com.android.systemui.DejankUtils.whitelistIpcs;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
-import android.app.ActivityManager;
import android.app.IActivityManager;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -39,8 +38,6 @@
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -94,24 +91,14 @@
private final ArrayList<WeakReference<StatusBarWindowCallback>>
mCallbacks = Lists.newArrayList();
- private final SysuiColorExtractor mColorExtractor = Dependency.get(SysuiColorExtractor.class);
+ private final SysuiColorExtractor mColorExtractor;
@Inject
- public StatusBarWindowController(Context context,
- StatusBarStateController statusBarStateController,
- ConfigurationController configurationController,
- KeyguardBypassController keyguardBypassController) {
- this(context, context.getSystemService(WindowManager.class), ActivityManager.getService(),
- DozeParameters.getInstance(context), statusBarStateController,
- configurationController, keyguardBypassController);
- }
-
- @VisibleForTesting
public StatusBarWindowController(Context context, WindowManager windowManager,
IActivityManager activityManager, DozeParameters dozeParameters,
StatusBarStateController statusBarStateController,
ConfigurationController configurationController,
- KeyguardBypassController keyguardBypassController) {
+ KeyguardBypassController keyguardBypassController, SysuiColorExtractor colorExtractor) {
mContext = context;
mWindowManager = windowManager;
mActivityManager = activityManager;
@@ -120,6 +107,7 @@
mScreenBrightnessDoze = mDozeParameters.getScreenBrightnessDoze();
mLpChanged = new LayoutParams();
mKeyguardBypassController = keyguardBypassController;
+ mColorExtractor = colorExtractor;
mLockScreenDisplayTimeout = context.getResources()
.getInteger(R.integer.config_lockScreenDisplayTimeout);
((SysuiStatusBarStateController) statusBarStateController)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
index f21085e..fd3f9c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
@@ -37,14 +37,17 @@
import com.android.systemui.R;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.InjectionInflationController;
@@ -88,7 +91,10 @@
ShadeController shadeController,
NotificationLockscreenUserManager notificationLockscreenUserManager,
NotificationEntryManager notificationEntryManager,
- DozeLog dozeLog) {
+ KeyguardStateController keyguardStateController,
+ SysuiStatusBarStateController statusBarStateController,
+ DozeLog dozeLog,
+ DozeParameters dozeParameters) {
mView = view;
mFalsingManager = falsingManager;
@@ -106,7 +112,10 @@
shadeController,
notificationLockscreenUserManager,
notificationEntryManager,
- dozeLog);
+ keyguardStateController,
+ statusBarStateController,
+ dozeLog,
+ dozeParameters);
ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
notificationPanelView.setVisibility(View.INVISIBLE);
@@ -472,10 +481,13 @@
private final FalsingManager mFalsingManager;
private final PluginManager mPluginManager;
private final TunerService mTunerService;
+ private final KeyguardStateController mKeyguardStateController;
+ private final SysuiStatusBarStateController mStatusBarStateController;
private ShadeController mShadeController;
private final NotificationLockscreenUserManager mNotificationLockScreenUserManager;
private final NotificationEntryManager mNotificationEntryManager;
private final DozeLog mDozeLog;
+ private final DozeParameters mDozeParameters;
private StatusBarWindowView mView;
@Inject
@@ -490,7 +502,10 @@
TunerService tunerService,
NotificationLockscreenUserManager notificationLockscreenUserManager,
NotificationEntryManager notificationEntryManager,
- DozeLog dozeLog) {
+ KeyguardStateController keyguardStateController,
+ StatusBarStateController statusBarStateController,
+ DozeLog dozeLog,
+ DozeParameters dozeParameters) {
mInjectionInflationController = injectionInflationController;
mCoordinator = coordinator;
mPulseExpansionHandler = pulseExpansionHandler;
@@ -501,7 +516,10 @@
mTunerService = tunerService;
mNotificationLockScreenUserManager = notificationLockscreenUserManager;
mNotificationEntryManager = notificationEntryManager;
+ mKeyguardStateController = keyguardStateController;
+ mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
mDozeLog = dozeLog;
+ mDozeParameters = dozeParameters;
}
/**
@@ -537,7 +555,10 @@
mShadeController,
mNotificationLockScreenUserManager,
mNotificationEntryManager,
- mDozeLog);
+ mKeyguardStateController,
+ mStatusBarStateController,
+ mDozeLog,
+ mDozeParameters);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index ce929b7..44be6bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -93,10 +93,10 @@
public static void setShowForAllUsers(Dialog dialog, boolean show) {
if (show) {
dialog.getWindow().getAttributes().privateFlags |=
- WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
} else {
dialog.getWindow().getAttributes().privateFlags &=
- ~WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ ~WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index aefe201..692c34c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -146,7 +146,7 @@
/** **/
default void notifyKeyguardDoneFading() {}
/** **/
- default void notifyKeyguardState(boolean showing, boolean methodSecure, boolean occluded) {}
+ default void notifyKeyguardState(boolean showing, boolean occluded) {}
/**
* Callback for authentication events.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 392094d..1cb2bd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -25,11 +25,12 @@
import android.os.Build;
import android.os.Trace;
+import androidx.annotation.VisibleForTesting;
+
import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import java.io.FileDescriptor;
@@ -42,25 +43,22 @@
/**
*/
@Singleton
-public class KeyguardStateControllerImpl extends KeyguardUpdateMonitorCallback
- implements KeyguardStateController, Dumpable {
+public class KeyguardStateControllerImpl implements KeyguardStateController, Dumpable {
private static final boolean DEBUG_AUTH_WITH_ADB = false;
private static final String AUTH_BROADCAST_KEY = "debug_trigger_auth";
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
- private final Context mContext;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final LockPatternUtils mLockPatternUtils;
private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
- new LockedStateInvalidator();
+ new UpdateMonitorCallback();
private boolean mCanDismissLockScreen;
private boolean mShowing;
private boolean mSecure;
private boolean mOccluded;
- private boolean mListening;
private boolean mKeyguardFadingAway;
private long mKeyguardFadingAwayDelay;
private long mKeyguardFadingAwayDuration;
@@ -75,10 +73,10 @@
/**
*/
@Inject
- public KeyguardStateControllerImpl(Context context) {
- mContext = context;
- mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
- mLockPatternUtils = new LockPatternUtils(context);
+ public KeyguardStateControllerImpl(Context context,
+ KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils) {
+ mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mLockPatternUtils = lockPatternUtils;
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
update(true /* updateAlways */);
@@ -104,19 +102,12 @@
if (!mCallbacks.contains(callback)) {
mCallbacks.add(callback);
}
- if (mCallbacks.size() != 0 && !mListening) {
- mListening = true;
- mKeyguardUpdateMonitor.registerCallback(this);
- }
}
@Override
public void removeCallback(@NonNull Callback callback) {
Preconditions.checkNotNull(callback, "Callback must not be null. b/128895449");
- if (mCallbacks.remove(callback) && mCallbacks.size() == 0 && mListening) {
- mListening = false;
- mKeyguardUpdateMonitor.removeCallback(this);
- }
+ mCallbacks.remove(callback);
}
@Override
@@ -140,19 +131,13 @@
}
@Override
- public void notifyKeyguardState(boolean showing, boolean secure, boolean occluded) {
- if (mShowing == showing && mSecure == secure && mOccluded == occluded) return;
+ public void notifyKeyguardState(boolean showing, boolean occluded) {
+ if (mShowing == showing && mOccluded == occluded) return;
mShowing = showing;
- mSecure = secure;
mOccluded = occluded;
notifyKeyguardChanged();
}
- @Override
- public void onTrustChanged(int userId) {
- notifyKeyguardChanged();
- }
-
private void notifyKeyguardChanged() {
Trace.beginSection("KeyguardStateController#notifyKeyguardChanged");
// Copy the list to allow removal during callback.
@@ -191,7 +176,8 @@
setKeyguardFadingAway(false);
}
- private void update(boolean updateAlways) {
+ @VisibleForTesting
+ void update(boolean updateAlways) {
Trace.beginSection("KeyguardStateController#update");
int user = KeyguardUpdateMonitor.getCurrentUser();
boolean secure = mLockPatternUtils.isSecure(user);
@@ -201,7 +187,7 @@
boolean trusted = mKeyguardUpdateMonitor.getUserHasTrust(user);
boolean faceAuthEnabled = mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(user);
boolean changed = secure != mSecure || canDismissLockScreen != mCanDismissLockScreen
- || trustManaged != mTrustManaged
+ || trustManaged != mTrustManaged || mTrusted != trusted
|| mFaceAuthEnabled != faceAuthEnabled;
if (changed || updateAlways) {
mSecure = secure;
@@ -284,7 +270,7 @@
pw.println(" mFaceAuthEnabled: " + mFaceAuthEnabled);
}
- private class LockedStateInvalidator extends KeyguardUpdateMonitorCallback {
+ private class UpdateMonitorCallback extends KeyguardUpdateMonitorCallback {
@Override
public void onUserSwitchComplete(int userId) {
update(false /* updateAlways */);
@@ -293,6 +279,7 @@
@Override
public void onTrustChanged(int userId) {
update(false /* updateAlways */);
+ notifyKeyguardChanged();
}
@Override
@@ -327,11 +314,6 @@
}
@Override
- public void onScreenTurnedOff() {
- update(false /* updateAlways */);
- }
-
- @Override
public void onKeyguardVisibilityChanged(boolean showing) {
update(false /* updateAlways */);
}
@@ -340,5 +322,5 @@
public void onBiometricsCleared() {
update(false /* alwaysUpdate */);
}
- };
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 43795dc..cca9479 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -23,12 +23,16 @@
import android.app.Notification;
import android.app.PendingIntent;
import android.app.RemoteInput;
+import android.content.ClipDescription;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Bundle;
+import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.text.Editable;
@@ -53,8 +57,13 @@
import android.widget.ProgressBar;
import android.widget.TextView;
+import androidx.core.view.inputmethod.EditorInfoCompat;
+import androidx.core.view.inputmethod.InputConnectionCompat;
+import androidx.core.view.inputmethod.InputContentInfoCompat;
+
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -65,6 +74,7 @@
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.LightBarController;
+import java.util.HashMap;
import java.util.function.Consumer;
/**
@@ -88,6 +98,8 @@
private RemoteInputController mController;
private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
+ private IStatusBarService mStatusBarManagerService;
+
private NotificationEntry mEntry;
private boolean mRemoved;
@@ -103,6 +115,8 @@
public RemoteInputView(Context context, AttributeSet attrs) {
super(context, attrs);
mRemoteInputQuickSettingsDisabler = Dependency.get(RemoteInputQuickSettingsDisabler.class);
+ mStatusBarManagerService = IStatusBarService.Stub.asInterface(
+ ServiceManager.getService(Context.STATUS_BAR_SERVICE));
}
@Override
@@ -128,7 +142,7 @@
if (isSoftImeEvent || isKeyboardEnterKey) {
if (mEditText.length() > 0) {
- sendRemoteInput();
+ sendRemoteInput(prepareRemoteInputFromText());
}
// Consume action to prevent IME from closing.
return true;
@@ -141,7 +155,7 @@
mEditText.mRemoteInputView = this;
}
- private void sendRemoteInput() {
+ protected Intent prepareRemoteInputFromText() {
Bundle results = new Bundle();
results.putString(mRemoteInput.getResultKey(), mEditText.getText().toString());
Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -153,6 +167,25 @@
RemoteInput.setResultsSource(fillInIntent, RemoteInput.SOURCE_CHOICE);
}
+ return fillInIntent;
+ }
+
+ protected Intent prepareRemoteInputFromData(String contentType, Uri data) {
+ HashMap<String, Uri> results = new HashMap<>();
+ results.put(contentType, data);
+ try {
+ mStatusBarManagerService.grantInlineReplyUriPermission(
+ mEntry.notification.getKey(), data);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to grant URI permissions:" + e.getMessage(), e);
+ }
+ Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ RemoteInput.addDataResultToIntent(mRemoteInput, fillInIntent, results);
+
+ return fillInIntent;
+ }
+
+ private void sendRemoteInput(Intent intent) {
mEditText.setEnabled(false);
mSendButton.setVisibility(INVISIBLE);
mProgressBar.setVisibility(VISIBLE);
@@ -176,7 +209,7 @@
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_SEND,
mEntry.notification.getPackageName());
try {
- mPendingIntent.send(mContext, 0, fillInIntent);
+ mPendingIntent.send(mContext, 0, intent);
} catch (PendingIntent.CanceledException e) {
Log.i(TAG, "Unable to send remote input result", e);
MetricsLogger.action(mContext, MetricsProto.MetricsEvent.ACTION_REMOTE_INPUT_FAIL,
@@ -195,7 +228,9 @@
LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
v.mController = controller;
v.mEntry = entry;
- v.mEditText.setTextOperationUser(computeTextOperationUser(entry.notification.getUser()));
+ UserHandle user = computeTextOperationUser(entry.notification.getUser());
+ v.mEditText.mUser = user;
+ v.mEditText.setTextOperationUser(user);
v.setTag(VIEW_TAG);
return v;
@@ -204,7 +239,7 @@
@Override
public void onClick(View v) {
if (v == mSendButton) {
- sendRemoteInput();
+ sendRemoteInput(prepareRemoteInputFromText());
}
}
@@ -518,6 +553,7 @@
private RemoteInputView mRemoteInputView;
boolean mShowImeOnInputConnection;
private LightBarController mLightBarController;
+ UserHandle mUser;
public RemoteEditText(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -617,11 +653,47 @@
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ String[] allowedDataTypes = mRemoteInputView.mRemoteInput.getAllowedDataTypes()
+ .toArray(new String[0]);
+ EditorInfoCompat.setContentMimeTypes(outAttrs, allowedDataTypes);
final InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
- if (mShowImeOnInputConnection && inputConnection != null) {
+ final InputConnectionCompat.OnCommitContentListener callback =
+ new InputConnectionCompat.OnCommitContentListener() {
+ @Override
+ public boolean onCommitContent(
+ InputContentInfoCompat inputContentInfoCompat, int i,
+ Bundle bundle) {
+ Uri contentUri = inputContentInfoCompat.getContentUri();
+ ClipDescription description = inputContentInfoCompat.getDescription();
+ String mimeType = null;
+ if (description != null && description.getMimeTypeCount() > 0) {
+ mimeType = description.getMimeType(0);
+ }
+ if (mimeType != null) {
+ Intent dataIntent = mRemoteInputView.prepareRemoteInputFromData(
+ mimeType, contentUri);
+ mRemoteInputView.sendRemoteInput(dataIntent);
+ }
+ return true;
+ }
+ };
+
+ InputConnection ic = InputConnectionCompat.createWrapper(
+ inputConnection, outAttrs, callback);
+
+ Context userContext = null;
+ try {
+ userContext = mContext.createPackageContextAsUser(
+ mContext.getPackageName(), 0, mUser);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unable to create user context:" + e.getMessage(), e);
+ }
+
+ if (mShowImeOnInputConnection && ic != null) {
+ Context targetContext = userContext != null ? userContext : getContext();
final InputMethodManager imm =
- getContext().getSystemService(InputMethodManager.class);
+ targetContext.getSystemService(InputMethodManager.class);
if (imm != null) {
// onCreateInputConnection is called by InputMethodManager in the middle of
// setting up the connection to the IME; wait with requesting the IME until that
@@ -636,7 +708,7 @@
}
}
- return inputConnection;
+ return ic;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 95ae23c..bcfbdac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -314,7 +314,6 @@
// adb shell settings put system enable_fullscreen_user_switcher 1 # Turn it on.
// Restart SystemUI or adb reboot.
final int DEFAULT = -1;
- // TODO(b/140061064)
final int overrideUseFullscreenUserSwitcher =
whitelistIpcs(() -> Settings.System.getInt(mContext.getContentResolver(),
"enable_fullscreen_user_switcher", DEFAULT));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/tv/OWNERS
new file mode 100644
index 0000000..a601e9b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/OWNERS
@@ -0,0 +1,8 @@
+# Android TV Core Framework
+rgl@google.com
+valiiftime@google.com
+galinap@google.com
+patrikf@google.com
+robhor@google.com
+sergeynv@google.com
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index b80b6d5..c2ed7df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -36,6 +36,10 @@
*/
public class TvStatusBar extends SystemUI implements CommandQueue.Callbacks {
+ public TvStatusBar(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
putComponent(TvStatusBar.class, this);
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 89aa797..9a58a35 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -61,6 +61,10 @@
private ThemeOverlayManager mThemeManager;
private UserManager mUserManager;
+ public ThemeOverlayController(Context context) {
+ super(context);
+ }
+
@Override
public void start() {
if (DEBUG) Log.d(TAG, "Start");
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index ff5bd03..11885c5 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -60,6 +60,10 @@
private NotificationManager mNotificationManager;
private StorageManager mStorageManager;
+ public StorageNotification(Context context) {
+ super(context);
+ }
+
private static class MoveInfo {
public int moveId;
public Bundle extras;
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
index 0a3e34e..fd99ef3 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.hardware.usb.IUsbManager;
@@ -63,6 +64,7 @@
mDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
mAccessory = (UsbAccessory)intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
mResolveInfo = (ResolveInfo) intent.getParcelableExtra("rinfo");
+ String packageName = intent.getStringExtra(UsbManager.EXTRA_PACKAGE);
PackageManager packageManager = getPackageManager();
String appName = mResolveInfo.loadLabel(packageManager).toString();
@@ -74,8 +76,20 @@
mAccessory.getDescription());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory);
} else {
- ap.mMessage = getString(R.string.usb_device_confirm_prompt, appName,
- mDevice.getProductName());
+ int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
+ boolean hasRecordPermission =
+ PermissionChecker.checkPermissionForPreflight(
+ this, android.Manifest.permission.RECORD_AUDIO, -1, uid,
+ packageName)
+ == android.content.pm.PackageManager.PERMISSION_GRANTED;
+ boolean isAudioCaptureDevice = mDevice.getHasAudioCapture();
+ boolean useRecordWarning = isAudioCaptureDevice && !hasRecordPermission;
+
+ int strID = useRecordWarning
+ ? R.string.usb_device_confirm_prompt_warn
+ : R.string.usb_device_confirm_prompt;
+
+ ap.mMessage = getString(strID, appName, mDevice.getProductName());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice);
}
ap.mPositiveButtonText = getString(android.R.string.ok);
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index f35af90..8c60747 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
+++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
@@ -38,6 +38,10 @@
public static String BATTERY = "BAT";
public static String HINTS = "HNT";
+ public NotificationChannels(Context context) {
+ super(context);
+ }
+
public static void createAll(Context context) {
final NotificationManager nm = context.getSystemService(NotificationManager.class);
final NotificationChannel batteryChannel = new NotificationChannel(BATTERY,
diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
index 2d5ebc4..63db755 100644
--- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java
@@ -526,11 +526,13 @@
}
/** */
+ @Singleton
public static class Service extends SystemUI implements Dumpable {
private final GarbageMonitor mGarbageMonitor;
@Inject
- public Service(GarbageMonitor garbageMonitor) {
+ public Service(Context context, GarbageMonitor garbageMonitor) {
+ super(context);
mGarbageMonitor = garbageMonitor;
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
index dcd0c58..2224c9c 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
@@ -139,6 +139,12 @@
@Override
protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) {
+ if (listener == null) {
+ throw new IllegalArgumentException("listener cannot be null");
+ }
+ if (sensor == null) {
+ throw new IllegalArgumentException("sensor cannot be null");
+ }
mHandler.post(() -> {
if (!mInner.requestTriggerSensor(listener, sensor)) {
Log.e(TAG, "Requesting " + listener + " for " + sensor + " failed.");
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index d2f185a..25a5139 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -27,7 +27,6 @@
import com.android.settingslib.applications.InterestingConfigChanges;
import com.android.systemui.Dependency;
-import com.android.systemui.SystemUI;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.PluginDependencyProvider;
@@ -40,9 +39,13 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
/**
* Implementation of VolumeComponent backed by the new volume dialog.
*/
+@Singleton
public class VolumeDialogComponent implements VolumeComponent, TunerService.Tunable,
VolumeDialogControllerImpl.UserActivityListener{
@@ -54,12 +57,12 @@
public static final boolean DEFAULT_VOLUME_UP_TO_EXIT_SILENT = false;
public static final boolean DEFAULT_DO_NOT_DISTURB_WHEN_SILENT = false;
- private final SystemUI mSysui;
protected final Context mContext;
private final VolumeDialogControllerImpl mController;
private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE
| ActivityInfo.CONFIG_ASSETS_PATHS | ActivityInfo.CONFIG_UI_MODE);
+ private final KeyguardViewMediator mKeyguardViewMediator;
private VolumeDialog mDialog;
private VolumePolicy mVolumePolicy = new VolumePolicy(
DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT, // volumeDownToEnterSilent
@@ -68,9 +71,10 @@
400 // vibrateToSilentDebounce
);
- public VolumeDialogComponent(SystemUI sysui, Context context) {
- mSysui = sysui;
+ @Inject
+ public VolumeDialogComponent(Context context, KeyguardViewMediator keyguardViewMediator) {
mContext = context;
+ mKeyguardViewMediator = keyguardViewMediator;
mController = (VolumeDialogControllerImpl) Dependency.get(VolumeDialogController.class);
mController.setUserActivityListener(this);
// Allow plugins to reference the VolumeDialogController.
@@ -133,10 +137,7 @@
@Override
public void onUserActivity() {
- final KeyguardViewMediator kvm = mSysui.getComponent(KeyguardViewMediator.class);
- if (kvm != null) {
- kvm.userActivity();
- }
+ mKeyguardViewMediator.userActivity();
}
private void applyConfiguration() {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index f8cf793..b7431397 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -16,18 +16,22 @@
package com.android.systemui.volume;
+import android.content.Context;
import android.content.res.Configuration;
import android.os.Handler;
import android.util.Log;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
-import com.android.systemui.SystemUIFactory;
import com.android.systemui.qs.tiles.DndTile;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+@Singleton
public class VolumeUI extends SystemUI {
private static final String TAG = "VolumeUI";
private static boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
@@ -37,6 +41,12 @@
private boolean mEnabled;
private VolumeDialogComponent mVolumeComponent;
+ @Inject
+ public VolumeUI(Context context, VolumeDialogComponent volumeDialogComponent) {
+ super(context);
+ mVolumeComponent = volumeDialogComponent;
+ }
+
@Override
public void start() {
boolean enableVolumeUi = mContext.getResources().getBoolean(R.bool.enable_volume_ui);
@@ -45,8 +55,6 @@
mEnabled = enableVolumeUi || enableSafetyWarning;
if (!mEnabled) return;
- mVolumeComponent = SystemUIFactory.getInstance()
- .createVolumeDialogComponent(this, mContext);
mVolumeComponent.setEnableDialogs(enableVolumeUi, enableSafetyWarning);
putComponent(VolumeComponent.class, getVolumeComponent());
setDefaultVolumeController();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
index 38537fd..1dd4863 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
@@ -144,7 +144,7 @@
mCarrierTextCallbackInfo = new CarrierTextController.CarrierTextCallbackInfo("",
new CharSequence[]{}, false, new int[]{});
- when(mTelephonyManager.getMaxPhoneCount()).thenReturn(3);
+ when(mTelephonyManager.getSupportedModemCount()).thenReturn(3);
mCarrierTextController = new CarrierTextController(mContext, SEPARATOR, true, true);
// This should not start listening on any of the real dependencies but will test that
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
index a07f25a..364ee66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
@@ -30,6 +30,7 @@
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.util.Assert;
@@ -50,9 +51,10 @@
@Before
public void setUp() throws Exception {
mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
+ mDependency.injectMockDependency(NotificationMediaManager.class);
Assert.sMainLooper = TestableLooper.get(this).getLooper();
Context context = getContext();
- mRow = new NotificationTestHelper(context).createRow();
+ mRow = new NotificationTestHelper(context, mDependency).createRow();
mCallback = mock(ExpandHelper.Callback.class);
mExpandHelper = new ExpandHelper(context, mCallback, 10, 100);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 64ab060..c338d70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -102,7 +102,7 @@
when(mFragmentService.getFragmentHostManager(any())).thenReturn(mFragmentHostManager);
- mScreenDecorations = new ScreenDecorations() {
+ mScreenDecorations = new ScreenDecorations(mContext) {
@Override
public void start() {
super.start();
@@ -126,7 +126,6 @@
mTestableLooper.processAllMessages();
}
};
- mScreenDecorations.mContext = mContext;
mScreenDecorations.mComponents = mContext.getComponents();
reset(mTunerService);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
index 3ea7150..06999bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SizeCompatModeActivityControllerTest.java
@@ -58,13 +58,12 @@
MockitoAnnotations.initMocks(this);
doReturn(true).when(mMockButton).show();
- mController = new SizeCompatModeActivityController(mMockAm) {
+ mController = new SizeCompatModeActivityController(mContext, mMockAm) {
@Override
RestartActivityButton createRestartButton(Context context) {
return mMockButton;
};
};
- mController.mContext = mContext;
ArgumentCaptor<TaskStackChangeListener> listenerCaptor =
ArgumentCaptor.forClass(TaskStackChangeListener.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java
index 19e1a5c..a766885 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SliceBroadcastRelayHandlerTest.java
@@ -35,6 +35,7 @@
import com.android.settingslib.SliceBroadcastRelay;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -44,6 +45,14 @@
public class SliceBroadcastRelayHandlerTest extends SysuiTestCase {
private static final String TEST_ACTION = "com.android.systemui.action.TEST_ACTION";
+ private SliceBroadcastRelayHandler mRelayHandler;
+ private Context mSpyContext;
+ @Before
+ public void setup() {
+ mSpyContext = spy(mContext);
+
+ mRelayHandler = new SliceBroadcastRelayHandler(mSpyContext);
+ }
@Test
public void testRegister() {
@@ -52,8 +61,6 @@
.authority("something")
.path("test")
.build();
- SliceBroadcastRelayHandler relayHandler = new SliceBroadcastRelayHandler();
- relayHandler.mContext = spy(mContext);
Intent intent = new Intent(SliceBroadcastRelay.ACTION_REGISTER);
intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0));
@@ -63,8 +70,8 @@
intent.putExtra(SliceBroadcastRelay.EXTRA_FILTER, value);
intent.putExtra(SliceBroadcastRelay.EXTRA_URI, testUri);
- relayHandler.handleIntent(intent);
- verify(relayHandler.mContext).registerReceiver(any(), eq(value));
+ mRelayHandler.handleIntent(intent);
+ verify(mSpyContext).registerReceiver(any(), eq(value));
}
@Test
@@ -74,8 +81,6 @@
.authority("something")
.path("test")
.build();
- SliceBroadcastRelayHandler relayHandler = new SliceBroadcastRelayHandler();
- relayHandler.mContext = spy(mContext);
Intent intent = new Intent(SliceBroadcastRelay.ACTION_REGISTER);
intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0));
@@ -84,14 +89,14 @@
IntentFilter value = new IntentFilter(TEST_ACTION);
intent.putExtra(SliceBroadcastRelay.EXTRA_FILTER, value);
- relayHandler.handleIntent(intent);
+ mRelayHandler.handleIntent(intent);
ArgumentCaptor<BroadcastReceiver> relay = ArgumentCaptor.forClass(BroadcastReceiver.class);
- verify(relayHandler.mContext).registerReceiver(relay.capture(), eq(value));
+ verify(mSpyContext).registerReceiver(relay.capture(), eq(value));
intent = new Intent(SliceBroadcastRelay.ACTION_UNREGISTER);
intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0));
- relayHandler.handleIntent(intent);
- verify(relayHandler.mContext).unregisterReceiver(eq(relay.getValue()));
+ mRelayHandler.handleIntent(intent);
+ verify(mSpyContext).unregisterReceiver(eq(relay.getValue()));
}
@Test
@@ -101,12 +106,10 @@
.authority("something")
.path("test")
.build();
- SliceBroadcastRelayHandler relayHandler = new SliceBroadcastRelayHandler();
- relayHandler.mContext = spy(mContext);
Intent intent = new Intent(SliceBroadcastRelay.ACTION_UNREGISTER);
intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0));
- relayHandler.handleIntent(intent);
+ mRelayHandler.handleIntent(intent);
// No crash
}
@@ -118,9 +121,6 @@
.authority("something")
.path("test")
.build();
- SliceBroadcastRelayHandler relayHandler = new SliceBroadcastRelayHandler();
- relayHandler.mContext = spy(mContext);
-
Intent intent = new Intent(SliceBroadcastRelay.ACTION_REGISTER);
intent.putExtra(SliceBroadcastRelay.EXTRA_URI, ContentProvider.maybeAddUserId(testUri, 0));
intent.putExtra(SliceBroadcastRelay.EXTRA_RECEIVER,
@@ -128,10 +128,10 @@
IntentFilter value = new IntentFilter(TEST_ACTION);
intent.putExtra(SliceBroadcastRelay.EXTRA_FILTER, value);
- relayHandler.handleIntent(intent);
+ mRelayHandler.handleIntent(intent);
ArgumentCaptor<BroadcastReceiver> relay = ArgumentCaptor.forClass(BroadcastReceiver.class);
- verify(relayHandler.mContext).registerReceiver(relay.capture(), eq(value));
- relay.getValue().onReceive(relayHandler.mContext, new Intent(TEST_ACTION));
+ verify(mSpyContext).registerReceiver(relay.capture(), eq(value));
+ relay.getValue().onReceive(mSpyContext, new Intent(TEST_ACTION));
verify(Receiver.sReceiver, timeout(2000)).onReceive(any(), any());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index dcdb5c3..e1eb3b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -34,6 +34,7 @@
import android.app.ActivityManager;
import android.app.IActivityTaskManager;
import android.content.ComponentName;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.hardware.biometrics.Authenticator;
@@ -98,8 +99,7 @@
when(mDialog1.isAllowDeviceCredentials()).thenReturn(false);
when(mDialog2.isAllowDeviceCredentials()).thenReturn(false);
- mAuthController = new TestableAuthController(new MockInjector());
- mAuthController.mContext = context;
+ mAuthController = new TestableAuthController(context, new MockInjector());
mAuthController.mComponents = mContext.getComponents();
mAuthController.start();
@@ -404,8 +404,8 @@
private int mBuildCount = 0;
private Bundle mLastBiometricPromptBundle;
- public TestableAuthController(Injector injector) {
- super(injector);
+ TestableAuthController(Context context, Injector injector) {
+ super(context, injector);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 2798c6b..3472573 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -33,6 +33,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -55,8 +56,10 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.colorextraction.ColorExtractor;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -91,7 +94,6 @@
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class BubbleControllerTest extends SysuiTestCase {
-
@Mock
private NotificationEntryManager mNotificationEntryManager;
@Mock
@@ -141,6 +143,10 @@
private BubbleController.BubbleExpandListener mBubbleExpandListener;
@Mock
private PendingIntent mDeleteIntent;
+ @Mock
+ private SysuiColorExtractor mColorExtractor;
+ @Mock
+ ColorExtractor.GradientColors mGradientColors;
private BubbleData mBubbleData;
@@ -150,15 +156,16 @@
mStatusBarView = new FrameLayout(mContext);
mDependency.injectTestDependency(NotificationEntryManager.class, mNotificationEntryManager);
mContext.addMockSystemService(FaceManager.class, mFaceManager);
+ when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
// Bubbles get added to status bar window view
mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
mActivityManager, mDozeParameters, mStatusBarStateController,
- mConfigurationController, mKeyguardBypassController);
+ mConfigurationController, mKeyguardBypassController, mColorExtractor);
mStatusBarWindowController.add(mStatusBarView, 120 /* height */);
// Need notifications for bubbles
- mNotificationTestHelper = new NotificationTestHelper(mContext);
+ mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
mNonBubbleNotifRow = mNotificationTestHelper.createRow();
@@ -223,13 +230,13 @@
mBubbleController.updateBubble(mRow.getEntry());
assertNotNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
assertTrue(mBubbleController.hasBubbles());
- verify(mNotificationEntryManager).updateNotifications();
+ verify(mNotificationEntryManager).updateNotifications(any());
verify(mBubbleStateChangeListener).onHasBubblesChanged(true);
mBubbleController.removeBubble(mRow.getEntry().key, BubbleController.DISMISS_USER_GESTURE);
assertFalse(mStatusBarWindowController.getBubblesShowing());
assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
- verify(mNotificationEntryManager, times(2)).updateNotifications();
+ verify(mNotificationEntryManager, times(2)).updateNotifications(anyString());
verify(mBubbleStateChangeListener).onHasBubblesChanged(false);
}
@@ -257,16 +264,16 @@
@Test
public void testDismissStack() {
mBubbleController.updateBubble(mRow.getEntry());
- verify(mNotificationEntryManager, times(1)).updateNotifications();
+ verify(mNotificationEntryManager, times(1)).updateNotifications(any());
assertNotNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
mBubbleController.updateBubble(mRow2.getEntry());
- verify(mNotificationEntryManager, times(2)).updateNotifications();
+ verify(mNotificationEntryManager, times(2)).updateNotifications(any());
assertNotNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().key));
assertTrue(mBubbleController.hasBubbles());
mBubbleController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
assertFalse(mStatusBarWindowController.getBubblesShowing());
- verify(mNotificationEntryManager, times(3)).updateNotifications();
+ verify(mNotificationEntryManager, times(3)).updateNotifications(any());
assertNull(mBubbleData.getBubbleWithKey(mRow.getEntry().key));
assertNull(mBubbleData.getBubbleWithKey(mRow2.getEntry().key));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index 392a7cb..96ee079 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -99,7 +99,7 @@
@Before
public void setUp() throws Exception {
- mNotificationTestHelper = new NotificationTestHelper(mContext);
+ mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
MockitoAnnotations.initMocks(this);
mEntryA1 = createBubbleEntry(1, "a1", "package.a");
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java
index d011e48..3ba5d1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/ClassifierTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.classifier.brightline;
+import static com.android.systemui.classifier.Classifier.UNLOCK;
+
import android.util.DisplayMetrics;
import android.view.MotionEvent;
@@ -42,6 +44,7 @@
displayMetrics.widthPixels = 1000;
displayMetrics.heightPixels = 1000;
mDataProvider = new FalsingDataProvider(displayMetrics);
+ mDataProvider.setInteractionType(UNLOCK);
}
@After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/PointerCountClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/PointerCountClassifierTest.java
index 341b74b..96b2028 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/PointerCountClassifierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/brightline/PointerCountClassifierTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.classifier.brightline;
+import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
+
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
@@ -74,4 +76,21 @@
motionEvent.recycle();
assertThat(mClassifier.isFalseTouch(), is(true));
}
+
+ @Test
+ public void testPass_multiPointerDragDown() {
+ MotionEvent.PointerProperties[] pointerProperties =
+ MotionEvent.PointerProperties.createArray(2);
+ pointerProperties[0].id = 0;
+ pointerProperties[1].id = 1;
+ MotionEvent.PointerCoords[] pointerCoords = MotionEvent.PointerCoords.createArray(2);
+ MotionEvent motionEvent = MotionEvent.obtain(
+ 1, 1, MotionEvent.ACTION_DOWN, 2, pointerProperties, pointerCoords, 0, 0, 0, 0, 0,
+ 0,
+ 0, 0);
+ mClassifier.onTouchEvent(motionEvent);
+ motionEvent.recycle();
+ getDataProvider().setInteractionType(QUICK_SETTINGS);
+ assertThat(mClassifier.isFalseTouch(), is(false));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
index 1ce0172..98ec4594 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeDockHandlerTest.java
@@ -20,13 +20,11 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.Instrumentation;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Handler;
import android.os.Looper;
@@ -34,7 +32,6 @@
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -45,23 +42,24 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
public class DozeDockHandlerTest extends SysuiTestCase {
- private DozeDockHandler mDockHandler;
+ @Mock
private DozeMachine mMachine;
- private DozeHostFake mHost;
+ @Mock
+ private DozeHost mHost;
private AmbientDisplayConfiguration mConfig;
- private Instrumentation mInstrumentation;
private DockManagerFake mDockManagerFake;
+ private DozeDockHandler mDockHandler;
@Before
public void setUp() throws Exception {
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
- mMachine = mock(DozeMachine.class);
- mHost = spy(new DozeHostFake());
+ MockitoAnnotations.initMocks(this);
mConfig = DozeConfigurationUtil.createMockConfig();
doReturn(false).when(mConfig).alwaysOnEnabled(anyInt());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
deleted file mode 100644
index abfa755..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.doze;
-
-import android.annotation.NonNull;
-
-/**
- * A rudimentary fake for DozeHost.
- */
-class DozeHostFake implements DozeHost {
- Callback callback;
- boolean pulseExtended;
- boolean animateWakeup;
- boolean animateScreenOff;
- boolean dozing;
- float doubleTapX;
- float doubleTapY;
- float aodDimmingScrimOpacity;
-
- @Override
- public void addCallback(@NonNull Callback callback) {
- this.callback = callback;
- }
-
- @Override
- public void removeCallback(@NonNull Callback callback) {
- this.callback = null;
- }
-
- @Override
- public void startDozing() {
- dozing = true;
- }
-
- @Override
- public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) {
- throw new RuntimeException("not implemented");
- }
-
- @Override
- public void stopDozing() {
- dozing = false;
- }
-
- @Override
- public void dozeTimeTick() {
- // Nothing to do in here. Real host would just update the UI.
- }
-
- @Override
- public boolean isPowerSaveActive() {
- return false;
- }
-
- @Override
- public boolean isPulsingBlocked() {
- return false;
- }
-
- @Override
- public boolean isProvisioned() {
- return false;
- }
-
- @Override
- public boolean isBlockingDoze() {
- return false;
- }
-
- @Override
- public void onIgnoreTouchWhilePulsing(boolean ignore) {
- }
-
- @Override
- public void extendPulse(int reason) {
- pulseExtended = true;
- }
-
- @Override
- public void stopPulsing() {}
-
- @Override
- public void setAnimateWakeup(boolean animateWakeup) {
- this.animateWakeup = animateWakeup;
- }
-
- @Override
- public void setAnimateScreenOff(boolean animateScreenOff) {
- this.animateScreenOff = animateScreenOff;
- }
-
- @Override
- public void onSlpiTap(float x, float y) {
- doubleTapX = y;
- doubleTapY = y;
- }
-
- @Override
- public void setDozeScreenBrightness(int value) {
- }
-
- @Override
- public void setAodDimmingScrim(float scrimOpacity) {
- aodDimmingScrimOpacity = scrimOpacity;
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index aa62e9a..316b891 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -30,6 +30,11 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
import android.content.Intent;
import android.os.PowerManager;
@@ -45,6 +50,8 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -55,22 +62,27 @@
static final int[] SENSOR_TO_OPACITY = new int[]{-1, 10, 0, 0, 0};
DozeServiceFake mServiceFake;
- DozeScreenBrightness mScreen;
FakeSensorManager.FakeGenericSensor mSensor;
FakeSensorManager mSensorManager;
- DozeHostFake mHostFake;
+ @Mock
+ DozeHost mDozeHost;
+ DozeScreenBrightness mScreen;
@Before
public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
Settings.System.putIntForUser(mContext.getContentResolver(),
Settings.System.SCREEN_BRIGHTNESS, DEFAULT_BRIGHTNESS,
UserHandle.USER_CURRENT);
+ doAnswer(invocation -> {
+ ((Runnable) invocation.getArgument(0)).run();
+ return null;
+ }).when(mDozeHost).prepareForGentleSleep(any());
mServiceFake = new DozeServiceFake();
- mHostFake = new DozeHostFake();
mSensorManager = new FakeSensorManager(mContext);
mSensor = mSensorManager.getFakeLightSensor();
mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
- mSensor.getSensor(), mHostFake, null /* handler */,
+ mSensor.getSensor(), mDozeHost, null /* handler */,
DEFAULT_BRIGHTNESS, SENSOR_TO_BRIGHTNESS, SENSOR_TO_OPACITY,
true /* debuggable */);
}
@@ -173,7 +185,7 @@
@Test
public void testNullSensor() throws Exception {
mScreen = new DozeScreenBrightness(mContext, mServiceFake, mSensorManager,
- null /* sensor */, mHostFake, null /* handler */,
+ null /* sensor */, mDozeHost, null /* handler */,
DEFAULT_BRIGHTNESS, SENSOR_TO_BRIGHTNESS, SENSOR_TO_OPACITY,
true /* debuggable */);
@@ -203,26 +215,7 @@
mSensor.sendSensorEvent(0);
assertEquals(1, mServiceFake.screenBrightness);
- assertEquals(10/255f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
- }
-
- @Test
- public void pausingAod_softBlanks() throws Exception {
- mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
- mScreen.transitionTo(INITIALIZED, DOZE_AOD);
-
- mSensor.sendSensorEvent(2);
-
- mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
- mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
-
- assertEquals(1f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
-
- mSensor.sendSensorEvent(0);
- assertEquals(1f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
-
- mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
- assertEquals(1f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
+ verify(mDozeHost).setAodDimmingScrim(eq(10f / 255f));
}
@Test
@@ -232,8 +225,9 @@
mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
+ reset(mDozeHost);
mSensor.sendSensorEvent(1);
- assertEquals(1f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
+ verify(mDozeHost).setAodDimmingScrim(eq(1f));
}
@Test
@@ -241,11 +235,12 @@
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
mScreen.transitionTo(DOZE_AOD, DOZE);
- assertEquals(1f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
+ verify(mDozeHost).setAodDimmingScrim(eq(1f));
+ reset(mDozeHost);
mScreen.transitionTo(DOZE, DOZE_AOD);
mSensor.sendSensorEvent(2);
- assertEquals(0f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
+ verify(mDozeHost).setAodDimmingScrim(eq(0f));
}
@Test
@@ -260,11 +255,10 @@
mSensor.sendSensorEvent(0);
+ reset(mDozeHost);
mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
-
mSensor.sendSensorEvent(2);
-
- assertEquals(0f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
+ verify(mDozeHost).setAodDimmingScrim(eq(0f));
}
@Test
@@ -273,11 +267,11 @@
mScreen.transitionTo(INITIALIZED, DOZE_AOD);
mSensor.sendSensorEvent(2);
-
mScreen.transitionTo(DOZE_AOD, DOZE_AOD_PAUSING);
mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
- mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
- assertEquals(0f, mHostFake.aodDimmingScrimOpacity, 0.001f /* delta */);
+ reset(mDozeHost);
+ mScreen.transitionTo(DOZE_AOD_PAUSED, DOZE_AOD);
+ verify(mDozeHost).setAodDimmingScrim(eq(0f));
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
index bfd448a..b92f173 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenStateTest.java
@@ -18,6 +18,8 @@
import static com.android.systemui.doze.DozeMachine.State.DOZE;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSED;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSING;
import static com.android.systemui.doze.DozeMachine.State.DOZE_REQUEST_PULSE;
import static com.android.systemui.doze.DozeMachine.State.FINISH;
@@ -30,6 +32,9 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.os.Looper;
@@ -46,6 +51,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -53,12 +59,14 @@
@SmallTest
public class DozeScreenStateTest extends SysuiTestCase {
- DozeServiceFake mServiceFake;
- DozeScreenState mScreen;
- FakeHandler mHandlerFake;
+ private DozeServiceFake mServiceFake;
+ private FakeHandler mHandlerFake;
@Mock
- DozeParameters mDozeParameters;
- WakeLockFake mWakeLock;
+ private DozeHost mDozeHost;
+ @Mock
+ private DozeParameters mDozeParameters;
+ private WakeLockFake mWakeLock;
+ private DozeScreenState mScreen;
@Before
public void setUp() throws Exception {
@@ -68,7 +76,8 @@
mServiceFake = new DozeServiceFake();
mHandlerFake = new FakeHandler(Looper.getMainLooper());
mWakeLock = new WakeLockFake();
- mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeParameters, mWakeLock);
+ mScreen = new DozeScreenState(mServiceFake, mHandlerFake, mDozeHost, mDozeParameters,
+ mWakeLock);
}
@Test
@@ -183,4 +192,20 @@
assertThat(mWakeLock.isHeld(), is(false));
}
+ @Test
+ public void test_animatesPausing() {
+ ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+ doAnswer(invocation -> null).when(mDozeHost).prepareForGentleSleep(captor.capture());
+ mHandlerFake.setMode(QUEUEING);
+
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_AOD_PAUSING);
+ mScreen.transitionTo(DOZE_AOD_PAUSING, DOZE_AOD_PAUSED);
+
+ mHandlerFake.dispatchQueuedMessages();
+ verify(mDozeHost).prepareForGentleSleep(eq(captor.getValue()));
+ captor.getValue().run();
+ assertEquals(Display.STATE_OFF, mServiceFake.screenState);
+ }
+
}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index e5ae6d5..777fec7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -20,6 +20,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -50,14 +51,22 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
public class DozeTriggersTest extends SysuiTestCase {
- private DozeTriggers mTriggers;
+
+ @Mock
private DozeMachine mMachine;
- private DozeHostFake mHost;
+ @Mock
+ private DozeHost mHost;
+ @Mock
+ private AlarmManager mAlarmManager;
+ private DozeTriggers mTriggers;
private FakeSensorManager mSensors;
private Sensor mTapSensor;
private DockManager mDockManagerFake;
@@ -65,9 +74,7 @@
@Before
public void setUp() throws Exception {
- mMachine = mock(DozeMachine.class);
- AlarmManager alarmManager = mock(AlarmManager.class);
- mHost = spy(new DozeHostFake());
+ MockitoAnnotations.initMocks(this);
AmbientDisplayConfiguration config = DozeConfigurationUtil.createMockConfig();
DozeParameters parameters = DozeConfigurationUtil.createMockParameters();
mSensors = spy(new FakeSensorManager(mContext));
@@ -78,7 +85,7 @@
new AsyncSensorManager(mSensors, null, new Handler());
mProximitySensor = new FakeProximitySensor(getContext(), asyncSensorManager);
- mTriggers = new DozeTriggers(mContext, mMachine, mHost, alarmManager, config, parameters,
+ mTriggers = new DozeTriggers(mContext, mMachine, mHost, mAlarmManager, config, parameters,
asyncSensorManager, Handler.createAsync(Looper.myLooper()), wakeLock, true,
mDockManagerFake, mProximitySensor, mock(DozeLog.class));
waitForSensorManager();
@@ -87,19 +94,21 @@
@Test
public void testOnNotification_stillWorksAfterOneFailedProxCheck() throws Exception {
when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE);
+ ArgumentCaptor<DozeHost.Callback> captor = ArgumentCaptor.forClass(DozeHost.Callback.class);
+ doAnswer(invocation -> null).when(mHost).addCallback(captor.capture());
mTriggers.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
clearInvocations(mMachine);
mProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(true, 1));
- mHost.callback.onNotificationAlerted(null /* pulseSuppressedListener */);
+ captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
mProximitySensor.alertListeners();
verify(mMachine, never()).requestState(any());
verify(mMachine, never()).requestPulse(anyInt());
- mHost.callback.onNotificationAlerted(null /* pulseSuppressedListener */);
+ captor.getValue().onNotificationAlerted(null /* pulseSuppressedListener */);
waitForSensorManager();
mProximitySensor.setLastEvent(new ProximitySensor.ProximityEvent(false, 2));
mProximitySensor.alertListeners();
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 4958c64..8f4de3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -682,8 +682,7 @@
}
private void createPowerUi() {
- mPowerUI = new PowerUI(mBroadcastDispatcher);
- mPowerUI.mContext = mContext;
+ mPowerUI = new PowerUI(mContext, mBroadcastDispatcher);
mPowerUI.mComponents = mContext.getComponents();
mPowerUI.mThermalService = mThermalServiceMock;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
new file mode 100644
index 0000000..4becd52
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.qs.external
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.ServiceInfo
+import android.graphics.drawable.Drawable
+import android.graphics.drawable.Icon
+import android.service.quicksettings.IQSTileService
+import android.service.quicksettings.Tile
+import android.test.suitebuilder.annotation.SmallTest
+import android.view.IWindowManager
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.QSTileHost
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CustomTileTest : SysuiTestCase() {
+
+ companion object {
+ const val packageName = "test_package"
+ const val className = "test_class"
+ val componentName = ComponentName(packageName, className)
+ val TILE_SPEC = CustomTile.toSpec(componentName)
+ }
+
+ @Mock private lateinit var mTileHost: QSTileHost
+ @Mock private lateinit var mTileService: IQSTileService
+ @Mock private lateinit var mTileServices: TileServices
+ @Mock private lateinit var mTileServiceManager: TileServiceManager
+ @Mock private lateinit var mWindowService: IWindowManager
+ @Mock private lateinit var mPackageManager: PackageManager
+ @Mock private lateinit var mApplicationInfo: ApplicationInfo
+ @Mock private lateinit var mServiceInfo: ServiceInfo
+
+ private lateinit var customTile: CustomTile
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ mContext.addMockSystemService("window", mWindowService)
+ mContext.setMockPackageManager(mPackageManager)
+ `when`(mTileHost.tileServices).thenReturn(mTileServices)
+ `when`(mTileHost.context).thenReturn(mContext)
+ `when`(mTileServices.getTileWrapper(any(CustomTile::class.java)))
+ .thenReturn(mTileServiceManager)
+ `when`(mTileServiceManager.tileService).thenReturn(mTileService)
+ `when`(mPackageManager.getApplicationInfo(anyString(), anyInt()))
+ .thenReturn(mApplicationInfo)
+
+ `when`(mPackageManager.getServiceInfo(any(ComponentName::class.java), anyInt()))
+ .thenReturn(mServiceInfo)
+ mServiceInfo.applicationInfo = mApplicationInfo
+
+ customTile = CustomTile.create(mTileHost, TILE_SPEC)
+ }
+
+ @Test
+ fun testBooleanTileHasBooleanState() {
+ `when`(mTileServiceManager.isBooleanTile).thenReturn(true)
+ customTile = CustomTile.create(mTileHost, TILE_SPEC)
+
+ assertTrue(customTile.state is QSTile.BooleanState)
+ assertTrue(customTile.newTileState() is QSTile.BooleanState)
+ }
+
+ @Test
+ fun testRegularTileHasNotBooleanState() {
+ assertFalse(customTile.state is QSTile.BooleanState)
+ assertFalse(customTile.newTileState() is QSTile.BooleanState)
+ }
+
+ @Test
+ fun testValueUpdatedInBooleanTile() {
+ `when`(mTileServiceManager.isBooleanTile).thenReturn(true)
+ customTile = CustomTile.create(mTileHost, TILE_SPEC)
+ customTile.qsTile.icon = mock(Icon::class.java)
+ `when`(customTile.qsTile.icon.loadDrawable(any(Context::class.java)))
+ .thenReturn(mock(Drawable::class.java))
+
+ val state = customTile.newTileState()
+ assertTrue(state is QSTile.BooleanState)
+
+ customTile.qsTile.state = Tile.STATE_INACTIVE
+ customTile.handleUpdateState(state, null)
+ assertFalse((state as QSTile.BooleanState).value)
+
+ customTile.qsTile.state = Tile.STATE_ACTIVE
+ customTile.handleUpdateState(state, null)
+ assertTrue(state.value)
+
+ customTile.qsTile.state = Tile.STATE_UNAVAILABLE
+ customTile.handleUpdateState(state, null)
+ assertFalse(state.value)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index f35295c..11b0c69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -101,6 +101,7 @@
defaultServiceInfo = new ServiceInfo();
defaultServiceInfo.metaData = new Bundle();
defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_ACTIVE_TILE, true);
+ defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_BOOLEAN_TILE, true);
}
when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt(), anyInt()))
.thenReturn(defaultServiceInfo);
@@ -237,4 +238,9 @@
verifyBind(2);
verify(mMockTileService, times(2)).onStartListening();
}
+
+ @Test
+ public void testBooleanTile() throws Exception {
+ assertTrue(mStateManager.isBooleanTile());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NavigationBarControllerTest.java
index 618272c..cfa4065a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NavigationBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NavigationBarControllerTest.java
@@ -23,6 +23,7 @@
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -31,15 +32,10 @@
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import android.content.Context;
-import android.hardware.display.DisplayManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.util.SparseArray;
-import android.view.Display;
-import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -59,7 +55,6 @@
public class NavigationBarControllerTest extends SysuiTestCase {
private NavigationBarController mNavigationBarController;
- private Display mDisplay;
private NavigationBarFragment mDefaultNavBar;
private NavigationBarFragment mSecondaryNavBar;
@@ -89,37 +84,28 @@
@After
public void tearDown() {
mNavigationBarController = null;
- mDisplay = null;
mDefaultNavBar = null;
mSecondaryNavBar = null;
}
@Test
public void testCreateNavigationBarsIncludeDefaultTrue() {
- initializeDisplayManager();
doNothing().when(mNavigationBarController).createNavigationBar(any(), any());
mNavigationBarController.createNavigationBars(true, null);
- verify(mNavigationBarController).createNavigationBar(any(Display.class), any());
+ verify(mNavigationBarController).createNavigationBar(
+ argThat(display -> display.getDisplayId() == DEFAULT_DISPLAY), any());
}
@Test
public void testCreateNavigationBarsIncludeDefaultFalse() {
- initializeDisplayManager();
doNothing().when(mNavigationBarController).createNavigationBar(any(), any());
mNavigationBarController.createNavigationBars(false, null);
- verify(mNavigationBarController, never()).createNavigationBar(any(), any());
- }
-
- private void initializeDisplayManager() {
- DisplayManager displayManager = mock(DisplayManager.class);
- mDisplay = mContext.getSystemService(WindowManager.class).getDefaultDisplay();
- Display[] displays = {mDisplay};
- when(displayManager.getDisplays()).thenReturn(displays);
- mContext.addMockSystemService(Context.DISPLAY_SERVICE, displayManager);
+ verify(mNavigationBarController, never()).createNavigationBar(
+ argThat(display -> display.getDisplayId() == DEFAULT_DISPLAY), any());
}
// Tests if NPE occurs when call checkNavBarModes() with invalid display.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
index a0a410d..e67aa69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -52,7 +52,7 @@
*/
@SmallTest
@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class NonPhoneDependencyTest extends SysuiTestCase {
@Mock private NotificationPresenter mPresenter;
@Mock private NotificationListContainer mListContainer;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index a027643..85a0fbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -25,6 +25,7 @@
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -51,6 +52,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.google.android.collect.Lists;
@@ -82,6 +84,7 @@
mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
mDependency.injectTestDependency(DeviceProvisionedController.class,
mDeviceProvisionedController);
+ mDependency.injectMockDependency(KeyguardStateController.class);
mContext.addMockSystemService(UserManager.class, mUserManager);
mCurrentUserId = ActivityManager.getCurrentUser();
@@ -99,7 +102,7 @@
@Test
public void testLockScreenShowNotificationsChangeUpdatesNotifications() {
mLockscreenUserManager.getLockscreenSettingsObserverForTest().onChange(false);
- verify(mEntryManager, times(1)).updateNotifications();
+ verify(mEntryManager, times(1)).updateNotifications(anyString());
}
@Test
@@ -138,7 +141,7 @@
public void testSettingsObserverUpdatesNotifications() {
when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
mLockscreenUserManager.getSettingsObserverForTest().onChange(false);
- verify(mEntryManager, times(1)).updateNotifications();
+ verify(mEntryManager, times(1)).updateNotifications(anyString());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index de77af8..cb4096c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -42,6 +42,8 @@
import androidx.test.InstrumentationRegistry;
+import com.android.systemui.TestableDependency;
+import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.bubbles.BubblesTestActivity;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -51,6 +53,7 @@
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.StatusBarWindowController;
import com.android.systemui.tests.R;
/**
@@ -75,8 +78,11 @@
private ExpandableNotificationRow mRow;
private HeadsUpManagerPhone mHeadsUpManager;
- public NotificationTestHelper(Context context) {
+ public NotificationTestHelper(Context context, TestableDependency dependency) {
mContext = context;
+ dependency.injectMockDependency(NotificationMediaManager.class);
+ dependency.injectMockDependency(BubbleController.class);
+ dependency.injectMockDependency(StatusBarWindowController.class);
mInstrumentation = InstrumentationRegistry.getInstrumentation();
StatusBarStateController stateController = mock(StatusBarStateController.class);
mGroupManager = new NotificationGroupManager(stateController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 9e72504..6efa57d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -103,7 +103,7 @@
mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
mDependency.injectTestDependency(ShadeController.class, mShadeController);
- mHelper = new NotificationTestHelper(mContext);
+ mHelper = new NotificationTestHelper(mContext, mDependency);
when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
index db2c878..4103ede 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
@@ -47,7 +47,7 @@
@Before
public void setUp() throws Exception {
com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
- mNotificationTestHelper = new NotificationTestHelper(getContext());
+ mNotificationTestHelper = new NotificationTestHelper(getContext(), mDependency);
mHostLayout = new FrameLayout(getContext());
mObserver = new AboveShelfObserver(mHostLayout);
ExpandableNotificationRow row = mNotificationTestHelper.createRow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 866ea51..e52a258 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -28,6 +28,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
@@ -80,6 +81,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -146,7 +148,8 @@
private final CountDownLatch mCountDownLatch;
TestableNotificationEntryManager() {
- super(new NotificationData(mock(NotificationSectionsFeatureManager.class)));
+ super(new NotificationData(mock(NotificationSectionsFeatureManager.class),
+ mock(NotifLog.class)), mock(NotifLog.class));
mCountDownLatch = new CountDownLatch(1);
}
@@ -259,7 +262,9 @@
NotificationRowBinderImpl notificationRowBinder =
new NotificationRowBinderImpl(mContext, true, /* allowLongPress */
- mock(KeyguardBypassController.class), mock(StatusBarStateController.class));
+ mock(KeyguardBypassController.class),
+ mock(StatusBarStateController.class),
+ mock(NotifLog.class));
notificationRowBinder.setUpWithPresenter(
mPresenter, mListContainer, mHeadsUpManager, mEntryManager, mBindCallback);
notificationRowBinder.setNotificationClicker(mock(NotificationClicker.class));
@@ -350,7 +355,7 @@
// Ensure that update callbacks happen in correct order
InOrder order = inOrder(mEntryListener, notifData, mPresenter, mEntryListener);
order.verify(mEntryListener).onPreEntryUpdated(mEntry);
- order.verify(notifData).filterAndSort();
+ order.verify(notifData).filterAndSort(anyString());
order.verify(mPresenter).updateNotificationViews();
order.verify(mEntryListener).onPostEntryUpdated(mEntry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index edd0a10..45e1721 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -42,6 +42,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -94,10 +95,11 @@
mDependency.injectTestDependency(NotificationGroupManager.class,
new NotificationGroupManager(mock(StatusBarStateController.class)));
mDependency.injectMockDependency(ShadeController.class);
+ mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
mDependency.injectTestDependency(NotificationData.KeyguardEnvironment.class, mEnvironment);
when(mEnvironment.isDeviceProvisioned()).thenReturn(true);
when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
- mRow = new NotificationTestHelper(getContext()).createRow();
+ mRow = new NotificationTestHelper(getContext(), mDependency).createRow();
mNotificationFilter = new NotificationFilter();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
index 6d275419..170c661 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationListControllerTest.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -41,8 +42,10 @@
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -76,13 +79,14 @@
// TODO: Remove this once EntryManager no longer needs to be mocked
private NotificationData mNotificationData =
new NotificationData(new NotificationSectionsFeatureManager(
- new DeviceConfigProxyFake(), mContext));
+ new DeviceConfigProxyFake(), mContext), mock(NotifLog.class));
private int mNextNotifId = 0;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
when(mEntryManager.getNotificationData()).thenReturn(mNotificationData);
@@ -113,7 +117,7 @@
@Test
public void testCallUpdateNotificationsOnDeviceProvisionedChange() {
mProvisionedListener.onDeviceProvisionedChanged();
- verify(mEntryManager).updateNotifications();
+ verify(mEntryManager).updateNotifications(anyString());
}
@Test
@@ -133,8 +137,8 @@
// THEN the app op is added to the entry
assertTrue(entry.mActiveAppOps.contains(AppOpsManager.OP_CAMERA));
- // THEN updateNotifications() is called
- verify(mEntryManager, times(1)).updateNotifications();
+ // THEN updateNotifications(TEST) is called
+ verify(mEntryManager, times(1)).updateNotifications(anyString());
}
@Test
@@ -146,8 +150,8 @@
// WHEN An unrelated notification gets a new app op
mController.updateNotificationsForAppOp(AppOpsManager.OP_CAMERA, 1000, "pkg", true);
- // THEN We never call updateNotifications()
- verify(mEntryManager, never()).updateNotifications();
+ // THEN We never call updateNotifications(TEST)
+ verify(mEntryManager, never()).updateNotifications(anyString());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
index 5fbacb1..1be6f61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationDataTest.java
@@ -74,11 +74,13 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SbnBuilder;
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.collection.NotificationData.KeyguardEnvironment;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ShadeController;
@@ -136,13 +138,14 @@
mDependency.injectTestDependency(NotificationGroupManager.class,
new NotificationGroupManager(mock(StatusBarStateController.class)));
mDependency.injectMockDependency(ShadeController.class);
+ mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
when(mEnvironment.isDeviceProvisioned()).thenReturn(true);
when(mEnvironment.isNotificationForCurrentProfiles(any())).thenReturn(true);
mNotificationData = new TestableNotificationData(
mock(NotificationSectionsFeatureManager.class));
- mNotificationData.updateRanking(mock(NotificationListenerService.RankingMap.class));
- mRow = new NotificationTestHelper(getContext()).createRow();
+ mNotificationData.updateRanking(mock(NotificationListenerService.RankingMap.class), "");
+ mRow = new NotificationTestHelper(getContext(), mDependency).createRow();
Dependency.get(InitController.class).executePostInitTasks();
}
@@ -158,10 +161,11 @@
@Test
public void testAllRelevantNotisTaggedWithAppOps() throws Exception {
mNotificationData.add(mRow.getEntry());
- ExpandableNotificationRow row2 = new NotificationTestHelper(getContext()).createRow();
+ ExpandableNotificationRow row2 = new NotificationTestHelper(getContext(), mDependency)
+ .createRow();
mNotificationData.add(row2.getEntry());
ExpandableNotificationRow diffPkg =
- new NotificationTestHelper(getContext()).createRow("pkg", 4000,
+ new NotificationTestHelper(getContext(), mDependency).createRow("pkg", 4000,
Process.myUserHandle());
mNotificationData.add(diffPkg.getEntry());
@@ -188,7 +192,8 @@
@Test
public void testAppOpsRemoval() throws Exception {
mNotificationData.add(mRow.getEntry());
- ExpandableNotificationRow row2 = new NotificationTestHelper(getContext()).createRow();
+ ExpandableNotificationRow row2 = new NotificationTestHelper(getContext(), mDependency)
+ .createRow();
mNotificationData.add(row2.getEntry());
ArraySet<Integer> expectedOps = new ArraySet<>();
@@ -220,7 +225,8 @@
public void testGetNotificationsForCurrentUser_shouldFilterNonCurrentUserNotifications()
throws Exception {
mNotificationData.add(mRow.getEntry());
- ExpandableNotificationRow row2 = new NotificationTestHelper(getContext()).createRow();
+ ExpandableNotificationRow row2 = new NotificationTestHelper(getContext(), mDependency)
+ .createRow();
mNotificationData.add(row2.getEntry());
when(mEnvironment.isNotificationForCurrentProfiles(
@@ -633,7 +639,7 @@
public static class TestableNotificationData extends NotificationData {
public TestableNotificationData(NotificationSectionsFeatureManager sectionsFeatureManager) {
- super(sectionsFeatureManager);
+ super(sectionsFeatureManager, mock(NotifLog.class));
}
public static final String OVERRIDE_RANK = "r";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index c56a168..5e6c963 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -82,7 +82,7 @@
@Before
public void setUp() throws Exception {
com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
- mNotificationTestHelper = new NotificationTestHelper(mContext);
+ mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
mGroupRow = mNotificationTestHelper.createGroup();
mGroupRow.setHeadsUpAnimatingAwayListener(
animatingAway -> mHeadsUpAnimatingAway = animatingAway);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
index 6d64395..444a6e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
@@ -27,6 +27,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -62,7 +63,6 @@
@org.junit.runner.RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class NotificationBlockingHelperManagerTest extends SysuiTestCase {
-
private NotificationBlockingHelperManager mBlockingHelperManager;
private NotificationTestHelper mHelper;
@@ -88,7 +88,7 @@
mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
mDependency.injectMockDependency(BubbleController.class);
- mHelper = new NotificationTestHelper(mContext);
+ mHelper = new NotificationTestHelper(mContext, mDependency);
mBlockingHelperManager = new NotificationBlockingHelperManager(mContext);
// By default, have the shade visible/expanded.
@@ -112,7 +112,7 @@
assertTrue(mBlockingHelperManager.dismissCurrentBlockingHelper());
assertTrue(mBlockingHelperManager.isBlockingHelperRowNull());
- verify(mEntryManager, times(0)).updateNotifications();
+ verify(mEntryManager, times(0)).updateNotifications(anyString());
}
@Test
@@ -125,7 +125,7 @@
assertTrue(mBlockingHelperManager.dismissCurrentBlockingHelper());
assertTrue(mBlockingHelperManager.isBlockingHelperRowNull());
- verify(mEntryManager).updateNotifications();
+ verify(mEntryManager).updateNotifications(anyString());
}
@Test
@@ -267,7 +267,7 @@
assertTrue(mBlockingHelperManager.dismissCurrentBlockingHelper());
assertTrue(mBlockingHelperManager.isBlockingHelperRowNull());
- verify(mEntryManager).updateNotifications();
+ verify(mEntryManager).updateNotifications(anyString());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index ccadcc3..71c2e11 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -77,7 +77,7 @@
.setContentTitle("Title")
.setContentText("Text")
.setStyle(new Notification.BigTextStyle().bigText("big text"));
- ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow(
+ ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow(
mBuilder.build());
mRow = spy(row);
mNotificationInflater = new NotificationContentInflater(mRow);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index db6b613..41fe173 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -62,6 +62,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
@@ -118,9 +119,10 @@
mDeviceProvisionedController);
mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
+ mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
mHandler = Handler.createAsync(mTestableLooper.getLooper());
mContext.putComponent(StatusBar.class, mStatusBar);
- mHelper = new NotificationTestHelper(mContext);
+ mHelper = new NotificationTestHelper(mContext, mDependency);
mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager);
mGutsManager.setUpWithPresenter(mPresenter, mStackScroller,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
index 49a6410..d280f18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
@@ -44,7 +44,7 @@
@Before
public void setUp() throws Exception {
com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
- mRow = new NotificationTestHelper(mContext).createRow();
+ mRow = new NotificationTestHelper(mContext, mDependency).createRow();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
index 4f844f0..4f45f68 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
@@ -94,7 +94,7 @@
mNotif = builder.build();
assertTrue(mNotif.hasMediaSession());
- mRow = new NotificationTestHelper(mContext).createRow(mNotif);
+ mRow = new NotificationTestHelper(mContext, mDependency).createRow(mNotif);
RemoteViews views = new RemoteViews(mContext.getPackageName(),
com.android.internal.R.layout.notification_template_material_big_media);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
index 5463159..14e2fde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
@@ -50,7 +50,7 @@
public void setup() throws Exception {
Assert.sMainLooper = TestableLooper.get(this).getLooper();
mView = mock(View.class);
- mRow = new NotificationTestHelper(getContext()).createRow();
+ mRow = new NotificationTestHelper(getContext(), mDependency).createRow();
mNotificationViewWrapper = new TestableNotificationViewWrapper(mContext, mView, mRow);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 22d2585..ddd2884e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -45,7 +45,7 @@
@Before
public void setUp() throws Exception {
com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
- mNotificationTestHelper = new NotificationTestHelper(mContext);
+ mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
mGroup = mNotificationTestHelper.createGroup();
mChildrenContainer = mGroup.getChildrenContainer();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index 3f467ea..34a309f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -70,7 +70,7 @@
mBypassController,
new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext));
com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
- NotificationTestHelper testHelper = new NotificationTestHelper(getContext());
+ NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency);
mFirst = testHelper.createRow();
mFirst.setHeadsUpAnimatingAwayListener(animatingAway
-> mRoundnessManager.onHeadsupAnimatingAwayChanged(mFirst, animatingAway));
@@ -150,7 +150,8 @@
createSection(mFirst, mSecond),
createSection(null, null)
});
- ExpandableNotificationRow row = new NotificationTestHelper(getContext()).createRow();
+ ExpandableNotificationRow row = new NotificationTestHelper(getContext(), mDependency)
+ .createRow();
NotificationEntry entry = mock(NotificationEntry.class);
when(entry.getRow()).thenReturn(row);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 11ae0cc..4b82f59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -56,6 +56,7 @@
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.EmptyShadeView;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShelf;
@@ -70,6 +71,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.FooterView;
import com.android.systemui.statusbar.notification.row.NotificationBlockingHelperManager;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -167,6 +169,8 @@
mHeadsUpManager,
mKeyguardBypassController,
new FalsingManagerFake(),
+ mock(NotificationLockscreenUserManager.class),
+ mock(NotificationGutsManager.class),
new NotificationSectionsFeatureManager(new DeviceConfigProxyFake(), mContext));
mStackScroller = spy(mStackScrollerInternal);
mStackScroller.setShelf(notificationShelf);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoHideControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoHideControllerTest.java
index f614354..16f02d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoHideControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoHideControllerTest.java
@@ -31,6 +31,7 @@
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
+import android.view.IWindowManager;
import android.view.View;
import androidx.test.filters.SmallTest;
@@ -38,6 +39,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
import org.junit.After;
import org.junit.Before;
@@ -58,7 +60,8 @@
public void setUp() {
mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
mAutoHideController =
- spy(new AutoHideController(mContext, Dependency.get(Dependency.MAIN_HANDLER)));
+ spy(new AutoHideController(mContext, Dependency.get(Dependency.MAIN_HANDLER),
+ mock(NotificationRemoteInputManager.class), mock(IWindowManager.class)));
mAutoHideController.mDisplayId = DEFAULT_DISPLAY;
mAutoHideController.mSystemUiVisibility = View.VISIBLE;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index ff9aae7..72bea56 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -76,6 +76,8 @@
private Handler mHandler;
@Mock
private KeyguardBypassController mKeyguardBypassController;
+ @Mock
+ private DozeParameters mDozeParameters;
private BiometricUnlockController mBiometricUnlockController;
@Before
@@ -92,7 +94,8 @@
mStatusBarWindowController);
mBiometricUnlockController = new BiometricUnlockController(mContext, mDozeScrimController,
mKeyguardViewMediator, mScrimController, mStatusBar, mKeyguardStateController,
- mHandler, mUpdateMonitor, 0 /* wakeUpDelay */, mKeyguardBypassController);
+ mHandler, mUpdateMonitor, 0 /* wakeUpDelay */, mKeyguardBypassController,
+ mDozeParameters);
mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DoubleTapHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DoubleTapHelperTest.java
new file mode 100644
index 0000000..df1233a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DoubleTapHelperTest.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyLong;
+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 android.content.res.Resources;
+import android.os.SystemClock;
+import android.testing.AndroidTestingRunner;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class DoubleTapHelperTest extends SysuiTestCase {
+
+ private DoubleTapHelper mDoubleTapHelper;
+ private int mTouchSlop;
+ private int mDoubleTouchSlop;
+ @Mock private View mView;
+ @Mock private DoubleTapHelper.ActivationListener mActivationListener;
+ @Mock private DoubleTapHelper.DoubleTapListener mDoubleTapListener;
+ @Mock private DoubleTapHelper.SlideBackListener mSlideBackListener;
+ @Mock private DoubleTapHelper.DoubleTapLogListener mDoubleTapLogListener;
+ @Mock private Resources mResources;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+ // The double tap slop has to be less than the regular slop, otherwise it has no effect.
+ mDoubleTouchSlop = mTouchSlop - 1;
+ when(mView.getContext()).thenReturn(mContext);
+ when(mView.getResources()).thenReturn(mResources);
+ when(mResources.getDimension(R.dimen.double_tap_slop))
+ .thenReturn((float) mDoubleTouchSlop);
+
+ mDoubleTapHelper = new DoubleTapHelper(mView,
+ mActivationListener,
+ mDoubleTapListener,
+ mSlideBackListener, mDoubleTapLogListener);
+ }
+
+ @Test
+ public void testDoubleTap_success() {
+ long downtimeA = SystemClock.uptimeMillis();
+ long downtimeB = downtimeA + 100;
+
+ MotionEvent evDownA = MotionEvent.obtain(downtimeA,
+ downtimeA,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpA = MotionEvent.obtain(downtimeA,
+ downtimeA + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+ MotionEvent evDownB = MotionEvent.obtain(downtimeB,
+ downtimeB,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpB = MotionEvent.obtain(downtimeB,
+ downtimeB + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+
+ mDoubleTapHelper.onTouchEvent(evDownA);
+ mDoubleTapHelper.onTouchEvent(evUpA);
+ verify(mActivationListener).onActiveChanged(true);
+ verify(mView).postDelayed(any(Runnable.class), anyLong());
+ verify(mDoubleTapLogListener, never()).onDoubleTapLog(anyBoolean(), anyFloat(), anyFloat());
+ verify(mDoubleTapListener, never()).onDoubleTap();
+
+ mDoubleTapHelper.onTouchEvent(evDownB);
+ mDoubleTapHelper.onTouchEvent(evUpB);
+ verify(mDoubleTapLogListener).onDoubleTapLog(true, 0, 0);
+ verify(mDoubleTapListener).onDoubleTap();
+
+ evDownA.recycle();
+ evUpA.recycle();
+ evDownB.recycle();
+ evUpB.recycle();
+ }
+
+ @Test
+ public void testSingleTap_timeout() {
+ long downtimeA = SystemClock.uptimeMillis();
+
+ MotionEvent evDownA = MotionEvent.obtain(downtimeA,
+ downtimeA,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpA = MotionEvent.obtain(downtimeA,
+ downtimeA + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ mDoubleTapHelper.onTouchEvent(evDownA);
+ mDoubleTapHelper.onTouchEvent(evUpA);
+ verify(mActivationListener).onActiveChanged(true);
+ verify(mView).postDelayed(runnableCaptor.capture(), anyLong());
+ runnableCaptor.getValue().run();
+ verify(mActivationListener).onActiveChanged(true);
+
+ evDownA.recycle();
+ evUpA.recycle();
+ }
+
+ @Test
+ public void testSingleTap_slop() {
+ long downtimeA = SystemClock.uptimeMillis();
+
+ MotionEvent evDownA = MotionEvent.obtain(downtimeA,
+ downtimeA,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpA = MotionEvent.obtain(downtimeA,
+ downtimeA + 1,
+ MotionEvent.ACTION_UP,
+ 1 + mTouchSlop,
+ 1,
+ 0);
+
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ mDoubleTapHelper.onTouchEvent(evDownA);
+ mDoubleTapHelper.onTouchEvent(evUpA);
+ verify(mActivationListener, never()).onActiveChanged(true);
+ verify(mDoubleTapListener, never()).onDoubleTap();
+
+ evDownA.recycle();
+ evUpA.recycle();
+ }
+
+ @Test
+ public void testDoubleTap_slop() {
+ long downtimeA = SystemClock.uptimeMillis();
+ long downtimeB = downtimeA + 100;
+
+ MotionEvent evDownA = MotionEvent.obtain(downtimeA,
+ downtimeA,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpA = MotionEvent.obtain(downtimeA,
+ downtimeA + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+ MotionEvent evDownB = MotionEvent.obtain(downtimeB,
+ downtimeB,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpB = MotionEvent.obtain(downtimeB,
+ downtimeB + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1 + mDoubleTouchSlop,
+ 0);
+
+ mDoubleTapHelper.onTouchEvent(evDownA);
+ mDoubleTapHelper.onTouchEvent(evUpA);
+ verify(mActivationListener).onActiveChanged(true);
+ verify(mView).postDelayed(any(Runnable.class), anyLong());
+
+ mDoubleTapHelper.onTouchEvent(evDownB);
+ mDoubleTapHelper.onTouchEvent(evUpB);
+ verify(mDoubleTapLogListener).onDoubleTapLog(false, 0, mDoubleTouchSlop);
+ verify(mActivationListener).onActiveChanged(false);
+ verify(mDoubleTapListener, never()).onDoubleTap();
+
+ evDownA.recycle();
+ evUpA.recycle();
+ evDownB.recycle();
+ evUpB.recycle();
+ }
+
+ @Test
+ public void testSlideBack() {
+ long downtimeA = SystemClock.uptimeMillis();
+ long downtimeB = downtimeA + 100;
+
+ MotionEvent evDownA = MotionEvent.obtain(downtimeA,
+ downtimeA,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpA = MotionEvent.obtain(downtimeA,
+ downtimeA + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+ MotionEvent evDownB = MotionEvent.obtain(downtimeB,
+ downtimeB,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpB = MotionEvent.obtain(downtimeB,
+ downtimeB + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+
+ when(mSlideBackListener.onSlideBack()).thenReturn(true);
+
+ mDoubleTapHelper.onTouchEvent(evDownA);
+ mDoubleTapHelper.onTouchEvent(evUpA);
+ verify(mActivationListener, never()).onActiveChanged(true);
+ verify(mActivationListener, never()).onActiveChanged(false);
+ verify(mDoubleTapListener, never()).onDoubleTap();
+ mDoubleTapHelper.onTouchEvent(evDownB);
+ mDoubleTapHelper.onTouchEvent(evUpB);
+ verify(mActivationListener, never()).onActiveChanged(true);
+ verify(mActivationListener, never()).onActiveChanged(false);
+ verify(mDoubleTapListener, never()).onDoubleTap();
+
+ evDownA.recycle();
+ evUpA.recycle();
+ evDownB.recycle();
+ evUpB.recycle();
+ }
+
+
+ @Test
+ public void testMoreThanTwoTaps() {
+ long downtimeA = SystemClock.uptimeMillis();
+ long downtimeB = downtimeA + 100;
+ long downtimeC = downtimeB + 100;
+ long downtimeD = downtimeC + 100;
+
+ MotionEvent evDownA = MotionEvent.obtain(downtimeA,
+ downtimeA,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpA = MotionEvent.obtain(downtimeA,
+ downtimeA + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+ MotionEvent evDownB = MotionEvent.obtain(downtimeB,
+ downtimeB,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpB = MotionEvent.obtain(downtimeB,
+ downtimeB + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+ MotionEvent evDownC = MotionEvent.obtain(downtimeC,
+ downtimeC,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpC = MotionEvent.obtain(downtimeC,
+ downtimeC + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+ MotionEvent evDownD = MotionEvent.obtain(downtimeD,
+ downtimeD,
+ MotionEvent.ACTION_DOWN,
+ 1,
+ 1,
+ 0);
+ MotionEvent evUpD = MotionEvent.obtain(downtimeD,
+ downtimeD + 1,
+ MotionEvent.ACTION_UP,
+ 1,
+ 1,
+ 0);
+
+ mDoubleTapHelper.onTouchEvent(evDownA);
+ mDoubleTapHelper.onTouchEvent(evUpA);
+ verify(mActivationListener).onActiveChanged(true);
+ verify(mView).postDelayed(any(Runnable.class), anyLong());
+ verify(mDoubleTapLogListener, never()).onDoubleTapLog(anyBoolean(), anyFloat(), anyFloat());
+ verify(mDoubleTapListener, never()).onDoubleTap();
+
+ mDoubleTapHelper.onTouchEvent(evDownB);
+ mDoubleTapHelper.onTouchEvent(evUpB);
+ verify(mDoubleTapLogListener).onDoubleTapLog(true, 0, 0);
+ verify(mDoubleTapListener).onDoubleTap();
+
+ reset(mView);
+ reset(mActivationListener);
+ reset(mDoubleTapLogListener);
+ reset(mDoubleTapListener);
+
+ mDoubleTapHelper.onTouchEvent(evDownC);
+ mDoubleTapHelper.onTouchEvent(evUpC);
+ verify(mActivationListener).onActiveChanged(true);
+ verify(mView).postDelayed(any(Runnable.class), anyLong());
+ verify(mDoubleTapLogListener, never()).onDoubleTapLog(anyBoolean(), anyFloat(), anyFloat());
+ verify(mDoubleTapListener, never()).onDoubleTap();
+
+ mDoubleTapHelper.onTouchEvent(evDownD);
+ mDoubleTapHelper.onTouchEvent(evUpD);
+ verify(mDoubleTapLogListener).onDoubleTapLog(true, 0, 0);
+ verify(mDoubleTapListener).onDoubleTap();
+
+ evDownA.recycle();
+ evUpA.recycle();
+ evDownB.recycle();
+ evUpB.recycle();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 60050b1..debc840 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -17,68 +17,73 @@
package com.android.systemui.statusbar.phone;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
-import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.AmbientDisplayConfiguration;
import android.os.PowerManager;
import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.doze.DozeScreenState;
+import com.android.systemui.tuner.TunerService;
import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DozeParametersTest extends SysuiTestCase {
+ private DozeParameters mDozeParameters;
+
+ @Mock Resources mResources;
+ @Mock private AmbientDisplayConfiguration mAmbientDisplayConfiguration;
+ @Mock private AlwaysOnDisplayPolicy mAlwaysOnDisplayPolicy;
+ @Mock private PowerManager mPowerManager;
+ @Mock private TunerService mTunerService;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mDozeParameters = new DozeParameters(
+ mResources,
+ mAmbientDisplayConfiguration,
+ mAlwaysOnDisplayPolicy,
+ mPowerManager,
+ mTunerService
+ );
+ }
@Test
public void test_setControlScreenOffAnimation_setsDozeAfterScreenOff_false() {
- TestableDozeParameters dozeParameters = new TestableDozeParameters(getContext());
- PowerManager mockedPowerManager = dozeParameters.getPowerManager();
- dozeParameters.setControlScreenOffAnimation(true);
- reset(mockedPowerManager);
- dozeParameters.setControlScreenOffAnimation(false);
- verify(mockedPowerManager).setDozeAfterScreenOff(eq(true));
+ mDozeParameters.setControlScreenOffAnimation(true);
+ reset(mPowerManager);
+ mDozeParameters.setControlScreenOffAnimation(false);
+ verify(mPowerManager).setDozeAfterScreenOff(eq(true));
}
@Test
public void test_setControlScreenOffAnimation_setsDozeAfterScreenOff_true() {
- TestableDozeParameters dozeParameters = new TestableDozeParameters(getContext());
- PowerManager mockedPowerManager = dozeParameters.getPowerManager();
- dozeParameters.setControlScreenOffAnimation(false);
- reset(mockedPowerManager);
- dozeParameters.setControlScreenOffAnimation(true);
- verify(dozeParameters.getPowerManager()).setDozeAfterScreenOff(eq(false));
+ mDozeParameters.setControlScreenOffAnimation(false);
+ reset(mPowerManager);
+ mDozeParameters.setControlScreenOffAnimation(true);
+ verify(mPowerManager).setDozeAfterScreenOff(eq(false));
}
@Test
public void test_getWallpaperAodDuration_when_shouldControlScreenOff() {
- TestableDozeParameters dozeParameters = new TestableDozeParameters(getContext());
- dozeParameters.setControlScreenOffAnimation(true);
- Assert.assertEquals("wallpaper hides faster when controlling screen off",
- dozeParameters.getWallpaperAodDuration(),
+ mDozeParameters.setControlScreenOffAnimation(true);
+ Assert.assertEquals(
+ "wallpaper hides faster when controlling screen off",
+ mDozeParameters.getWallpaperAodDuration(),
DozeScreenState.ENTER_DOZE_HIDE_WALLPAPER_DELAY);
}
-
- private class TestableDozeParameters extends DozeParameters {
- private PowerManager mPowerManager;
-
- TestableDozeParameters(Context context) {
- super(context);
- mPowerManager = mock(PowerManager.class);
- }
-
- @Override
- protected PowerManager getPowerManager() {
- return mPowerManager;
- }
- }
-
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index a38094d..0216d2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -38,6 +38,7 @@
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Assert;
import org.junit.Before;
@@ -61,11 +62,12 @@
private StatusBarStateController mStatusbarStateController;
private KeyguardBypassController mBypassController;
private NotificationWakeUpCoordinator mWakeUpCoordinator;
+ private KeyguardStateController mKeyguardStateController;
@Before
public void setUp() throws Exception {
com.android.systemui.util.Assert.sMainLooper = TestableLooper.get(this).getLooper();
- NotificationTestHelper testHelper = new NotificationTestHelper(getContext());
+ NotificationTestHelper testHelper = new NotificationTestHelper(getContext(), mDependency);
mFirst = testHelper.createRow();
mDependency.injectTestDependency(DarkIconDispatcher.class, mDarkIconDispatcher);
mHeadsUpStatusBarView = new HeadsUpStatusBarView(mContext, mock(View.class),
@@ -75,12 +77,14 @@
mStatusbarStateController = mock(StatusBarStateController.class);
mBypassController = mock(KeyguardBypassController.class);
mWakeUpCoordinator = mock(NotificationWakeUpCoordinator.class);
+ mKeyguardStateController = mock(KeyguardStateController.class);
mHeadsUpAppearanceController = new HeadsUpAppearanceController(
mock(NotificationIconAreaController.class),
mHeadsUpManager,
mStatusbarStateController,
mBypassController,
mWakeUpCoordinator,
+ mKeyguardStateController,
mHeadsUpStatusBarView,
mStackScroller,
mPanelView,
@@ -158,6 +162,7 @@
mStatusbarStateController,
mBypassController,
mWakeUpCoordinator,
+ mKeyguardStateController,
mHeadsUpStatusBarView,
mStackScroller,
mPanelView,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index ef9665a..6fcf550 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -29,6 +29,7 @@
import androidx.test.filters.SmallTest;
+import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.AlertingNotificationManagerTest;
@@ -36,6 +37,7 @@
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import org.junit.Before;
import org.junit.Rule;
@@ -85,6 +87,9 @@
when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
.thenReturn(TEST_AUTO_DISMISS_TIME);
when(mVSManager.isReorderingAllowed()).thenReturn(true);
+ mDependency.injectMockDependency(BubbleController.class);
+ mDependency.injectMockDependency(StatusBarWindowController.class);
+ mDependency.injectMockDependency(ConfigurationController.class);
mHeadsUpManager = new TestableHeadsUpManagerPhone(mContext, mStatusBarWindowView,
mGroupManager, mBar, mVSManager, mStatusBarStateController, mBypassController);
super.setUp();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
index 688a6fb..2b091f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
@@ -1,14 +1,11 @@
package com.android.systemui.statusbar.phone
-import androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.LayoutInflater
-
+import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.KeyguardIndicationController
-
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -17,7 +14,7 @@
@SmallTest
@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
class KeyguardBottomAreaTest : SysuiTestCase() {
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index cb87d7d..67b8e07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -100,6 +100,7 @@
MockitoAnnotations.initMocks(this);
mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor);
mDependency.injectTestDependency(KeyguardSecurityModel.class, mKeyguardSecurityModel);
+ mDependency.injectMockDependency(KeyguardStateController.class);
when(mKeyguardSecurityModel.getSecurityMode(anyInt()))
.thenReturn(KeyguardSecurityModel.SecurityMode.None);
DejankUtils.setImmediate(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
index 93fdce1..b1580ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarTransitionsControllerTest.java
@@ -28,8 +28,10 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.LightBarTransitionsController.DarkIntensityApplier;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Test;
@@ -50,6 +52,8 @@
public void setup() {
MockitoAnnotations.initMocks(this);
mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
+ mDependency.injectMockDependency(KeyguardStateController.class);
+ mDependency.injectMockDependency(StatusBarStateController.class);
mLightBarTransitionsController = new LightBarTransitionsController(mContext, mApplier);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java
index 81e8abf..098a69f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarButtonTest.java
@@ -35,7 +35,10 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.SysuiTestableContext;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.After;
import org.junit.Before;
@@ -63,6 +66,9 @@
(SysuiTestableContext) mContext.createDisplayContext(display);
context.putComponent(CommandQueue.class, mock(CommandQueue.class));
+ mDependency.injectMockDependency(AssistManager.class);
+ mDependency.injectMockDependency(OverviewProxyService.class);
+ mDependency.injectMockDependency(KeyguardStateController.class);
mNavBar = new NavigationBarView(context, null);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
index be69f5f..6433376 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java
@@ -33,6 +33,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.assist.AssistManager;
import com.android.systemui.statusbar.policy.KeyButtonDrawable;
import org.junit.Before;
@@ -60,6 +61,8 @@
@Before
public void setup() {
+ mDependency.injectMockDependency(AssistManager.class);
+
mGroup = new ContextualButtonGroup(GROUP_ID);
mBtn0 = new ContextualButton(BUTTON_0_ID, ICON_RES_ID);
mBtn1 = new ContextualButton(BUTTON_1_ID, ICON_RES_ID);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarInflaterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarInflaterViewTest.java
index cec7feb..991e495 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarInflaterViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarInflaterViewTest.java
@@ -31,6 +31,8 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.CommandQueue;
import org.junit.After;
@@ -51,6 +53,9 @@
@Before
public void setUp() {
mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
+ mDependency.injectMockDependency(AssistManager.class);
+ mDependency.injectMockDependency(OverviewProxyService.class);
+ mDependency.injectMockDependency(NavigationModeController.class);
mNavBarInflaterView = spy(new NavigationBarInflaterView(mContext, null));
doNothing().when(mNavBarInflaterView).createInflaters();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
index bb109bd..1e9378a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
@@ -28,7 +28,11 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.assist.AssistManager;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Test;
@@ -44,6 +48,12 @@
@Before
public void setup() {
mDependency.injectMockDependency(IWindowManager.class);
+ mDependency.injectMockDependency(AssistManager.class);
+ mDependency.injectMockDependency(OverviewProxyService.class);
+ mDependency.injectMockDependency(NavigationModeController.class);
+ mDependency.injectMockDependency(StatusBarStateController.class);
+ mDependency.injectMockDependency(KeyguardStateController.class);
+
mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
NavigationBarView navBar = spy(new NavigationBarView(mContext, null));
when(navBar.getCurrentView()).thenReturn(navBar);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
index a0d264d..2254234 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
@@ -33,6 +33,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -71,6 +72,7 @@
@Before
public void setup() {
+ mDependency.injectMockDependency(BubbleController.class);
mHeadsUpManager = new HeadsUpManager(mContext) {};
when(mNotificationEntryManager.getPendingNotificationsIterator())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
index dd274c7..493b74d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupManagerTest.java
@@ -30,6 +30,7 @@
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -57,6 +58,7 @@
@Before
public void setup() {
+ mDependency.injectMockDependency(BubbleController.class);
initializeGroupManager();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 98485b3..cff6635 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -56,9 +56,11 @@
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotificationData;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.util.InjectionInflationController;
@@ -114,6 +116,7 @@
private FalsingManager mFalsingManager;
@Mock
private KeyguardBypassController mKeyguardBypassController;
+ @Mock private DozeParameters mDozeParameters;
private NotificationPanelView mNotificationPanelView;
@Before
@@ -128,10 +131,11 @@
mDependency.injectMockDependency(ConfigurationController.class);
mDependency.injectMockDependency(ZenModeController.class);
NotificationWakeUpCoordinator coordinator =
- new NotificationWakeUpCoordinator(mContext,
+ new NotificationWakeUpCoordinator(
mock(HeadsUpManagerPhone.class),
new StatusBarStateControllerImpl(),
- mKeyguardBypassController);
+ mKeyguardBypassController,
+ mDozeParameters);
PulseExpansionHandler expansionHandler = new PulseExpansionHandler(
mContext,
coordinator,
@@ -140,7 +144,7 @@
mStatusBarStateController,
new FalsingManagerFake());
mNotificationPanelView = new TestableNotificationPanelView(coordinator, expansionHandler,
- mKeyguardBypassController);
+ mKeyguardBypassController, mStatusBarStateController);
mNotificationPanelView.setHeadsUpManager(mHeadsUpManager);
mNotificationPanelView.setBar(mPanelBar);
@@ -217,7 +221,8 @@
private class TestableNotificationPanelView extends NotificationPanelView {
TestableNotificationPanelView(NotificationWakeUpCoordinator coordinator,
PulseExpansionHandler expansionHandler,
- KeyguardBypassController bypassController) {
+ KeyguardBypassController bypassController,
+ SysuiStatusBarStateController statusBarStateController) {
super(
NotificationPanelViewTest.this.mContext,
null,
@@ -232,8 +237,12 @@
mock(ShadeController.class),
mock(NotificationLockscreenUserManager.class),
new NotificationEntryManager(new NotificationData(mock(
- NotificationSectionsFeatureManager.class))),
- mock(DozeLog.class));
+ NotificationSectionsFeatureManager.class), mock(NotifLog.class)),
+ mock(NotifLog.class)),
+ mock(KeyguardStateController.class),
+ statusBarStateController,
+ mock(DozeLog.class),
+ mDozeParameters);
mNotificationStackScroller = mNotificationStackScrollLayout;
mKeyguardStatusView = NotificationPanelViewTest.this.mKeyguardStatusView;
mKeyguardStatusBar = NotificationPanelViewTest.this.mKeyguardStatusBar;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 214e26a..f3ff7ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -133,6 +133,20 @@
}
@Test
+ public void transitionToOff() {
+ mScrimController.transitionTo(ScrimState.OFF);
+ mScrimController.finishAnimationsImmediately();
+
+ assertScrimAlpha(OPAQUE /* front */,
+ SEMI_TRANSPARENT /* back */,
+ TRANSPARENT /* bubble */);
+
+ assertScrimTint(true /* front */,
+ true /* behind */,
+ false /* bubble */);
+ }
+
+ @Test
public void transitionToAod_withRegularWallpaper() {
mScrimController.transitionTo(ScrimState.AOD);
mScrimController.finishAnimationsImmediately();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index aafcdd0..3e07cff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -45,6 +45,7 @@
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -92,6 +93,7 @@
MockitoAnnotations.initMocks(this);
mDependency.injectMockDependency(StatusBarWindowController.class);
mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
+ mDependency.injectMockDependency(NotificationMediaManager.class);
mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController);
mDependency.injectTestDependency(KeyguardStateController.class, mKeyguardStateController);
when(mLockIconContainer.getParent()).thenReturn(mock(ViewGroup.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 266abcf..d8a68b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -140,7 +140,7 @@
when(mContentIntent.getCreatorUserHandle()).thenReturn(UserHandle.of(1));
when(mContentIntent.getIntent()).thenReturn(mContentIntentInner);
- mNotificationTestHelper = new NotificationTestHelper(mContext);
+ mNotificationTestHelper = new NotificationTestHelper(mContext, mDependency);
// Create standard notification with contentIntent
mNotificationRow = mNotificationTestHelper.createRow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 24ec109..210c9d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -35,21 +35,37 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.logging.testing.FakeMetricsLogger;
+import com.android.systemui.InitController;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationViewHierarchyManager;
+import com.android.systemui.statusbar.RemoteInputController;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationAlertingManager;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@RunWithLooper()
@@ -63,11 +79,33 @@
@Before
public void setup() {
+ NotificationRemoteInputManager notificationRemoteInputManager =
+ mock(NotificationRemoteInputManager.class);
+ when(notificationRemoteInputManager.getController())
+ .thenReturn(mock(RemoteInputController.class));
mMetricsLogger = new FakeMetricsLogger();
mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
mCommandQueue = new CommandQueue(mContext);
mContext.putComponent(CommandQueue.class, mCommandQueue);
+ mDependency.injectTestDependency(StatusBarStateController.class,
+ mock(SysuiStatusBarStateController.class));
mDependency.injectTestDependency(ShadeController.class, mShadeController);
+ mDependency.injectTestDependency(NotificationRemoteInputManager.class,
+ notificationRemoteInputManager);
+ mDependency.injectMockDependency(NotificationViewHierarchyManager.class);
+ mDependency.injectMockDependency(NotificationRemoteInputManager.Callback.class);
+ mDependency.injectMockDependency(NotificationLockscreenUserManager.class);
+ mDependency.injectMockDependency(NotificationInterruptionStateProvider.class);
+ mDependency.injectMockDependency(NotificationMediaManager.class);
+ mDependency.injectMockDependency(VisualStabilityManager.class);
+ mDependency.injectMockDependency(NotificationGutsManager.class);
+ mDependency.injectMockDependency(StatusBarWindowController.class);
+ mDependency.injectMockDependency(InitController.class);
+ NotificationData notificationData = mock(NotificationData.class);
+ when(notificationData.getNotificationsForCurrentUser()).thenReturn(new ArrayList<>());
+ NotificationEntryManager entryManager =
+ mDependency.injectMockDependency(NotificationEntryManager.class);
+ when(entryManager.getNotificationData()).thenReturn(notificationData);
StatusBarWindowView statusBarWindowView = mock(StatusBarWindowView.class);
when(statusBarWindowView.getResources()).thenReturn(mContext.getResources());
@@ -77,7 +115,7 @@
mock(DozeScrimController.class), mock(ScrimController.class),
mock(ActivityLaunchAnimator.class), mock(DynamicPrivacyController.class),
mock(NotificationAlertingManager.class),
- mock(NotificationRowBinderImpl.class));
+ mock(NotificationRowBinderImpl.class), mock(KeyguardStateController.class));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index 4b6ca56..a65f5a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -24,18 +24,19 @@
import static org.mockito.internal.verification.VerificationModeFactory.times;
import android.content.Intent;
-import android.os.UserManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationPresenter;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Test;
@@ -47,14 +48,13 @@
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
- @Mock private NotificationPresenter mPresenter;
- @Mock private UserManager mUserManager;
-
- // Dependency mocks:
@Mock private NotificationEntryManager mEntryManager;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@Mock private ShadeController mShadeController;
@Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
+ @Mock private KeyguardStateController mKeyguardStateController;
+ @Mock private SysuiStatusBarStateController mStatusBarStateController;
+ @Mock private ActivityStarter mActivityStarter;
private int mCurrentUserId = 0;
private StatusBarRemoteInputCallback mRemoteInputCallback;
@@ -71,7 +71,9 @@
mContext.putComponent(CommandQueue.class, mock(CommandQueue.class));
mRemoteInputCallback = spy(new StatusBarRemoteInputCallback(mContext,
- mock(NotificationGroupManager.class)));
+ mock(NotificationGroupManager.class), mNotificationLockscreenUserManager,
+ mKeyguardStateController, mStatusBarStateController, mActivityStarter,
+ mShadeController));
mRemoteInputCallback.mChallengeReceiver = mRemoteInputCallback.new ChallengeReceiver();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 914717c..8f1b6017 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -117,6 +117,7 @@
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationData;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -218,6 +219,8 @@
@Mock private NotificationIconAreaController mNotificationIconAreaController;
@Mock private StatusBarWindowViewController.Builder mStatusBarWindowViewControllerBuilder;
@Mock private StatusBarWindowViewController mStatusBarWindowViewController;
+ @Mock private NotifLog mNotifLog;
+ @Mock private DozeParameters mDozeParameters;
@Before
public void setup() throws Exception {
@@ -284,6 +287,7 @@
.thenReturn(mStatusBarWindowViewController);
mStatusBar = new StatusBar(
+ mContext,
mLightBarController,
mAutoHideController,
mKeyguardUpdateMonitor,
@@ -339,10 +343,11 @@
mNotificationListener,
configurationController,
mStatusBarWindowController,
- mStatusBarWindowViewControllerBuilder);
+ mStatusBarWindowViewControllerBuilder,
+ mNotifLog,
+ mDozeParameters);
// TODO: we should be able to call mStatusBar.start() and have all the below values
// initialized automatically.
- mStatusBar.mContext = mContext;
mStatusBar.mComponents = mContext.getComponents();
mStatusBar.mStatusBarKeyguardViewManager = mStatusBarKeyguardViewManager;
mStatusBar.mStatusBarWindow = mStatusBarWindowView;
@@ -873,7 +878,7 @@
public static class TestableNotificationEntryManager extends NotificationEntryManager {
public TestableNotificationEntryManager(NotificationData notificationData) {
- super(notificationData);
+ super(notificationData, mock(NotifLog.class));
}
public void setUpForTest(NotificationPresenter presenter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
index 4ffaeae..a21a658 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowControllerTest.java
@@ -32,7 +32,9 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.colorextraction.ColorExtractor;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -48,20 +50,15 @@
@SmallTest
public class StatusBarWindowControllerTest extends SysuiTestCase {
- @Mock
- private WindowManager mWindowManager;
- @Mock
- private DozeParameters mDozeParameters;
- @Mock
- private ViewGroup mStatusBarView;
- @Mock
- private IActivityManager mActivityManager;
- @Mock
- private SysuiStatusBarStateController mStatusBarStateController;
- @Mock
- private ConfigurationController mConfigurationController;
- @Mock
- private KeyguardBypassController mKeyguardBypassController;
+ @Mock private WindowManager mWindowManager;
+ @Mock private DozeParameters mDozeParameters;
+ @Mock private ViewGroup mStatusBarView;
+ @Mock private IActivityManager mActivityManager;
+ @Mock private SysuiStatusBarStateController mStatusBarStateController;
+ @Mock private ConfigurationController mConfigurationController;
+ @Mock private KeyguardBypassController mKeyguardBypassController;
+ @Mock private SysuiColorExtractor mColorExtractor;
+ @Mock ColorExtractor.GradientColors mGradientColors;
private StatusBarWindowController mStatusBarWindowController;
@@ -69,10 +66,11 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
when(mDozeParameters.getAlwaysOn()).thenReturn(true);
+ when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
mActivityManager, mDozeParameters, mStatusBarStateController,
- mConfigurationController, mKeyguardBypassController);
+ mConfigurationController, mKeyguardBypassController, mColorExtractor);
mStatusBarWindowController.add(mStatusBarView, 100 /* height */);
}
@@ -96,9 +94,6 @@
@Test
public void testOnThemeChanged_doesntCrash() {
- mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
- mActivityManager, mDozeParameters, mStatusBarStateController,
- mConfigurationController, mKeyguardBypassController);
mStatusBarWindowController.onThemeChanged();
}
@@ -109,9 +104,6 @@
@Test
public void testSetForcePluginOpen_beforeStatusBarInitialization() {
- mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager,
- mActivityManager, mDozeParameters, mStatusBarStateController,
- mConfigurationController, mKeyguardBypassController);
mStatusBarWindowController.setForcePluginOpen(true);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
index 9f4dfb4..7c1dfa6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
@@ -34,9 +34,11 @@
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.InjectionInflationController;
@@ -61,11 +63,14 @@
@Mock private PluginManager mPluginManager;
@Mock private TunerService mTunerService;
@Mock private DragDownHelper mDragDownHelper;
+ @Mock private KeyguardStateController mKeyguardStateController;
+ @Mock private SysuiStatusBarStateController mStatusBarStateController;
@Mock private ShadeController mShadeController;
@Mock private NotificationLockscreenUserManager mNotificationLockScreenUserManager;
@Mock private NotificationEntryManager mNotificationEntryManager;
@Mock private StatusBar mStatusBar;
@Mock private DozeLog mDozeLog;
+ @Mock private DozeParameters mDozeParameters;
@Before
public void setUp() {
@@ -88,7 +93,10 @@
mTunerService,
mNotificationLockScreenUserManager,
mNotificationEntryManager,
- mDozeLog)
+ mKeyguardStateController,
+ mStatusBarStateController,
+ mDozeLog,
+ mDozeParameters)
.setShadeController(mShadeController)
.setStatusBarWindowView(mView)
.build();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java
index d16dc16..943674a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyButtonViewTest.java
@@ -40,6 +40,7 @@
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.recents.OverviewProxyService;
import org.junit.Before;
import org.junit.Test;
@@ -68,6 +69,7 @@
MockitoAnnotations.initMocks(this);
mMetricsLogger = mDependency.injectMockDependency(MetricsLogger.class);
mBubbleController = mDependency.injectMockDependency(BubbleController.class);
+ mDependency.injectMockDependency(OverviewProxyService.class);
TestableLooper.get(this).runWithLooper(() -> {
mKeyButtonView = new KeyButtonView(mContext, null, 0, mInputManager);
});
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
new file mode 100644
index 0000000..e57bbc1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class KeyguardStateControllerTest extends SysuiTestCase {
+
+ @Mock
+ private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @Mock
+ private LockPatternUtils mLockPatternUtils;
+ private KeyguardStateController mKeyguardStateController;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mKeyguardStateController = new KeyguardStateControllerImpl(mContext,
+ mKeyguardUpdateMonitor, mLockPatternUtils);
+ }
+
+ @Test
+ public void testAddCallback_registersListener() {
+ verify(mKeyguardUpdateMonitor).registerCallback(any());
+ }
+
+ @Test
+ public void testIsShowing() {
+ assertThat(mKeyguardStateController.isShowing()).isFalse();
+ mKeyguardStateController.notifyKeyguardState(true /* showing */, false /* occluded */);
+ assertThat(mKeyguardStateController.isShowing()).isTrue();
+ }
+
+ @Test
+ public void testIsMethodSecure() {
+ assertThat(mKeyguardStateController.isMethodSecure()).isFalse();
+
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+ ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+ assertThat(mKeyguardStateController.isMethodSecure()).isTrue();
+ }
+
+ @Test
+ public void testIsOccluded() {
+ assertThat(mKeyguardStateController.isOccluded()).isFalse();
+ mKeyguardStateController.notifyKeyguardState(false /* showing */, true /* occluded */);
+ assertThat(mKeyguardStateController.isOccluded()).isTrue();
+ }
+
+ @Test
+ public void testCanSkipLockScreen() {
+ // Can skip because LockPatternUtils#isSecure is false
+ assertThat(mKeyguardStateController.canDismissLockScreen()).isTrue();
+
+ // Cannot skip after there's a password/pin/pattern
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+ ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+ assertThat(mKeyguardStateController.canDismissLockScreen()).isFalse();
+
+ // Unless user is authenticated
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(anyInt())).thenReturn(true);
+ ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+ assertThat(mKeyguardStateController.canDismissLockScreen()).isTrue();
+ }
+
+ @Test
+ public void testIsUnlocked() {
+ // Is unlocked whenever the keyguard is not showing
+ assertThat(mKeyguardStateController.isShowing()).isFalse();
+ assertThat(mKeyguardStateController.isUnlocked()).isTrue();
+
+ // Unlocked if showing, but insecure
+ mKeyguardStateController.notifyKeyguardState(true /* showing */, false /* occluded */);
+ assertThat(mKeyguardStateController.isUnlocked()).isTrue();
+
+ // Locked if showing, and requires password
+ when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true);
+ ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+ assertThat(mKeyguardStateController.isUnlocked()).isFalse();
+
+ // But unlocked after #getUserCanSkipBouncer allows it
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(anyInt())).thenReturn(true);
+ ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+ assertThat(mKeyguardStateController.isUnlocked()).isTrue();
+ }
+
+ @Test
+ public void testIsTrusted() {
+ assertThat(mKeyguardStateController.isTrusted()).isFalse();
+
+ when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+ ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+
+ assertThat(mKeyguardStateController.isTrusted()).isTrue();
+ }
+
+ @Test
+ public void testCallbacksAreInvoked() {
+ KeyguardStateController.Callback callback = mock(KeyguardStateController.Callback.class);
+ mKeyguardStateController.addCallback(callback);
+
+ when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true);
+ ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */);
+
+ verify(callback).onUnlockedChanged();
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index bc468bf..390e812 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -106,7 +106,8 @@
@Test
public void testSendRemoteInput_intentContainsResultsAndSource() throws Exception {
- ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow();
+ ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency)
+ .createRow();
RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
setTestPendingIntent(view);
@@ -127,7 +128,7 @@
private UserHandle getTargetInputMethodUser(UserHandle fromUser, UserHandle toUser)
throws Exception {
- ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow(
+ ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow(
DUMMY_MESSAGE_APP_PKG,
UserHandle.getUid(fromUser.getIdentifier(), DUMMY_MESSAGE_APP_ID),
toUser);
@@ -169,7 +170,8 @@
@Test
public void testNoCrashWithoutVisibilityListener() throws Exception {
- ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow();
+ ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency)
+ .createRow();
RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
view.setOnVisibilityChangedListener(null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index 0d56cbe..b5e4cb9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -52,6 +52,7 @@
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.statusbar.NotificationEntryBuilder;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -115,6 +116,7 @@
});
mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
mDependency.injectMockDependency(ShadeController.class);
+ mDependency.injectMockDependency(NotificationRemoteInputManager.class);
mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
mDependency.injectTestDependency(SmartReplyConstants.class, mConstants);
diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto
index 5b826b1..b0e401b 100644
--- a/proto/src/metrics_constants/metrics_constants.proto
+++ b/proto/src/metrics_constants/metrics_constants.proto
@@ -2584,15 +2584,15 @@
// ACTION: Logged when trampoline activity finishes.
// TIME: Indicates total time taken by trampoline activity to finish in MS.
- PROVISIONING_TRAMPOLINE_ACTIVITY_TIME_MS = 523;
+ PROVISIONING_TRAMPOLINE_ACTIVITY_TIME_MS = 523 [deprecated=true];
// ACTION: Logged when encryption activity finishes.
// TIME: Indicates total time taken by post encryption activity to finish in MS.
- PROVISIONING_POST_ENCRYPTION_ACTIVITY_TIME_MS = 524;
+ PROVISIONING_POST_ENCRYPTION_ACTIVITY_TIME_MS = 524 [deprecated=true];
// ACTION: Logged when finalization activity finishes.
// TIME: Indicates time taken by finalization activity to finish in MS.
- PROVISIONING_FINALIZATION_ACTIVITY_TIME_MS = 525;
+ PROVISIONING_FINALIZATION_ACTIVITY_TIME_MS = 525 [deprecated=true];
// OPEN: Settings Support > Phone/Chat -> Disclaimer
DIALOG_SUPPORT_DISCLAIMER = 526;
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 514eb77..950fa8d 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -91,7 +91,7 @@
protected final Context mContext;
protected final SystemSupport mSystemSupport;
protected final WindowManagerInternal mWindowManagerService;
- private final GlobalActionPerformer mGlobalActionPerformer;
+ private final SystemActionPerformer mSystemActionPerformer;
private final AccessibilityWindowManager mA11yWindowManager;
private final DisplayManager mDisplayManager;
private final PowerManager mPowerManager;
@@ -213,7 +213,7 @@
AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
Object lock, AccessibilitySecurityPolicy securityPolicy, SystemSupport systemSupport,
WindowManagerInternal windowManagerInternal,
- GlobalActionPerformer globalActionPerfomer,
+ SystemActionPerformer systemActionPerfomer,
AccessibilityWindowManager a11yWindowManager) {
mContext = context;
mWindowManagerService = windowManagerInternal;
@@ -222,7 +222,7 @@
mAccessibilityServiceInfo = accessibilityServiceInfo;
mLock = lock;
mSecurityPolicy = securityPolicy;
- mGlobalActionPerformer = globalActionPerfomer;
+ mSystemActionPerformer = systemActionPerfomer;
mSystemSupport = systemSupport;
mInvocationHandler = new InvocationHandler(mainHandler.getLooper());
mA11yWindowManager = a11yWindowManager;
@@ -760,7 +760,7 @@
return false;
}
}
- return mGlobalActionPerformer.performGlobalAction(action);
+ return mSystemActionPerformer.performSystemAction(action);
}
@Override
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 3b105ad..e909e7a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -16,13 +16,6 @@
package com.android.server.accessibility;
-import static android.accessibilityservice.AccessibilityService.SHOW_MODE_AUTO;
-import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE;
-import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN;
-import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
-import static android.accessibilityservice.AccessibilityService.SHOW_MODE_IGNORE_HARD_KEYBOARD;
-import static android.accessibilityservice.AccessibilityService.SHOW_MODE_MASK;
-
import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -50,8 +43,6 @@
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
-import android.graphics.Point;
-import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManager;
import android.hardware.fingerprint.IFingerprintService;
@@ -119,7 +110,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -136,6 +126,7 @@
*/
public class AccessibilityManagerService extends IAccessibilityManager.Stub
implements AbstractAccessibilityServiceConnection.SystemSupport,
+ AccessibilityUserState.ServiceInfoChangeListener,
AccessibilityWindowManager.AccessibilityEventSender,
AccessibilitySecurityPolicy.AccessibilityUserManager {
@@ -178,12 +169,6 @@
private final SimpleStringSplitter mStringColonSplitter =
new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
- private final Rect mTempRect = new Rect();
-
- private final Rect mTempRect1 = new Rect();
-
- private final Point mTempPoint = new Point();
-
private final PackageManager mPackageManager;
private final PowerManager mPowerManager;
@@ -198,7 +183,7 @@
private final MainHandler mMainHandler;
- private final GlobalActionPerformer mGlobalActionPerformer;
+ private final SystemActionPerformer mSystemActionPerformer;
private MagnificationController mMagnificationController;
@@ -226,7 +211,7 @@
private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients =
new RemoteCallbackList<>();
- private final SparseArray<UserState> mUserStates = new SparseArray<>();
+ private final SparseArray<AccessibilityUserState> mUserStates = new SparseArray<>();
private final UiAutomationManager mUiAutomationManager = new UiAutomationManager(mLock);
@@ -237,7 +222,7 @@
private boolean mIsAccessibilityButtonShown;
- private UserState getCurrentUserStateLocked() {
+ private AccessibilityUserState getCurrentUserStateLocked() {
return getUserStateLocked(mCurrentUserId);
}
@@ -272,7 +257,7 @@
mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this);
mMainHandler = new MainHandler(mContext.getMainLooper());
- mGlobalActionPerformer = new GlobalActionPerformer(mContext, mWindowManagerService);
+ mSystemActionPerformer = new SystemActionPerformer(mContext, mWindowManagerService);
mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler,
mWindowManagerService, this, mSecurityPolicy, this);
mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
@@ -293,6 +278,11 @@
return mIsAccessibilityButtonShown;
}
+ @Override
+ public void onServiceInfoChangedLocked(AccessibilityUserState userState) {
+ scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+ }
+
@Nullable
public FingerprintGestureDispatcher getFingerprintGestureDispatcher() {
return mFingerprintGestureDispatcher;
@@ -307,40 +297,40 @@
}
}
- private UserState getUserState(int userId) {
+ private AccessibilityUserState getUserState(int userId) {
synchronized (mLock) {
return getUserStateLocked(userId);
}
}
- private UserState getUserStateLocked(int userId) {
- UserState state = mUserStates.get(userId);
+ @NonNull
+ private AccessibilityUserState getUserStateLocked(int userId) {
+ AccessibilityUserState state = mUserStates.get(userId);
if (state == null) {
- state = new UserState(userId);
+ state = new AccessibilityUserState(userId, mContext, this);
mUserStates.put(userId, state);
}
return state;
}
boolean getBindInstantServiceAllowed(int userId) {
- final UserState userState = getUserState(userId);
- if (userState == null) return false;
- return userState.getBindInstantServiceAllowed();
+ synchronized (mLock) {
+ final AccessibilityUserState userState = getUserStateLocked(userId);
+ return userState.getBindInstantServiceAllowedLocked();
+ }
}
void setBindInstantServiceAllowed(int userId, boolean allowed) {
- UserState userState;
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
+ "setBindInstantServiceAllowed");
synchronized (mLock) {
- userState = getUserState(userId);
- if (userState == null) {
- if (!allowed) {
- return;
- }
- userState = new UserState(userId);
- mUserStates.put(userId, userState);
+ final AccessibilityUserState userState = getUserStateLocked(userId);
+ if (allowed != userState.getBindInstantServiceAllowedLocked()) {
+ userState.setBindInstantServiceAllowedLocked(allowed);
+ onUserStateChangedLocked(userState);
}
}
- userState.setBindInstantServiceAllowed(allowed);
}
private void registerBroadcastReceivers() {
@@ -354,7 +344,7 @@
return;
}
// We will update when the automation service dies.
- UserState userState = getCurrentUserStateLocked();
+ AccessibilityUserState userState = getCurrentUserStateLocked();
// We have to reload the installed services since some services may
// have different attributes, resolve info (does not support equals),
// etc. Remove them then to force reload.
@@ -376,8 +366,8 @@
if (userId != mCurrentUserId) {
return;
}
- UserState userState = getUserStateLocked(userId);
- boolean reboundAService = userState.mBindingServices.removeIf(
+ AccessibilityUserState userState = getUserStateLocked(userId);
+ boolean reboundAService = userState.getBindingServicesLocked().removeIf(
component -> component != null
&& component.getPackageName().equals(packageName));
if (reboundAService) {
@@ -395,14 +385,14 @@
if (userId != mCurrentUserId) {
return;
}
- UserState userState = getUserStateLocked(userId);
+ AccessibilityUserState userState = getUserStateLocked(userId);
Iterator<ComponentName> it = userState.mEnabledServices.iterator();
while (it.hasNext()) {
ComponentName comp = it.next();
String compPkg = comp.getPackageName();
if (compPkg.equals(packageName)) {
it.remove();
- userState.mBindingServices.remove(comp);
+ userState.getBindingServicesLocked().remove(comp);
// Update the enabled services setting.
persistComponentNamesToSettingLocked(
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
@@ -430,7 +420,7 @@
if (userId != mCurrentUserId) {
return false;
}
- UserState userState = getUserStateLocked(userId);
+ AccessibilityUserState userState = getUserStateLocked(userId);
Iterator<ComponentName> it = userState.mEnabledServices.iterator();
while (it.hasNext()) {
ComponentName comp = it.next();
@@ -441,7 +431,7 @@
return true;
}
it.remove();
- userState.mBindingServices.remove(comp);
+ userState.getBindingServicesLocked().remove(comp);
persistComponentNamesToSettingLocked(
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
userState.mEnabledServices, userId);
@@ -478,7 +468,7 @@
} else if (Intent.ACTION_USER_PRESENT.equals(action)) {
// We will update when the automation service dies.
synchronized (mLock) {
- UserState userState = getCurrentUserStateLocked();
+ AccessibilityUserState userState = getCurrentUserStateLocked();
if (readConfigurationForUserStateLocked(userState)) {
onUserStateChangedLocked(userState);
}
@@ -509,7 +499,7 @@
// If the client is from a process that runs across users such as
// the system UI or the system we add it to the global state that
// is shared across users.
- UserState userState = getUserStateLocked(resolvedUserId);
+ AccessibilityUserState userState = getUserStateLocked(resolvedUserId);
Client client = new Client(callback, Binder.getCallingUid(), userState);
if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) {
mGlobalClients.register(callback, client);
@@ -517,7 +507,7 @@
Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid());
}
return IntPair.of(
- userState.getClientState(),
+ getClientStateLocked(userState),
client.mLastSentRelevantEventTypes);
} else {
userState.mUserClients.register(callback, client);
@@ -529,7 +519,7 @@
+ " and userId:" + mCurrentUserId);
}
return IntPair.of(
- (resolvedUserId == mCurrentUserId) ? userState.getClientState() : 0,
+ (resolvedUserId == mCurrentUserId) ? getClientStateLocked(userState) : 0,
client.mLastSentRelevantEventTypes);
}
}
@@ -644,7 +634,7 @@
.resolveCallingUserIdEnforcingPermissionsLocked(userId);
// The automation service can suppress other services.
- final UserState userState = getUserStateLocked(resolvedUserId);
+ final AccessibilityUserState userState = getUserStateLocked(resolvedUserId);
if (mUiAutomationManager.suppressingAccessibilityServicesLocked()) {
return Collections.emptyList();
}
@@ -730,7 +720,7 @@
synchronized (mLock) {
mUiAutomationManager.registerUiTestAutomationServiceLocked(owner, serviceClient,
mContext, accessibilityServiceInfo, sIdCounter++, mMainHandler,
- mSecurityPolicy, this, mWindowManagerService, mGlobalActionPerformer,
+ mSecurityPolicy, this, mWindowManagerService, mSystemActionPerformer,
mA11yWindowManager, flags);
onUserStateChangedLocked(getCurrentUserStateLocked());
}
@@ -754,15 +744,15 @@
}
synchronized (mLock) {
// Set the temporary state.
- UserState userState = getCurrentUserStateLocked();
+ AccessibilityUserState userState = getCurrentUserStateLocked();
- userState.mIsTouchExplorationEnabled = touchExplorationEnabled;
- userState.mIsDisplayMagnificationEnabled = false;
- userState.mIsNavBarMagnificationEnabled = false;
- userState.mIsAutoclickEnabled = false;
+ userState.setTouchExplorationEnabledLocked(touchExplorationEnabled);
+ userState.setDisplayMagnificationEnabledLocked(false);
+ userState.setNavBarMagnificationEnabledLocked(false);
+ userState.setAutoclickEnabledLocked(false);
userState.mEnabledServices.clear();
userState.mEnabledServices.add(service);
- userState.mBindingServices.clear();
+ userState.getBindingServicesLocked().clear();
userState.mTouchExplorationGrantedServices.clear();
userState.mTouchExplorationGrantedServices.add(service);
@@ -943,7 +933,7 @@
}
// Disconnect from services for the old user.
- UserState oldUserState = getCurrentUserStateLocked();
+ AccessibilityUserState oldUserState = getCurrentUserStateLocked();
oldUserState.onSwitchToAnotherUserLocked();
// Disable the local managers for the old user.
@@ -960,7 +950,7 @@
// The user changed.
mCurrentUserId = userId;
- UserState userState = getCurrentUserStateLocked();
+ AccessibilityUserState userState = getCurrentUserStateLocked();
readConfigurationForUserStateLocked(userState);
// Even if reading did not yield change, we have to update
@@ -979,8 +969,8 @@
private void announceNewUserIfNeeded() {
synchronized (mLock) {
- UserState userState = getCurrentUserStateLocked();
- if (userState.isHandlingAccessibilityEvents()) {
+ AccessibilityUserState userState = getCurrentUserStateLocked();
+ if (userState.isHandlingAccessibilityEventsLocked()) {
UserManager userManager = (UserManager) mContext.getSystemService(
Context.USER_SERVICE);
String message = mContext.getString(R.string.user_switched,
@@ -997,7 +987,7 @@
synchronized (mLock) {
int parentUserId = mSecurityPolicy.resolveProfileParentLocked(userId);
if (parentUserId == mCurrentUserId) {
- UserState userState = getUserStateLocked(mCurrentUserId);
+ AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
onUserStateChangedLocked(userState);
}
}
@@ -1015,7 +1005,7 @@
readComponentNamesFromStringLocked(oldSetting, mTempComponentNameSet, false);
readComponentNamesFromStringLocked(newSetting, mTempComponentNameSet, true);
- UserState userState = getUserStateLocked(UserHandle.USER_SYSTEM);
+ AccessibilityUserState userState = getUserStateLocked(UserHandle.USER_SYSTEM);
userState.mEnabledServices.clear();
userState.mEnabledServices.addAll(mTempComponentNameSet);
persistComponentNamesToSettingLocked(
@@ -1025,6 +1015,10 @@
onUserStateChangedLocked(userState);
}
+ private int getClientStateLocked(AccessibilityUserState userState) {
+ return userState.getClientStateLocked(mUiAutomationManager.isUiAutomationRunningLocked());
+ }
+
private InteractionBridge getInteractionBridge() {
synchronized (mLock) {
if (mInteractionBridge == null) {
@@ -1044,7 +1038,7 @@
// gestures to avoid user frustration when different
// behavior is observed from different combinations of
// enabled accessibility services.
- UserState state = getCurrentUserStateLocked();
+ AccessibilityUserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
AccessibilityServiceConnection service = state.mBoundServices.get(i);
if (service.mRequestTouchExplorationMode && service.mIsDefault == isDefault) {
@@ -1056,7 +1050,7 @@
}
private void notifyClearAccessibilityCacheLocked() {
- UserState state = getCurrentUserStateLocked();
+ AccessibilityUserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
AccessibilityServiceConnection service = state.mBoundServices.get(i);
service.notifyClearAccessibilityNodeInfoCache();
@@ -1065,25 +1059,17 @@
private void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
float scale, float centerX, float centerY) {
- final UserState state = getCurrentUserStateLocked();
+ final AccessibilityUserState state = getCurrentUserStateLocked();
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
final AccessibilityServiceConnection service = state.mBoundServices.get(i);
service.notifyMagnificationChangedLocked(displayId, region, scale, centerX, centerY);
}
}
- private void notifySoftKeyboardShowModeChangedLocked(int showMode) {
- final UserState state = getCurrentUserStateLocked();
- for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final AccessibilityServiceConnection service = state.mBoundServices.get(i);
- service.notifySoftKeyboardShowModeChangedLocked(showMode);
- }
- }
-
private void notifyAccessibilityButtonClickedLocked(int displayId) {
- final UserState state = getCurrentUserStateLocked();
+ final AccessibilityUserState state = getCurrentUserStateLocked();
- int potentialTargets = state.mIsNavBarMagnificationEnabled ? 1 : 0;
+ int potentialTargets = state.isNavBarMagnificationEnabledLocked() ? 1 : 0;
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
final AccessibilityServiceConnection service = state.mBoundServices.get(i);
if (service.mRequestAccessibilityButton) {
@@ -1095,7 +1081,7 @@
return;
}
if (potentialTargets == 1) {
- if (state.mIsNavBarMagnificationEnabled) {
+ if (state.isNavBarMagnificationEnabledLocked()) {
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
displayId));
@@ -1110,13 +1096,13 @@
}
}
} else {
- if (state.mServiceAssignedToAccessibilityButton == null
- && !state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ if (state.getServiceAssignedToAccessibilityButtonLocked() == null
+ && !state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::showAccessibilityButtonTargetSelection, this,
displayId));
- } else if (state.mIsNavBarMagnificationEnabled
- && state.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ } else if (state.isNavBarMagnificationEnabledLocked()
+ && state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
displayId));
@@ -1125,7 +1111,7 @@
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
final AccessibilityServiceConnection service = state.mBoundServices.get(i);
if (service.mRequestAccessibilityButton && (service.mComponentName.equals(
- state.mServiceAssignedToAccessibilityButton))) {
+ state.getServiceAssignedToAccessibilityButtonLocked()))) {
service.notifyAccessibilityButtonClickedLocked(displayId);
return;
}
@@ -1154,7 +1140,7 @@
}
private void notifyAccessibilityButtonVisibilityChangedLocked(boolean available) {
- final UserState state = getCurrentUserStateLocked();
+ final AccessibilityUserState state = getCurrentUserStateLocked();
mIsAccessibilityButtonShown = available;
for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
final AccessibilityServiceConnection clientConnection = state.mBoundServices.get(i);
@@ -1165,7 +1151,7 @@
}
}
- private boolean readInstalledAccessibilityServiceLocked(UserState userState) {
+ private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState) {
mTempAccessibilityServiceInfoList.clear();
int flags = PackageManager.GET_SERVICES
@@ -1174,7 +1160,7 @@
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
- if (userState.getBindInstantServiceAllowed()) {
+ if (userState.getBindInstantServiceAllowedLocked()) {
flags |= PackageManager.MATCH_INSTANT;
}
@@ -1209,7 +1195,7 @@
return false;
}
- private boolean readInstalledAccessibilityShortcutLocked(UserState userState) {
+ private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState) {
final List<AccessibilityShortcutInfo> shortcutInfos = AccessibilityManager
.getInstance(mContext).getInstalledAccessibilityShortcutListAsUser(
mContext, mCurrentUserId);
@@ -1221,7 +1207,7 @@
return false;
}
- private boolean readEnabledAccessibilityServicesLocked(UserState userState) {
+ private boolean readEnabledAccessibilityServicesLocked(AccessibilityUserState userState) {
mTempComponentNameSet.clear();
readComponentNamesFromSettingLocked(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
userState.mUserId, mTempComponentNameSet);
@@ -1236,7 +1222,7 @@
}
private boolean readTouchExplorationGrantedAccessibilityServicesLocked(
- UserState userState) {
+ AccessibilityUserState userState) {
mTempComponentNameSet.clear();
readComponentNamesFromSettingLocked(
Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
@@ -1261,7 +1247,7 @@
private void notifyAccessibilityServicesDelayedLocked(AccessibilityEvent event,
boolean isDefault) {
try {
- UserState state = getCurrentUserStateLocked();
+ AccessibilityUserState state = getCurrentUserStateLocked();
for (int i = 0, count = state.mBoundServices.size(); i < count; i++) {
AccessibilityServiceConnection service = state.mBoundServices.get(i);
@@ -1276,7 +1262,7 @@
}
}
- private void updateRelevantEventsLocked(UserState userState) {
+ private void updateRelevantEventsLocked(AccessibilityUserState userState) {
mMainHandler.post(() -> {
broadcastToClients(userState, ignoreRemoteException(client -> {
int relevantEventTypes;
@@ -1296,7 +1282,7 @@
});
}
- private int computeRelevantEventTypesLocked(UserState userState, Client client) {
+ private int computeRelevantEventTypesLocked(AccessibilityUserState userState, Client client) {
int relevantEventTypes = 0;
int serviceCount = userState.mBoundServices.size();
@@ -1342,20 +1328,11 @@
}
private void broadcastToClients(
- UserState userState, Consumer<Client> clientAction) {
+ AccessibilityUserState userState, Consumer<Client> clientAction) {
mGlobalClients.broadcastForEachCookie(clientAction);
userState.mUserClients.broadcastForEachCookie(clientAction);
}
- private void unbindAllServicesLocked(UserState userState) {
- List<AccessibilityServiceConnection> services = userState.mBoundServices;
- for (int count = services.size(); count > 0; count--) {
- // When the service is unbound, it disappears from the list, so there's no need to
- // keep track of the index
- services.get(0).unbindLocked();
- }
- }
-
/**
* Populates a set with the {@link ComponentName}s stored in a colon
* separated value setting for a given user.
@@ -1422,7 +1399,7 @@
}
}
- private void updateServicesLocked(UserState userState) {
+ private void updateServicesLocked(AccessibilityUserState userState) {
Map<ComponentName, AccessibilityServiceConnection> componentNameToServiceMap =
userState.mComponentNameToServiceMap;
boolean isUnlockingOrUnlocked = LocalServices.getService(UserManagerInternal.class)
@@ -1442,7 +1419,7 @@
}
// Wait for the binding if it is in process.
- if (userState.mBindingServices.contains(componentName)) {
+ if (userState.getBindingServicesLocked().contains(componentName)) {
continue;
}
if (userState.mEnabledServices.contains(componentName)
@@ -1450,7 +1427,7 @@
if (service == null) {
service = new AccessibilityServiceConnection(userState, mContext, componentName,
installedService, sIdCounter++, mMainHandler, mLock, mSecurityPolicy,
- this, mWindowManagerService, mGlobalActionPerformer,
+ this, mWindowManagerService, mSystemActionPerformer,
mA11yWindowManager);
} else if (userState.mBoundServices.contains(service)) {
continue;
@@ -1478,15 +1455,15 @@
if (audioManager != null) {
audioManager.setAccessibilityServiceUids(mTempIntArray);
}
- updateAccessibilityEnabledSetting(userState);
+ updateAccessibilityEnabledSettingLocked(userState);
}
- private void scheduleUpdateClientsIfNeededLocked(UserState userState) {
- final int clientState = userState.getClientState();
- if (userState.mLastSentClientState != clientState
+ private void scheduleUpdateClientsIfNeededLocked(AccessibilityUserState userState) {
+ final int clientState = getClientStateLocked(userState);
+ if (userState.getLastSentClientStateLocked() != clientState
&& (mGlobalClients.getRegisteredCallbackCount() > 0
|| userState.mUserClients.getRegisteredCallbackCount() > 0)) {
- userState.mLastSentClientState = clientState;
+ userState.setLastSentClientStateLocked(clientState);
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::sendStateToAllClients,
this, clientState, userState.mUserId));
@@ -1508,7 +1485,8 @@
client -> client.setState(clientState)));
}
- private void scheduleNotifyClientsOfServicesStateChangeLocked(UserState userState) {
+ private void scheduleNotifyClientsOfServicesStateChangeLocked(
+ AccessibilityUserState userState) {
updateRecommendedUiTimeoutLocked(userState);
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::sendServicesStateChanged,
@@ -1527,44 +1505,45 @@
client -> client.notifyServicesStateChanged(uiTimeout)));
}
- private void scheduleUpdateInputFilter(UserState userState) {
+ private void scheduleUpdateInputFilter(AccessibilityUserState userState) {
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::updateInputFilter, this, userState));
}
- private void scheduleUpdateFingerprintGestureHandling(UserState userState) {
+ private void scheduleUpdateFingerprintGestureHandling(AccessibilityUserState userState) {
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::updateFingerprintGestureHandling,
this, userState));
}
- private void updateInputFilter(UserState userState) {
+ private void updateInputFilter(AccessibilityUserState userState) {
if (mUiAutomationManager.suppressingAccessibilityServicesLocked()) return;
boolean setInputFilter = false;
AccessibilityInputFilter inputFilter = null;
synchronized (mLock) {
int flags = 0;
- if (userState.mIsDisplayMagnificationEnabled) {
+ if (userState.isDisplayMagnificationEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER;
}
- if (userState.mIsNavBarMagnificationEnabled) {
+ if (userState.isNavBarMagnificationEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
}
if (userHasMagnificationServicesLocked(userState)) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER;
}
// Touch exploration without accessibility makes no sense.
- if (userState.isHandlingAccessibilityEvents() && userState.mIsTouchExplorationEnabled) {
+ if (userState.isHandlingAccessibilityEventsLocked()
+ && userState.isTouchExplorationEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_TOUCH_EXPLORATION;
}
- if (userState.mIsFilterKeyEventsEnabled) {
+ if (userState.isFilterKeyEventsEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_FILTER_KEY_EVENTS;
}
- if (userState.mIsAutoclickEnabled) {
+ if (userState.isAutoclickEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_AUTOCLICK;
}
- if (userState.mIsPerformGesturesEnabled) {
+ if (userState.isPerformGesturesEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS;
}
if (flags != 0) {
@@ -1597,8 +1576,8 @@
String label = service.getServiceInfo().getResolveInfo()
.loadLabel(mContext.getPackageManager()).toString();
- final UserState userState = getCurrentUserStateLocked();
- if (userState.mIsTouchExplorationEnabled) {
+ final AccessibilityUserState userState = getCurrentUserStateLocked();
+ if (userState.isTouchExplorationEnabledLocked()) {
return;
}
if (mEnableTouchExplorationDialog != null
@@ -1608,40 +1587,40 @@
mEnableTouchExplorationDialog = new AlertDialog.Builder(mContext)
.setIconAttribute(android.R.attr.alertDialogIcon)
.setPositiveButton(android.R.string.ok, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // The user allowed the service to toggle touch exploration.
- userState.mTouchExplorationGrantedServices.add(service.mComponentName);
- persistComponentNamesToSettingLocked(
- Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
- userState.mTouchExplorationGrantedServices, userState.mUserId);
- // Enable touch exploration.
- userState.mIsTouchExplorationEnabled = true;
- final long identity = Binder.clearCallingIdentity();
- try {
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1,
- userState.mUserId);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- onUserStateChangedLocked(userState);
- }
- })
- .setNegativeButton(android.R.string.cancel, new OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- })
- .setTitle(R.string.enable_explore_by_touch_warning_title)
- .setMessage(mContext.getString(
- R.string.enable_explore_by_touch_warning_message, label))
- .create();
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // The user allowed the service to toggle touch exploration.
+ userState.mTouchExplorationGrantedServices.add(service.mComponentName);
+ persistComponentNamesToSettingLocked(
+ Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+ userState.mTouchExplorationGrantedServices, userState.mUserId);
+ // Enable touch exploration.
+ userState.setTouchExplorationEnabledLocked(true);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1,
+ userState.mUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ onUserStateChangedLocked(userState);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ dialog.dismiss();
+ }
+ })
+ .setTitle(R.string.enable_explore_by_touch_warning_title)
+ .setMessage(mContext.getString(
+ R.string.enable_explore_by_touch_warning_message, label))
+ .create();
mEnableTouchExplorationDialog.getWindow().setType(
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
mEnableTouchExplorationDialog.getWindow().getAttributes().privateFlags
- |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
mEnableTouchExplorationDialog.setCanceledOnTouchOutside(true);
mEnableTouchExplorationDialog.show();
}
@@ -1652,7 +1631,7 @@
*
* @param userState the new user state
*/
- private void onUserStateChangedLocked(UserState userState) {
+ private void onUserStateChangedLocked(AccessibilityUserState userState) {
// TODO: Remove this hack
mInitialized = true;
updateLegacyCapabilitiesLocked(userState);
@@ -1670,7 +1649,7 @@
updateAccessibilityButtonTargetsLocked(userState);
}
- private void updateWindowsForAccessibilityCallbackLocked(UserState userState) {
+ private void updateWindowsForAccessibilityCallbackLocked(AccessibilityUserState userState) {
// We observe windows for accessibility only if there is at least
// one bound service that can retrieve window content that specified
// it is interested in accessing such windows. For services that are
@@ -1702,7 +1681,7 @@
}
}
- private void updateLegacyCapabilitiesLocked(UserState userState) {
+ private void updateLegacyCapabilitiesLocked(AccessibilityUserState userState) {
// Up to JB-MR1 we had a white list with services that can enable touch
// exploration. When a service is first started we show a dialog to the
// use to get a permission to white list the service.
@@ -1724,20 +1703,20 @@
}
}
- private void updatePerformGesturesLocked(UserState userState) {
+ private void updatePerformGesturesLocked(AccessibilityUserState userState) {
final int serviceCount = userState.mBoundServices.size();
for (int i = 0; i < serviceCount; i++) {
AccessibilityServiceConnection service = userState.mBoundServices.get(i);
if ((service.getCapabilities()
& AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES) != 0) {
- userState.mIsPerformGesturesEnabled = true;
+ userState.setPerformGesturesEnabledLocked(true);
return;
}
}
- userState.mIsPerformGesturesEnabled = false;
+ userState.setPerformGesturesEnabledLocked(false);
}
- private void updateFilterKeyEventsLocked(UserState userState) {
+ private void updateFilterKeyEventsLocked(AccessibilityUserState userState) {
final int serviceCount = userState.mBoundServices.size();
for (int i = 0; i < serviceCount; i++) {
AccessibilityServiceConnection service = userState.mBoundServices.get(i);
@@ -1745,14 +1724,14 @@
&& (service.getCapabilities()
& AccessibilityServiceInfo
.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) != 0) {
- userState.mIsFilterKeyEventsEnabled = true;
+ userState.setFilterKeyEventsEnabledLocked(true);
return;
}
}
- userState.mIsFilterKeyEventsEnabled = false;
+ userState.setFilterKeyEventsEnabledLocked(false);
}
- private boolean readConfigurationForUserStateLocked(UserState userState) {
+ private boolean readConfigurationForUserStateLocked(AccessibilityUserState userState) {
boolean somethingChanged = readInstalledAccessibilityServiceLocked(userState);
somethingChanged |= readInstalledAccessibilityShortcutLocked(userState);
somethingChanged |= readEnabledAccessibilityServicesLocked(userState);
@@ -1767,10 +1746,10 @@
return somethingChanged;
}
- private void updateAccessibilityEnabledSetting(UserState userState) {
+ private void updateAccessibilityEnabledSettingLocked(AccessibilityUserState userState) {
final long identity = Binder.clearCallingIdentity();
final boolean isA11yEnabled = mUiAutomationManager.isUiAutomationRunningLocked()
- || userState.isHandlingAccessibilityEvents();
+ || userState.isHandlingAccessibilityEventsLocked();
try {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_ENABLED,
@@ -1781,18 +1760,18 @@
}
}
- private boolean readTouchExplorationEnabledSettingLocked(UserState userState) {
+ private boolean readTouchExplorationEnabledSettingLocked(AccessibilityUserState userState) {
final boolean touchExplorationEnabled = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0, userState.mUserId) == 1;
- if (touchExplorationEnabled != userState.mIsTouchExplorationEnabled) {
- userState.mIsTouchExplorationEnabled = touchExplorationEnabled;
+ if (touchExplorationEnabled != userState.isTouchExplorationEnabledLocked()) {
+ userState.setTouchExplorationEnabledLocked(touchExplorationEnabled);
return true;
}
return false;
}
- private boolean readMagnificationEnabledSettingsLocked(UserState userState) {
+ private boolean readMagnificationEnabledSettingsLocked(AccessibilityUserState userState) {
final boolean displayMagnificationEnabled = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
@@ -1801,40 +1780,40 @@
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
0, userState.mUserId) == 1;
- if ((displayMagnificationEnabled != userState.mIsDisplayMagnificationEnabled)
- || (navBarMagnificationEnabled != userState.mIsNavBarMagnificationEnabled)) {
- userState.mIsDisplayMagnificationEnabled = displayMagnificationEnabled;
- userState.mIsNavBarMagnificationEnabled = navBarMagnificationEnabled;
+ if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())
+ || (navBarMagnificationEnabled != userState.isNavBarMagnificationEnabledLocked())) {
+ userState.setDisplayMagnificationEnabledLocked(displayMagnificationEnabled);
+ userState.setNavBarMagnificationEnabledLocked(navBarMagnificationEnabled);
return true;
}
return false;
}
- private boolean readAutoclickEnabledSettingLocked(UserState userState) {
+ private boolean readAutoclickEnabledSettingLocked(AccessibilityUserState userState) {
final boolean autoclickEnabled = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
0, userState.mUserId) == 1;
- if (autoclickEnabled != userState.mIsAutoclickEnabled) {
- userState.mIsAutoclickEnabled = autoclickEnabled;
+ if (autoclickEnabled != userState.isAutoclickEnabledLocked()) {
+ userState.setAutoclickEnabledLocked(autoclickEnabled);
return true;
}
return false;
}
- private boolean readHighTextContrastEnabledSettingLocked(UserState userState) {
+ private boolean readHighTextContrastEnabledSettingLocked(AccessibilityUserState userState) {
final boolean highTextContrastEnabled = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, 0,
userState.mUserId) == 1;
- if (highTextContrastEnabled != userState.mIsTextHighContrastEnabled) {
- userState.mIsTextHighContrastEnabled = highTextContrastEnabled;
+ if (highTextContrastEnabled != userState.isTextHighContrastEnabledLocked()) {
+ userState.setTextHighContrastEnabledLocked(highTextContrastEnabled);
return true;
}
return false;
}
- private void updateTouchExplorationLocked(UserState userState) {
+ private void updateTouchExplorationLocked(AccessibilityUserState userState) {
boolean enabled = mUiAutomationManager.isTouchExplorationEnabledLocked();
final int serviceCount = userState.mBoundServices.size();
for (int i = 0; i < serviceCount; i++) {
@@ -1844,8 +1823,8 @@
break;
}
}
- if (enabled != userState.mIsTouchExplorationEnabled) {
- userState.mIsTouchExplorationEnabled = enabled;
+ if (enabled != userState.isTouchExplorationEnabledLocked()) {
+ userState.setTouchExplorationEnabledLocked(enabled);
final long identity = Binder.clearCallingIdentity();
try {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
@@ -1857,60 +1836,61 @@
}
}
- private boolean readAccessibilityShortcutSettingLocked(UserState userState) {
+ private boolean readAccessibilityShortcutSettingLocked(AccessibilityUserState userState) {
String componentNameToEnableString = AccessibilityShortcutController
.getTargetServiceComponentNameString(mContext, userState.mUserId);
if ((componentNameToEnableString == null) || componentNameToEnableString.isEmpty()) {
- if (userState.mServiceToEnableWithShortcut == null) {
+ if (userState.getServiceToEnableWithShortcutLocked() == null) {
return false;
}
- userState.mServiceToEnableWithShortcut = null;
+ userState.setServiceToEnableWithShortcutLocked(null);
return true;
}
ComponentName componentNameToEnable =
ComponentName.unflattenFromString(componentNameToEnableString);
if ((componentNameToEnable != null)
- && componentNameToEnable.equals(userState.mServiceToEnableWithShortcut)) {
+ && componentNameToEnable.equals(userState.getServiceToEnableWithShortcutLocked())) {
return false;
}
- userState.mServiceToEnableWithShortcut = componentNameToEnable;
+ userState.setServiceToEnableWithShortcutLocked(componentNameToEnable);
scheduleNotifyClientsOfServicesStateChangeLocked(userState);
return true;
}
- private boolean readAccessibilityButtonSettingsLocked(UserState userState) {
+ private boolean readAccessibilityButtonSettingsLocked(AccessibilityUserState userState) {
String componentId = Settings.Secure.getStringForUser(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT, userState.mUserId);
if (TextUtils.isEmpty(componentId)) {
- if ((userState.mServiceAssignedToAccessibilityButton == null)
- && !userState.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ if ((userState.getServiceAssignedToAccessibilityButtonLocked() == null)
+ && !userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
return false;
}
- userState.mServiceAssignedToAccessibilityButton = null;
- userState.mIsNavBarMagnificationAssignedToAccessibilityButton = false;
+ userState.setServiceAssignedToAccessibilityButtonLocked(null);
+ userState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(false);
return true;
}
if (componentId.equals(MagnificationController.class.getName())) {
- if (userState.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ if (userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
return false;
}
- userState.mServiceAssignedToAccessibilityButton = null;
- userState.mIsNavBarMagnificationAssignedToAccessibilityButton = true;
+ userState.setServiceAssignedToAccessibilityButtonLocked(null);
+ userState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(true);
return true;
}
ComponentName componentName = ComponentName.unflattenFromString(componentId);
- if (Objects.equals(componentName, userState.mServiceAssignedToAccessibilityButton)) {
+ if (Objects.equals(componentName,
+ userState.getServiceAssignedToAccessibilityButtonLocked())) {
return false;
}
- userState.mServiceAssignedToAccessibilityButton = componentName;
- userState.mIsNavBarMagnificationAssignedToAccessibilityButton = false;
+ userState.setServiceAssignedToAccessibilityButtonLocked(componentName);
+ userState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(false);
return true;
}
- private boolean readUserRecommendedUiTimeoutSettingsLocked(UserState userState) {
+ private boolean readUserRecommendedUiTimeoutSettingsLocked(AccessibilityUserState userState) {
final int nonInteractiveUiTimeout = Settings.Secure.getIntForUser(
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_NON_INTERACTIVE_UI_TIMEOUT_MS, 0,
@@ -1919,10 +1899,10 @@
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS, 0,
userState.mUserId);
- if (nonInteractiveUiTimeout != userState.mUserNonInteractiveUiTimeout
- || interactiveUiTimeout != userState.mUserInteractiveUiTimeout) {
- userState.mUserNonInteractiveUiTimeout = nonInteractiveUiTimeout;
- userState.mUserInteractiveUiTimeout = interactiveUiTimeout;
+ if (nonInteractiveUiTimeout != userState.getUserNonInteractiveUiTimeoutLocked()
+ || interactiveUiTimeout != userState.getUserInteractiveUiTimeoutLocked()) {
+ userState.setUserNonInteractiveUiTimeoutLocked(nonInteractiveUiTimeout);
+ userState.setUserInteractiveUiTimeoutLocked(interactiveUiTimeout);
scheduleNotifyClientsOfServicesStateChangeLocked(userState);
return true;
}
@@ -1936,22 +1916,22 @@
*
* @param userState
*/
- private void updateAccessibilityShortcutLocked(UserState userState) {
- if (userState.mServiceToEnableWithShortcut == null) {
+ private void updateAccessibilityShortcutLocked(AccessibilityUserState userState) {
+ if (userState.getServiceToEnableWithShortcutLocked() == null) {
return;
}
boolean shortcutServiceIsInstalled =
AccessibilityShortcutController.getFrameworkShortcutFeaturesMap()
- .containsKey(userState.mServiceToEnableWithShortcut);
+ .containsKey(userState.getServiceToEnableWithShortcutLocked());
for (int i = 0; !shortcutServiceIsInstalled && (i < userState.mInstalledServices.size());
i++) {
if (userState.mInstalledServices.get(i).getComponentName()
- .equals(userState.mServiceToEnableWithShortcut)) {
+ .equals(userState.getServiceToEnableWithShortcutLocked())) {
shortcutServiceIsInstalled = true;
}
}
if (!shortcutServiceIsInstalled) {
- userState.mServiceToEnableWithShortcut = null;
+ userState.setServiceToEnableWithShortcutLocked(null);
final long identity = Binder.clearCallingIdentity();
try {
Settings.Secure.putStringForUser(mContext.getContentResolver(),
@@ -1967,7 +1947,7 @@
}
private boolean canRequestAndRequestsTouchExplorationLocked(
- AccessibilityServiceConnection service, UserState userState) {
+ AccessibilityServiceConnection service, AccessibilityUserState userState) {
// Service not ready or cannot request the feature - well nothing to do.
if (!service.canReceiveEventsLocked() || !service.mRequestTouchExplorationMode) {
return false;
@@ -1997,7 +1977,7 @@
return false;
}
- private void updateMagnificationLocked(UserState userState) {
+ private void updateMagnificationLocked(AccessibilityUserState userState) {
if (userState.mUserId != mCurrentUserId) {
return;
}
@@ -2012,8 +1992,8 @@
// We would skip overlay display because it uses overlay window to simulate secondary
// displays in one display. It's not a real display and there's no input events for it.
final ArrayList<Display> displays = getValidDisplayList();
- if (userState.mIsDisplayMagnificationEnabled
- || userState.mIsNavBarMagnificationEnabled) {
+ if (userState.isDisplayMagnificationEnabledLocked()
+ || userState.isNavBarMagnificationEnabledLocked()) {
for (int i = 0; i < displays.size(); i++) {
final Display display = displays.get(i);
getMagnificationController().register(display.getDisplayId());
@@ -2037,7 +2017,7 @@
* Returns whether the specified user has any services that are capable of
* controlling magnification.
*/
- private boolean userHasMagnificationServicesLocked(UserState userState) {
+ private boolean userHasMagnificationServicesLocked(AccessibilityUserState userState) {
final List<AccessibilityServiceConnection> services = userState.mBoundServices;
for (int i = 0, count = services.size(); i < count; i++) {
final AccessibilityServiceConnection service = services.get(i);
@@ -2052,7 +2032,7 @@
* Returns whether the specified user has any services that are capable of
* controlling magnification and are actively listening for magnification updates.
*/
- private boolean userHasListeningMagnificationServicesLocked(UserState userState,
+ private boolean userHasListeningMagnificationServicesLocked(AccessibilityUserState userState,
int displayId) {
final List<AccessibilityServiceConnection> services = userState.mBoundServices;
for (int i = 0, count = services.size(); i < count; i++) {
@@ -2065,7 +2045,7 @@
return false;
}
- private void updateFingerprintGestureHandling(UserState userState) {
+ private void updateFingerprintGestureHandling(AccessibilityUserState userState) {
final List<AccessibilityServiceConnection> services;
synchronized (mLock) {
services = userState.mBoundServices;
@@ -2097,7 +2077,7 @@
}
}
- private void updateAccessibilityButtonTargetsLocked(UserState userState) {
+ private void updateAccessibilityButtonTargetsLocked(AccessibilityUserState userState) {
for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
if (service.mRequestAccessibilityButton) {
@@ -2107,9 +2087,9 @@
}
}
- private void updateRecommendedUiTimeoutLocked(UserState userState) {
- int newNonInteractiveUiTimeout = userState.mUserNonInteractiveUiTimeout;
- int newInteractiveUiTimeout = userState.mUserInteractiveUiTimeout;
+ private void updateRecommendedUiTimeoutLocked(AccessibilityUserState userState) {
+ int newNonInteractiveUiTimeout = userState.getUserNonInteractiveUiTimeoutLocked();
+ int newInteractiveUiTimeout = userState.getUserInteractiveUiTimeoutLocked();
// read from a11y services if user does not specify value
if (newNonInteractiveUiTimeout == 0 || newInteractiveUiTimeout == 0) {
int serviceNonInteractiveUiTimeout = 0;
@@ -2132,8 +2112,8 @@
newInteractiveUiTimeout = serviceInteractiveUiTimeout;
}
}
- userState.mNonInteractiveUiTimeout = newNonInteractiveUiTimeout;
- userState.mInteractiveUiTimeout = newInteractiveUiTimeout;
+ userState.setNonInteractiveUiTimeoutLocked(newNonInteractiveUiTimeout);
+ userState.setInteractiveUiTimeoutLocked(newInteractiveUiTimeout);
}
@GuardedBy("mLock")
@@ -2180,8 +2160,8 @@
final Map<ComponentName, ToggleableFrameworkFeatureInfo> frameworkFeatureMap =
AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
synchronized(mLock) {
- final UserState userState = getUserStateLocked(mCurrentUserId);
- final ComponentName serviceName = userState.mServiceToEnableWithShortcut;
+ final AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
+ final ComponentName serviceName = userState.getServiceToEnableWithShortcutLocked();
if (serviceName == null) {
return;
}
@@ -2218,11 +2198,11 @@
"getAccessibilityShortcutService requires the MANAGE_ACCESSIBILITY permission");
}
synchronized(mLock) {
- final UserState userState = getUserStateLocked(mCurrentUserId);
- if (userState.mServiceToEnableWithShortcut == null) {
+ final AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
+ if (userState.getServiceToEnableWithShortcutLocked() == null) {
return null;
}
- return userState.mServiceToEnableWithShortcut.flattenToString();
+ return userState.getServiceToEnableWithShortcutLocked().flattenToString();
}
}
@@ -2237,7 +2217,7 @@
userId);
setting.write(ComponentNameSet.add(setting.read(), componentName));
- UserState userState = getUserStateLocked(userId);
+ AccessibilityUserState userState = getUserStateLocked(userId);
if (userState.mEnabledServices.add(componentName)) {
onUserStateChangedLocked(userState);
}
@@ -2254,7 +2234,7 @@
userId);
setting.write(ComponentNameSet.remove(setting.read(), componentName));
- UserState userState = getUserStateLocked(userId);
+ AccessibilityUserState userState = getUserStateLocked(userId);
if (userState.mEnabledServices.remove(componentName)) {
onUserStateChangedLocked(userState);
}
@@ -2322,14 +2302,14 @@
@Override
public long getRecommendedTimeoutMillis() {
synchronized(mLock) {
- final UserState userState = getCurrentUserStateLocked();
+ final AccessibilityUserState userState = getCurrentUserStateLocked();
return getRecommendedTimeoutMillisLocked(userState);
}
}
- private long getRecommendedTimeoutMillisLocked(UserState userState) {
- return IntPair.of(userState.mInteractiveUiTimeout,
- userState.mNonInteractiveUiTimeout);
+ private long getRecommendedTimeoutMillisLocked(AccessibilityUserState userState) {
+ return IntPair.of(userState.getInteractiveUiTimeoutLocked(),
+ userState.getNonInteractiveUiTimeoutLocked());
}
@Override
@@ -2338,78 +2318,20 @@
synchronized (mLock) {
pw.println("ACCESSIBILITY MANAGER (dumpsys accessibility)");
pw.println();
+ pw.append("currentUserId=").append(String.valueOf(mCurrentUserId));
+ pw.println();
final int userCount = mUserStates.size();
for (int i = 0; i < userCount; i++) {
- UserState userState = mUserStates.valueAt(i);
- pw.append("User state[attributes:{id=" + userState.mUserId);
- pw.append(", currentUser=" + (userState.mUserId == mCurrentUserId));
- pw.append(", touchExplorationEnabled=" + userState.mIsTouchExplorationEnabled);
- pw.append(", displayMagnificationEnabled="
- + userState.mIsDisplayMagnificationEnabled);
- pw.append(", navBarMagnificationEnabled="
- + userState.mIsNavBarMagnificationEnabled);
- pw.append(", autoclickEnabled=" + userState.mIsAutoclickEnabled);
- pw.append(", nonInteractiveUiTimeout=" + userState.mNonInteractiveUiTimeout);
- pw.append(", interactiveUiTimeout=" + userState.mInteractiveUiTimeout);
- pw.append(", installedServiceCount=" + userState.mInstalledServices.size());
- if (mUiAutomationManager.isUiAutomationRunningLocked()) {
- pw.append(", ");
- mUiAutomationManager.dumpUiAutomationService(fd, pw, args);
- pw.println();
- }
- pw.append("}");
- pw.println();
- pw.append(" Bound services:{");
- final int serviceCount = userState.mBoundServices.size();
- for (int j = 0; j < serviceCount; j++) {
- if (j > 0) {
- pw.append(", ");
- pw.println();
- pw.append(" ");
- }
- AccessibilityServiceConnection service = userState.mBoundServices.get(j);
- service.dump(fd, pw, args);
- }
- pw.println("}");
- pw.append(" Enabled services:{");
- Iterator<ComponentName> it = userState.mEnabledServices.iterator();
- if (it.hasNext()) {
- ComponentName componentName = it.next();
- pw.append(componentName.toShortString());
- while (it.hasNext()) {
- componentName = it.next();
- pw.append(", ");
- pw.append(componentName.toShortString());
- }
- }
- pw.println("}");
- pw.append(" Binding services:{");
- it = userState.mBindingServices.iterator();
- if (it.hasNext()) {
- ComponentName componentName = it.next();
- pw.append(componentName.toShortString());
- while (it.hasNext()) {
- componentName = it.next();
- pw.append(", ");
- pw.append(componentName.toShortString());
- }
- }
- pw.println("}]");
+ mUserStates.valueAt(i).dump(fd, pw, args);
+ }
+ if (mUiAutomationManager.isUiAutomationRunningLocked()) {
+ mUiAutomationManager.dumpUiAutomationService(fd, pw, args);
pw.println();
}
mA11yWindowManager.dump(fd, pw, args);
}
}
- private void putSecureIntForUser(String key, int value, int userid) {
- final long identity = Binder.clearCallingIdentity();
- try {
- Settings.Secure.putIntForUser(mContext.getContentResolver(), key, value, userid);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
//TODO remove after refactoring KeyEventDispatcherTest
final class MainHandler extends Handler {
public static final int MSG_SEND_KEY_EVENT_TO_INPUT_FILTER = 8;
@@ -2446,7 +2368,7 @@
@Override
public void onClientChangeLocked(boolean serviceInfoChanged) {
- AccessibilityManagerService.UserState userState = getUserStateLocked(mCurrentUserId);
+ AccessibilityUserState userState = getUserStateLocked(mCurrentUserId);
onUserStateChangedLocked(userState);
if (serviceInfoChanged) {
scheduleNotifyClientsOfServicesStateChangeLocked(userState);
@@ -2474,7 +2396,7 @@
info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT);
info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
info.flags |= AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
- final UserState userState;
+ final AccessibilityUserState userState;
synchronized (mLock) {
userState = getCurrentUserStateLocked();
}
@@ -2482,7 +2404,7 @@
userState, mContext,
COMPONENT_NAME, info, sIdCounter++, mMainHandler, mLock, mSecurityPolicy,
AccessibilityManagerService.this, mWindowManagerService,
- mGlobalActionPerformer, mA11yWindowManager) {
+ mSystemActionPerformer, mA11yWindowManager) {
@Override
public boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) {
return true;
@@ -2593,7 +2515,7 @@
if (mInputFilter != null) {
mInputFilter.onDisplayChanged();
}
- UserState userState = getCurrentUserStateLocked();
+ AccessibilityUserState userState = getCurrentUserStateLocked();
if (displayId != Display.DEFAULT_DISPLAY) {
final List<AccessibilityServiceConnection> services = userState.mBoundServices;
for (int i = 0; i < services.size(); i++) {
@@ -2618,7 +2540,7 @@
if (mInputFilter != null) {
mInputFilter.onDisplayChanged();
}
- UserState userState = getCurrentUserStateLocked();
+ AccessibilityUserState userState = getCurrentUserStateLocked();
if (displayId != Display.DEFAULT_DISPLAY) {
final List<AccessibilityServiceConnection> services = userState.mBoundServices;
for (int i = 0; i < services.size(); i++) {
@@ -2658,7 +2580,8 @@
final String[] mPackageNames;
int mLastSentRelevantEventTypes;
- private Client(IAccessibilityManagerClient callback, int clientUid, UserState userState) {
+ private Client(IAccessibilityManagerClient callback, int clientUid,
+ AccessibilityUserState userState) {
mCallback = callback;
mPackageNames = mPackageManager.getPackagesForUid(clientUid);
synchronized (mLock) {
@@ -2667,316 +2590,6 @@
}
}
- public class UserState {
- public final int mUserId;
-
- // Non-transient state.
-
- public final RemoteCallbackList<IAccessibilityManagerClient> mUserClients =
- new RemoteCallbackList<>();
-
- // Transient state.
-
- public final ArrayList<AccessibilityServiceConnection> mBoundServices = new ArrayList<>();
-
- public final Map<ComponentName, AccessibilityServiceConnection> mComponentNameToServiceMap =
- new HashMap<>();
-
- public final List<AccessibilityServiceInfo> mInstalledServices =
- new ArrayList<>();
-
- public final List<AccessibilityShortcutInfo> mInstalledShortcuts = new ArrayList<>();
-
- private final Set<ComponentName> mBindingServices = new HashSet<>();
-
- public final Set<ComponentName> mEnabledServices = new HashSet<>();
-
- public final Set<ComponentName> mTouchExplorationGrantedServices =
- new HashSet<>();
-
- public ComponentName mServiceChangingSoftKeyboardMode;
-
- public ComponentName mServiceToEnableWithShortcut;
-
- public int mLastSentClientState = -1;
- public int mNonInteractiveUiTimeout = 0;
- public int mInteractiveUiTimeout = 0;
-
- private int mSoftKeyboardShowMode = 0;
-
- public boolean mIsNavBarMagnificationAssignedToAccessibilityButton;
- public ComponentName mServiceAssignedToAccessibilityButton;
-
- public boolean mIsTouchExplorationEnabled;
- public boolean mIsTextHighContrastEnabled;
- public boolean mIsDisplayMagnificationEnabled;
- public boolean mIsNavBarMagnificationEnabled;
- public boolean mIsAutoclickEnabled;
- public boolean mIsPerformGesturesEnabled;
- public boolean mIsFilterKeyEventsEnabled;
- public int mUserNonInteractiveUiTimeout;
- public int mUserInteractiveUiTimeout;
-
- private boolean mBindInstantServiceAllowed;
-
- public UserState(int userId) {
- mUserId = userId;
- }
-
- public int getClientState() {
- int clientState = 0;
- final boolean a11yEnabled = (mUiAutomationManager.isUiAutomationRunningLocked()
- || isHandlingAccessibilityEvents());
- if (a11yEnabled) {
- clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
- }
- // Touch exploration relies on enabled accessibility.
- if (a11yEnabled && mIsTouchExplorationEnabled) {
- clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
- }
- if (mIsTextHighContrastEnabled) {
- clientState |= AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED;
- }
- return clientState;
- }
-
- public boolean isHandlingAccessibilityEvents() {
- return !mBoundServices.isEmpty() || !mBindingServices.isEmpty();
- }
-
- public void onSwitchToAnotherUserLocked() {
- // Unbind all services.
- unbindAllServicesLocked(this);
-
- // Clear service management state.
- mBoundServices.clear();
- mBindingServices.clear();
-
- // Clear event management state.
- mLastSentClientState = -1;
-
- // clear UI timeout
- mNonInteractiveUiTimeout = 0;
- mInteractiveUiTimeout = 0;
-
- // Clear state persisted in settings.
- mEnabledServices.clear();
- mTouchExplorationGrantedServices.clear();
- mIsTouchExplorationEnabled = false;
- mIsDisplayMagnificationEnabled = false;
- mIsNavBarMagnificationEnabled = false;
- mServiceAssignedToAccessibilityButton = null;
- mIsNavBarMagnificationAssignedToAccessibilityButton = false;
- mIsAutoclickEnabled = false;
- mUserNonInteractiveUiTimeout = 0;
- mUserInteractiveUiTimeout = 0;
- }
-
- public void addServiceLocked(AccessibilityServiceConnection serviceConnection) {
- if (!mBoundServices.contains(serviceConnection)) {
- serviceConnection.onAdded();
- mBoundServices.add(serviceConnection);
- mComponentNameToServiceMap.put(serviceConnection.mComponentName, serviceConnection);
- scheduleNotifyClientsOfServicesStateChangeLocked(this);
- }
- }
-
- /**
- * Removes a service.
- * There are three states to a service here: off, bound, and binding.
- * This stops tracking the service as bound.
- *
- * @param serviceConnection The service.
- */
- public void removeServiceLocked(AccessibilityServiceConnection serviceConnection) {
- mBoundServices.remove(serviceConnection);
- serviceConnection.onRemoved();
- if ((mServiceChangingSoftKeyboardMode != null)
- && (mServiceChangingSoftKeyboardMode.equals(
- serviceConnection.getServiceInfo().getComponentName()))) {
- setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null);
- }
- // It may be possible to bind a service twice, which confuses the map. Rebuild the map
- // to make sure we can still reach a service
- mComponentNameToServiceMap.clear();
- for (int i = 0; i < mBoundServices.size(); i++) {
- AccessibilityServiceConnection boundClient = mBoundServices.get(i);
- mComponentNameToServiceMap.put(boundClient.mComponentName, boundClient);
- }
- scheduleNotifyClientsOfServicesStateChangeLocked(this);
- }
-
- /**
- * Make sure a services disconnected but still 'on' state is reflected in UserState
- * There are three states to a service here: off, bound, and binding.
- * This drops a service from a bound state, to the binding state.
- * The binding state describes the situation where a service is on, but not bound.
- *
- * @param serviceConnection The service.
- */
- public void serviceDisconnectedLocked(AccessibilityServiceConnection serviceConnection) {
- removeServiceLocked(serviceConnection);
- mBindingServices.add(serviceConnection.getComponentName());
- }
-
- public Set<ComponentName> getBindingServicesLocked() {
- return mBindingServices;
- }
-
- /**
- * Returns enabled service list.
- */
- public Set<ComponentName> getEnabledServicesLocked() {
- return mEnabledServices;
- }
-
- public int getSoftKeyboardShowMode() {
- return mSoftKeyboardShowMode;
- }
-
- /**
- * Set the soft keyboard mode. This mode is a bit odd, as it spans multiple settings.
- * The ACCESSIBILITY_SOFT_KEYBOARD_MODE setting can be checked by the rest of the system
- * to see if it should suppress showing the IME. The SHOW_IME_WITH_HARD_KEYBOARD setting
- * setting can be changed by the user, and prevents the system from suppressing the soft
- * keyboard when the hard keyboard is connected. The hard keyboard setting needs to defer
- * to the user's preference, if they have supplied one.
- *
- * @param newMode The new mode
- * @param requester The service requesting the change, so we can undo it when the
- * service stops. Set to null if something other than a service is forcing
- * the change.
- *
- * @return Whether or not the soft keyboard mode equals the new mode after the call
- */
- public boolean setSoftKeyboardModeLocked(int newMode, @Nullable ComponentName requester) {
- if ((newMode != SHOW_MODE_AUTO) && (newMode != SHOW_MODE_HIDDEN)
- && (newMode != SHOW_MODE_IGNORE_HARD_KEYBOARD))
- {
- Slog.w(LOG_TAG, "Invalid soft keyboard mode");
- return false;
- }
- if (mSoftKeyboardShowMode == newMode) return true;
-
- if (newMode == SHOW_MODE_IGNORE_HARD_KEYBOARD) {
- if (hasUserOverriddenHardKeyboardSettingLocked()) {
- // The user has specified a default for this setting
- return false;
- }
- // Save the original value. But don't do this if the value in settings is already
- // the new mode. That happens when we start up after a reboot, and we don't want
- // to overwrite the value we had from when we first started controlling the setting.
- if (getSoftKeyboardValueFromSettings() != SHOW_MODE_IGNORE_HARD_KEYBOARD) {
- setOriginalHardKeyboardValue(
- Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0);
- }
- putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 1, mUserId);
- } else if (mSoftKeyboardShowMode == SHOW_MODE_IGNORE_HARD_KEYBOARD) {
- putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
- getOriginalHardKeyboardValue() ? 1 : 0, mUserId);
- }
-
- saveSoftKeyboardValueToSettings(newMode);
- mSoftKeyboardShowMode = newMode;
- mServiceChangingSoftKeyboardMode = requester;
- notifySoftKeyboardShowModeChangedLocked(mSoftKeyboardShowMode);
- return true;
- }
-
- /**
- * If the settings are inconsistent with the internal state, make the internal state
- * match the settings.
- */
- public void reconcileSoftKeyboardModeWithSettingsLocked() {
- final ContentResolver cr = mContext.getContentResolver();
- final boolean showWithHardKeyboardSettings =
- Settings.Secure.getInt(cr, Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0;
- if (mSoftKeyboardShowMode == SHOW_MODE_IGNORE_HARD_KEYBOARD) {
- if (!showWithHardKeyboardSettings) {
- // The user has overridden the setting. Respect that and prevent further changes
- // to this behavior.
- setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null);
- setUserOverridesHardKeyboardSettingLocked();
- }
- }
-
- // If the setting and the internal state are out of sync, set both to default
- if (getSoftKeyboardValueFromSettings() != mSoftKeyboardShowMode)
- {
- Slog.e(LOG_TAG,
- "Show IME setting inconsistent with internal state. Overwriting");
- setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null);
- putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
- SHOW_MODE_AUTO, mUserId);
- }
- }
-
- private void setUserOverridesHardKeyboardSettingLocked() {
- final int softKeyboardSetting = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0);
- putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
- softKeyboardSetting | SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN,
- mUserId);
- }
-
- private boolean hasUserOverriddenHardKeyboardSettingLocked() {
- final int softKeyboardSetting = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0);
- return (softKeyboardSetting & SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN)
- != 0;
- }
-
- private void setOriginalHardKeyboardValue(boolean originalHardKeyboardValue) {
- final int oldSoftKeyboardSetting = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0);
- final int newSoftKeyboardSetting = oldSoftKeyboardSetting
- & (~SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE)
- | ((originalHardKeyboardValue) ? SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE : 0);
- putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
- newSoftKeyboardSetting, mUserId);
- }
-
- private void saveSoftKeyboardValueToSettings(int softKeyboardShowMode) {
- final int oldSoftKeyboardSetting = Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0);
- final int newSoftKeyboardSetting = oldSoftKeyboardSetting & (~SHOW_MODE_MASK)
- | softKeyboardShowMode;
- putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
- newSoftKeyboardSetting, mUserId);
- }
-
- private int getSoftKeyboardValueFromSettings() {
- return Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
- SHOW_MODE_AUTO) & SHOW_MODE_MASK;
- }
-
- private boolean getOriginalHardKeyboardValue() {
- return (Settings.Secure.getInt(mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0)
- & SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE) != 0;
- }
-
- public boolean getBindInstantServiceAllowed() {
- synchronized (mLock) {
- return mBindInstantServiceAllowed;
- }
- }
-
- public void setBindInstantServiceAllowed(boolean allowed) {
- synchronized (mLock) {
- mContext.enforceCallingOrSelfPermission(
- Manifest.permission.MANAGE_BIND_INSTANT_SERVICE,
- "setBindInstantServiceAllowed");
- if (allowed) {
- mBindInstantServiceAllowed = allowed;
- onUserStateChangedLocked(this);
- }
- }
- }
- }
-
private final class AccessibilityContentObserver extends ContentObserver {
private final Uri mTouchExplorationEnabledUri = Settings.Secure.getUriFor(
@@ -3057,7 +2670,7 @@
synchronized (mLock) {
// Profiles share the accessibility state of the parent. Therefore,
// we are checking for changes only the parent settings.
- UserState userState = getCurrentUserStateLocked();
+ AccessibilityUserState userState = getCurrentUserStateLocked();
if (mTouchExplorationEnabledUri.equals(uri)) {
if (readTouchExplorationEnabledSettingLocked(userState)) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index d7f61e5..d154060 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -35,7 +35,6 @@
import android.util.Slog;
import android.view.Display;
-import com.android.server.accessibility.AccessibilityManagerService.UserState;
import com.android.server.wm.WindowManagerInternal;
import java.lang.ref.WeakReference;
@@ -52,13 +51,13 @@
class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection {
private static final String LOG_TAG = "AccessibilityServiceConnection";
/*
- Holding a weak reference so there isn't a loop of references. UserState keeps lists of bound
- and binding services. These are freed on user changes, but just in case it somehow gets lost
- the weak reference will let the memory get GCed.
+ Holding a weak reference so there isn't a loop of references. AccessibilityUserState keeps
+ lists of bound and binding services. These are freed on user changes, but just in case it
+ somehow gets lost the weak reference will let the memory get GCed.
Having the reference be null when being called is a very bad sign, but we check the condition.
*/
- final WeakReference<UserState> mUserStateWeakReference;
+ final WeakReference<AccessibilityUserState> mUserStateWeakReference;
final Intent mIntent;
private final Handler mMainHandler;
@@ -66,15 +65,15 @@
private boolean mWasConnectedAndDied;
- public AccessibilityServiceConnection(UserState userState, Context context,
+ AccessibilityServiceConnection(AccessibilityUserState userState, Context context,
ComponentName componentName,
AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
Object lock, AccessibilitySecurityPolicy securityPolicy, SystemSupport systemSupport,
WindowManagerInternal windowManagerInternal,
- GlobalActionPerformer globalActionPerfomer, AccessibilityWindowManager awm) {
+ SystemActionPerformer systemActionPerfomer, AccessibilityWindowManager awm) {
super(context, componentName, accessibilityServiceInfo, id, mainHandler, lock,
- securityPolicy, systemSupport, windowManagerInternal, globalActionPerfomer, awm);
- mUserStateWeakReference = new WeakReference<UserState>(userState);
+ securityPolicy, systemSupport, windowManagerInternal, systemActionPerfomer, awm);
+ mUserStateWeakReference = new WeakReference<AccessibilityUserState>(userState);
mIntent = new Intent().setComponent(mComponentName);
mMainHandler = mainHandler;
mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
@@ -89,13 +88,13 @@
}
public void bindLocked() {
- UserState userState = mUserStateWeakReference.get();
+ AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState == null) return;
final long identity = Binder.clearCallingIdentity();
try {
int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
| Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS;
- if (userState.getBindInstantServiceAllowed()) {
+ if (userState.getBindInstantServiceAllowedLocked()) {
flags |= Context.BIND_ALLOW_INSTANT;
}
if (mService == null && mContext.bindServiceAsUser(
@@ -109,7 +108,7 @@
public void unbindLocked() {
mContext.unbindService(this);
- UserState userState = mUserStateWeakReference.get();
+ AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState == null) return;
userState.removeServiceLocked(this);
mSystemSupport.getMagnificationController().resetAllIfNeeded(mId);
@@ -123,7 +122,7 @@
@Override
public void disableSelf() {
synchronized (mLock) {
- UserState userState = mUserStateWeakReference.get();
+ AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState == null) return;
if (userState.getEnabledServicesLocked().remove(mComponentName)) {
final long identity = Binder.clearCallingIdentity();
@@ -156,7 +155,7 @@
}
}
mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service);
- UserState userState = mUserStateWeakReference.get();
+ AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState == null) return;
userState.addServiceLocked(this);
mSystemSupport.onClientChangeLocked(false);
@@ -177,7 +176,7 @@
private void initializeService() {
IAccessibilityServiceClient serviceInterface = null;
synchronized (mLock) {
- UserState userState = mUserStateWeakReference.get();
+ AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState == null) return;
Set<ComponentName> bindingServices = userState.getBindingServicesLocked();
if (bindingServices.contains(mComponentName) || mWasConnectedAndDied) {
@@ -240,7 +239,7 @@
if (!hasRightsToCurrentUserLocked()) {
return false;
}
- final UserState userState = mUserStateWeakReference.get();
+ final AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState == null) return false;
return userState.setSoftKeyboardModeLocked(showMode, mComponentName);
}
@@ -248,8 +247,8 @@
@Override
public int getSoftKeyboardShowMode() {
- final UserState userState = mUserStateWeakReference.get();
- return (userState != null) ? userState.getSoftKeyboardShowMode() : 0;
+ final AccessibilityUserState userState = mUserStateWeakReference.get();
+ return (userState != null) ? userState.getSoftKeyboardShowModeLocked() : 0;
}
@Override
@@ -258,7 +257,7 @@
if (!hasRightsToCurrentUserLocked()) {
return false;
}
- UserState userState = mUserStateWeakReference.get();
+ AccessibilityUserState userState = mUserStateWeakReference.get();
return (userState != null) && isAccessibilityButtonAvailableLocked(userState);
}
}
@@ -273,7 +272,7 @@
return;
}
mWasConnectedAndDied = true;
- UserState userState = mUserStateWeakReference.get();
+ AccessibilityUserState userState = mUserStateWeakReference.get();
if (userState != null) {
userState.serviceDisconnectedLocked(this);
}
@@ -283,7 +282,7 @@
}
}
- public boolean isAccessibilityButtonAvailableLocked(UserState userState) {
+ public boolean isAccessibilityButtonAvailableLocked(AccessibilityUserState userState) {
// If the service does not request the accessibility button, it isn't available
if (!mRequestAccessibilityButton) {
return false;
@@ -295,8 +294,8 @@
}
// If magnification is on and assigned to the accessibility button, services cannot be
- if (userState.mIsNavBarMagnificationEnabled
- && userState.mIsNavBarMagnificationAssignedToAccessibilityButton) {
+ if (userState.isNavBarMagnificationEnabledLocked()
+ && userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
return false;
}
@@ -314,13 +313,14 @@
return true;
} else {
// With more than one active service, we derive the target from the user's settings
- if (userState.mServiceAssignedToAccessibilityButton == null) {
+ if (userState.getServiceAssignedToAccessibilityButtonLocked() == null) {
// If the user has not made an assignment, we treat the button as available to
// all services until the user interacts with the button to make an assignment
return true;
} else {
// If an assignment was made, it defines availability
- return mComponentName.equals(userState.mServiceAssignedToAccessibilityButton);
+ return mComponentName.equals(
+ userState.getServiceAssignedToAccessibilityButtonLocked());
}
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
new file mode 100644
index 0000000..69f1e0e
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -0,0 +1,573 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.accessibility;
+
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_AUTO;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_IGNORE_HARD_KEYBOARD;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_MASK;
+
+import android.accessibilityservice.AccessibilityService.SoftKeyboardShowMode;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.AccessibilityShortcutInfo;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteCallbackList;
+import android.provider.Settings;
+import android.util.Slog;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManagerClient;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Class that hold states and settings per user and share between
+ * {@link AccessibilityManagerService} and {@link AccessibilityServiceConnection}.
+ */
+class AccessibilityUserState {
+ private static final String LOG_TAG = AccessibilityUserState.class.getSimpleName();
+
+ final int mUserId;
+
+ // Non-transient state.
+
+ final RemoteCallbackList<IAccessibilityManagerClient> mUserClients = new RemoteCallbackList<>();
+
+ // Transient state.
+
+ final ArrayList<AccessibilityServiceConnection> mBoundServices = new ArrayList<>();
+
+ final Map<ComponentName, AccessibilityServiceConnection> mComponentNameToServiceMap =
+ new HashMap<>();
+
+ final List<AccessibilityServiceInfo> mInstalledServices = new ArrayList<>();
+
+ final List<AccessibilityShortcutInfo> mInstalledShortcuts = new ArrayList<>();
+
+ final Set<ComponentName> mBindingServices = new HashSet<>();
+
+ final Set<ComponentName> mEnabledServices = new HashSet<>();
+
+ final Set<ComponentName> mTouchExplorationGrantedServices = new HashSet<>();
+
+ private final ServiceInfoChangeListener mServiceInfoChangeListener;
+
+ private ComponentName mServiceAssignedToAccessibilityButton;
+
+ private ComponentName mServiceChangingSoftKeyboardMode;
+
+ private ComponentName mServiceToEnableWithShortcut;
+
+ private boolean mBindInstantServiceAllowed;
+ private boolean mIsAutoclickEnabled;
+ private boolean mIsDisplayMagnificationEnabled;
+ private boolean mIsFilterKeyEventsEnabled;
+ private boolean mIsNavBarMagnificationAssignedToAccessibilityButton;
+ private boolean mIsNavBarMagnificationEnabled;
+ private boolean mIsPerformGesturesEnabled;
+ private boolean mIsTextHighContrastEnabled;
+ private boolean mIsTouchExplorationEnabled;
+ private int mUserInteractiveUiTimeout;
+ private int mUserNonInteractiveUiTimeout;
+ private int mNonInteractiveUiTimeout = 0;
+ private int mInteractiveUiTimeout = 0;
+ private int mLastSentClientState = -1;
+
+ private Context mContext;
+
+ @SoftKeyboardShowMode
+ private int mSoftKeyboardShowMode = SHOW_MODE_AUTO;
+
+ interface ServiceInfoChangeListener {
+ void onServiceInfoChangedLocked(AccessibilityUserState userState);
+ }
+
+ AccessibilityUserState(int userId, @NonNull Context context,
+ @NonNull ServiceInfoChangeListener serviceInfoChangeListener) {
+ mUserId = userId;
+ mContext = context;
+ mServiceInfoChangeListener = serviceInfoChangeListener;
+ }
+
+ boolean isHandlingAccessibilityEventsLocked() {
+ return !mBoundServices.isEmpty() || !mBindingServices.isEmpty();
+ }
+
+ void onSwitchToAnotherUserLocked() {
+ // Unbind all services.
+ unbindAllServicesLocked();
+
+ // Clear service management state.
+ mBoundServices.clear();
+ mBindingServices.clear();
+
+ // Clear event management state.
+ mLastSentClientState = -1;
+
+ // clear UI timeout
+ mNonInteractiveUiTimeout = 0;
+ mInteractiveUiTimeout = 0;
+
+ // Clear state persisted in settings.
+ mEnabledServices.clear();
+ mTouchExplorationGrantedServices.clear();
+ mIsTouchExplorationEnabled = false;
+ mIsDisplayMagnificationEnabled = false;
+ mIsNavBarMagnificationEnabled = false;
+ mServiceAssignedToAccessibilityButton = null;
+ mIsNavBarMagnificationAssignedToAccessibilityButton = false;
+ mIsAutoclickEnabled = false;
+ mUserNonInteractiveUiTimeout = 0;
+ mUserInteractiveUiTimeout = 0;
+ }
+
+ void addServiceLocked(AccessibilityServiceConnection serviceConnection) {
+ if (!mBoundServices.contains(serviceConnection)) {
+ serviceConnection.onAdded();
+ mBoundServices.add(serviceConnection);
+ mComponentNameToServiceMap.put(serviceConnection.getComponentName(), serviceConnection);
+ mServiceInfoChangeListener.onServiceInfoChangedLocked(this);
+ }
+ }
+
+ /**
+ * Removes a service.
+ * There are three states to a service here: off, bound, and binding.
+ * This stops tracking the service as bound.
+ *
+ * @param serviceConnection The service.
+ */
+ void removeServiceLocked(AccessibilityServiceConnection serviceConnection) {
+ mBoundServices.remove(serviceConnection);
+ serviceConnection.onRemoved();
+ if ((mServiceChangingSoftKeyboardMode != null)
+ && (mServiceChangingSoftKeyboardMode.equals(
+ serviceConnection.getServiceInfo().getComponentName()))) {
+ setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null);
+ }
+ // It may be possible to bind a service twice, which confuses the map. Rebuild the map
+ // to make sure we can still reach a service
+ mComponentNameToServiceMap.clear();
+ for (int i = 0; i < mBoundServices.size(); i++) {
+ AccessibilityServiceConnection boundClient = mBoundServices.get(i);
+ mComponentNameToServiceMap.put(boundClient.getComponentName(), boundClient);
+ }
+ mServiceInfoChangeListener.onServiceInfoChangedLocked(this);
+ }
+
+ /**
+ * Make sure a services disconnected but still 'on' state is reflected in AccessibilityUserState
+ * There are three states to a service here: off, bound, and binding.
+ * This drops a service from a bound state, to the binding state.
+ * The binding state describes the situation where a service is on, but not bound.
+ *
+ * @param serviceConnection The service.
+ */
+ void serviceDisconnectedLocked(AccessibilityServiceConnection serviceConnection) {
+ removeServiceLocked(serviceConnection);
+ mBindingServices.add(serviceConnection.getComponentName());
+ }
+
+ /**
+ * Set the soft keyboard mode. This mode is a bit odd, as it spans multiple settings.
+ * The ACCESSIBILITY_SOFT_KEYBOARD_MODE setting can be checked by the rest of the system
+ * to see if it should suppress showing the IME. The SHOW_IME_WITH_HARD_KEYBOARD setting
+ * setting can be changed by the user, and prevents the system from suppressing the soft
+ * keyboard when the hard keyboard is connected. The hard keyboard setting needs to defer
+ * to the user's preference, if they have supplied one.
+ *
+ * @param newMode The new mode
+ * @param requester The service requesting the change, so we can undo it when the
+ * service stops. Set to null if something other than a service is forcing
+ * the change.
+ *
+ * @return Whether or not the soft keyboard mode equals the new mode after the call
+ */
+ boolean setSoftKeyboardModeLocked(@SoftKeyboardShowMode int newMode,
+ @Nullable ComponentName requester) {
+ if ((newMode != SHOW_MODE_AUTO)
+ && (newMode != SHOW_MODE_HIDDEN)
+ && (newMode != SHOW_MODE_IGNORE_HARD_KEYBOARD)) {
+ Slog.w(LOG_TAG, "Invalid soft keyboard mode");
+ return false;
+ }
+ if (mSoftKeyboardShowMode == newMode) {
+ return true;
+ }
+
+ if (newMode == SHOW_MODE_IGNORE_HARD_KEYBOARD) {
+ if (hasUserOverriddenHardKeyboardSetting()) {
+ // The user has specified a default for this setting
+ return false;
+ }
+ // Save the original value. But don't do this if the value in settings is already
+ // the new mode. That happens when we start up after a reboot, and we don't want
+ // to overwrite the value we had from when we first started controlling the setting.
+ if (getSoftKeyboardValueFromSettings() != SHOW_MODE_IGNORE_HARD_KEYBOARD) {
+ setOriginalHardKeyboardValue(getSecureIntForUser(
+ Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0, mUserId) != 0);
+ }
+ putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 1, mUserId);
+ } else if (mSoftKeyboardShowMode == SHOW_MODE_IGNORE_HARD_KEYBOARD) {
+ putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
+ getOriginalHardKeyboardValue() ? 1 : 0, mUserId);
+ }
+
+ saveSoftKeyboardValueToSettings(newMode);
+ mSoftKeyboardShowMode = newMode;
+ mServiceChangingSoftKeyboardMode = requester;
+ for (int i = mBoundServices.size() - 1; i >= 0; i--) {
+ final AccessibilityServiceConnection service = mBoundServices.get(i);
+ service.notifySoftKeyboardShowModeChangedLocked(mSoftKeyboardShowMode);
+ }
+ return true;
+ }
+
+ @SoftKeyboardShowMode
+ int getSoftKeyboardShowModeLocked() {
+ return mSoftKeyboardShowMode;
+ }
+
+ /**
+ * If the settings are inconsistent with the internal state, make the internal state
+ * match the settings.
+ */
+ void reconcileSoftKeyboardModeWithSettingsLocked() {
+ final boolean showWithHardKeyboardSettings =
+ getSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0, mUserId) != 0;
+ if (mSoftKeyboardShowMode == SHOW_MODE_IGNORE_HARD_KEYBOARD) {
+ if (!showWithHardKeyboardSettings) {
+ // The user has overridden the setting. Respect that and prevent further changes
+ // to this behavior.
+ setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null);
+ setUserOverridesHardKeyboardSetting();
+ }
+ }
+
+ // If the setting and the internal state are out of sync, set both to default
+ if (getSoftKeyboardValueFromSettings() != mSoftKeyboardShowMode) {
+ Slog.e(LOG_TAG, "Show IME setting inconsistent with internal state. Overwriting");
+ setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null);
+ putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ SHOW_MODE_AUTO, mUserId);
+ }
+ }
+
+ boolean getBindInstantServiceAllowedLocked() {
+ return mBindInstantServiceAllowed;
+ }
+
+ /* Need to have a permission check on callee */
+ void setBindInstantServiceAllowedLocked(boolean allowed) {
+ mBindInstantServiceAllowed = allowed;
+ }
+
+ Set<ComponentName> getBindingServicesLocked() {
+ return mBindingServices;
+ }
+
+ /**
+ * Returns enabled service list.
+ */
+ Set<ComponentName> getEnabledServicesLocked() {
+ return mEnabledServices;
+ }
+
+ List<AccessibilityServiceConnection> getBoundServicesLocked() {
+ return mBoundServices;
+ }
+
+ int getClientStateLocked(boolean isUiAutomationRunning) {
+ int clientState = 0;
+ final boolean a11yEnabled = isUiAutomationRunning
+ || isHandlingAccessibilityEventsLocked();
+ if (a11yEnabled) {
+ clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
+ }
+ // Touch exploration relies on enabled accessibility.
+ if (a11yEnabled && mIsTouchExplorationEnabled) {
+ clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
+ }
+ if (mIsTextHighContrastEnabled) {
+ clientState |= AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED;
+ }
+ return clientState;
+ }
+
+ private void setUserOverridesHardKeyboardSetting() {
+ final int softKeyboardSetting = getSecureIntForUser(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, SHOW_MODE_AUTO, mUserId);
+ putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ softKeyboardSetting | SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN,
+ mUserId);
+ }
+
+ private boolean hasUserOverriddenHardKeyboardSetting() {
+ final int softKeyboardSetting = getSecureIntForUser(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, SHOW_MODE_AUTO, mUserId);
+ return (softKeyboardSetting & SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN)
+ != 0;
+ }
+
+ private void setOriginalHardKeyboardValue(boolean originalHardKeyboardValue) {
+ final int oldSoftKeyboardSetting = getSecureIntForUser(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, SHOW_MODE_AUTO, mUserId);
+ final int newSoftKeyboardSetting = oldSoftKeyboardSetting
+ & (~SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE)
+ | ((originalHardKeyboardValue) ? SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE : 0);
+ putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ newSoftKeyboardSetting, mUserId);
+ }
+
+ private void saveSoftKeyboardValueToSettings(int softKeyboardShowMode) {
+ final int oldSoftKeyboardSetting = getSecureIntForUser(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, SHOW_MODE_AUTO, mUserId);
+ final int newSoftKeyboardSetting = oldSoftKeyboardSetting & (~SHOW_MODE_MASK)
+ | softKeyboardShowMode;
+ putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ newSoftKeyboardSetting, mUserId);
+ }
+
+ private int getSoftKeyboardValueFromSettings() {
+ return getSecureIntForUser(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, SHOW_MODE_AUTO, mUserId)
+ & SHOW_MODE_MASK;
+ }
+
+ private boolean getOriginalHardKeyboardValue() {
+ return (getSecureIntForUser(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, SHOW_MODE_AUTO, mUserId)
+ & SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE) != 0;
+ }
+
+ private void unbindAllServicesLocked() {
+ final List<AccessibilityServiceConnection> services = mBoundServices;
+ for (int count = services.size(); count > 0; count--) {
+ // When the service is unbound, it disappears from the list, so there's no need to
+ // keep track of the index
+ services.get(0).unbindLocked();
+ }
+ }
+
+ private int getSecureIntForUser(String key, int def, int userId) {
+ return Settings.Secure.getIntForUser(mContext.getContentResolver(), key, def, userId);
+ }
+
+ private void putSecureIntForUser(String key, int value, int userId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(), key, value, userId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.append("User state[");
+ pw.println();
+ pw.append(" attributes:{id=").append(String.valueOf(mUserId));
+ pw.append(", touchExplorationEnabled=").append(String.valueOf(mIsTouchExplorationEnabled));
+ pw.append(", displayMagnificationEnabled=").append(String.valueOf(
+ mIsDisplayMagnificationEnabled));
+ pw.append(", navBarMagnificationEnabled=").append(String.valueOf(
+ mIsNavBarMagnificationEnabled));
+ pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled));
+ pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveUiTimeout));
+ pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveUiTimeout));
+ pw.append(", installedServiceCount=").append(String.valueOf(mInstalledServices.size()));
+ pw.append("}");
+ pw.println();
+ pw.append(" Bound services:{");
+ final int serviceCount = mBoundServices.size();
+ for (int j = 0; j < serviceCount; j++) {
+ if (j > 0) {
+ pw.append(", ");
+ pw.println();
+ pw.append(" ");
+ }
+ AccessibilityServiceConnection service = mBoundServices.get(j);
+ service.dump(fd, pw, args);
+ }
+ pw.println("}");
+ pw.append(" Enabled services:{");
+ Iterator<ComponentName> it = mEnabledServices.iterator();
+ if (it.hasNext()) {
+ ComponentName componentName = it.next();
+ pw.append(componentName.toShortString());
+ while (it.hasNext()) {
+ componentName = it.next();
+ pw.append(", ");
+ pw.append(componentName.toShortString());
+ }
+ }
+ pw.println("}");
+ pw.append(" Binding services:{");
+ it = mBindingServices.iterator();
+ if (it.hasNext()) {
+ ComponentName componentName = it.next();
+ pw.append(componentName.toShortString());
+ while (it.hasNext()) {
+ componentName = it.next();
+ pw.append(", ");
+ pw.append(componentName.toShortString());
+ }
+ }
+ pw.println("}]");
+ }
+
+ public boolean isAutoclickEnabledLocked() {
+ return mIsAutoclickEnabled;
+ }
+
+ public void setAutoclickEnabledLocked(boolean enabled) {
+ mIsAutoclickEnabled = enabled;
+ }
+
+ public boolean isDisplayMagnificationEnabledLocked() {
+ return mIsDisplayMagnificationEnabled;
+ }
+
+ public void setDisplayMagnificationEnabledLocked(boolean enabled) {
+ mIsDisplayMagnificationEnabled = enabled;
+ }
+
+ public boolean isFilterKeyEventsEnabledLocked() {
+ return mIsFilterKeyEventsEnabled;
+ }
+
+ public void setFilterKeyEventsEnabledLocked(boolean enabled) {
+ mIsFilterKeyEventsEnabled = enabled;
+ }
+
+ public int getInteractiveUiTimeoutLocked() {
+ return mInteractiveUiTimeout;
+ }
+
+ public void setInteractiveUiTimeoutLocked(int timeout) {
+ mInteractiveUiTimeout = timeout;
+ }
+
+ public int getLastSentClientStateLocked() {
+ return mLastSentClientState;
+ }
+
+ public void setLastSentClientStateLocked(int state) {
+ mLastSentClientState = state;
+ }
+
+ public boolean isNavBarMagnificationAssignedToAccessibilityButtonLocked() {
+ return mIsNavBarMagnificationAssignedToAccessibilityButton;
+ }
+
+ public void setNavBarMagnificationAssignedToAccessibilityButtonLocked(boolean assigned) {
+ mIsNavBarMagnificationAssignedToAccessibilityButton = assigned;
+ }
+
+ public boolean isNavBarMagnificationEnabledLocked() {
+ return mIsNavBarMagnificationEnabled;
+ }
+
+ public void setNavBarMagnificationEnabledLocked(boolean enabled) {
+ mIsNavBarMagnificationEnabled = enabled;
+ }
+
+ public int getNonInteractiveUiTimeoutLocked() {
+ return mNonInteractiveUiTimeout;
+ }
+
+ public void setNonInteractiveUiTimeoutLocked(int timeout) {
+ mNonInteractiveUiTimeout = timeout;
+ }
+
+ public boolean isPerformGesturesEnabledLocked() {
+ return mIsPerformGesturesEnabled;
+ }
+
+ public void setPerformGesturesEnabledLocked(boolean enabled) {
+ mIsPerformGesturesEnabled = enabled;
+ }
+
+ public ComponentName getServiceAssignedToAccessibilityButtonLocked() {
+ return mServiceAssignedToAccessibilityButton;
+ }
+
+ public void setServiceAssignedToAccessibilityButtonLocked(ComponentName componentName) {
+ mServiceAssignedToAccessibilityButton = componentName;
+ }
+
+ public ComponentName getServiceChangingSoftKeyboardModeLocked() {
+ return mServiceChangingSoftKeyboardMode;
+ }
+
+ public void setServiceChangingSoftKeyboardModeLocked(
+ ComponentName serviceChangingSoftKeyboardMode) {
+ mServiceChangingSoftKeyboardMode = serviceChangingSoftKeyboardMode;
+ }
+
+ public ComponentName getServiceToEnableWithShortcutLocked() {
+ return mServiceToEnableWithShortcut;
+ }
+
+ public void setServiceToEnableWithShortcutLocked(ComponentName componentName) {
+ mServiceToEnableWithShortcut = componentName;
+ }
+
+ public boolean isTextHighContrastEnabledLocked() {
+ return mIsTextHighContrastEnabled;
+ }
+
+ public void setTextHighContrastEnabledLocked(boolean enabled) {
+ mIsTextHighContrastEnabled = enabled;
+ }
+
+ public boolean isTouchExplorationEnabledLocked() {
+ return mIsTouchExplorationEnabled;
+ }
+
+ public void setTouchExplorationEnabledLocked(boolean enabled) {
+ mIsTouchExplorationEnabled = enabled;
+ }
+
+ public int getUserInteractiveUiTimeoutLocked() {
+ return mUserInteractiveUiTimeout;
+ }
+
+ public void setUserInteractiveUiTimeoutLocked(int timeout) {
+ mUserInteractiveUiTimeout = timeout;
+ }
+
+ public int getUserNonInteractiveUiTimeoutLocked() {
+ return mUserNonInteractiveUiTimeout;
+ }
+
+ public void setUserNonInteractiveUiTimeoutLocked(int timeout) {
+ mUserNonInteractiveUiTimeout = timeout;
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
similarity index 87%
rename from services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
rename to services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index b9b2654..19ac0d3 100644
--- a/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -1,17 +1,17 @@
/*
- ** 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.
+ * 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.accessibility;
@@ -40,12 +40,12 @@
/**
* Handle the back-end of AccessibilityService#performGlobalAction
*/
-public class GlobalActionPerformer {
+public class SystemActionPerformer {
private final WindowManagerInternal mWindowManagerService;
private final Context mContext;
private Supplier<ScreenshotHelper> mScreenshotHelperSupplier;
- public GlobalActionPerformer(Context context, WindowManagerInternal windowManagerInternal) {
+ public SystemActionPerformer(Context context, WindowManagerInternal windowManagerInternal) {
mContext = context;
mWindowManagerService = windowManagerInternal;
mScreenshotHelperSupplier = null;
@@ -53,13 +53,16 @@
// Used to mock ScreenshotHelper
@VisibleForTesting
- public GlobalActionPerformer(Context context, WindowManagerInternal windowManagerInternal,
+ public SystemActionPerformer(Context context, WindowManagerInternal windowManagerInternal,
Supplier<ScreenshotHelper> screenshotHelperSupplier) {
this(context, windowManagerInternal);
mScreenshotHelperSupplier = screenshotHelperSupplier;
}
- public boolean performGlobalAction(int action) {
+ /**
+ * Performe the system action matching the given action id.
+ */
+ public boolean performSystemAction(int action) {
final long identity = Binder.clearCallingIdentity();
try {
switch (action) {
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 79d975d..7dd4a70 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -87,7 +87,7 @@
AccessibilitySecurityPolicy securityPolicy,
AbstractAccessibilityServiceConnection.SystemSupport systemSupport,
WindowManagerInternal windowManagerInternal,
- GlobalActionPerformer globalActionPerfomer,
+ SystemActionPerformer systemActionPerfomer,
AccessibilityWindowManager awm, int flags) {
synchronized (mLock) {
accessibilityServiceInfo.setComponentName(COMPONENT_NAME);
@@ -108,7 +108,7 @@
mSystemSupport = systemSupport;
mUiAutomationService = new UiAutomationService(context, accessibilityServiceInfo, id,
mainHandler, mLock, securityPolicy, systemSupport, windowManagerInternal,
- globalActionPerfomer, awm);
+ systemActionPerfomer, awm);
mUiAutomationServiceOwner = owner;
mUiAutomationFlags = flags;
mUiAutomationServiceInfo = accessibilityServiceInfo;
@@ -226,9 +226,9 @@
int id, Handler mainHandler, Object lock,
AccessibilitySecurityPolicy securityPolicy,
SystemSupport systemSupport, WindowManagerInternal windowManagerInternal,
- GlobalActionPerformer globalActionPerfomer, AccessibilityWindowManager awm) {
+ SystemActionPerformer systemActionPerfomer, AccessibilityWindowManager awm) {
super(context, COMPONENT_NAME, accessibilityServiceInfo, id, mainHandler, lock,
- securityPolicy, systemSupport, windowManagerInternal, globalActionPerfomer,
+ securityPolicy, systemSupport, windowManagerInternal, systemActionPerfomer,
awm);
mMainHandler = mainHandler;
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index b35300c..7d129ea 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -286,7 +286,7 @@
window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
- window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS);
+ window.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
window.setGravity(Gravity.BOTTOM | Gravity.CENTER);
window.setCloseOnTouchOutside(true);
diff --git a/services/backup/backuplib/java/com/android/server/backup/TransportManager.java b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
index de48f4b..30ce4cf 100644
--- a/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/backuplib/java/com/android/server/backup/TransportManager.java
@@ -93,8 +93,7 @@
mTransportWhitelist = Preconditions.checkNotNull(whitelist);
mCurrentTransportName = selectedTransport;
mTransportStats = new TransportStats();
- mTransportClientManager = TransportClientManager.createEncryptingClientManager(mUserId,
- context, mTransportStats);
+ mTransportClientManager = new TransportClientManager(mUserId, context, mTransportStats);
}
@VisibleForTesting
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index e5e11ea..ac006df 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -239,7 +239,6 @@
private final KeyValueBackupReporter mReporter;
private final OnTaskFinishedListener mTaskFinishedListener;
private final boolean mUserInitiated;
- private final boolean mNonIncremental;
private final int mCurrentOpToken;
private final int mUserId;
private final File mStateDirectory;
@@ -264,6 +263,7 @@
// and at least one of the packages had data. Used to avoid updating current token for
// empty backups.
private boolean mHasDataToBackup;
+ private boolean mNonIncremental;
/**
* This {@link ConditionVariable} is used to signal that the cancel operation has been
@@ -412,6 +412,11 @@
try {
IBackupTransport transport = mTransportClient.connectOrThrow("KVBT.startTask()");
String transportName = transport.name();
+ if (transportName.contains("EncryptedLocalTransport")) {
+ // Temporary code for EiTF POC. Only supports non-incremental backups.
+ mNonIncremental = true;
+ }
+
mReporter.onTransportReady(transportName);
// If we haven't stored PM metadata yet, we must initialize the transport.
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index c8dbb36..27824af 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -18,11 +18,13 @@
import static android.Manifest.permission.MANAGE_CONTENT_CAPTURE;
import static android.content.Context.CONTENT_CAPTURE_MANAGER_SERVICE;
+import static android.service.contentcapture.ContentCaptureService.setClientState;
import static android.view.contentcapture.ContentCaptureHelper.toList;
import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_FALSE;
import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_OK;
import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_SECURITY_EXCEPTION;
import static android.view.contentcapture.ContentCaptureManager.RESULT_CODE_TRUE;
+import static android.view.contentcapture.ContentCaptureSession.STATE_DISABLED;
import static com.android.internal.util.SyncResultReceiver.bundleFor;
@@ -520,6 +522,17 @@
return true;
}
+ @GuardedBy("mLock")
+ private boolean isDefaultServiceLocked(int userId) {
+ final String defaultServiceName = mServiceNameResolver.getDefaultServiceName(userId);
+ if (defaultServiceName == null) {
+ return false;
+ }
+
+ final String currentServiceName = mServiceNameResolver.getServiceName(userId);
+ return defaultServiceName.equals(currentServiceName);
+ }
+
@Override // from AbstractMasterSystemService
protected void dumpLocked(String prefix, PrintWriter pw) {
super.dumpLocked(prefix, pw);
@@ -557,6 +570,10 @@
synchronized (mLock) {
final ContentCapturePerUserService service = getServiceForUserLocked(userId);
+ if (!isDefaultServiceLocked(userId) && !isCalledByServiceLocked("startSession()")) {
+ setClientState(result, STATE_DISABLED, /* binder= */ null);
+ return;
+ }
service.startSessionLocked(activityToken, activityPresentationInfo, sessionId,
Binder.getCallingUid(), flags, result);
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 4e80977..4f4e47a 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -146,7 +146,8 @@
name: "services.core.json.gz",
srcs: [":checked-protolog.json"],
out: ["services.core.protolog.json.gz"],
- cmd: "gzip < $(in) > $(out)",
+ cmd: "$(location minigzip) -c < $(in) > $(out)",
+ tools: ["minigzip"],
}
prebuilt_etc {
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
similarity index 98%
rename from core/java/android/content/pm/PackageManagerInternal.java
rename to services/core/java/android/content/pm/PackageManagerInternal.java
index 3eef92f..dc24cff 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -33,6 +33,8 @@
import android.util.ArraySet;
import android.util.SparseArray;
+import com.android.server.pm.PackageList;
+
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -57,6 +59,8 @@
public static final int PACKAGE_CONFIGURATOR = 9;
public static final int PACKAGE_INCIDENT_REPORT_APPROVER = 10;
public static final int PACKAGE_APP_PREDICTOR = 11;
+ public static final int PACKAGE_TELEPHONY = 12;
+ public static final int PACKAGE_WIFI = 13;
@IntDef(value = {
PACKAGE_SYSTEM,
PACKAGE_SETUP_WIZARD,
@@ -70,6 +74,8 @@
PACKAGE_CONFIGURATOR,
PACKAGE_INCIDENT_REPORT_APPROVER,
PACKAGE_APP_PREDICTOR,
+ PACKAGE_TELEPHONY,
+ PACKAGE_WIFI,
})
@Retention(RetentionPolicy.SOURCE)
public @interface KnownPackage {}
@@ -544,10 +550,11 @@
*/
public abstract boolean isResolveActivityComponent(@NonNull ComponentInfo component);
+
/**
- * Returns the package name for a known package.
+ * Returns a list of package names for a known package
*/
- public abstract @Nullable String getKnownPackageName(
+ public abstract @NonNull String[] getKnownPackageNames(
@KnownPackage int knownPackage, int userId);
/**
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 9a97ddb..b41e95f 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -47,6 +47,7 @@
import android.content.pm.PermissionInfo;
import android.database.ContentObserver;
import android.net.Uri;
+import android.os.BatteryManager;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -1564,6 +1565,7 @@
Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, UserHandle.ALL);
mClockReceiver = mInjector.getClockReceiver(this);
+ new ChargingReceiver();
new InteractiveStateReceiver();
new UninstallReceiver();
@@ -4148,7 +4150,7 @@
public static final int LISTENER_TIMEOUT = 3;
public static final int REPORT_ALARMS_ACTIVE = 4;
public static final int APP_STANDBY_BUCKET_CHANGED = 5;
- public static final int APP_STANDBY_PAROLE_CHANGED = 6;
+ public static final int CHARGING_STATUS_CHANGED = 6;
public static final int REMOVE_FOR_STOPPED = 7;
public static final int UNREGISTER_CANCEL_LISTENER = 8;
@@ -4206,7 +4208,7 @@
}
break;
- case APP_STANDBY_PAROLE_CHANGED:
+ case CHARGING_STATUS_CHANGED:
synchronized (mLock) {
mAppStandbyParole = (Boolean) msg.obj;
if (reorderAlarmsBasedOnStandbyBuckets(null)) {
@@ -4247,6 +4249,37 @@
}
}
+ @VisibleForTesting
+ class ChargingReceiver extends BroadcastReceiver {
+ ChargingReceiver() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BatteryManager.ACTION_CHARGING);
+ filter.addAction(BatteryManager.ACTION_DISCHARGING);
+ getContext().registerReceiver(this, filter);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ final boolean charging;
+ if (BatteryManager.ACTION_CHARGING.equals(action)) {
+ if (DEBUG_STANDBY) {
+ Slog.d(TAG, "Device is charging.");
+ }
+ charging = true;
+ } else {
+ if (DEBUG_STANDBY) {
+ Slog.d(TAG, "Disconnected from power.");
+ }
+ charging = false;
+ }
+ mHandler.removeMessages(AlarmHandler.CHARGING_STATUS_CHANGED);
+ mHandler.obtainMessage(AlarmHandler.CHARGING_STATUS_CHANGED, charging)
+ .sendToTarget();
+ }
+ }
+
+ @VisibleForTesting
class ClockReceiver extends BroadcastReceiver {
public ClockReceiver() {
IntentFilter filter = new IntentFilter();
@@ -4429,7 +4462,7 @@
@Override public void onUidCachedChanged(int uid, boolean cached) {
}
- };
+ }
/**
* Tracking of app assignments to standby buckets
@@ -4447,18 +4480,7 @@
mHandler.obtainMessage(AlarmHandler.APP_STANDBY_BUCKET_CHANGED, userId, -1, packageName)
.sendToTarget();
}
-
- @Override
- public void onParoleStateChanged(boolean isParoleOn) {
- if (DEBUG_STANDBY) {
- Slog.d(TAG, "Global parole state now " + (isParoleOn ? "ON" : "OFF"));
- }
- mHandler.removeMessages(AlarmHandler.APP_STANDBY_BUCKET_CHANGED);
- mHandler.removeMessages(AlarmHandler.APP_STANDBY_PAROLE_CHANGED);
- mHandler.obtainMessage(AlarmHandler.APP_STANDBY_PAROLE_CHANGED,
- Boolean.valueOf(isParoleOn)).sendToTarget();
- }
- };
+ }
private final Listener mForceAppStandbyListener = new Listener() {
@Override
diff --git a/services/core/java/com/android/server/AppStateTracker.java b/services/core/java/com/android/server/AppStateTracker.java
index 2c67c50..da760b6 100644
--- a/services/core/java/com/android/server/AppStateTracker.java
+++ b/services/core/java/com/android/server/AppStateTracker.java
@@ -71,8 +71,7 @@
* - Temporary power save whitelist
* - Global "force all apps standby" mode enforced by battery saver.
*
- * Test:
- atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java
+ * Test: atest com.android.server.AppStateTrackerTest
*/
public class AppStateTracker {
private static final String TAG = "AppStateTracker";
@@ -710,10 +709,6 @@
mHandler.notifyExemptChanged();
}
}
-
- @Override
- public void onParoleStateChanged(boolean isParoleOn) {
- }
}
private Listener[] cloneListeners() {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index e0f60b4..0bb72cb 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1149,7 +1149,6 @@
private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) {
final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
- netCap.addCapability(NET_CAPABILITY_NOT_RESTRICTED);
netCap.removeCapability(NET_CAPABILITY_NOT_VPN);
netCap.setSingleUid(uid);
return netCap;
@@ -1159,7 +1158,6 @@
int transportType, NetworkRequest.Type type) {
final NetworkCapabilities netCap = new NetworkCapabilities();
netCap.addCapability(NET_CAPABILITY_INTERNET);
- netCap.addCapability(NET_CAPABILITY_NOT_RESTRICTED);
if (transportType > -1) {
netCap.addTransportType(transportType);
}
diff --git a/services/core/java/com/android/server/ContextHubSystemService.java b/services/core/java/com/android/server/ContextHubSystemService.java
index 110847d..c6853a5 100644
--- a/services/core/java/com/android/server/ContextHubSystemService.java
+++ b/services/core/java/com/android/server/ContextHubSystemService.java
@@ -16,12 +16,12 @@
package com.android.server;
-import com.android.internal.util.ConcurrentUtils;
-import com.android.server.location.ContextHubService;
-import com.android.server.SystemServerInitThreadPool;
import android.content.Context;
import android.util.Log;
+import com.android.internal.util.ConcurrentUtils;
+import com.android.server.location.ContextHubService;
+
import java.util.concurrent.Future;
class ContextHubSystemService extends SystemService {
@@ -32,7 +32,7 @@
public ContextHubSystemService(Context context) {
super(context);
- mInit = SystemServerInitThreadPool.get().submit(() -> {
+ mInit = SystemServerInitThreadPool.submit(() -> {
mContextHubService = new ContextHubService(context);
}, "Init ContextHubSystemService");
}
diff --git a/services/core/java/com/android/server/GnssManagerService.java b/services/core/java/com/android/server/GnssManagerService.java
index b6e619e..44a8234 100644
--- a/services/core/java/com/android/server/GnssManagerService.java
+++ b/services/core/java/com/android/server/GnssManagerService.java
@@ -17,6 +17,7 @@
package com.android.server;
import android.Manifest;
+import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.Context;
@@ -298,7 +299,8 @@
* @param packageName name of requesting package
* @return true if callback is successfully added, false otherwise
*/
- public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName) {
+ public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName,
+ @NonNull String listenerIdentity) {
mContext.enforceCallingPermission(
android.Manifest.permission.LOCATION_HARDWARE,
"Location Hardware permission not granted to access hardware batching");
@@ -316,7 +318,8 @@
}
CallerIdentity callerIdentity =
- new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName);
+ new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName,
+ listenerIdentity);
synchronized (mGnssBatchingLock) {
mGnssBatchingCallback = callback;
mGnssBatchingDeathCallback =
@@ -494,7 +497,7 @@
private <TListener extends IInterface> boolean addGnssDataListenerLocked(
TListener listener,
String packageName,
- String listenerName,
+ @NonNull String listenerIdentifier,
RemoteListenerHelper<TListener> gnssDataProvider,
ArrayMap<IBinder,
LinkedListener<TListener>> gnssDataListeners,
@@ -513,10 +516,11 @@
}
CallerIdentity callerIdentity =
- new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName);
+ new CallerIdentity(Binder.getCallingUid(), Binder.getCallingPid(), packageName,
+ listenerIdentifier);
LinkedListener<TListener> linkedListener =
new LocationManagerServiceUtils.LinkedListener<>(
- listener, listenerName, callerIdentity, binderDeathCallback);
+ listener, listenerIdentifier, callerIdentity, binderDeathCallback);
IBinder binder = listener.asBinder();
if (!linkedListener.linkToListenerDeathNotificationLocked(binder)) {
return false;
@@ -606,7 +610,7 @@
return addGnssDataListenerLocked(
listener,
packageName,
- "GnssStatusListener",
+ "Gnss status",
mGnssStatusProvider,
mGnssStatusListeners,
this::unregisterGnssStatusCallback);
@@ -632,12 +636,13 @@
* @return true if listener is successfully added, false otherwise
*/
public boolean addGnssMeasurementsListener(
- IGnssMeasurementsListener listener, String packageName) {
+ IGnssMeasurementsListener listener, String packageName,
+ @NonNull String listenerIdentifier) {
synchronized (mGnssMeasurementsListeners) {
return addGnssDataListenerLocked(
listener,
packageName,
- "GnssMeasurementsListener",
+ listenerIdentifier,
mGnssMeasurementsProvider,
mGnssMeasurementsListeners,
this::removeGnssMeasurementsListener);
@@ -689,12 +694,13 @@
* @return true if listener is successfully added, false otherwise
*/
public boolean addGnssNavigationMessageListener(
- IGnssNavigationMessageListener listener, String packageName) {
+ IGnssNavigationMessageListener listener, String packageName,
+ @NonNull String listenerIdentifier) {
synchronized (mGnssNavigationMessageListeners) {
return addGnssDataListenerLocked(
listener,
packageName,
- "GnssNavigationMessageListener",
+ listenerIdentifier,
mGnssNavigationMessageProvider,
mGnssNavigationMessageListeners,
this::removeGnssNavigationMessageListener);
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 35a06a9..aa22feb 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -1223,8 +1223,10 @@
PowerManager.WakeLock mWakeLock;
private Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid,
- String packageName, WorkSource workSource, boolean hideFromAppOps) {
- super(new CallerIdentity(uid, pid, packageName), "LocationListener");
+ String packageName, WorkSource workSource, boolean hideFromAppOps,
+ @NonNull String listenerIdentifier) {
+ super(new CallerIdentity(uid, pid, packageName, listenerIdentifier),
+ "LocationListener");
mListener = listener;
mPendingIntent = intent;
if (listener != null) {
@@ -1532,9 +1534,12 @@
}
@Override
- public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName) {
+ public boolean addGnssBatchingCallback(IBatchedLocationCallback callback, String packageName,
+ String listenerIdentifier) {
+ Preconditions.checkNotNull(listenerIdentifier);
+
return mGnssManagerService == null ? false : mGnssManagerService.addGnssBatchingCallback(
- callback, packageName);
+ callback, packageName, listenerIdentifier);
}
@Override
@@ -1696,11 +1701,12 @@
}
}
- private boolean reportLocationAccessNoThrow(
- int pid, int uid, String packageName, int allowedResolutionLevel) {
+ private boolean reportLocationAccessNoThrow(int pid, int uid, String packageName,
+ int allowedResolutionLevel, @Nullable String message) {
int op = resolutionLevelToOp(allowedResolutionLevel);
if (op >= 0) {
- if (mAppOps.noteOpNoThrow(op, uid, packageName) != AppOpsManager.MODE_ALLOWED) {
+ if (mAppOps.noteOpNoThrow(op, uid, packageName, message)
+ != AppOpsManager.MODE_ALLOWED) {
return false;
}
}
@@ -2133,12 +2139,13 @@
@GuardedBy("mLock")
private Receiver getReceiverLocked(ILocationListener listener, int pid, int uid,
- String packageName, WorkSource workSource, boolean hideFromAppOps) {
+ String packageName, WorkSource workSource, boolean hideFromAppOps,
+ @NonNull String listenerIdentifier) {
IBinder binder = listener.asBinder();
Receiver receiver = mReceivers.get(binder);
if (receiver == null) {
receiver = new Receiver(listener, null, pid, uid, packageName, workSource,
- hideFromAppOps);
+ hideFromAppOps, listenerIdentifier);
if (!receiver.linkToListenerDeathNotificationLocked(
receiver.getListener().asBinder())) {
return null;
@@ -2150,11 +2157,11 @@
@GuardedBy("mLock")
private Receiver getReceiverLocked(PendingIntent intent, int pid, int uid, String packageName,
- WorkSource workSource, boolean hideFromAppOps) {
+ WorkSource workSource, boolean hideFromAppOps, @NonNull String listenerIdentifier) {
Receiver receiver = mReceivers.get(intent);
if (receiver == null) {
receiver = new Receiver(null, intent, pid, uid, packageName, workSource,
- hideFromAppOps);
+ hideFromAppOps, listenerIdentifier);
mReceivers.put(intent, receiver);
}
return receiver;
@@ -2216,7 +2223,9 @@
@Override
public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
- PendingIntent intent, String packageName) {
+ PendingIntent intent, String packageName, String listenerIdentifier) {
+ Preconditions.checkNotNull(listenerIdentifier);
+
synchronized (mLock) {
if (request == null) request = DEFAULT_LOCATION_REQUEST;
checkPackageName(packageName);
@@ -2271,10 +2280,10 @@
Receiver receiver;
if (intent != null) {
receiver = getReceiverLocked(intent, pid, uid, packageName, workSource,
- hideFromAppOps);
+ hideFromAppOps, listenerIdentifier);
} else {
receiver = getReceiverLocked(listener, pid, uid, packageName, workSource,
- hideFromAppOps);
+ hideFromAppOps, listenerIdentifier);
}
requestLocationUpdatesLocked(sanitizedRequest, receiver, uid, packageName);
} finally {
@@ -2343,9 +2352,9 @@
synchronized (mLock) {
Receiver receiver;
if (intent != null) {
- receiver = getReceiverLocked(intent, pid, uid, packageName, null, false);
+ receiver = getReceiverLocked(intent, pid, uid, packageName, null, false, "");
} else {
- receiver = getReceiverLocked(listener, pid, uid, packageName, null, false);
+ receiver = getReceiverLocked(listener, pid, uid, packageName, null, false, "");
}
long identity = Binder.clearCallingIdentity();
@@ -2464,8 +2473,8 @@
}
// Don't report location access if there is no last location to deliver.
if (lastLocation != null) {
- if (!reportLocationAccessNoThrow(
- pid, uid, packageName, allowedResolutionLevel)) {
+ if (!reportLocationAccessNoThrow(pid, uid, packageName, allowedResolutionLevel,
+ null)) {
if (D) {
Log.d(TAG, "not returning last loc for no op app: " + packageName);
}
@@ -2519,7 +2528,9 @@
@Override
public void requestGeofence(LocationRequest request, Geofence geofence, PendingIntent intent,
- String packageName) {
+ String packageName, String listenerIdentifier) {
+ Preconditions.checkNotNull(listenerIdentifier);
+
if (request == null) request = DEFAULT_LOCATION_REQUEST;
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
checkResolutionLevelIsSufficientForGeofenceUse(allowedResolutionLevel);
@@ -2564,9 +2575,8 @@
mActivityManager.getPackageImportance(packageName));
}
- mGeofenceManager.addFence(sanitizedRequest, geofence, intent,
- allowedResolutionLevel,
- uid, packageName);
+ mGeofenceManager.addFence(sanitizedRequest, geofence, intent, allowedResolutionLevel,
+ uid, packageName, listenerIdentifier);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -2613,10 +2623,13 @@
}
@Override
- public boolean addGnssMeasurementsListener(
- IGnssMeasurementsListener listener, String packageName) {
+ public boolean addGnssMeasurementsListener(IGnssMeasurementsListener listener,
+ String packageName, String listenerIdentifier) {
+ Preconditions.checkNotNull(listenerIdentifier);
+
return mGnssManagerService == null ? false
- : mGnssManagerService.addGnssMeasurementsListener(listener, packageName);
+ : mGnssManagerService.addGnssMeasurementsListener(listener, packageName,
+ listenerIdentifier);
}
@Override
@@ -2643,10 +2656,13 @@
}
@Override
- public boolean addGnssNavigationMessageListener(
- IGnssNavigationMessageListener listener, String packageName) {
+ public boolean addGnssNavigationMessageListener(IGnssNavigationMessageListener listener,
+ String packageName, String listenerIdentifier) {
+ Preconditions.checkNotNull(listenerIdentifier);
+
return mGnssManagerService == null ? false
- : mGnssManagerService.addGnssNavigationMessageListener(listener, packageName);
+ : mGnssManagerService.addGnssNavigationMessageListener(listener, packageName,
+ listenerIdentifier);
}
@Override
@@ -2719,12 +2735,21 @@
return true;
}
}
-
return false;
}
}
@Override
+ public List<String> getProviderPackages(String providerName) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG,
+ Manifest.permission.READ_DEVICE_CONFIG + " permission required");
+ synchronized (mLock) {
+ LocationProvider provider = getLocationProviderLocked(providerName);
+ return provider == null ? Collections.emptyList() : provider.getPackagesLocked();
+ }
+ }
+
+ @Override
public void setExtraLocationControllerPackage(String packageName) {
mContext.enforceCallingPermission(Manifest.permission.LOCATION_HARDWARE,
Manifest.permission.LOCATION_HARDWARE + " permission required");
@@ -2943,7 +2968,8 @@
receiver.mCallerIdentity.mPid,
receiver.mCallerIdentity.mUid,
receiver.mCallerIdentity.mPackageName,
- receiver.mAllowedResolutionLevel)) {
+ receiver.mAllowedResolutionLevel,
+ "Location sent to " + receiver.mCallerIdentity.mListenerIdentifier)) {
if (D) {
Log.d(TAG, "skipping loc update for no op app: "
+ receiver.mCallerIdentity.mPackageName);
@@ -3064,11 +3090,6 @@
try {
LocationProvider oldProvider = getLocationProviderLocked(name);
if (oldProvider != null) {
- if (oldProvider.isMock()) {
- throw new IllegalArgumentException(
- "Provider \"" + name + "\" already exists");
- }
-
removeProviderLocked(oldProvider);
}
@@ -3093,7 +3114,7 @@
try {
LocationProvider testProvider = getLocationProviderLocked(name);
if (testProvider == null || !testProvider.isMock()) {
- throw new IllegalArgumentException("Provider \"" + name + "\" unknown");
+ return;
}
removeProviderLocked(testProvider);
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index bd5ad96..73c8520 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -162,7 +162,7 @@
@Override
public void onStart() {
// Do init on a separate thread, will join in PHASE_ACTIVITY_MANAGER_READY
- SystemServerInitThreadPool.get().submit(() -> {
+ SystemServerInitThreadPool.submit(() -> {
mAllowedUid = getAllowedUid(UserHandle.USER_SYSTEM);
enforceChecksumValidity();
formatIfOemUnlockEnabled();
diff --git a/services/core/java/com/android/server/SystemServerInitThreadPool.java b/services/core/java/com/android/server/SystemServerInitThreadPool.java
index ff6a537..5ed94e3 100644
--- a/services/core/java/com/android/server/SystemServerInitThreadPool.java
+++ b/services/core/java/com/android/server/SystemServerInitThreadPool.java
@@ -16,10 +16,12 @@
package com.android.server;
+import android.annotation.NonNull;
import android.os.Build;
import android.os.Process;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ConcurrentUtils;
import com.android.internal.util.Preconditions;
import com.android.server.am.ActivityManagerService;
@@ -32,9 +34,11 @@
/**
* Thread pool used during initialization of system server.
+ *
* <p>System services can {@link #submit(Runnable)} tasks for execution during boot.
* The pool will be shut down after {@link SystemService#PHASE_BOOT_COMPLETED}.
- * New tasks <em>should not</em> be submitted afterwards.
+ *
+ * <p>New tasks <em>should not</em> be submitted afterwards.
*
* @hide
*/
@@ -42,26 +46,49 @@
private static final String TAG = SystemServerInitThreadPool.class.getSimpleName();
private static final int SHUTDOWN_TIMEOUT_MILLIS = 20000;
private static final boolean IS_DEBUGGABLE = Build.IS_DEBUGGABLE;
+ private static final Object LOCK = new Object();
+ @GuardedBy("LOCK")
private static SystemServerInitThreadPool sInstance;
- private ExecutorService mService = ConcurrentUtils.newFixedThreadPool(
- Runtime.getRuntime().availableProcessors(),
- "system-server-init-thread", Process.THREAD_PRIORITY_FOREGROUND);
+ private final ExecutorService mService;
- private List<String> mPendingTasks = new ArrayList<>();
+ @GuardedBy("mPendingTasks")
+ private final List<String> mPendingTasks = new ArrayList<>();
- public static synchronized SystemServerInitThreadPool get() {
- if (sInstance == null) {
- sInstance = new SystemServerInitThreadPool();
- }
- Preconditions.checkState(sInstance.mService != null, "Cannot get " + TAG
- + " - it has been shut down");
- return sInstance;
+ @GuardedBy("mPendingTasks")
+ private boolean mShutDown;
+
+ private SystemServerInitThreadPool() {
+ final int size = Runtime.getRuntime().availableProcessors();
+ Slog.i(TAG, "Creating instance with " + size + " threads");
+ mService = ConcurrentUtils.newFixedThreadPool(size,
+ "system-server-init-thread", Process.THREAD_PRIORITY_FOREGROUND);
}
- public Future<?> submit(Runnable runnable, String description) {
+ /**
+ * Submits a task for execution.
+ *
+ * @throws IllegalStateException if it hasn't been started or has been shut down already.
+ */
+ public static @NonNull Future<?> submit(@NonNull Runnable runnable,
+ @NonNull String description) {
+ Preconditions.checkNotNull(description, "description cannot be null");
+
+ SystemServerInitThreadPool instance;
+ synchronized (LOCK) {
+ Preconditions.checkState(sInstance != null, "Cannot get " + TAG
+ + " - it has been shut down");
+ instance = sInstance;
+ }
+
+ return instance.submitTask(runnable, description);
+ }
+
+ private @NonNull Future<?> submitTask(@NonNull Runnable runnable,
+ @NonNull String description) {
synchronized (mPendingTasks) {
+ Preconditions.checkState(!mShutDown, TAG + " already shut down");
mPendingTasks.add(description);
}
return mService.submit(() -> {
@@ -83,10 +110,36 @@
});
}
- static synchronized void shutdown() {
- if (sInstance != null && sInstance.mService != null) {
+ /**
+ * Starts it.
+ *
+ * <p>Note:</p> should only be called by {@link SystemServer}.
+ *
+ * @throws IllegalStateException if it has been started already without being shut down yet.
+ */
+ static void start() {
+ synchronized (LOCK) {
+ Preconditions.checkState(sInstance == null, TAG + " already started");
+ sInstance = new SystemServerInitThreadPool();
+ }
+ }
+
+ /**
+ * Shuts it down.
+ *
+ * <p>Note:</p> should only be called by {@link SystemServer}.
+ */
+ static void shutdown() {
+ synchronized (LOCK) {
+ if (sInstance == null) {
+ Slog.wtf(TAG, "Already shutdown", new Exception());
+ return;
+ }
+ synchronized (sInstance.mPendingTasks) {
+ sInstance.mShutDown = true;
+ }
sInstance.mService.shutdown();
- boolean terminated;
+ final boolean terminated;
try {
terminated = sInstance.mService.awaitTermination(SHUTDOWN_TIMEOUT_MILLIS,
TimeUnit.MILLISECONDS);
@@ -100,7 +153,7 @@
// in the thread pool.
dumpStackTraces();
}
- List<Runnable> unstartedRunnables = sInstance.mService.shutdownNow();
+ final List<Runnable> unstartedRunnables = sInstance.mService.shutdownNow();
if (!terminated) {
final List<String> copy = new ArrayList<>();
synchronized (sInstance.mPendingTasks) {
@@ -109,8 +162,7 @@
throw new IllegalStateException("Cannot shutdown. Unstarted tasks "
+ unstartedRunnables + " Unfinished tasks " + copy);
}
- sInstance.mService = null; // Make mService eligible for GC
- sInstance.mPendingTasks = null;
+ sInstance = null; // Make eligible for GC
Slog.d(TAG, "Shutdown successful");
}
}
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 0f8a3b5..447ed59 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -385,7 +385,7 @@
mContext = context;
mBatteryStats = BatteryStatsService.getService();
- int numPhones = TelephonyManager.getDefault().getMaxPhoneCount();
+ int numPhones = TelephonyManager.getDefault().getSupportedModemCount();
if (DBG) log("TelephonyRegistry: ctor numPhones=" + numPhones);
mNumPhones = numPhones;
mCallState = new int[numPhones];
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index b9d7c68..a517467 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -288,7 +288,7 @@
updateNightModeFromSettings(context, res, UserHandle.getCallingUserId());
// Update the initial, static configurations.
- SystemServerInitThreadPool.get().submit(() -> {
+ SystemServerInitThreadPool.submit(() -> {
synchronized (mLock) {
updateConfigurationLocked();
sendConfigurationLocked();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7cbd1fc..55cd933 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -350,6 +350,7 @@
import com.android.server.am.ActivityManagerServiceDumpProcessesProto.UidObserverRegistrationProto;
import com.android.server.appop.AppOpsService;
import com.android.server.compat.CompatConfig;
+import com.android.server.compat.PlatformCompat;
import com.android.server.contentcapture.ContentCaptureManagerInternal;
import com.android.server.firewall.IntentFirewall;
import com.android.server.job.JobSchedulerInternal;
@@ -899,7 +900,7 @@
// The other observer methods are unused
@Override
- public void onIntentStarted(Intent intent) {
+ public void onIntentStarted(Intent intent, long timestampNs) {
}
@Override
@@ -911,7 +912,11 @@
}
@Override
- public void onActivityLaunchFinished(byte[] finalActivity) {
+ public void onActivityLaunchFinished(byte[] finalActivity, long timestampNs) {
+ }
+
+ @Override
+ public void onReportFullyDrawn(byte[] finalActivity, long timestampNs) {
}
};
@@ -1575,6 +1580,8 @@
// Encapsulates the global setting "hidden_api_blacklist_exemptions"
final HiddenApiSettings mHiddenApiBlacklist;
+ private final PlatformCompat mPlatformCompat;
+
PackageManagerInternal mPackageManagerInt;
PermissionManagerServiceInternal mPermissionManagerInt;
@@ -2427,6 +2434,7 @@
mFactoryTest = FACTORY_TEST_OFF;
mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
mInternal = new LocalService();
+ mPlatformCompat = null;
}
// Note: This method is invoked on the main thread but may need to attach various
@@ -2563,6 +2571,9 @@
mHiddenApiBlacklist = new HiddenApiSettings(mHandler, mContext);
+ mPlatformCompat = (PlatformCompat) ServiceManager.getService(
+ Context.PLATFORM_COMPAT_SERVICE);
+
Watchdog.getInstance().addMonitor(this);
Watchdog.getInstance().addThread(mHandler);
@@ -5015,7 +5026,9 @@
if (preBindAgent != null) {
thread.attachAgent(preBindAgent);
}
-
+ if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+ thread.attachStartupAgents(app.info.dataDir);
+ }
// Figure out whether the app needs to run in autofill compat mode.
AutofillOptions autofillOptions = null;
@@ -5042,6 +5055,9 @@
mAtmInternal.preBindApplication(app.getWindowProcessController());
final ActiveInstrumentation instr2 = app.getActiveInstrumentation();
long[] disabledCompatChanges = CompatConfig.get().getDisabledChanges(app.info);
+ if (mPlatformCompat != null) {
+ mPlatformCompat.resetReporting(app.info);
+ }
if (app.isolatedEntryPoint != null) {
// This is an isolated process which should just call an entry point instead of
// being bound to an application.
@@ -8646,7 +8662,7 @@
lp.format = v.getBackground().getOpacity();
lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
((WindowManager)mContext.getSystemService(
Context.WINDOW_SERVICE)).addView(v, lp);
}
@@ -18353,7 +18369,7 @@
@Override
public int getCurrentUserId() {
- return mUserController.getCurrentUserIdLU();
+ return mUserController.getCurrentUserId();
}
@Override
diff --git a/services/core/java/com/android/server/am/AppErrorDialog.java b/services/core/java/com/android/server/am/AppErrorDialog.java
index a80a5b5..852c9b65 100644
--- a/services/core/java/com/android/server/am/AppErrorDialog.java
+++ b/services/core/java/com/android/server/am/AppErrorDialog.java
@@ -92,7 +92,7 @@
WindowManager.LayoutParams attrs = getWindow().getAttributes();
attrs.setTitle("Application Error: " + mProc.info.processName);
attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR
- | WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ | WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
getWindow().setAttributes(attrs);
if (mProc.isPersistent()) {
getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
diff --git a/services/core/java/com/android/server/am/AppNotRespondingDialog.java b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
index cb76e2f..65d7e01 100644
--- a/services/core/java/com/android/server/am/AppNotRespondingDialog.java
+++ b/services/core/java/com/android/server/am/AppNotRespondingDialog.java
@@ -94,7 +94,7 @@
WindowManager.LayoutParams attrs = getWindow().getAttributes();
attrs.setTitle("Application Not Responding: " + mProc.info.processName);
attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR |
- WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
getWindow().setAttributes(attrs);
}
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index 6ffd8a9..5c840ad 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -1101,7 +1101,7 @@
}
boolean sepNeeded = false;
- if (dumpAll || isCheckin) {
+ if ((dumpAll || isCheckin) && !currentOnly) {
mWriteLock.lock();
try {
ArrayList<String> files = getCommittedFiles(0, false, !isCheckin);
diff --git a/services/core/java/com/android/server/am/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java
index 98f5557..bbf5772 100644
--- a/services/core/java/com/android/server/am/UserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/UserSwitchingDialog.java
@@ -78,7 +78,7 @@
WindowManager.LayoutParams attrs = getWindow().getAttributes();
attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR |
- WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
getWindow().setAttributes(attrs);
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 798185a..2ac6eb0 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -65,10 +65,10 @@
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION;
-import android.media.AudioAttributes;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -109,6 +109,7 @@
import com.android.internal.app.IAppOpsService;
import com.android.internal.os.Zygote;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.Preconditions;
@@ -257,6 +258,9 @@
@GuardedBy("this")
private CheckOpsDelegate mCheckOpsDelegate;
+ @GuardedBy("this")
+ private SparseArray<List<Integer>> mSwitchOpToOps;
+
/**
* All times are in milliseconds. These constants are kept synchronized with the system
* global Settings. Any access to this class or its fields should be done while
@@ -1291,6 +1295,8 @@
verifyIncomingOp(code);
code = AppOpsManager.opToSwitch(code);
+ updatePermissionRevokedCompat(uid, code, mode);
+
synchronized (this) {
final int defaultMode = AppOpsManager.opToDefaultMode(code);
@@ -1392,6 +1398,92 @@
notifyOpChangedSync(code, uid, null, mode);
}
+ private void updatePermissionRevokedCompat(int uid, int switchCode, int mode) {
+ PackageManagerInternal packageManagerInternal = LocalServices.getService(
+ PackageManagerInternal.class);
+ if (packageManagerInternal.getUidTargetSdkVersion(uid) >= Build.VERSION_CODES.M) {
+ return;
+ }
+
+ PackageManager packageManager = mContext.getPackageManager();
+ String[] packageNames = packageManager.getPackagesForUid(uid);
+ if (ArrayUtils.isEmpty(packageNames)) {
+ return;
+ }
+ String packageName = packageNames[0];
+
+ List<Integer> ops = getSwitchOpToOps().get(switchCode);
+ int opsSize = CollectionUtils.size(ops);
+ for (int i = 0; i < opsSize; i++) {
+ int code = ops.get(i);
+
+ String permissionName = AppOpsManager.opToPermission(code);
+ if (permissionName == null) {
+ continue;
+ }
+
+ PermissionInfo permissionInfo;
+ try {
+ permissionInfo = packageManager.getPermissionInfo(permissionName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ continue;
+ }
+
+ if (!permissionInfo.isRuntime()) {
+ continue;
+ }
+
+ UserHandle user = UserHandle.getUserHandleForUid(uid);
+ boolean isRevokedCompat;
+ if (permissionInfo.backgroundPermission != null) {
+ boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
+ long identity = Binder.clearCallingIdentity();
+ try {
+ packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
+ packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+ isBackgroundRevokedCompat
+ ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+
+ isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED
+ && mode != AppOpsManager.MODE_FOREGROUND;
+ } else {
+ isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
+ }
+
+ long identity = Binder.clearCallingIdentity();
+ try {
+ packageManager.updatePermissionFlags(permissionName, packageName,
+ PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, isRevokedCompat
+ ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @NonNull
+ private SparseArray<List<Integer>> getSwitchOpToOps() {
+ synchronized (this) {
+ if (mSwitchOpToOps == null) {
+ mSwitchOpToOps = new SparseArray<>();
+ for (int op = 0; op < _NUM_OP; op++) {
+ int switchOp = AppOpsManager.opToSwitch(op);
+ List<Integer> ops = mSwitchOpToOps.get(switchOp);
+ if (ops == null) {
+ ops = new ArrayList<>();
+ mSwitchOpToOps.put(switchOp, ops);
+ }
+ ops.add(op);
+ }
+ }
+ return mSwitchOpToOps;
+ }
+ }
+
private void notifyOpChangedSync(int code, int uid, @NonNull String packageName, int mode) {
final StorageManagerInternal storageManagerInternal =
LocalServices.getService(StorageManagerInternal.class);
diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING
index 1a5dac5..e9d2b31 100644
--- a/services/core/java/com/android/server/appop/TEST_MAPPING
+++ b/services/core/java/com/android/server/appop/TEST_MAPPING
@@ -18,6 +18,23 @@
"include-filter": "com.android.server.appop"
}
]
+ },
+ {
+ "name": "CtsPermissionTestCases",
+ "options": [
+ {
+ "include-filter": "android.permission.cts.BackgroundPermissionsTest"
+ },
+ {
+ "include-filter": "android.permission.cts.SplitPermissionTest"
+ },
+ {
+ "include-filter": "android.permission.cts.PermissionFlagsTest"
+ },
+ {
+ "include-filter": "android.permission.cts.SharedUidPermissionsTest"
+ }
+ ]
}
]
}
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index 9eb0d50..1b13212 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -1089,7 +1089,7 @@
publishBinderService(Context.FACE_SERVICE, new FaceServiceWrapper());
// Get the face daemon on FaceService's on thread so SystemServerInitThreadPool isn't
// blocked
- SystemServerInitThreadPool.get().submit(() -> mHandler.post(this::getFaceDaemon),
+ SystemServerInitThreadPool.submit(() -> mHandler.post(this::getFaceDaemon),
TAG + ".onStart");
}
diff --git a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
index 320e102..d85af2e 100644
--- a/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/fingerprint/FingerprintService.java
@@ -725,7 +725,7 @@
public void onStart() {
super.onStart();
publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper());
- SystemServerInitThreadPool.get().submit(this::getFingerprintDaemon, TAG + ".onStart");
+ SystemServerInitThreadPool.submit(this::getFingerprintDaemon, TAG + ".onStart");
}
@Override
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index 027e2fb..0fabd9a 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -25,6 +25,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.CompatibilityChangeConfig;
import com.android.server.compat.config.Change;
import com.android.server.compat.config.XmlParser;
@@ -186,6 +187,43 @@
}
return overrideExists;
}
+ /**
+ * Overrides the enabled state for a given change and app. This method is intended to be used
+ * *only* for debugging purposes.
+ *
+ * <p>Note, package overrides are not persistent and will be lost on system or runtime restart.
+ *
+ * @param overrides list of overrides to default changes config.
+ * @param packageName app for which the overrides will be applied.
+ */
+ public void addOverrides(
+ CompatibilityChangeConfig overrides, String packageName) {
+ synchronized (mChanges) {
+ for (Long changeId: overrides.enabledChanges()) {
+ addOverride(changeId, packageName, true);
+ }
+ for (Long changeId: overrides.disabledChanges()) {
+ addOverride(changeId, packageName, false);
+ }
+ }
+ }
+
+ /**
+ * Removes all overrides previously added via {@link #addOverride(long, String, boolean)} or
+ * {@link #addAppOverrides(CompatibilityChangeConfig, String)} for a certain package.
+ *
+ * <p>This restores the default behaviour for the given change and app, once any app
+ * processes have been restarted.
+ *
+ * @param packageName The package for which the overrides should be purged.
+ */
+ public void removePackageOverrides(String packageName) {
+ synchronized (mChanges) {
+ for (int i = 0; i < mChanges.size(); ++i) {
+ mChanges.valueAt(i).removePackageOverride(packageName);
+ }
+ }
+ }
/**
* Dumps the current list of compatibility config information.
diff --git a/services/core/java/com/android/server/compat/OWNERS b/services/core/java/com/android/server/compat/OWNERS
new file mode 100644
index 0000000..2b7cdb0
--- /dev/null
+++ b/services/core/java/com/android/server/compat/OWNERS
@@ -0,0 +1,7 @@
+# Use this reviewer by default.
+platform-compat-eng+reviews@google.com
+
+andreionea@google.com
+atrost@google.com
+mathewi@google.com
+satayev@google.com
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 852b26d..8a7dcc1 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -23,6 +23,7 @@
import android.util.StatsLog;
import com.android.internal.compat.ChangeReporter;
+import com.android.internal.compat.CompatibilityChangeConfig;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.util.DumpUtils;
@@ -100,11 +101,31 @@
}
@Override
+ public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
+ CompatConfig.get().addOverrides(overrides, packageName);
+ }
+
+ @Override
+ public void clearOverrides(String packageName) {
+ CompatConfig config = CompatConfig.get();
+ config.removePackageOverrides(packageName);
+ }
+
+ @Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return;
CompatConfig.get().dumpConfig(pw);
}
+ /**
+ * Clears information stored about events reported on behalf of an app.
+ * To be called once upon app start or end. A second call would be a no-op.
+ * @param appInfo the app to reset
+ */
+ public void resetReporting(ApplicationInfo appInfo) {
+ mChangeReporter.resetReportedChanges(appInfo.uid);
+ }
+
private ApplicationInfo getApplicationInfo(String packageName) {
try {
return mContext.getPackageManager().getApplicationInfo(packageName, 0);
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index c46fc20..29026e8 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -114,6 +114,8 @@
private final FloatBuffer mVertexBuffer = createNativeFloatBuffer(8);
private final FloatBuffer mTexCoordBuffer = createNativeFloatBuffer(8);
+ private final Transaction mTransaction = new Transaction();
+
/**
* Animates an color fade warming up.
*/
@@ -659,14 +661,10 @@
private boolean showSurface(float alpha) {
if (!mSurfaceVisible || mSurfaceAlpha != alpha) {
- SurfaceControl.openTransaction();
- try {
- mSurfaceControl.setLayer(COLOR_FADE_LAYER);
- mSurfaceControl.setAlpha(alpha);
- mSurfaceControl.show();
- } finally {
- SurfaceControl.closeTransaction();
- }
+ mTransaction.setLayer(mSurfaceControl, COLOR_FADE_LAYER)
+ .setAlpha(mSurfaceControl, alpha)
+ .show(mSurfaceControl)
+ .apply();
mSurfaceVisible = true;
mSurfaceAlpha = alpha;
}
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 96ba8ef..648e07a 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -394,14 +394,10 @@
static final String PROPERTY_PREFERRED_ADDRESS_PLAYBACK = "persist.sys.hdmi.addr.playback";
static final String PROPERTY_PREFERRED_ADDRESS_TV = "persist.sys.hdmi.addr.tv";
-
// TODO(OEM): Set this to false to keep the playback device in sleep upon hotplug event.
// True by default.
static final String PROPERTY_WAKE_ON_HOTPLUG = "ro.hdmi.wake_on_hotplug";
- // TODO(OEM): Set this to true to enable 'Set Menu Language' feature. False by default.
- static final String PROPERTY_SET_MENU_LANGUAGE = "ro.hdmi.set_menu_language";
-
/**
* Property to save the ARC port id on system audio device.
* <p>When ARC is initiated, this port will be used to turn on ARC.
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index b4c7dd3..080e6ce 100755
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -333,7 +333,8 @@
current.mPhysicalAddress = HdmiUtils.twoBytesToInt(params);
current.mPortId = getPortId(current.mPhysicalAddress);
current.mDeviceType = params[2] & 0xFF;
- current.mDisplayName = HdmiUtils.getDefaultDeviceName(current.mDeviceType);
+ // Keep display name empty. TIF fallbacks to the service label provided by the package mg.
+ current.mDisplayName = "";
// This is to manager CEC device separately in case they don't have address.
if (mIsTvDevice) {
@@ -359,17 +360,13 @@
return;
}
- String displayName = null;
+ String displayName = "";
try {
- if (cmd.getOpcode() == Constants.MESSAGE_FEATURE_ABORT) {
- displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress);
- } else {
+ if (cmd.getOpcode() != Constants.MESSAGE_FEATURE_ABORT) {
displayName = new String(cmd.getParams(), "US-ASCII");
}
} catch (UnsupportedEncodingException e) {
Slog.w(TAG, "Failed to decode display name: " + cmd.toString());
- // If failed to get display name, use the default name of device.
- displayName = HdmiUtils.getDefaultDeviceName(current.mLogicalAddress);
}
current.mDisplayName = displayName;
increaseProcessedDeviceCount();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 61d4d4b..dde873b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -489,7 +489,7 @@
if (oldDevice == null || oldDevice.getPhysicalAddress() != path) {
addCecDevice(new HdmiDeviceInfo(
address, path, mService.pathToPortId(path), type,
- Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address)));
+ Constants.UNKNOWN_VENDOR_ID, ""));
// if we are adding a new device info, send out a give osd name command
// to update the name of the device in TIF
mService.sendCecCommand(
@@ -526,7 +526,8 @@
return true;
}
- if (deviceInfo.getDisplayName().equals(osdName)) {
+ if (deviceInfo.getDisplayName() != null
+ && deviceInfo.getDisplayName().equals(osdName)) {
Slog.d(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
return true;
}
@@ -1238,8 +1239,8 @@
}
// Wake up if the current device if ready to route.
mService.wakeUp();
- if (getLocalActivePort() == portId) {
- HdmiLogger.debug("Not switching to the same port " + portId);
+ if ((getLocalActivePort() == portId) && (portId != Constants.CEC_SWITCH_ARC)) {
+ HdmiLogger.debug("Not switching to the same port " + portId + " except for arc");
return;
}
// Switch to HOME if the current active port is not HOME yet
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 413e7a0..0944324 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -24,6 +24,7 @@
import android.os.PowerManager.WakeLock;
import android.os.SystemProperties;
import android.provider.Settings.Global;
+import android.sysprop.HdmiProperties;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -47,7 +48,7 @@
SystemProperties.getBoolean(Constants.PROPERTY_WAKE_ON_HOTPLUG, true);
private static final boolean SET_MENU_LANGUAGE =
- SystemProperties.getBoolean(Constants.PROPERTY_SET_MENU_LANGUAGE, false);
+ HdmiProperties.set_menu_language().orElse(false);
// Used to keep the device awake while it is the active source. For devices that
// cannot wake up via CEC commands, this address the inconvenience of having to
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 7e6e668..362955d 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -71,7 +71,6 @@
import android.view.IInputFilter;
import android.view.IInputFilterHost;
import android.view.IInputMonitorHost;
-import android.view.IWindow;
import android.view.InputApplicationHandle;
import android.view.InputChannel;
import android.view.InputDevice;
@@ -186,9 +185,6 @@
IInputFilter mInputFilter; // guarded by mInputFilterLock
InputFilterHost mInputFilterHost; // guarded by mInputFilterLock
- private IWindow mFocusedWindow;
- private boolean mFocusedWindowHasCapture;
-
private static native long nativeInit(InputManagerService service,
Context context, MessageQueue messageQueue);
private static native void nativeStart(long ptr);
@@ -203,8 +199,7 @@
int deviceId, int sourceMask, int sw);
private static native boolean nativeHasKeys(long ptr,
int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
- private static native void nativeRegisterInputChannel(long ptr, InputChannel inputChannel,
- int displayId);
+ private static native void nativeRegisterInputChannel(long ptr, InputChannel inputChannel);
private static native void nativeRegisterInputMonitor(long ptr, InputChannel inputChannel,
int displayId, boolean isGestureMonitor);
private static native void nativeUnregisterInputChannel(long ptr, InputChannel inputChannel);
@@ -529,7 +524,6 @@
throw new IllegalArgumentException("displayId must >= 0.");
}
-
final long ident = Binder.clearCallingIdentity();
try {
InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName);
@@ -537,29 +531,25 @@
inputChannels[0].setToken(host.asBinder());
nativeRegisterInputMonitor(mPtr, inputChannels[0], displayId,
true /*isGestureMonitor*/);
- return new InputMonitor(inputChannelName, inputChannels[1], host);
+ return new InputMonitor(inputChannels[1], host);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
/**
- * Registers an input channel so that it can be used as an input event target.
+ * Registers an input channel so that it can be used as an input event target. The channel is
+ * registered with a generated token.
+ *
* @param inputChannel The input channel to register.
- * @param inputWindowHandle The handle of the input window associated with the
- * input channel, or null if none.
*/
- public void registerInputChannel(InputChannel inputChannel, IBinder token) {
+ public void registerInputChannel(InputChannel inputChannel) {
if (inputChannel == null) {
throw new IllegalArgumentException("inputChannel must not be null.");
}
+ inputChannel.setToken(new Binder());
- if (token == null) {
- token = new Binder();
- }
- inputChannel.setToken(token);
-
- nativeRegisterInputChannel(mPtr, inputChannel, Display.INVALID_DISPLAY);
+ nativeRegisterInputChannel(mPtr, inputChannel);
}
/**
@@ -1513,26 +1503,9 @@
@Override
public void requestPointerCapture(IBinder windowToken, boolean enabled) {
- if (mFocusedWindow == null || mFocusedWindow.asBinder() != windowToken) {
- Slog.e(TAG, "requestPointerCapture called for a window that has no focus: "
- + windowToken);
- return;
- }
- if (mFocusedWindowHasCapture == enabled) {
- Slog.i(TAG, "requestPointerCapture: already " + (enabled ? "enabled" : "disabled"));
- return;
- }
- setPointerCapture(enabled);
- }
-
- private void setPointerCapture(boolean enabled) {
- if (mFocusedWindowHasCapture != enabled) {
- mFocusedWindowHasCapture = enabled;
- try {
- mFocusedWindow.dispatchPointerCaptureChanged(enabled);
- } catch (RemoteException ex) {
- /* ignore */
- }
+ boolean requestConfigurationRefresh =
+ mWindowManagerCallbacks.requestPointerCapture(windowToken, enabled);
+ if (requestConfigurationRefresh) {
nativeSetPointerCapture(mPtr, enabled);
}
}
@@ -1829,16 +1802,11 @@
// Native callback
private void notifyFocusChanged(IBinder oldToken, IBinder newToken) {
- if (mFocusedWindow != null) {
- if (mFocusedWindow.asBinder() == newToken) {
- Slog.w(TAG, "notifyFocusChanged called with unchanged mFocusedWindow="
- + mFocusedWindow);
- return;
- }
- setPointerCapture(false);
+ final boolean requestConfigurationRefresh =
+ mWindowManagerCallbacks.notifyFocusChanged(oldToken, newToken);
+ if (requestConfigurationRefresh) {
+ nativeSetPointerCapture(mPtr, false);
}
-
- mFocusedWindow = IWindow.Stub.asInterface(newToken);
}
// Native callback.
@@ -2116,6 +2084,20 @@
* @param touchedToken The token for the window that received the input event.
*/
void onPointerDownOutsideFocus(IBinder touchedToken);
+
+ /**
+ * Called when the focused window has changed.
+ *
+ * @return true if we want to request a configuration refresh.
+ */
+ boolean notifyFocusChanged(IBinder oldToken, IBinder newToken);
+
+ /**
+ * Called by the client to request pointer capture.
+ *
+ * @return true if we want to request a configuration refresh.
+ */
+ boolean requestPointerCapture(IBinder windowToken, boolean enabled);
}
/**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index b7fcd3f..471fa72 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -3570,10 +3570,14 @@
return;
}
if (!setVisible) {
- // Client hides the IME directly.
- if (mCurClient != null && mCurClient.client != null) {
- executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
- MSG_APPLY_IME_VISIBILITY, setVisible ? 1 : 0, mCurClient));
+ if (mCurClient != null) {
+ // IMMS only knows of focused window, not the actual IME target.
+ // e.g. it isn't aware of any window that has both
+ // NOT_FOCUSABLE, ALT_FOCUSABLE_IM flags set and can the IME target.
+ // Send it to window manager to hide IME from IME target window.
+ // TODO(b/139861270): send to mCurClient.client once IMMS is aware of
+ // actual IME target.
+ mWindowManagerInternal.hideIme(mCurClient.selfReportedDisplayId);
}
} else {
// Send to window manager to show IME after IME layout finishes.
@@ -4208,7 +4212,7 @@
// with other IME windows based on type vs. grouping based on whichever token happens
// to get selected by the system later on.
attrs.token = mSwitchingDialogToken;
- attrs.privateFlags |= LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ attrs.privateFlags |= LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
attrs.setTitle("Select input method");
w.setAttributes(attrs);
updateSystemUiLocked(mImeWindowVis, mBackDisposition);
diff --git a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
index fc44306..e90612e 100644
--- a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
+++ b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java
@@ -16,6 +16,10 @@
package com.android.server.integrity.engine;
+import android.util.Slog;
+
+import com.android.server.integrity.model.AppInstallMetadata;
+import com.android.server.integrity.model.IntegrityCheckResult;
import com.android.server.integrity.model.Rule;
import java.util.ArrayList;
@@ -24,8 +28,8 @@
/**
* The engine used to evaluate rules against app installs.
*
- * <p>Every app install is evaluated against rules (pushed by the verifier) by the evaluation engine
- * to allow/block that install.
+ * <p>Every app install is evaluated against rules (pushed by the verifier) by the evaluation
+ * engine to allow/block that install.
*/
public final class RuleEvaluationEngine {
private static final String TAG = "RuleEvaluation";
@@ -34,15 +38,6 @@
// installs against rules.
private static RuleEvaluationEngine sRuleEvaluationEngine;
- // The subset of rules loaded to be used to evaluate an app install request.
- // TODO: Load rules relevant to app installs.
- private List<Rule> mRules;
-
- private RuleEvaluationEngine() {
- // Initialize rules with the empty rule set.
- mRules = new ArrayList<>();
- }
-
/**
* Provide a singleton instance of the rule evaluation engine.
*/
@@ -52,4 +47,33 @@
}
return sRuleEvaluationEngine;
}
+
+ /**
+ * Load, and match the list of rules against an app install metadata.
+ *
+ * @param appInstallMetadata Metadata of the app to be installed, and to evaluate the rules
+ * against.
+ * @return A rule matching the metadata. If there are multiple matching rules, returns any. If
+ * no rules are matching, returns {@link Rule#EMPTY}.
+ */
+ public IntegrityCheckResult evaluate(AppInstallMetadata appInstallMetadata) {
+ List<Rule> rules = loadRules(appInstallMetadata);
+ Rule matchedRule = RuleEvaluator.evaluateRules(rules, appInstallMetadata);
+ if (matchedRule == Rule.EMPTY) {
+ return IntegrityCheckResult.allow();
+ } else {
+ switch (matchedRule.getEffect()) {
+ case DENY:
+ return IntegrityCheckResult.deny(matchedRule);
+ default:
+ Slog.e(TAG, "Matched a non-DENY rule: " + matchedRule);
+ return IntegrityCheckResult.allow();
+ }
+ }
+ }
+
+ private List<Rule> loadRules(AppInstallMetadata appInstallMetadata) {
+ // TODO: Load rules
+ return new ArrayList<>();
+ }
}
diff --git a/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java
new file mode 100644
index 0000000..6416505
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.integrity.engine;
+
+import android.util.Slog;
+
+import com.android.server.integrity.model.AppInstallMetadata;
+import com.android.server.integrity.model.AtomicFormula;
+import com.android.server.integrity.model.Formula;
+import com.android.server.integrity.model.OpenFormula;
+import com.android.server.integrity.model.Rule;
+
+import java.util.List;
+
+/**
+ * A helper class for evaluating rules against app install metadata to find if there are matching
+ * rules.
+ */
+final class RuleEvaluator {
+
+ private static final String TAG = "RuleEvaluator";
+
+ /**
+ * Match the list of rules against an app install metadata.
+ *
+ * <p>Rules must be in disjunctive normal form (DNF). A rule should contain AND'ed formulas
+ * only. All rules are OR'ed together by default.
+ *
+ * @param rules The list of rules to evaluate.
+ * @param appInstallMetadata Metadata of the app to be installed, and to evaluate the rules
+ * against.
+ * @return A rule matching the metadata. If there are multiple matching rules, returns any. If
+ * no rules are matching, returns {@link Rule#EMPTY}.
+ */
+ static Rule evaluateRules(List<Rule> rules, AppInstallMetadata appInstallMetadata) {
+ for (Rule rule : rules) {
+ if (isConjunctionOfFormulas(rule.getFormula()) && isMatch(rule, appInstallMetadata)) {
+ return rule;
+ }
+ }
+ return Rule.EMPTY;
+ }
+
+ /**
+ * Match a rule against app install metadata.
+ */
+ private static boolean isMatch(Rule rule, AppInstallMetadata appInstallMetadata) {
+ return isMatch(rule.getFormula(), appInstallMetadata);
+ }
+
+ private static boolean isMatch(Formula formula, AppInstallMetadata appInstallMetadata) {
+ if (formula instanceof AtomicFormula) {
+ AtomicFormula atomicFormula = (AtomicFormula) formula;
+ switch (atomicFormula.getKey()) {
+ case PACKAGE_NAME:
+ return atomicFormula.isMatch(appInstallMetadata.getPackageName());
+ case APP_CERTIFICATE:
+ return atomicFormula.isMatch(appInstallMetadata.getAppCertificate());
+ case INSTALLER_NAME:
+ return atomicFormula.isMatch(appInstallMetadata.getInstallerName());
+ case INSTALLER_CERTIFICATE:
+ return atomicFormula.isMatch(appInstallMetadata.getInstallerCertificate());
+ case VERSION_CODE:
+ return atomicFormula.isMatch(appInstallMetadata.getVersionCode());
+ case PRE_INSTALLED:
+ return atomicFormula.isMatch(appInstallMetadata.isPreInstalled());
+ default:
+ Slog.i(TAG, String.format("Returned no match for unknown key %s",
+ atomicFormula.getKey()));
+ return false;
+ }
+ } else if (formula instanceof OpenFormula) {
+ OpenFormula openFormula = (OpenFormula) formula;
+ // A rule is in disjunctive normal form, so there are no OR connectors.
+ switch (openFormula.getConnector()) {
+ case NOT:
+ // NOT connector has only 1 formula attached.
+ return !isMatch(openFormula.getFormulas().get(0), appInstallMetadata);
+ case AND:
+ return openFormula.getFormulas().stream().allMatch(
+ subFormula -> isMatch(subFormula, appInstallMetadata));
+ default:
+ Slog.i(TAG, String.format("Returned no match for unknown connector %s",
+ openFormula.getConnector()));
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean isConjunctionOfFormulas(Formula formula) {
+ if (formula == null) {
+ return false;
+ }
+ if (isAtomicFormula(formula)) {
+ return true;
+ }
+ OpenFormula openFormula = (OpenFormula) formula;
+ return openFormula.getConnector() == OpenFormula.Connector.AND
+ && openFormula.getFormulas().stream().allMatch(RuleEvaluator::isAtomicFormula);
+ }
+
+ private static boolean isAtomicFormula(Formula formula) {
+ if (formula instanceof AtomicFormula) {
+ return true;
+ }
+ OpenFormula openFormula = (OpenFormula) formula;
+ return openFormula.getConnector() == OpenFormula.Connector.NOT
+ && openFormula.getFormulas().get(0) instanceof AtomicFormula;
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/model/AtomicFormula.java b/services/core/java/com/android/server/integrity/model/AtomicFormula.java
index a9cc62a4da..b9b46e3 100644
--- a/services/core/java/com/android/server/integrity/model/AtomicFormula.java
+++ b/services/core/java/com/android/server/integrity/model/AtomicFormula.java
@@ -20,6 +20,9 @@
import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.Nullable;
+import android.util.Slog;
+
+import java.util.Objects;
/**
* Represents a simple formula consisting of an app install metadata field and a value.
@@ -28,7 +31,9 @@
*/
public final class AtomicFormula extends Formula {
- enum Key {
+ private static final String TAG = "AtomicFormula";
+
+ public enum Key {
PACKAGE_NAME,
APP_CERTIFICATE,
INSTALLER_NAME,
@@ -37,7 +42,7 @@
PRE_INSTALLED
}
- enum Operator {
+ public enum Operator {
EQ,
LT,
LE,
@@ -112,6 +117,100 @@
return mBoolValue;
}
+ /**
+ * Get string representation of the value of the key in the formula.
+ *
+ * @return string representation of the value of the key.
+ */
+ public String getValue() {
+ if (mStringValue != null) {
+ return mStringValue;
+ }
+ if (mIntValue != null) {
+ return mIntValue.toString();
+ }
+ return mBoolValue.toString();
+ }
+
+ /**
+ * Check if the formula is true when substituting its {@link Key} with the string value.
+ *
+ * @param value String value to substitute the key with.
+ * @return {@code true} if the formula is true, and {@code false} otherwise.
+ */
+ public boolean isMatch(String value) {
+ switch (mOperator) {
+ case EQ:
+ return mStringValue.equals(value);
+ }
+ Slog.i(TAG, String.format("Found operator %s for value %s", mOperator, mStringValue));
+ return false;
+ }
+
+ /**
+ * Check if the formula is true when substituting its {@link Key} with the integer value.
+ *
+ * @param value Integer value to substitute the key with.
+ * @return {@code true} if the formula is true, and {@code false} otherwise.
+ */
+ public boolean isMatch(int value) {
+ switch (mOperator) {
+ case EQ:
+ return mIntValue == value;
+ case LE:
+ return mIntValue <= value;
+ case LT:
+ return mIntValue < value;
+ case GE:
+ return mIntValue >= value;
+ case GT:
+ return mIntValue > value;
+ }
+ Slog.i(TAG, String.format("Found operator %s for value %s", mOperator, mIntValue));
+ return false;
+ }
+
+ /**
+ * Check if the formula is true when substituting its {@link Key} with the boolean value.
+ *
+ * @param value Boolean value to substitute the key with.
+ * @return {@code true} if the formula is true, and {@code false} otherwise.
+ */
+ public boolean isMatch(boolean value) {
+ switch (mOperator) {
+ case EQ:
+ return mBoolValue == value;
+ }
+ Slog.i(TAG, String.format("Found operator %s for value %s", mOperator, mBoolValue));
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s %s %s", mKey, mOperator, getValue());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ AtomicFormula that = (AtomicFormula) o;
+ return mKey == that.mKey
+ && mOperator == that.mOperator
+ && Objects.equals(mStringValue, that.mStringValue)
+ && Objects.equals(mIntValue, that.mIntValue)
+ && Objects.equals(mBoolValue, that.mBoolValue);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mKey, mOperator, mStringValue, mIntValue, mBoolValue);
+ }
+
private void validateOperator(Key key, Operator operator) {
boolean validOperator;
switch (key) {
@@ -126,6 +225,7 @@
validOperator = true;
break;
default:
+ Slog.i(TAG, String.format("Found operator %s for key %s", operator, key));
validOperator = false;
}
if (!validOperator) {
diff --git a/services/core/java/com/android/server/integrity/model/Formula.java b/services/core/java/com/android/server/integrity/model/Formula.java
index 4cfa2c7..9db4453 100644
--- a/services/core/java/com/android/server/integrity/model/Formula.java
+++ b/services/core/java/com/android/server/integrity/model/Formula.java
@@ -19,6 +19,6 @@
/**
* Represents a rule logic/content.
*/
-abstract class Formula {
+public abstract class Formula {
}
diff --git a/services/core/java/com/android/server/integrity/model/EvaluationOutcome.java b/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java
similarity index 82%
rename from services/core/java/com/android/server/integrity/model/EvaluationOutcome.java
rename to services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java
index dc30dc3..7aeb0c1 100644
--- a/services/core/java/com/android/server/integrity/model/EvaluationOutcome.java
+++ b/services/core/java/com/android/server/integrity/model/IntegrityCheckResult.java
@@ -23,7 +23,7 @@
* <p>It contains the outcome effect (whether to allow or block the install), and the rule causing
* that effect.
*/
-public final class EvaluationOutcome {
+public final class IntegrityCheckResult {
public enum Effect {
ALLOW,
@@ -33,7 +33,7 @@
private final Effect mEffect;
private final Rule mRule;
- private EvaluationOutcome(Effect effect, Rule rule) {
+ private IntegrityCheckResult(Effect effect, Rule rule) {
this.mEffect = effect;
this.mRule = rule;
}
@@ -51,8 +51,8 @@
*
* @return An evaluation outcome with ALLOW effect and empty rule.
*/
- public static EvaluationOutcome allow() {
- return new EvaluationOutcome(Effect.ALLOW, Rule.EMPTY);
+ public static IntegrityCheckResult allow() {
+ return new IntegrityCheckResult(Effect.ALLOW, Rule.EMPTY);
}
/**
@@ -61,7 +61,7 @@
* @param rule Rule causing the DENY effect.
* @return An evaluation outcome with DENY effect and rule causing that effect.
*/
- public static EvaluationOutcome deny(Rule rule) {
- return new EvaluationOutcome(Effect.DENY, rule);
+ public static IntegrityCheckResult deny(Rule rule) {
+ return new IntegrityCheckResult(Effect.DENY, rule);
}
}
diff --git a/services/core/java/com/android/server/integrity/model/OpenFormula.java b/services/core/java/com/android/server/integrity/model/OpenFormula.java
index 218cdc9..21da629 100644
--- a/services/core/java/com/android/server/integrity/model/OpenFormula.java
+++ b/services/core/java/com/android/server/integrity/model/OpenFormula.java
@@ -16,9 +16,12 @@
package com.android.server.integrity.model;
+import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkNotNull;
-import android.annotation.Nullable;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
/**
* Represents a complex formula consisting of other simple and complex formulas.
@@ -27,53 +30,70 @@
*/
public final class OpenFormula extends Formula {
- enum Connector {
+ public enum Connector {
AND,
OR,
NOT
}
private final Connector mConnector;
- private final Formula mMainFormula;
- private final Formula mAuxiliaryFormula;
+ private final List<Formula> mFormulas;
- public OpenFormula(Connector connector, Formula mainFormula,
- @Nullable Formula auxiliaryFormula) {
- validateAuxiliaryFormula(connector, auxiliaryFormula);
+ public OpenFormula(Connector connector, List<Formula> formulas) {
+ validateFormulas(connector, formulas);
this.mConnector = checkNotNull(connector);
- this.mMainFormula = checkNotNull(mainFormula);
- // TODO: Add validators on auxiliary formula
- this.mAuxiliaryFormula = auxiliaryFormula;
+ this.mFormulas = Collections.unmodifiableList(checkNotNull(formulas));
}
public Connector getConnector() {
return mConnector;
}
- public Formula getMainFormula() {
- return mMainFormula;
+ public List<Formula> getFormulas() {
+ return mFormulas;
}
- public Formula getAuxiliaryFormula() {
- return mAuxiliaryFormula;
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < mFormulas.size(); i++) {
+ if (i > 0) {
+ sb.append(String.format(" %s ", mConnector));
+ }
+ sb.append(mFormulas.get(i).toString());
+ }
+ return sb.toString();
}
- private void validateAuxiliaryFormula(Connector connector, Formula auxiliaryFormula) {
- boolean validAuxiliaryFormula;
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ OpenFormula that = (OpenFormula) o;
+ return mConnector == that.mConnector
+ && mFormulas.equals(that.mFormulas);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mConnector, mFormulas);
+ }
+
+ private void validateFormulas(Connector connector, List<Formula> formulas) {
switch (connector) {
case AND:
case OR:
- validAuxiliaryFormula = (auxiliaryFormula != null);
+ checkArgument(formulas.size() >= 2,
+ String.format("Connector %s must have at least 2 formulas", connector));
break;
case NOT:
- validAuxiliaryFormula = (auxiliaryFormula == null);
+ checkArgument(formulas.size() == 1,
+ String.format("Connector %s must have 1 formula only", connector));
break;
- default:
- validAuxiliaryFormula = false;
- }
- if (!validAuxiliaryFormula) {
- throw new IllegalArgumentException(
- String.format("Invalid formulas used for connector %s", connector));
}
}
}
diff --git a/services/core/java/com/android/server/integrity/model/Rule.java b/services/core/java/com/android/server/integrity/model/Rule.java
index 3d233ab..63b9b91 100644
--- a/services/core/java/com/android/server/integrity/model/Rule.java
+++ b/services/core/java/com/android/server/integrity/model/Rule.java
@@ -18,6 +18,8 @@
import static com.android.internal.util.Preconditions.checkNotNull;
+import java.util.Objects;
+
/**
* Represent rules to be used in the rule evaluation engine to match against app installs.
*
@@ -61,4 +63,27 @@
public Effect getEffect() {
return mEffect;
}
+
+ @Override
+ public String toString() {
+ return String.format("Rule: %s, %s", mFormula, mEffect);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Rule that = (Rule) o;
+ return Objects.equals(mFormula, that.mFormula)
+ && Objects.equals(mEffect, that.mEffect);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFormula, mEffect);
+ }
}
diff --git a/services/core/java/com/android/server/location/CallerIdentity.java b/services/core/java/com/android/server/location/CallerIdentity.java
index da31d0b..61e5d1f 100644
--- a/services/core/java/com/android/server/location/CallerIdentity.java
+++ b/services/core/java/com/android/server/location/CallerIdentity.java
@@ -16,6 +16,8 @@
package com.android.server.location;
+import android.annotation.NonNull;
+
/**
* Represents the calling process's uid, pid, and package name.
*/
@@ -23,10 +25,13 @@
public final int mUid;
public final int mPid;
public final String mPackageName;
+ public final @NonNull String mListenerIdentifier;
- public CallerIdentity(int uid, int pid, String packageName) {
+ public CallerIdentity(int uid, int pid, String packageName,
+ @NonNull String listenerIdentifier) {
mUid = uid;
mPid = pid;
mPackageName = packageName;
+ mListenerIdentifier = listenerIdentifier;
}
}
diff --git a/services/core/java/com/android/server/location/GeofenceManager.java b/services/core/java/com/android/server/location/GeofenceManager.java
index a192206..895c4b3 100644
--- a/services/core/java/com/android/server/location/GeofenceManager.java
+++ b/services/core/java/com/android/server/location/GeofenceManager.java
@@ -16,6 +16,7 @@
package com.android.server.location;
+import android.annotation.NonNull;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.ContentResolver;
@@ -151,14 +152,16 @@
}
public void addFence(LocationRequest request, Geofence geofence, PendingIntent intent,
- int allowedResolutionLevel, int uid, String packageName) {
+ int allowedResolutionLevel, int uid, String packageName,
+ @NonNull String listenerIdentifier) {
if (D) {
Slog.d(TAG, "addFence: request=" + request + ", geofence=" + geofence
+ ", intent=" + intent + ", uid=" + uid + ", packageName=" + packageName);
}
GeofenceState state = new GeofenceState(geofence,
- request.getExpireAt(), allowedResolutionLevel, uid, packageName, intent);
+ request.getExpireAt(), allowedResolutionLevel, uid, packageName, listenerIdentifier,
+ intent);
synchronized (mLock) {
// first make sure it doesn't already exist
for (int i = mFences.size() - 1; i >= 0; i--) {
@@ -301,7 +304,8 @@
int op = LocationManagerService.resolutionLevelToOp(state.mAllowedResolutionLevel);
if (op >= 0) {
if (mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, state.mUid,
- state.mPackageName) != AppOpsManager.MODE_ALLOWED) {
+ state.mPackageName, state.mListenerIdentifier)
+ != AppOpsManager.MODE_ALLOWED) {
if (D) {
Slog.d(TAG, "skipping geofence processing for no op app: "
+ state.mPackageName);
diff --git a/services/core/java/com/android/server/location/GeofenceState.java b/services/core/java/com/android/server/location/GeofenceState.java
index 3ebe20a..fe0719d 100644
--- a/services/core/java/com/android/server/location/GeofenceState.java
+++ b/services/core/java/com/android/server/location/GeofenceState.java
@@ -17,6 +17,7 @@
package com.android.server.location;
+import android.annotation.NonNull;
import android.app.PendingIntent;
import android.location.Geofence;
import android.location.Location;
@@ -38,13 +39,14 @@
public final int mAllowedResolutionLevel;
public final int mUid;
public final String mPackageName;
+ public final @NonNull String mListenerIdentifier;
public final PendingIntent mIntent;
int mState; // current state
double mDistanceToCenter; // current distance to center of fence
- public GeofenceState(Geofence fence, long expireAt,
- int allowedResolutionLevel, int uid, String packageName, PendingIntent intent) {
+ public GeofenceState(Geofence fence, long expireAt, int allowedResolutionLevel, int uid,
+ String packageName, @NonNull String listenerIdentifier, PendingIntent intent) {
mState = STATE_UNKNOWN;
mDistanceToCenter = Double.MAX_VALUE;
@@ -53,6 +55,7 @@
mAllowedResolutionLevel = allowedResolutionLevel;
mUid = uid;
mPackageName = packageName;
+ mListenerIdentifier = listenerIdentifier;
mIntent = intent;
mLocation = new Location("");
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index c6226fa..8bf01a3 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -74,6 +74,8 @@
import com.android.internal.location.ProviderRequest;
import com.android.internal.location.gnssmetrics.GnssMetrics;
import com.android.internal.telephony.TelephonyIntents;
+import com.android.server.DeviceIdleInternal;
+import com.android.server.LocalServices;
import com.android.server.location.GnssSatelliteBlacklistHelper.GnssSatelliteBlacklistCallback;
import com.android.server.location.NtpTimeHelper.InjectNtpTimeCallback;
@@ -177,6 +179,7 @@
private static final int AGPS_SUPL_MODE_MSA = 0x02;
private static final int AGPS_SUPL_MODE_MSB = 0x01;
+ private static final int UPDATE_LOW_POWER_MODE = 1;
private static final int SET_REQUEST = 3;
private static final int INJECT_NTP_TIME = 5;
// PSDS stands for Predicted Satellite Data Service
@@ -360,6 +363,12 @@
private boolean mDisableGpsForPowerManager = false;
/**
+ * True if the device idle controller has determined that the device is stationary. This is only
+ * updated when the device enters idle mode.
+ */
+ private volatile boolean mIsDeviceStationary = false;
+
+ /**
* Properties loaded from PROPERTIES_FILE.
* It must be accessed only inside {@link #mHandler}.
*/
@@ -451,6 +460,15 @@
public GnssNavigationMessageProvider getGnssNavigationMessageProvider() {
return mGnssNavigationMessageProvider;
}
+
+ private final DeviceIdleInternal.StationaryListener mDeviceIdleStationaryListener =
+ isStationary -> {
+ mIsDeviceStationary = isStationary;
+ // Call updateLowPowerMode on handler thread so it's always called from the same
+ // thread.
+ mHandler.sendEmptyMessage(UPDATE_LOW_POWER_MODE);
+ };
+
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -467,11 +485,22 @@
case ALARM_TIMEOUT:
hibernate();
break;
- case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED:
case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
+ DeviceIdleInternal deviceIdleService = LocalServices.getService(
+ DeviceIdleInternal.class);
+ if (mPowerManager.isDeviceIdleMode()) {
+ deviceIdleService.registerStationaryListener(mDeviceIdleStationaryListener);
+ } else {
+ deviceIdleService.unregisterStationaryListener(
+ mDeviceIdleStationaryListener);
+ }
+ // Intentional fall-through.
+ case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED:
case Intent.ACTION_SCREEN_OFF:
case Intent.ACTION_SCREEN_ON:
- updateLowPowerMode();
+ // Call updateLowPowerMode on handler thread so it's always called from the
+ // same thread.
+ mHandler.sendEmptyMessage(UPDATE_LOW_POWER_MODE);
break;
case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
case TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
@@ -529,10 +558,9 @@
}
private void updateLowPowerMode() {
- // Disable GPS if we are in device idle mode.
- boolean disableGpsForPowerManager = mPowerManager.isDeviceIdleMode();
- final PowerSaveState result =
- mPowerManager.getPowerSaveState(ServiceType.LOCATION);
+ // Disable GPS if we are in device idle mode and the device is stationary.
+ boolean disableGpsForPowerManager = mPowerManager.isDeviceIdleMode() && mIsDeviceStationary;
+ final PowerSaveState result = mPowerManager.getPowerSaveState(ServiceType.LOCATION);
switch (result.locationMode) {
case PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF:
case PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF:
@@ -2005,6 +2033,9 @@
case REPORT_SV_STATUS:
handleReportSvStatus((SvStatusInfo) msg.obj);
break;
+ case UPDATE_LOW_POWER_MODE:
+ updateLowPowerMode();
+ break;
}
if (msg.arg2 == 1) {
// wakelock was taken for this message, release it
diff --git a/services/core/java/com/android/server/location/RemoteListenerHelper.java b/services/core/java/com/android/server/location/RemoteListenerHelper.java
index aa8a25a..0929d93 100644
--- a/services/core/java/com/android/server/location/RemoteListenerHelper.java
+++ b/services/core/java/com/android/server/location/RemoteListenerHelper.java
@@ -182,7 +182,9 @@
}
return mAppOps.noteOpNoThrow(AppOpsManager.OP_FINE_LOCATION, callerIdentity.mUid,
- callerIdentity.mPackageName) == AppOpsManager.MODE_ALLOWED;
+ callerIdentity.mPackageName,
+ "Location sent to " + callerIdentity.mListenerIdentifier)
+ == AppOpsManager.MODE_ALLOWED;
}
protected void logPermissionDisabledEventNotReported(String tag, String packageName,
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index bad484f..34fb641 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -118,6 +118,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternUtils.CredentialType;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
@@ -2058,7 +2059,8 @@
@UserIdInt int userHandle) {
synchronized (this) {
mUserPasswordMetrics.put(userHandle,
- PasswordMetrics.computeForCredential(credentialType, password));
+ PasswordMetrics.computeForCredential(
+ LockscreenCredential.createRaw(credentialType, password)));
}
}
@@ -2069,7 +2071,7 @@
// since the user never unlock the device manually. In this case, always
// return a default metrics object. This is to distinguish this case from
// the case where during boot user password is unknown yet (returning null here)
- return new PasswordMetrics();
+ return new PasswordMetrics(CREDENTIAL_TYPE_NONE);
}
synchronized (this) {
return mUserPasswordMetrics.get(userHandle);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index a5d59e3..0a8e5bd 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -16,16 +16,12 @@
package com.android.server.locksettings;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
-
-import static com.android.internal.widget.LockPatternUtils.stringToPattern;
-
import android.app.ActivityManager;
import android.os.ShellCommand;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
+import com.android.internal.widget.LockscreenCredential;
import java.io.PrintWriter;
@@ -189,31 +185,49 @@
mLockPatternUtils.isSyntheticPasswordEnabled()));
}
+ private LockscreenCredential getOldCredential() {
+ if (mLockPatternUtils.isLockPasswordEnabled(mCurrentUserId)) {
+ final int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(mCurrentUserId);
+ if (LockPatternUtils.isQualityAlphabeticPassword(quality)) {
+ return LockscreenCredential.createPassword(mOld);
+ } else {
+ return LockscreenCredential.createPin(mOld);
+ }
+ } else if (mLockPatternUtils.isLockPatternEnabled(mCurrentUserId)) {
+ return LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
+ mOld.getBytes()));
+ } else {
+ return LockscreenCredential.createNone();
+ }
+ }
+
private void runSetPattern() {
- byte[] oldBytes = mOld != null ? mOld.getBytes() : null;
- mLockPatternUtils.saveLockPattern(stringToPattern(mNew), oldBytes, mCurrentUserId);
+ mLockPatternUtils.setLockCredential(
+ LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
+ mNew.getBytes())),
+ getOldCredential(),
+ mCurrentUserId);
getOutPrintWriter().println("Pattern set to '" + mNew + "'");
}
private void runSetPassword() {
- byte[] newBytes = mNew != null ? mNew.getBytes() : null;
- byte[] oldBytes = mOld != null ? mOld.getBytes() : null;
- mLockPatternUtils.saveLockPassword(newBytes, oldBytes, PASSWORD_QUALITY_ALPHABETIC,
+ mLockPatternUtils.setLockCredential(LockscreenCredential.createPassword(mNew),
+ getOldCredential(),
mCurrentUserId);
getOutPrintWriter().println("Password set to '" + mNew + "'");
}
private void runSetPin() {
- byte[] newBytes = mNew != null ? mNew.getBytes() : null;
- byte[] oldBytes = mOld != null ? mOld.getBytes() : null;
- mLockPatternUtils.saveLockPassword(newBytes, oldBytes, PASSWORD_QUALITY_NUMERIC,
+ mLockPatternUtils.setLockCredential(LockscreenCredential.createPin(mNew),
+ getOldCredential(),
mCurrentUserId);
getOutPrintWriter().println("Pin set to '" + mNew + "'");
}
private void runClear() {
- byte[] oldBytes = mOld != null ? mOld.getBytes() : null;
- mLockPatternUtils.clearLock(oldBytes, mCurrentUserId);
+ mLockPatternUtils.setLockCredential(LockscreenCredential.createNone(),
+ getOldCredential(),
+ mCurrentUserId);
getOutPrintWriter().println("Lock credential cleared");
}
@@ -238,13 +252,8 @@
}
try {
- final boolean result;
- if (havePassword) {
- byte[] passwordBytes = mOld != null ? mOld.getBytes() : null;
- result = mLockPatternUtils.checkPassword(passwordBytes, mCurrentUserId);
- } else {
- result = mLockPatternUtils.checkPattern(stringToPattern(mOld), mCurrentUserId);
- }
+ final boolean result = mLockPatternUtils.checkCredential(getOldCredential(),
+ mCurrentUserId, null);
if (!result) {
if (!mLockPatternUtils.isManagedProfileWithUnifiedChallenge(mCurrentUserId)) {
mLockPatternUtils.reportFailedPasswordAttempt(mCurrentUserId);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 5594614..f4cad63 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -95,8 +95,6 @@
@VisibleForTesting
public static class CredentialHash {
- /** Deprecated private static final int VERSION_LEGACY = 0; */
- private static final int VERSION_GATEKEEPER = 1;
private CredentialHash(byte[] hash, @CredentialType int type) {
if (type != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
@@ -126,42 +124,6 @@
byte[] hash;
@CredentialType int type;
-
- public byte[] toBytes() {
- try {
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- DataOutputStream dos = new DataOutputStream(os);
- dos.write(VERSION_GATEKEEPER);
- dos.write(type);
- if (hash != null && hash.length > 0) {
- dos.writeInt(hash.length);
- dos.write(hash);
- } else {
- dos.writeInt(0);
- }
- dos.close();
- return os.toByteArray();
- } catch (IOException e) {
- throw new IllegalStateException("Fail to serialze credential hash", e);
- }
- }
-
- public static CredentialHash fromBytes(byte[] bytes) {
- try {
- DataInputStream is = new DataInputStream(new ByteArrayInputStream(bytes));
- /* int version = */ is.read();
- int type = is.read();
- int hashSize = is.readInt();
- byte[] hash = null;
- if (hashSize > 0) {
- hash = new byte[hashSize];
- is.readFully(hash);
- }
- return new CredentialHash(hash, type);
- } catch (IOException e) {
- throw new IllegalStateException("Fail to deserialze credential hash", e);
- }
- }
}
public LockSettingsStorage(Context context) {
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
index e753a7b..9eac252 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -93,7 +93,7 @@
public void unselectRoute(String packageName, String routeId) {
if (mConnectionReady) {
- mActiveConnection.unselectRotue(packageName, routeId);
+ mActiveConnection.unselectRoute(packageName, routeId);
updateBinding();
}
}
@@ -105,6 +105,20 @@
}
}
+ public void requestSetVolume(MediaRoute2Info route, int volume) {
+ if (mConnectionReady) {
+ mActiveConnection.requestSetVolume(route.getId(), volume);
+ updateBinding();
+ }
+ }
+
+ public void requestUpdateVolume(MediaRoute2Info route, int delta) {
+ if (mConnectionReady) {
+ mActiveConnection.requestUpdateVolume(route.getId(), delta);
+ updateBinding();
+ }
+ }
+
@Nullable
public MediaRoute2ProviderInfo getProviderInfo() {
return mProviderInfo;
@@ -324,7 +338,7 @@
}
}
- public void unselectRotue(String packageName, String routeId) {
+ public void unselectRoute(String packageName, String routeId) {
try {
mProvider.unselectRoute(packageName, routeId);
} catch (RemoteException ex) {
@@ -340,6 +354,22 @@
}
}
+ public void requestSetVolume(String routeId, int volume) {
+ try {
+ mProvider.requestSetVolume(routeId, volume);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed to deliver request to request set volume.", ex);
+ }
+ }
+
+ public void requestUpdateVolume(String routeId, int delta) {
+ try {
+ mProvider.requestUpdateVolume(routeId, delta);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed to deliver request to request update volume.", ex);
+ }
+ }
+
@Override
public void binderDied() {
mHandler.post(() -> onConnectionDied(Connection.this));
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 668f2be..74d59ac 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -198,6 +198,34 @@
}
}
+ public void requestSetVolume2(IMediaRouter2Client client, MediaRoute2Info route, int volume) {
+ Objects.requireNonNull(client, "client must not be null");
+ Objects.requireNonNull(route, "route must not be null");
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ requestSetVolumeLocked(client, route, volume);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public void requestUpdateVolume2(IMediaRouter2Client client, MediaRoute2Info route, int delta) {
+ Objects.requireNonNull(client, "client must not be null");
+ Objects.requireNonNull(route, "route must not be null");
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ requestUpdateVolumeLocked(client, route, delta);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
public void selectClientRoute2(@NonNull IMediaRouter2Manager manager,
String packageName, @Nullable MediaRoute2Info route) {
final long token = Binder.clearCallingIdentity();
@@ -210,6 +238,37 @@
}
}
+ public void requestSetVolume2Manager(IMediaRouter2Manager manager,
+ MediaRoute2Info route, int volume) {
+ Objects.requireNonNull(manager, "manager must not be null");
+ Objects.requireNonNull(route, "route must not be null");
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ requestSetVolumeLocked(manager, route, volume);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ public void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
+ MediaRoute2Info route, int delta) {
+ Objects.requireNonNull(manager, "manager must not be null");
+ Objects.requireNonNull(route, "route must not be null");
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ requestUpdateVolumeLocked(manager, route, delta);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+
public void registerClient(@NonNull IMediaRouterClient client, @NonNull String packageName) {
Objects.requireNonNull(client, "client must not be null");
@@ -362,6 +421,30 @@
}
}
+ private void requestSetVolumeLocked(IMediaRouter2Client client, MediaRoute2Info route,
+ int volume) {
+ final IBinder binder = client.asBinder();
+ ClientRecord clientRecord = mAllClientRecords.get(binder);
+
+ if (clientRecord != null) {
+ clientRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::requestSetVolume,
+ clientRecord.mUserRecord.mHandler, route, volume));
+ }
+ }
+
+ private void requestUpdateVolumeLocked(IMediaRouter2Client client, MediaRoute2Info route,
+ int delta) {
+ final IBinder binder = client.asBinder();
+ ClientRecord clientRecord = mAllClientRecords.get(binder);
+
+ if (clientRecord != null) {
+ clientRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::requestUpdateVolume,
+ clientRecord.mUserRecord.mHandler, route, delta));
+ }
+ }
+
private void registerManagerLocked(IMediaRouter2Manager manager,
int uid, int pid, String packageName, int userId, boolean trusted) {
final IBinder binder = manager.asBinder();
@@ -424,6 +507,31 @@
}
}
+ private void requestSetVolumeLocked(IMediaRouter2Manager manager, MediaRoute2Info route,
+ int volume) {
+ final IBinder binder = manager.asBinder();
+ ManagerRecord managerRecord = mAllManagerRecords.get(binder);
+
+ if (managerRecord != null) {
+ managerRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::requestSetVolume,
+ managerRecord.mUserRecord.mHandler, route, volume));
+ }
+ }
+
+ private void requestUpdateVolumeLocked(IMediaRouter2Manager manager, MediaRoute2Info route,
+ int delta) {
+ final IBinder binder = manager.asBinder();
+ ManagerRecord managerRecord = mAllManagerRecords.get(binder);
+
+ if (managerRecord != null) {
+ managerRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::requestUpdateVolume,
+ managerRecord.mUserRecord.mHandler, route, delta));
+ }
+ }
+
+
private void initializeUserLocked(UserRecord userRecord) {
if (DEBUG) {
Slog.d(TAG, userRecord + ": Initialized");
@@ -679,6 +787,20 @@
}
}
+ private void requestSetVolume(MediaRoute2Info route, int volume) {
+ final MediaRoute2ProviderProxy provider = findProvider(route.getProviderId());
+ if (provider != null) {
+ provider.requestSetVolume(route, volume);
+ }
+ }
+
+ private void requestUpdateVolume(MediaRoute2Info route, int delta) {
+ final MediaRoute2ProviderProxy provider = findProvider(route.getProviderId());
+ if (provider != null) {
+ provider.requestUpdateVolume(route, delta);
+ }
+ }
+
private void scheduleUpdateProviderInfos() {
if (!mProviderInfosUpdateScheduled) {
mProviderInfosUpdateScheduled = true;
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 796a25d..afd92f6 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -499,6 +499,32 @@
mService2.setControlCategories2(client, categories);
}
+ // Binder call
+ @Override
+ public void requestSetVolume2(IMediaRouter2Client client, MediaRoute2Info route, int volume) {
+ mService2.requestSetVolume2(client, route, volume);
+ }
+
+ // Binder call
+ @Override
+ public void requestUpdateVolume2(IMediaRouter2Client client, MediaRoute2Info route, int delta) {
+ mService2.requestUpdateVolume2(client, route, delta);
+ }
+
+ // Binder call
+ @Override
+ public void requestSetVolume2Manager(IMediaRouter2Manager manager,
+ MediaRoute2Info route, int volume) {
+ mService2.requestSetVolume2Manager(manager, route, volume);
+ }
+
+ // Binder call
+ @Override
+ public void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
+ MediaRoute2Info route, int delta) {
+ mService2.requestUpdateVolume2Manager(manager, route, delta);
+ }
+
void restoreBluetoothA2dp() {
try {
boolean a2dpOn;
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 976a0c6..09be474 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -217,7 +217,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
-import com.android.internal.os.SomeArgs;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ConcurrentUtils;
@@ -385,6 +384,7 @@
private static final int MSG_SUBSCRIPTION_OVERRIDE = 16;
private static final int MSG_METERED_RESTRICTED_PACKAGES_CHANGED = 17;
private static final int MSG_SET_NETWORK_TEMPLATE_ENABLED = 18;
+ private static final int MSG_SUBSCRIPTION_PLANS_CHANGED = 19;
private static final int UID_MSG_STATE_CHANGED = 100;
private static final int UID_MSG_GONE = 101;
@@ -797,6 +797,7 @@
writePolicyAL();
}
+ enableFirewallChainUL(FIREWALL_CHAIN_STANDBY, true);
setRestrictBackgroundUL(mLoadedRestrictBackground);
updateRulesForGlobalChangeAL(false);
updateNotificationsNL();
@@ -1509,6 +1510,11 @@
latch.await(5, TimeUnit.SECONDS);
}
+ @VisibleForTesting
+ Handler getHandlerForTesting() {
+ return mHandler;
+ }
+
/**
* Update mobile policies with data cycle information from {@link CarrierConfigManager}
* if necessary.
@@ -3064,6 +3070,34 @@
mContext.enforceCallingOrSelfPermission(MANAGE_SUBSCRIPTION_PLANS, TAG);
}
+ private void enforceSubscriptionPlanValidity(SubscriptionPlan[] plans) {
+ // nothing to check if no plans
+ if (plans.length == 0) {
+ return;
+ }
+
+ long applicableNetworkTypes = 0;
+ boolean allNetworks = false;
+ for (SubscriptionPlan plan : plans) {
+ if (plan.getNetworkTypes() == null) {
+ allNetworks = true;
+ } else {
+ if ((applicableNetworkTypes & plan.getNetworkTypesBitMask()) != 0) {
+ throw new IllegalArgumentException(
+ "Multiple subscription plans defined for a single network type.");
+ } else {
+ applicableNetworkTypes |= plan.getNetworkTypesBitMask();
+ }
+ }
+ }
+
+ // ensure at least one plan applies for every network type
+ if (!allNetworks) {
+ throw new IllegalArgumentException(
+ "No generic subscription plan that applies to all network types.");
+ }
+ }
+
@Override
public SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage) {
enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage);
@@ -3228,6 +3262,7 @@
@Override
public void setSubscriptionPlans(int subId, SubscriptionPlan[] plans, String callingPackage) {
enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage);
+ enforceSubscriptionPlanValidity(plans);
for (SubscriptionPlan plan : plans) {
Preconditions.checkNotNull(plan);
@@ -3256,6 +3291,8 @@
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
mContext.sendBroadcast(intent, android.Manifest.permission.MANAGE_SUBSCRIPTION_PLANS);
+ mHandler.sendMessage(
+ mHandler.obtainMessage(MSG_SUBSCRIPTION_PLANS_CHANGED, subId, 0, plans));
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -3282,7 +3319,7 @@
@Override
public void setSubscriptionOverride(int subId, int overrideMask, int overrideValue,
- long networkTypeMask, long timeoutMillis, String callingPackage) {
+ long timeoutMillis, String callingPackage) {
enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage);
// We can only override when carrier told us about plans
@@ -3300,16 +3337,11 @@
final boolean overrideEnabled = Settings.Global.getInt(mContext.getContentResolver(),
NETPOLICY_OVERRIDE_ENABLED, 1) != 0;
if (overrideEnabled || overrideValue == 0) {
- SomeArgs args = SomeArgs.obtain();
- args.arg1 = subId;
- args.arg2 = overrideMask;
- args.arg3 = overrideValue;
- args.arg4 = networkTypeMask;
- mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, args));
+ mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE,
+ overrideMask, overrideValue, subId));
if (timeoutMillis > 0) {
- args.arg3 = 0;
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, args),
- timeoutMillis);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE,
+ overrideMask, 0, subId), timeoutMillis);
}
}
}
@@ -3799,39 +3831,6 @@
}
/**
- * Toggle the firewall standby chain and inform listeners if the uid rules have effectively
- * changed.
- */
- @GuardedBy("mUidRulesFirstLock")
- void updateRulesForAppIdleParoleUL() {
- boolean paroled = mUsageStats.isAppIdleParoleOn();
- boolean enableChain = !paroled;
- enableFirewallChainUL(FIREWALL_CHAIN_STANDBY, enableChain);
-
- int ruleCount = mUidFirewallStandbyRules.size();
- for (int i = 0; i < ruleCount; i++) {
- int uid = mUidFirewallStandbyRules.keyAt(i);
- int oldRules = mUidRules.get(uid);
- if (enableChain) {
- // Chain wasn't enabled before and the other power-related
- // chains are whitelists, so we can clear the
- // MASK_ALL_NETWORKS part of the rules and re-inform listeners if
- // the effective rules result in blocking network access.
- oldRules &= MASK_METERED_NETWORKS;
- } else {
- // Skip if it had no restrictions to begin with
- if ((oldRules & MASK_ALL_NETWORKS) == 0) continue;
- }
- final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldRules, paroled);
- if (newUidRules == RULE_NONE) {
- mUidRules.delete(uid);
- } else {
- mUidRules.put(uid, newUidRules);
- }
- }
- }
-
- /**
* Update rules that might be changed by {@link #mRestrictBackground},
* {@link #mRestrictPower}, or {@link #mDeviceIdleMode} value.
*/
@@ -4286,7 +4285,7 @@
private void updateRulesForPowerRestrictionsUL(int uid) {
final int oldUidRules = mUidRules.get(uid, RULE_NONE);
- final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldUidRules, false);
+ final int newUidRules = updateRulesForPowerRestrictionsUL(uid, oldUidRules);
if (newUidRules == RULE_NONE) {
mUidRules.delete(uid);
@@ -4300,30 +4299,28 @@
*
* @param uid the uid of the app to update rules for
* @param oldUidRules the current rules for the uid, in order to determine if there's a change
- * @param paroled whether to ignore idle state of apps and only look at other restrictions.
*
* @return the new computed rules for the uid
*/
- private int updateRulesForPowerRestrictionsUL(int uid, int oldUidRules, boolean paroled) {
+ private int updateRulesForPowerRestrictionsUL(int uid, int oldUidRules) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_NETWORK)) {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK,
- "updateRulesForPowerRestrictionsUL: " + uid + "/" + oldUidRules + "/"
- + (paroled ? "P" : "-"));
+ "updateRulesForPowerRestrictionsUL: " + uid + "/" + oldUidRules);
}
try {
- return updateRulesForPowerRestrictionsULInner(uid, oldUidRules, paroled);
+ return updateRulesForPowerRestrictionsULInner(uid, oldUidRules);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
}
}
- private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules, boolean paroled) {
+ private int updateRulesForPowerRestrictionsULInner(int uid, int oldUidRules) {
if (!isUidValidForBlacklistRules(uid)) {
if (LOGD) Slog.d(TAG, "no need to update restrict power rules for uid " + uid);
return RULE_NONE;
}
- final boolean isIdle = !paroled && isUidIdle(uid);
+ final boolean isIdle = isUidIdle(uid);
final boolean restrictMode = isIdle || mRestrictPower || mDeviceIdleMode;
final boolean isForeground = isUidForegroundOnRestrictPowerUL(uid);
@@ -4395,14 +4392,6 @@
} catch (NameNotFoundException nnfe) {
}
}
-
- @Override
- public void onParoleStateChanged(boolean isParoleOn) {
- synchronized (mUidRulesFirstLock) {
- mLogger.paroleStateChanged(isParoleOn);
- updateRulesForAppIdleParoleUL();
- }
- }
}
private void dispatchUidRulesChanged(INetworkPolicyListener listener, int uid, int uidRules) {
@@ -4445,11 +4434,20 @@
}
private void dispatchSubscriptionOverride(INetworkPolicyListener listener, int subId,
- int overrideMask, int overrideValue, long networkTypeMask) {
+ int overrideMask, int overrideValue) {
if (listener != null) {
try {
- listener.onSubscriptionOverride(subId, overrideMask, overrideValue,
- networkTypeMask);
+ listener.onSubscriptionOverride(subId, overrideMask, overrideValue);
+ } catch (RemoteException ignored) {
+ }
+ }
+ }
+
+ private void dispatchSubscriptionPlansChanged(INetworkPolicyListener listener, int subId,
+ SubscriptionPlan[] plans) {
+ if (listener != null) {
+ try {
+ listener.onSubscriptionPlansChanged(subId, plans);
} catch (RemoteException ignored) {
}
}
@@ -4550,16 +4548,13 @@
return true;
}
case MSG_SUBSCRIPTION_OVERRIDE: {
- final SomeArgs args = (SomeArgs) msg.obj;
- final int subId = (int) args.arg1;
- final int overrideMask = (int) args.arg2;
- final int overrideValue = (int) args.arg3;
- final long networkTypeMask = (long) args.arg4;
+ final int overrideMask = msg.arg1;
+ final int overrideValue = msg.arg2;
+ final int subId = (int) msg.obj;
final int length = mListeners.beginBroadcast();
for (int i = 0; i < length; i++) {
final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
- dispatchSubscriptionOverride(listener, subId, overrideMask, overrideValue,
- networkTypeMask);
+ dispatchSubscriptionOverride(listener, subId, overrideMask, overrideValue);
}
mListeners.finishBroadcast();
return true;
@@ -4576,6 +4571,17 @@
setNetworkTemplateEnabledInner(template, enabled);
return true;
}
+ case MSG_SUBSCRIPTION_PLANS_CHANGED: {
+ final SubscriptionPlan[] plans = (SubscriptionPlan[]) msg.obj;
+ final int subId = msg.arg1;
+ final int length = mListeners.beginBroadcast();
+ for (int i = 0; i < length; i++) {
+ final INetworkPolicyListener listener = mListeners.getBroadcastItem(i);
+ dispatchSubscriptionPlansChanged(listener, subId, plans);
+ }
+ mListeners.finishBroadcast();
+ return true;
+ }
default: {
return false;
}
@@ -4727,7 +4733,7 @@
}
/**
- * Calls {@link #setUidFirewallRules(int, SparseIntArray)} and
+ * Calls {@link #setUidFirewallRulesUL(int, SparseIntArray)} and
* {@link #enableFirewallChainUL(int, boolean)} synchronously.
*
* @param chain firewall chain.
diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
index 61be1f5..6f0ad33 100644
--- a/services/core/java/com/android/server/notification/NotificationDelegate.java
+++ b/services/core/java/com/android/server/notification/NotificationDelegate.java
@@ -17,6 +17,7 @@
package com.android.server.notification;
import android.app.Notification;
+import android.net.Uri;
import android.service.notification.NotificationStats;
import com.android.internal.statusbar.NotificationVisibility;
@@ -49,6 +50,12 @@
void onNotificationBubbleChanged(String key, boolean isBubble);
/**
+ * Grant permission to read the specified URI to the package associated with the
+ * NotificationRecord associated with the given key.
+ */
+ void grantInlineReplyUriPermission(String key, Uri uri, int callingUid);
+
+ /**
* Notifies that smart replies and actions have been added to the UI.
*/
void onNotificationSmartSuggestionsAdded(String key, int smartReplyCount, int smartActionCount,
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index d7efa1b..cd3343b 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -21,6 +21,7 @@
import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
import static android.app.Notification.FLAG_BUBBLE;
import static android.app.Notification.FLAG_FOREGROUND_SERVICE;
+import static android.app.Notification.FLAG_INSISTENT;
import static android.app.Notification.FLAG_NO_CLEAR;
import static android.app.Notification.FLAG_ONGOING_EVENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
@@ -49,13 +50,13 @@
import static android.content.Context.BIND_AUTO_CREATE;
import static android.content.Context.BIND_FOREGROUND_SERVICE;
import static android.content.Context.BIND_NOT_PERCEPTIBLE;
-import static android.content.pm.ActivityInfo.DOCUMENT_LAUNCH_ALWAYS;
import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_TELEVISION;
import static android.content.pm.PackageManager.MATCH_ALL;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
import static android.os.UserHandle.USER_NULL;
@@ -88,7 +89,6 @@
import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_MISSING;
import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__ACTIVITY_INFO_NOT_RESIZABLE;
-import static android.util.StatsLogInternal.BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
@@ -1156,6 +1156,56 @@
}
}
}
+
+ @Override
+ /**
+ * Grant permission to read the specified URI to the package specified in the
+ * NotificationRecord associated with the given key. The callingUid represents the UID of
+ * SystemUI from which this method is being called.
+ *
+ * For this to work, SystemUI must have permission to read the URI when running under the
+ * user associated with the NotificationRecord, and this grant will fail when trying
+ * to grant URI permissions across users.
+ */
+ public void grantInlineReplyUriPermission(String key, Uri uri, int callingUid) {
+ synchronized (mNotificationLock) {
+ NotificationRecord r = mNotificationsByKey.get(key);
+ if (r != null) {
+ IBinder owner = r.permissionOwner;
+ if (owner == null) {
+ r.permissionOwner = mUgmInternal.newUriPermissionOwner("NOTIF:" + key);
+ owner = r.permissionOwner;
+ }
+ int uid = callingUid;
+ int userId = r.sbn.getUserId();
+ if (userId == UserHandle.USER_ALL) {
+ userId = USER_SYSTEM;
+ }
+ if (UserHandle.getUserId(uid) != userId) {
+ try {
+ final String[] pkgs = mPackageManager.getPackagesForUid(callingUid);
+ if (pkgs == null) {
+ Log.e(TAG, "Cannot grant uri permission to unknown UID: "
+ + callingUid);
+ }
+ final String pkg = pkgs[0]; // Get the SystemUI package
+ // Find the UID for SystemUI for the correct user
+ uid = mPackageManager.getPackageUid(pkg, 0, userId);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Cannot talk to package manager", re);
+ }
+ }
+ grantUriPermission(owner, uri, uid, r.sbn.getPackageName(), userId);
+ } else {
+ Log.w(TAG, "No record found for notification key:" + key);
+
+ // TODO: figure out cancel story. I think it's: sysui needs to tell us
+ // whenever noitifications held by a lifetimextender go away
+ // IBinder owner = mUgmInternal.newUriPermissionOwner("InlineReply:" + key);
+ // pass in userId and package as well as key (key for logging purposes)
+ }
+ }
+ }
};
@VisibleForTesting
@@ -1185,7 +1235,7 @@
}
@GuardedBy("mNotificationLock")
- private void clearSoundLocked() {
+ void clearSoundLocked() {
mSoundNotificationKey = null;
long identity = Binder.clearCallingIdentity();
try {
@@ -1200,7 +1250,7 @@
}
@GuardedBy("mNotificationLock")
- private void clearVibrateLocked() {
+ void clearVibrateLocked() {
mVibrateNotificationKey = null;
long identity = Binder.clearCallingIdentity();
try {
@@ -1985,6 +2035,7 @@
public void updateAutogroupSummary(String key, boolean needsOngoingFlag) {
synchronized (mNotificationLock) {
NotificationRecord r = mNotificationsByKey.get(key);
+ if (r == null) return;
updateAutobundledSummaryFlags(r.getUser().getIdentifier(),
r.sbn.getPackageName(), needsOngoingFlag);
}
@@ -4496,13 +4547,13 @@
if (record != null && record.getAudioAttributes() != null) {
if ((mListenerHints & HINT_HOST_DISABLE_NOTIFICATION_EFFECTS) != 0) {
if (record.getAudioAttributes().getUsage()
- != AudioAttributes.USAGE_VOICE_COMMUNICATION) {
+ != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
return "listenerNoti";
}
}
if ((mListenerHints & HINT_HOST_DISABLE_CALL_EFFECTS) != 0) {
if (record.getAudioAttributes().getUsage()
- == AudioAttributes.USAGE_VOICE_COMMUNICATION) {
+ == AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
return "listenerCall";
}
}
@@ -5061,8 +5112,8 @@
}
if (contentViewSize >= mStripRemoteViewsSizeBytes) {
mUsageStats.registerImageRemoved(pkg);
- Slog.w(TAG,
- "Removed too large RemoteViews on pkg: " + pkg + " tag: " + tag + " id: " + id);
+ Slog.w(TAG, "Removed too large RemoteViews (" + contentViewSize + " bytes) on pkg: "
+ + pkg + " tag: " + tag + " id: " + id);
return true;
}
return false;
@@ -5250,18 +5301,6 @@
+ intent);
return false;
}
- if (info.documentLaunchMode != DOCUMENT_LAUNCH_ALWAYS) {
- StatsLog.write(StatsLog.BUBBLE_DEVELOPER_ERROR_REPORTED, packageName,
- BUBBLE_DEVELOPER_ERROR_REPORTED__ERROR__DOCUMENT_LAUNCH_NOT_ALWAYS);
- Log.w(TAG, "Unable to send as bubble -- activity is not documentLaunchMode=always "
- + "for intent: " + intent);
- return false;
- }
- if ((info.flags & ActivityInfo.FLAG_ALLOW_EMBEDDED) == 0) {
- Log.w(TAG, "Unable to send as bubble -- activity is not embeddable for intent: "
- + intent);
- return false;
- }
return true;
}
@@ -6073,7 +6112,6 @@
mIsAutomotive
? record.getImportance() > NotificationManager.IMPORTANCE_DEFAULT
: record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT;
-
// Remember if this notification already owns the notification channels.
boolean wasBeep = key != null && key.equals(mSoundNotificationKey);
boolean wasBuzz = key != null && key.equals(mVibrateNotificationKey);
@@ -6089,7 +6127,6 @@
}
if (aboveThreshold && isNotificationForCurrentUser(record)) {
-
if (mSystemReady && mAudioManager != null) {
Uri soundUri = record.getSound();
hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri);
@@ -6104,7 +6141,6 @@
vibration = mFallbackVibrationPattern;
}
hasValidVibrate = vibration != null;
-
boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) {
if (!sentAccessibilityEvent) {
@@ -6261,11 +6297,29 @@
return true;
}
+ // A looping ringtone, such as an incoming call is playing
+ if (isLoopingRingtoneNotification(mNotificationsByKey.get(mSoundNotificationKey))
+ || isLoopingRingtoneNotification(
+ mNotificationsByKey.get(mVibrateNotificationKey))) {
+ return true;
+ }
+
+ return false;
+ }
+
+ @GuardedBy("mNotificationLock")
+ private boolean isLoopingRingtoneNotification(final NotificationRecord playingRecord) {
+ if (playingRecord != null) {
+ if (playingRecord.getAudioAttributes().getUsage() == USAGE_NOTIFICATION_RINGTONE
+ && (playingRecord.getNotification().flags & FLAG_INSISTENT) != 0) {
+ return true;
+ }
+ }
return false;
}
private boolean playSound(final NotificationRecord record, Uri soundUri) {
- boolean looping = (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
+ boolean looping = (record.getNotification().flags & FLAG_INSISTENT) != 0;
// play notifications if there is no user of exclusive audio focus
// and the stream volume is not 0 (non-zero volume implies not silenced by SILENT or
// VIBRATE ringer mode)
@@ -6317,7 +6371,6 @@
try {
Thread.sleep(waitMs);
} catch (InterruptedException e) { }
-
// Notifications might be canceled before it actually vibrates due to waitMs,
// so need to check the notification still valide for vibrate.
synchronized (mNotificationLock) {
@@ -7025,7 +7078,6 @@
private void grantUriPermission(IBinder owner, Uri uri, int sourceUid, String targetPkg,
int targetUserId) {
if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
-
final long ident = Binder.clearCallingIdentity();
try {
mUgm.grantUriPermissionFromOwner(owner, sourceUid, targetPkg,
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 9e7b4648..5f3e503 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -17,11 +17,13 @@
package com.android.server.om;
import static android.app.AppGlobals.getPackageManager;
+import static android.content.Intent.ACTION_OVERLAY_CHANGED;
import static android.content.Intent.ACTION_PACKAGE_ADDED;
import static android.content.Intent.ACTION_PACKAGE_CHANGED;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
import static android.content.Intent.ACTION_USER_ADDED;
import static android.content.Intent.ACTION_USER_REMOVED;
+import static android.content.Intent.EXTRA_REASON;
import static android.content.pm.PackageManager.SIGNATURE_MATCH;
import static android.os.Trace.TRACE_TAG_RRO;
import static android.os.Trace.traceBegin;
@@ -356,7 +358,11 @@
}
break;
case ACTION_PACKAGE_CHANGED:
- onPackageChanged(packageName, userIds);
+ // ignore the intent if it was sent by the package manager as a result of the
+ // overlay manager having sent ACTION_OVERLAY_CHANGED
+ if (!ACTION_OVERLAY_CHANGED.equals(intent.getStringExtra(EXTRA_REASON))) {
+ onPackageChanged(packageName, userIds);
+ }
break;
case ACTION_PACKAGE_REMOVED:
if (replacing) {
@@ -885,7 +891,7 @@
FgThread.getHandler().post(() -> {
updateAssets(userId, targetPackageName);
- final Intent intent = new Intent(Intent.ACTION_OVERLAY_CHANGED,
+ final Intent intent = new Intent(ACTION_OVERLAY_CHANGED,
Uri.fromParts("package", targetPackageName, null));
intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
diff --git a/services/core/java/com/android/server/os/SchedulingPolicyService.java b/services/core/java/com/android/server/os/SchedulingPolicyService.java
index 2371b04..c3ed7d7 100644
--- a/services/core/java/com/android/server/os/SchedulingPolicyService.java
+++ b/services/core/java/com/android/server/os/SchedulingPolicyService.java
@@ -21,7 +21,6 @@
import android.os.IBinder;
import android.os.ISchedulingPolicyService;
import android.os.Process;
-import android.os.RemoteException;
import android.util.Log;
import com.android.server.SystemServerInitThreadPool;
@@ -64,7 +63,7 @@
// (Note that if mediaserver thinks we're in boosted state before the crash,
// the state could go out of sync temporarily until mediaserver enables/disable
// boost next time, but this won't be a big issue.)
- SystemServerInitThreadPool.get().submit(() -> {
+ SystemServerInitThreadPool.submit(() -> {
synchronized (mDeathRecipient) {
// only do this if we haven't already got a request to boost.
if (mBoostedPid == -1) {
diff --git a/services/core/java/com/android/server/os/TEST_MAPPING b/services/core/java/com/android/server/os/TEST_MAPPING
new file mode 100644
index 0000000..502f1e8
--- /dev/null
+++ b/services/core/java/com/android/server/os/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsUsbTests"
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 61ea84f..c8179a7 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -41,7 +41,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.FgThread;
-import com.android.server.compat.PlatformCompat;
+import com.android.server.compat.CompatConfig;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -131,24 +131,22 @@
private static class FeatureConfigImpl implements FeatureConfig {
private static final String FILTERING_ENABLED_NAME = "package_query_filtering_enabled";
- private final PackageManagerService.Injector mInjector;
- private volatile boolean mFeatureEnabled = true;
+ private volatile boolean mFeatureEnabled = false;
+ private CompatConfig mCompatibility;
private FeatureConfigImpl(PackageManagerService.Injector injector) {
- mInjector = injector;
+ mCompatibility = injector.getCompatibility();
}
@Override
public void onSystemReady() {
mFeatureEnabled = DeviceConfig.getBoolean(
- NAMESPACE_PACKAGE_MANAGER_SERVICE, FILTERING_ENABLED_NAME,
- true);
+ NAMESPACE_PACKAGE_MANAGER_SERVICE, FILTERING_ENABLED_NAME, false);
DeviceConfig.addOnPropertiesChangedListener(
NAMESPACE_PACKAGE_MANAGER_SERVICE, FgThread.getExecutor(),
properties -> {
synchronized (FeatureConfigImpl.this) {
- mFeatureEnabled = properties.getBoolean(
- FILTERING_ENABLED_NAME, true);
+ mFeatureEnabled = properties.getBoolean(FILTERING_ENABLED_NAME, false);
}
});
}
@@ -160,12 +158,7 @@
@Override
public boolean packageIsEnabled(PackageParser.Package pkg) {
- final PlatformCompat compatibility = mInjector.getCompatibility();
- if (compatibility == null) {
- Slog.wtf(TAG, "PlatformCompat is null");
- return mFeatureEnabled;
- }
- return compatibility.isChangeEnabled(
+ return mCompatibility.isChangeEnabled(
PackageManager.FILTER_APPLICATION_QUERY, pkg.applicationInfo);
}
}
@@ -202,19 +195,19 @@
return false;
}
for (Intent intent : querying.mQueriesIntents) {
- if (matches(intent, potentialTarget.providers, potentialTarget.activities,
- potentialTarget.services, potentialTarget.receivers)) {
+ if (matches(intent, potentialTarget)) {
return true;
}
}
return false;
}
- private static boolean matches(Intent intent,
- ArrayList<PackageParser.Provider> providerList,
- ArrayList<? extends Component<? extends IntentInfo>>... componentLists) {
- for (int p = providerList.size() - 1; p >= 0; p--) {
- PackageParser.Provider provider = providerList.get(p);
+ private static boolean matches(Intent intent, PackageParser.Package potentialTarget) {
+ for (int p = potentialTarget.providers.size() - 1; p >= 0; p--) {
+ PackageParser.Provider provider = potentialTarget.providers.get(p);
+ if (!provider.info.exported) {
+ continue;
+ }
final ProviderInfo providerInfo = provider.info;
final Uri data = intent.getData();
if ("content".equalsIgnoreCase(intent.getScheme())
@@ -223,19 +216,44 @@
return true;
}
}
+ for (int s = potentialTarget.services.size() - 1; s >= 0; s--) {
+ PackageParser.Service service = potentialTarget.services.get(s);
+ if (!service.info.exported) {
+ continue;
+ }
+ if (matchesAnyFilter(intent, service)) {
+ return true;
+ }
+ }
+ for (int a = potentialTarget.activities.size() - 1; a >= 0; a--) {
+ PackageParser.Activity activity = potentialTarget.activities.get(a);
+ if (!activity.info.exported) {
+ continue;
+ }
+ if (matchesAnyFilter(intent, activity)) {
+ return true;
+ }
+ }
+ for (int r = potentialTarget.receivers.size() - 1; r >= 0; r--) {
+ PackageParser.Activity receiver = potentialTarget.receivers.get(r);
+ if (!receiver.info.exported) {
+ continue;
+ }
+ if (matchesAnyFilter(intent, receiver)) {
+ return true;
+ }
+ }
+ return false;
+ }
- for (int l = componentLists.length - 1; l >= 0; l--) {
- ArrayList<? extends Component<? extends IntentInfo>> components = componentLists[l];
- for (int c = components.size() - 1; c >= 0; c--) {
- Component<? extends IntentInfo> component = components.get(c);
- ArrayList<? extends IntentInfo> intents = component.intents;
- for (int i = intents.size() - 1; i >= 0; i--) {
- IntentFilter intentFilter = intents.get(i);
- if (intentFilter.match(intent.getAction(), intent.getType(), intent.getScheme(),
- intent.getData(), intent.getCategories(), "AppsFilter") > 0) {
- return true;
- }
- }
+ private static boolean matchesAnyFilter(
+ Intent intent, Component<? extends IntentInfo> component) {
+ ArrayList<? extends IntentInfo> intents = component.intents;
+ for (int i = intents.size() - 1; i >= 0; i--) {
+ IntentFilter intentFilter = intents.get(i);
+ if (intentFilter.match(intent.getAction(), intent.getType(), intent.getScheme(),
+ intent.getData(), intent.getCategories(), "AppsFilter") > 0) {
+ return true;
}
}
return false;
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index 8facce1..b1eb7e7 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -23,6 +23,7 @@
import static com.android.server.pm.PackageManagerService.DEBUG_REMOVE;
import static com.android.server.pm.PackageManagerService.fixProcessName;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -49,6 +50,7 @@
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
import com.android.server.IntentResolver;
import java.io.PrintWriter;
@@ -219,12 +221,14 @@
}
}
+ @Nullable
List<ResolveInfo> queryActivities(Intent intent, String resolvedType, int flags, int userId) {
synchronized (mLock) {
return mActivities.queryIntent(intent, resolvedType, flags, userId);
}
}
+ @Nullable
List<ResolveInfo> queryActivities(Intent intent, String resolvedType, int flags,
List<PackageParser.Activity> activities, int userId) {
synchronized (mLock) {
@@ -233,12 +237,14 @@
}
}
+ @Nullable
List<ResolveInfo> queryProviders(Intent intent, String resolvedType, int flags, int userId) {
synchronized (mLock) {
return mProviders.queryIntent(intent, resolvedType, flags, userId);
}
}
+ @Nullable
List<ResolveInfo> queryProviders(Intent intent, String resolvedType, int flags,
List<PackageParser.Provider> providers, int userId) {
synchronized (mLock) {
@@ -246,6 +252,7 @@
}
}
+ @Nullable
List<ProviderInfo> queryProviders(String processName, String metaDataKey, int uid, int flags,
int userId) {
if (!sUserManager.exists(userId)) {
@@ -285,6 +292,7 @@
return providerList;
}
+ @Nullable
ProviderInfo queryProvider(String authority, int flags, int userId) {
synchronized (mLock) {
final PackageParser.Provider p = mProvidersByAuthority.get(authority);
@@ -326,12 +334,14 @@
}
}
+ @Nullable
List<ResolveInfo> queryReceivers(Intent intent, String resolvedType, int flags, int userId) {
synchronized (mLock) {
return mReceivers.queryIntent(intent, resolvedType, flags, userId);
}
}
+ @Nullable
List<ResolveInfo> queryReceivers(Intent intent, String resolvedType, int flags,
List<PackageParser.Activity> receivers, int userId) {
synchronized (mLock) {
@@ -339,12 +349,14 @@
}
}
+ @Nullable
List<ResolveInfo> queryServices(Intent intent, String resolvedType, int flags, int userId) {
synchronized (mLock) {
return mServices.queryIntent(intent, resolvedType, flags, userId);
}
}
+ @Nullable
List<ResolveInfo> queryServices(Intent intent, String resolvedType, int flags,
List<PackageParser.Service> services, int userId) {
synchronized (mLock) {
@@ -375,8 +387,11 @@
addProvidersLocked(pkg, chatty);
addServicesLocked(pkg, chatty);
}
- final String setupWizardPackage = sPackageManagerInternal.getKnownPackageName(
- PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM);
+ // expect single setupwizard package
+ final String setupWizardPackage = ArrayUtils.firstOrNull(
+ sPackageManagerInternal.getKnownPackageNames(
+ PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM));
+
for (int i = newIntents.size() - 1; i >= 0; --i) {
final PackageParser.ActivityIntentInfo intentInfo = newIntents.get(i);
final PackageParser.Package disabledPkg = sPackageManagerInternal
@@ -410,8 +425,11 @@
final List<ActivityIntentInfo> protectedFilters = mProtectedFilters;
mProtectedFilters = null;
- final String setupWizardPackage = sPackageManagerInternal.getKnownPackageName(
- PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM);
+ // expect single setupwizard package
+ final String setupWizardPackage = ArrayUtils.firstOrNull(
+ sPackageManagerInternal.getKnownPackageNames(
+ PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM));
+
if (DEBUG_FILTERS && setupWizardPackage == null) {
Slog.i(TAG, "No setup wizard;"
+ " All protected intents capped to priority 0");
@@ -1355,6 +1373,7 @@
return super.queryIntent(intent, resolvedType, defaultOnly, userId);
}
+ @Nullable
List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags,
int userId) {
if (!sUserManager.exists(userId)) {
@@ -1366,6 +1385,7 @@
userId);
}
+ @Nullable
List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType,
int flags, List<PackageParser.Provider> packageProviders, int userId) {
if (!sUserManager.exists(userId)) {
diff --git a/services/core/java/com/android/server/pm/InstallSource.java b/services/core/java/com/android/server/pm/InstallSource.java
new file mode 100644
index 0000000..b0cf525
--- /dev/null
+++ b/services/core/java/com/android/server/pm/InstallSource.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.pm;
+
+import android.annotation.Nullable;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+/**
+ * Immutable class holding information about where the request to install or update an app
+ * came from.
+ */
+final class InstallSource {
+ private static final InstallSource EMPTY = new InstallSource(null);
+
+ /**
+ * The package that requested the installation, if known.
+ */
+ @Nullable
+ final String initiatingPackageName;
+
+ static InstallSource create(@Nullable String initiatingPackageName) {
+ return initiatingPackageName == null
+ ? EMPTY : new InstallSource(initiatingPackageName.intern());
+ }
+
+ private InstallSource(@Nullable String initiatingPackageName) {
+ this.initiatingPackageName = initiatingPackageName;
+ }
+
+ void dump(IndentingPrintWriter pw) {
+ pw.printPair("installInitiatingPackageName", initiatingPackageName);
+ }
+
+ /**
+ * Return an InstallSource the same as this one except it does not refer to the specified
+ * installer package name.
+ */
+ InstallSource removeInstallerPackage(String packageName) {
+ if (packageName != null && packageName.equals(initiatingPackageName)) {
+ return create(null);
+ }
+ return this;
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index a7d4237..eca93bb 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -482,15 +482,24 @@
throw new SecurityException("User restriction prevents installing");
}
+ String requestedInstallerPackageName = params.installerPackageName != null
+ ? params.installerPackageName : installerPackageName;
+
if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
params.installFlags |= PackageManager.INSTALL_FROM_ADB;
} else {
+ if (callingUid != Process.SYSTEM_UID) {
+ // The supplied installerPackageName must always belong to the calling app.
+ mAppOps.checkPackage(callingUid, installerPackageName);
+ }
// Only apps with INSTALL_PACKAGES are allowed to set an installer that is not the
// caller.
- if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES) !=
- PackageManager.PERMISSION_GRANTED) {
- mAppOps.checkPackage(callingUid, installerPackageName);
+ if (!requestedInstallerPackageName.equals(installerPackageName)) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
+ != PackageManager.PERMISSION_GRANTED) {
+ mAppOps.checkPackage(callingUid, requestedInstallerPackageName);
+ }
}
params.installFlags &= ~PackageManager.INSTALL_FROM_ADB;
@@ -614,11 +623,12 @@
stageCid = buildExternalStageCid(sessionId);
}
}
+ InstallSource installSource = InstallSource.create(installerPackageName);
session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
mInstallThread.getLooper(), mStagingManager, sessionId, userId,
- installerPackageName, callingUid, params, createdMillis, stageDir, stageCid, false,
- false, false, null, SessionInfo.INVALID_ID, false, false, false,
- SessionInfo.STAGED_SESSION_NO_ERROR, "");
+ requestedInstallerPackageName, callingUid, installSource, params, createdMillis,
+ stageDir, stageCid, false, false, false, null, SessionInfo.INVALID_ID,
+ false, false, false, SessionInfo.STAGED_SESSION_NO_ERROR, "");
synchronized (mSessions) {
mSessions.put(sessionId, session);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index b720290..d8bfa7d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -146,6 +146,8 @@
private static final String ATTR_USER_ID = "userId";
private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName";
private static final String ATTR_INSTALLER_UID = "installerUid";
+ private static final String ATTR_INITIATING_PACKAGE_NAME =
+ "installInitiatingPackageName";
private static final String ATTR_CREATED_MILLIS = "createdMillis";
private static final String ATTR_UPDATED_MILLIS = "updatedMillis";
private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir";
@@ -218,6 +220,10 @@
@GuardedBy("mLock")
private int mInstallerUid;
+ /** Where this install request came from */
+ @GuardedBy("mLock")
+ private InstallSource mInstallSource;
+
@GuardedBy("mLock")
private float mClientProgress = 0;
@GuardedBy("mLock")
@@ -413,7 +419,8 @@
Context context, PackageManagerService pm,
PackageSessionProvider sessionProvider, Looper looper, StagingManager stagingManager,
int sessionId, int userId,
- String installerPackageName, int installerUid, SessionParams params, long createdMillis,
+ String installerPackageName, int installerUid, @NonNull InstallSource installSource,
+ SessionParams params, long createdMillis,
File stageDir, String stageCid, boolean prepared, boolean committed, boolean sealed,
@Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
boolean isFailed, boolean isApplied, int stagedSessionErrorCode,
@@ -430,6 +437,7 @@
mOriginalInstallerUid = installerUid;
mInstallerPackageName = installerPackageName;
mInstallerUid = installerUid;
+ mInstallSource = Preconditions.checkNotNull(installSource);
this.params = params;
this.createdMillis = createdMillis;
this.updatedMillis = createdMillis;
@@ -1225,6 +1233,7 @@
mInstallerPackageName = packageName;
mInstallerUid = newOwnerAppInfo.uid;
+ mInstallSource = InstallSource.create(packageName);
}
// Persist the fact that we've sealed ourselves to prevent
@@ -1443,7 +1452,7 @@
mRelinquished = true;
return new PackageManagerService.ActiveInstallSession(mPackageName, stageDir,
- localObserver, params, mInstallerPackageName, mInstallerUid, user,
+ localObserver, params, mInstallerPackageName, mInstallerUid, mInstallSource, user,
mSigningDetails);
}
@@ -2336,6 +2345,7 @@
pw.printPair("mOriginalInstallerUid", mOriginalInstallerUid);
pw.printPair("mInstallerPackageName", mInstallerPackageName);
pw.printPair("mInstallerUid", mInstallerUid);
+ mInstallSource.dump(pw);
pw.printPair("createdMillis", createdMillis);
pw.printPair("updatedMillis", updatedMillis);
pw.printPair("stageDir", stageDir);
@@ -2416,6 +2426,8 @@
writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME,
mInstallerPackageName);
writeIntAttribute(out, ATTR_INSTALLER_UID, mInstallerUid);
+ writeStringAttribute(out, ATTR_INITIATING_PACKAGE_NAME,
+ mInstallSource.initiatingPackageName);
writeLongAttribute(out, ATTR_CREATED_MILLIS, createdMillis);
writeLongAttribute(out, ATTR_UPDATED_MILLIS, updatedMillis);
if (stageDir != null) {
@@ -2521,6 +2533,8 @@
final String installerPackageName = readStringAttribute(in, ATTR_INSTALLER_PACKAGE_NAME);
final int installerUid = readIntAttribute(in, ATTR_INSTALLER_UID, pm.getPackageUid(
installerPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId));
+ final String installInitiatingPackageName =
+ readStringAttribute(in, ATTR_INITIATING_PACKAGE_NAME);
final long createdMillis = readLongAttribute(in, ATTR_CREATED_MILLIS);
long updatedMillis = readLongAttribute(in, ATTR_UPDATED_MILLIS);
final String stageDirRaw = readStringAttribute(in, ATTR_SESSION_STAGE_DIR);
@@ -2612,17 +2626,11 @@
childSessionIdsArray = EMPTY_CHILD_SESSION_ARRAY;
}
+ InstallSource installSource = InstallSource.create(installInitiatingPackageName);
return new PackageInstallerSession(callback, context, pm, sessionProvider,
installerThread, stagingManager, sessionId, userId, installerPackageName,
- installerUid, params, createdMillis, stageDir, stageCid, prepared, committed,
- sealed, childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied,
- stagedSessionErrorCode, stagedSessionErrorMessage);
- }
-
- /**
- * Reads the session ID from a child session tag stored in the provided {@link XmlPullParser}
- */
- static int readChildSessionIdFromXml(@NonNull XmlPullParser in) {
- return readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID);
+ installerUid, installSource, params, createdMillis, stageDir, stageCid,
+ prepared, committed, sealed, childSessionIdsArray, parentSessionId,
+ isReady, isFailed, isApplied, stagedSessionErrorCode, stagedSessionErrorMessage);
}
}
diff --git a/core/java/android/content/pm/PackageList.java b/services/core/java/com/android/server/pm/PackageList.java
similarity index 96%
rename from core/java/android/content/pm/PackageList.java
rename to services/core/java/com/android/server/pm/PackageList.java
index e3eb2c5..60bc8a8 100644
--- a/core/java/android/content/pm/PackageList.java
+++ b/services/core/java/com/android/server/pm/PackageList.java
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package android.content.pm;
+package com.android.server.pm;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManagerInternal.PackageListObserver;
import com.android.server.LocalServices;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e0697a6..d057aa2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -162,7 +162,6 @@
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfoLite;
import android.content.pm.PackageInstaller;
-import android.content.pm.PackageList;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
import android.content.pm.PackageManager.ModuleInfoFlags;
@@ -300,7 +299,7 @@
import com.android.server.SystemConfig;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.Watchdog;
-import com.android.server.compat.PlatformCompat;
+import com.android.server.compat.CompatConfig;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.Settings.DatabaseVersion;
@@ -835,7 +834,7 @@
private final Singleton<StorageManager> mStorageManagerProducer;
private final Singleton<AppOpsManager> mAppOpsManagerProducer;
private final Singleton<AppsFilter> mAppsFilterProducer;
- private final Singleton<PlatformCompat> mPlatformCompatProducer;
+ private final Singleton<CompatConfig> mPlatformCompatProducer;
Injector(Context context, Object lock, Installer installer,
Object installLock, PackageAbiHelper abiHelper,
@@ -853,7 +852,7 @@
Producer<StorageManager> storageManagerProducer,
Producer<AppOpsManager> appOpsManagerProducer,
Producer<AppsFilter> appsFilterProducer,
- Producer<PlatformCompat> platformCompatProducer) {
+ Producer<CompatConfig> platformCompatProducer) {
mContext = context;
mLock = lock;
mInstaller = installer;
@@ -964,7 +963,7 @@
return mAppsFilterProducer.get(this, mPackageManager);
}
- public PlatformCompat getCompatibility() {
+ public CompatConfig getCompatibility() {
return mPlatformCompatProducer.get(this, mPackageManager);
}
}
@@ -975,124 +974,9 @@
@Override public final boolean hasFeature(String feature) {
return PackageManagerService.this.hasSystemFeature(feature, 0);
}
-
- final List<PackageParser.Package> getStaticOverlayPackages(
- Collection<PackageParser.Package> allPackages, String targetPackageName) {
- if ("android".equals(targetPackageName)) {
- // Static RROs targeting to "android", ie framework-res.apk, are already applied by
- // native AssetManager.
- return null;
- }
-
- List<PackageParser.Package> overlayPackages = null;
- for (PackageParser.Package p : allPackages) {
- if (targetPackageName.equals(p.mOverlayTarget) && p.mOverlayIsStatic) {
- if (overlayPackages == null) {
- overlayPackages = new ArrayList<>();
- }
- overlayPackages.add(p);
- }
- }
- if (overlayPackages != null) {
- Comparator<PackageParser.Package> cmp =
- Comparator.comparingInt(p -> p.mOverlayPriority);
- overlayPackages.sort(cmp);
- }
- return overlayPackages;
- }
-
- final String[] getStaticOverlayPaths(List<PackageParser.Package> overlayPackages,
- String targetPath) {
- if (overlayPackages == null || overlayPackages.isEmpty()) {
- return null;
- }
- List<String> overlayPathList = null;
- for (PackageParser.Package overlayPackage : overlayPackages) {
- if (targetPath == null) {
- if (overlayPathList == null) {
- overlayPathList = new ArrayList<>();
- }
- overlayPathList.add(overlayPackage.baseCodePath);
- continue;
- }
-
- try {
- // Creates idmaps for system to parse correctly the Android manifest of the
- // target package.
- //
- // OverlayManagerService will update each of them with a correct gid from its
- // target package app id.
- mInstaller.idmap(targetPath, overlayPackage.baseCodePath,
- UserHandle.getSharedAppGid(
- UserHandle.getUserGid(UserHandle.USER_SYSTEM)));
- if (overlayPathList == null) {
- overlayPathList = new ArrayList<>();
- }
- overlayPathList.add(overlayPackage.baseCodePath);
- } catch (InstallerException e) {
- Slog.e(TAG, "Failed to generate idmap for " + targetPath + " and " +
- overlayPackage.baseCodePath);
- }
- }
- return overlayPathList == null ? null : overlayPathList.toArray(new String[0]);
- }
-
- String[] getStaticOverlayPaths(String targetPackageName, String targetPath) {
- List<PackageParser.Package> overlayPackages;
- synchronized (mInstallLock) {
- synchronized (mLock) {
- overlayPackages = getStaticOverlayPackages(
- mPackages.values(), targetPackageName);
- }
- // It is safe to keep overlayPackages without holding mPackages because static overlay
- // packages can't be uninstalled or disabled.
- return getStaticOverlayPaths(overlayPackages, targetPath);
- }
- }
-
- @Override public final String[] getOverlayApks(String targetPackageName) {
- return getStaticOverlayPaths(targetPackageName, null);
- }
-
- @Override public final String[] getOverlayPaths(String targetPackageName,
- String targetPath) {
- return getStaticOverlayPaths(targetPackageName, targetPath);
- }
- }
-
- class ParallelPackageParserCallback extends PackageParserCallback {
- List<PackageParser.Package> mOverlayPackages = null;
-
- void findStaticOverlayPackages() {
- synchronized (mLock) {
- for (PackageParser.Package p : mPackages.values()) {
- if (p.mOverlayIsStatic) {
- if (mOverlayPackages == null) {
- mOverlayPackages = new ArrayList<>();
- }
- mOverlayPackages.add(p);
- }
- }
- }
- }
-
- @Override
- synchronized String[] getStaticOverlayPaths(String targetPackageName, String targetPath) {
- // We can trust mOverlayPackages without holding mPackages because package uninstall
- // can't happen while running parallel parsing.
- // And we can call mInstaller inside getStaticOverlayPaths without holding mInstallLock
- // because mInstallLock is held before running parallel parsing.
- // Moreover holding mPackages or mInstallLock on each parsing thread causes dead-lock.
- return mOverlayPackages == null ? null :
- getStaticOverlayPaths(
- getStaticOverlayPackages(mOverlayPackages, targetPackageName),
- targetPath);
- }
}
final PackageParser.Callback mPackageParserCallback = new PackageParserCallback();
- final ParallelPackageParserCallback mParallelPackageParserCallback =
- new ParallelPackageParserCallback();
// Currently known shared libraries.
final ArrayMap<String, LongSparseArray<SharedLibraryInfo>> mSharedLibraries = new ArrayMap<>();
@@ -1604,6 +1488,8 @@
final @Nullable String mConfiguratorPackage;
final @Nullable String mAppPredictionServicePackage;
final @Nullable String mIncidentReportApproverPackage;
+ final @Nullable String[] mTelephonyPackages;
+ final @Nullable String mWifiPackage;
final @NonNull String mServicesSystemSharedLibraryPackageName;
final @NonNull String mSharedSystemSharedLibraryPackageName;
@@ -1676,7 +1562,8 @@
}
// Send broadcasts
for (int i = 0; i < size; i++) {
- sendPackageChangedBroadcast(packages[i], true, components[i], uids[i]);
+ sendPackageChangedBroadcast(packages[i], true, components[i], uids[i],
+ null);
}
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
break;
@@ -2164,7 +2051,7 @@
// send broadcast that all consumers of the static shared library have changed
sendPackageChangedBroadcast(pkg.packageName, false /*killFlag*/,
new ArrayList<>(Collections.singletonList(pkg.packageName)),
- pkg.applicationInfo.uid);
+ pkg.applicationInfo.uid, null);
}
}
@@ -2466,7 +2353,7 @@
new Injector.SystemServiceProducer<>(StorageManager.class),
new Injector.SystemServiceProducer<>(AppOpsManager.class),
(i, pm) -> AppsFilter.create(i),
- (i, pm) -> (PlatformCompat) ServiceManager.getService("platform_compat"));
+ (i, pm) -> CompatConfig.get());
PackageManagerService m = new PackageManagerService(injector, factoryTest, onlyCore);
t.traceEnd(); // "create package manager"
@@ -2806,8 +2693,6 @@
systemScanFlags | partition.scanFlag, 0);
}
- mParallelPackageParserCallback.findStaticOverlayPackages();
-
scanDirTracedLI(frameworkDir, systemParseFlags,
systemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0);
if (!mPackages.containsKey("android")) {
@@ -3048,7 +2933,7 @@
// Resolve protected action filters. Only the setup wizard is allowed to
// have a high priority filter for these actions.
- mSetupWizardPackage = getSetupWizardPackageName();
+ mSetupWizardPackage = getSetupWizardPackageNameImpl();
mComponentResolver.fixProtectedFilterPriorities();
mSystemTextClassifierPackage = getSystemTextClassifierPackageName();
@@ -3059,6 +2944,8 @@
mContext.getString(R.string.config_deviceConfiguratorPackageName);
mAppPredictionServicePackage = getAppPredictionServicePackageName();
mIncidentReportApproverPackage = getIncidentReportApproverPackageName();
+ mTelephonyPackages = getTelephonyPackageNames();
+ mWifiPackage = mContext.getString(R.string.config_wifiPackage);
// Now that we know all of the shared libraries, update all clients to have
// the correct library paths.
@@ -3135,7 +3022,7 @@
List<String> deferPackages = reconcileAppsDataLI(StorageManager.UUID_PRIVATE_INTERNAL,
UserHandle.USER_SYSTEM, storageFlags, true /* migrateAppData */,
true /* onlyCoreApps */);
- mPrepareAppDataFuture = SystemServerInitThreadPool.get().submit(() -> {
+ mPrepareAppDataFuture = SystemServerInitThreadPool.submit(() -> {
TimingsTraceLog traceLog = new TimingsTraceLog("SystemServerTimingAsync",
Trace.TRACE_TAG_PACKAGE_MANAGER);
traceLog.traceBegin("AppDataFixup");
@@ -6976,7 +6863,7 @@
* @param intent
* @return A filtered list of resolved activities.
*/
- private List<ResolveInfo> applyPostResolutionFilter(List<ResolveInfo> resolveInfos,
+ private List<ResolveInfo> applyPostResolutionFilter(@NonNull List<ResolveInfo> resolveInfos,
String ephemeralPkgName, boolean allowDynamicSplits, int filterCallingUid,
boolean resolveForStart, int userId, Intent intent) {
final boolean blockInstant = intent.isWebIntent() && areWebInstantAppsDisabled(userId);
@@ -7633,6 +7520,9 @@
if (pkgName == null) {
final List<ResolveInfo> result =
mComponentResolver.queryReceivers(intent, resolvedType, flags, userId);
+ if (result == null) {
+ return Collections.emptyList();
+ }
return applyPostResolutionFilter(
result, instantAppPkgName, allowDynamicSplits, callingUid, false, userId,
intent);
@@ -7641,6 +7531,9 @@
if (pkg != null) {
final List<ResolveInfo> result = mComponentResolver.queryReceivers(
intent, resolvedType, flags, pkg.receivers, userId);
+ if (result == null) {
+ return Collections.emptyList();
+ }
return applyPostResolutionFilter(
result, instantAppPkgName, allowDynamicSplits, callingUid, false, userId,
intent);
@@ -7735,15 +7628,25 @@
synchronized (mLock) {
String pkgName = intent.getPackage();
if (pkgName == null) {
+ final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(intent,
+ resolvedType, flags, userId);
+ if (resolveInfos == null) {
+ return Collections.emptyList();
+ }
return applyPostServiceResolutionFilter(
- mComponentResolver.queryServices(intent, resolvedType, flags, userId),
+ resolveInfos,
instantAppPkgName);
}
final PackageParser.Package pkg = mPackages.get(pkgName);
if (pkg != null) {
+ final List<ResolveInfo> resolveInfos = mComponentResolver.queryServices(intent,
+ resolvedType, flags, pkg.services,
+ userId);
+ if (resolveInfos == null) {
+ return Collections.emptyList();
+ }
return applyPostServiceResolutionFilter(
- mComponentResolver.queryServices(intent, resolvedType, flags, pkg.services,
- userId),
+ resolveInfos,
instantAppPkgName);
}
return Collections.emptyList();
@@ -7853,15 +7756,25 @@
synchronized (mLock) {
String pkgName = intent.getPackage();
if (pkgName == null) {
+ final List<ResolveInfo> resolveInfos = mComponentResolver.queryProviders(intent,
+ resolvedType, flags, userId);
+ if (resolveInfos == null) {
+ return Collections.emptyList();
+ }
return applyPostContentProviderResolutionFilter(
- mComponentResolver.queryProviders(intent, resolvedType, flags, userId),
+ resolveInfos,
instantAppPkgName);
}
final PackageParser.Package pkg = mPackages.get(pkgName);
if (pkg != null) {
+ final List<ResolveInfo> resolveInfos = mComponentResolver.queryProviders(intent,
+ resolvedType, flags,
+ pkg.providers, userId);
+ if (resolveInfos == null) {
+ return Collections.emptyList();
+ }
return applyPostContentProviderResolutionFilter(
- mComponentResolver.queryProviders(intent, resolvedType, flags,
- pkg.providers, userId),
+ resolveInfos,
instantAppPkgName);
}
return Collections.emptyList();
@@ -8441,7 +8354,7 @@
}
try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
- mParallelPackageParserCallback)) {
+ mPackageParserCallback)) {
// Submit files for parsing in parallel
int fileCount = 0;
for (File file : files) {
@@ -10657,6 +10570,50 @@
return changedAbiCodePath;
}
+ /**
+ * Sets the enabled state of components configured through {@link SystemConfig}.
+ * This modifies the {@link PackageSetting} object.
+ **/
+ static void configurePackageComponents(PackageParser.Package pkg) {
+ final ArrayMap<String, Boolean> componentsEnabledStates = SystemConfig.getInstance()
+ .getComponentsEnabledStates(pkg.packageName);
+ if (componentsEnabledStates == null) {
+ return;
+ }
+
+ for (int i = pkg.activities.size() - 1; i >= 0; i--) {
+ final PackageParser.Activity component = pkg.activities.get(i);
+ final Boolean enabled = componentsEnabledStates.get(component.className);
+ if (enabled != null) {
+ component.info.enabled = enabled;
+ }
+ }
+
+ for (int i = pkg.receivers.size() - 1; i >= 0; i--) {
+ final PackageParser.Activity component = pkg.receivers.get(i);
+ final Boolean enabled = componentsEnabledStates.get(component.className);
+ if (enabled != null) {
+ component.info.enabled = enabled;
+ }
+ }
+
+ for (int i = pkg.providers.size() - 1; i >= 0; i--) {
+ final PackageParser.Provider component = pkg.providers.get(i);
+ final Boolean enabled = componentsEnabledStates.get(component.className);
+ if (enabled != null) {
+ component.info.enabled = enabled;
+ }
+ }
+
+ for (int i = pkg.services.size() - 1; i >= 0; i--) {
+ final PackageParser.Service component = pkg.services.get(i);
+ final Boolean enabled = componentsEnabledStates.get(component.className);
+ if (enabled != null) {
+ component.info.enabled = enabled;
+ }
+ }
+ }
+
/**
* Just scans the package without any side effects.
@@ -10824,6 +10781,10 @@
pkg.applicationInfo.initForUser(UserHandle.USER_SYSTEM);
}
+ if (pkg.isSystem()) {
+ configurePackageComponents(pkg);
+ }
+
final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting);
if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
@@ -13954,6 +13915,7 @@
final IPackageInstallObserver2 observer;
int installFlags;
final String installerPackageName;
+ final InstallSource installSource;
final String volumeUuid;
private boolean mVerificationCompleted;
private boolean mEnableRollbackCompleted;
@@ -13970,10 +13932,11 @@
final long requiredInstalledVersionCode;
InstallParams(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer,
- int installFlags, String installerPackageName, String volumeUuid,
+ int installFlags, String installerPackageName,
+ InstallSource installSource, String volumeUuid,
VerificationInfo verificationInfo, UserHandle user, String packageAbiOverride,
String[] grantedPermissions, List<String> whitelistedRestrictedPermissions,
- PackageParser.SigningDetails signingDetails, int installReason,
+ SigningDetails signingDetails, int installReason,
long requiredInstalledVersionCode) {
super(user);
this.origin = origin;
@@ -13981,6 +13944,7 @@
this.observer = observer;
this.installFlags = installFlags;
this.installerPackageName = installerPackageName;
+ this.installSource = installSource;
this.volumeUuid = volumeUuid;
this.verificationInfo = verificationInfo;
this.packageAbiOverride = packageAbiOverride;
@@ -14012,6 +13976,7 @@
observer = activeInstallSession.getObserver();
installFlags = activeInstallSession.getSessionParams().installFlags;
installerPackageName = activeInstallSession.getInstallerPackageName();
+ installSource = activeInstallSession.getInstallSource();
volumeUuid = activeInstallSession.getSessionParams().volumeUuid;
packageAbiOverride = activeInstallSession.getSessionParams().abiOverride;
grantedRuntimePermissions = activeInstallSession.getSessionParams()
@@ -14494,6 +14459,7 @@
// Always refers to PackageManager flags only
final int installFlags;
final String installerPackageName;
+ final InstallSource installSource;
final String volumeUuid;
final UserHandle user;
final String abiOverride;
@@ -14512,7 +14478,8 @@
/* nullable */ String[] instructionSets;
InstallArgs(OriginInfo origin, MoveInfo move, IPackageInstallObserver2 observer,
- int installFlags, String installerPackageName, String volumeUuid,
+ int installFlags, String installerPackageName,
+ InstallSource installSource, String volumeUuid,
UserHandle user, String[] instructionSets,
String abiOverride, String[] installGrantPermissions,
List<String> whitelistedRestrictedPermissions,
@@ -14524,6 +14491,7 @@
this.installFlags = installFlags;
this.observer = observer;
this.installerPackageName = installerPackageName;
+ this.installSource = installSource;
this.volumeUuid = volumeUuid;
this.user = user;
this.instructionSets = instructionSets;
@@ -14537,6 +14505,16 @@
this.mMultiPackageInstallParams = multiPackageInstallParams;
}
+ /** New install */
+ InstallArgs(InstallParams params) {
+ this(params.origin, params.move, params.observer, params.installFlags,
+ params.installerPackageName, params.installSource, params.volumeUuid,
+ params.getUser(), null /*instructionSets*/, params.packageAbiOverride,
+ params.grantedRuntimePermissions, params.whitelistedRestrictedPermissions,
+ params.traceMethod, params.traceCookie, params.signingDetails,
+ params.installReason, params.mParentInstallParams);
+ }
+
abstract int copyApk();
abstract int doPreInstall(int status);
@@ -14617,17 +14595,12 @@
/** New install */
FileInstallArgs(InstallParams params) {
- super(params.origin, params.move, params.observer, params.installFlags,
- params.installerPackageName, params.volumeUuid,
- params.getUser(), null /*instructionSets*/, params.packageAbiOverride,
- params.grantedRuntimePermissions, params.whitelistedRestrictedPermissions,
- params.traceMethod, params.traceCookie, params.signingDetails,
- params.installReason, params.mParentInstallParams);
+ super(params);
}
/** Existing install */
FileInstallArgs(String codePath, String resourcePath, String[] instructionSets) {
- super(OriginInfo.fromNothing(), null, null, 0, null, null, null, instructionSets,
+ super(OriginInfo.fromNothing(), null, null, 0, null, null, null, null, instructionSets,
null, null, null, null, 0, PackageParser.SigningDetails.UNKNOWN,
PackageManager.INSTALL_REASON_UNKNOWN, null /* parent */);
this.codeFile = (codePath != null) ? new File(codePath) : null;
@@ -14806,12 +14779,7 @@
/** New install */
MoveInstallArgs(InstallParams params) {
- super(params.origin, params.move, params.observer, params.installFlags,
- params.installerPackageName, params.volumeUuid,
- params.getUser(), null /* instruction sets */, params.packageAbiOverride,
- params.grantedRuntimePermissions, params.whitelistedRestrictedPermissions,
- params.traceMethod, params.traceCookie, params.signingDetails,
- params.installReason, params.mParentInstallParams);
+ super(params);
}
int copyApk() {
@@ -15070,38 +15038,38 @@
return disabled;
}
- private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,
- int[] allUsers, PackageInstalledInfo res, UserHandle user, int installReason) {
+ private void updateSettingsLI(PackageParser.Package newPackage,
+ InstallArgs installArgs, int[] allUsers, PackageInstalledInfo res) {
// Update the parent package setting
- updateSettingsInternalLI(newPackage, installerPackageName, allUsers, res.origUsers,
- res, user, installReason);
+ updateSettingsInternalLI(newPackage, installArgs, allUsers, res);
// Update the child packages setting
final int childCount = (newPackage.childPackages != null)
? newPackage.childPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
PackageParser.Package childPackage = newPackage.childPackages.get(i);
PackageInstalledInfo childRes = res.addedChildPackages.get(childPackage.packageName);
- updateSettingsInternalLI(childPackage, installerPackageName, allUsers,
- childRes.origUsers, childRes, user, installReason);
+ updateSettingsInternalLI(childPackage, installArgs, allUsers, childRes);
}
}
private void updateSettingsInternalLI(PackageParser.Package pkg,
- String installerPackageName, int[] allUsers, int[] installedForUsers,
- PackageInstalledInfo res, UserHandle user, int installReason) {
+ InstallArgs installArgs, int[] allUsers, PackageInstalledInfo res) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
final String pkgName = pkg.packageName;
+ final String installerPackageName = installArgs.installerPackageName;
+ final int[] installedForUsers = res.origUsers;
+ final int installReason = installArgs.installReason;
if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.codePath);
synchronized (mLock) {
// NOTE: This changes slightly to include UPDATE_PERMISSIONS_ALL regardless of the size of pkg.permissions
- mPermissionManager.updatePermissions(pkg.packageName, pkg);
+ mPermissionManager.updatePermissions(pkgName, pkg);
// For system-bundled packages, we assume that installing an upgraded version
// of the package implies that the user actually wants to run that new code,
// so we enable the package.
PackageSetting ps = mSettings.mPackages.get(pkgName);
- final int userId = user.getIdentifier();
+ final int userId = installArgs.user.getIdentifier();
if (ps != null) {
if (isSystemApp(pkg)) {
if (DEBUG_INSTALL) {
@@ -15137,6 +15105,9 @@
ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName);
}
+ ps.setInstallSource(installArgs.installSource);
+
+
// When replacing an existing package, preserve the original install reason for all
// users that had the package installed before.
final Set<Integer> previousUserIds = new ArraySet<>();
@@ -15780,8 +15751,7 @@
}
commitReconciledScanResultLocked(reconciledPkg);
- updateSettingsLI(pkg, reconciledPkg.installArgs.installerPackageName, request.mAllUsers,
- res, reconciledPkg.installArgs.user, reconciledPkg.installArgs.installReason);
+ updateSettingsLI(pkg, reconciledPkg.installArgs, request.mAllUsers, res);
final PackageSetting ps = mSettings.mPackages.get(packageName);
if (ps != null) {
@@ -15791,8 +15761,7 @@
final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
for (int i = 0; i < childCount; i++) {
PackageParser.Package childPkg = pkg.childPackages.get(i);
- PackageInstalledInfo childRes = res.addedChildPackages.get(
- childPkg.packageName);
+ PackageInstalledInfo childRes = res.addedChildPackages.get(childPkg.packageName);
PackageSetting childPs = mSettings.getPackageLPr(childPkg.packageName);
if (childPs != null) {
childRes.newUsers = childPs.queryInstalledUsers(
@@ -16039,10 +16008,6 @@
* will be used to scan and reconcile the package.
*/
private static class PrepareResult {
- public final int installReason;
- public final String volumeUuid;
- public final String installerPackageName;
- public final UserHandle user;
public final boolean replace;
public final int scanFlags;
public final int parseFlags;
@@ -16051,24 +16016,16 @@
public final PackageParser.Package packageToScan;
public final boolean clearCodeCache;
public final boolean system;
- /* The original package name if it was changed during an update, otherwise {@code null}. */
- @Nullable
- public final String renamedPackage;
public final PackageFreezer freezer;
public final PackageSetting originalPs;
public final PackageSetting disabledPs;
public final PackageSetting[] childPackageSettings;
- private PrepareResult(int installReason, String volumeUuid,
- String installerPackageName, UserHandle user, boolean replace, int scanFlags,
+ private PrepareResult(boolean replace, int scanFlags,
int parseFlags, PackageParser.Package existingPackage,
PackageParser.Package packageToScan, boolean clearCodeCache, boolean system,
- String renamedPackage, PackageFreezer freezer, PackageSetting originalPs,
+ PackageFreezer freezer, PackageSetting originalPs,
PackageSetting disabledPs, PackageSetting[] childPackageSettings) {
- this.installReason = installReason;
- this.volumeUuid = volumeUuid;
- this.installerPackageName = installerPackageName;
- this.user = user;
this.replace = replace;
this.scanFlags = scanFlags;
this.parseFlags = parseFlags;
@@ -16076,7 +16033,6 @@
this.packageToScan = packageToScan;
this.clearCodeCache = clearCodeCache;
this.system = system;
- this.renamedPackage = renamedPackage;
this.freezer = freezer;
this.originalPs = originalPs;
this.disabledPs = disabledPs;
@@ -16116,8 +16072,6 @@
private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res)
throws PrepareFailure {
final int installFlags = args.installFlags;
- final String installerPackageName = args.installerPackageName;
- final String volumeUuid = args.volumeUuid;
final File tmpPackageFile = new File(args.getCodePath());
final boolean onExternal = args.volumeUuid != null;
final boolean instantApp = ((installFlags & PackageManager.INSTALL_INSTANT_APP) != 0);
@@ -16542,14 +16496,12 @@
final PackageParser.Package existingPackage;
String renamedPackage = null;
boolean sysPkg = false;
- String targetVolumeUuid = volumeUuid;
int targetScanFlags = scanFlags;
int targetParseFlags = parseFlags;
final PackageSetting ps;
final PackageSetting disabledPs;
final PackageSetting[] childPackages;
if (replace) {
- targetVolumeUuid = null;
if (pkg.applicationInfo.isStaticSharedLibrary()) {
// Static libs have a synthetic package name containing the version
// and cannot be updated as an update would get a new package name,
@@ -16801,9 +16753,8 @@
// we're passing the freezer back to be closed in a later phase of install
shouldCloseFreezerBeforeReturn = false;
- return new PrepareResult(args.installReason, targetVolumeUuid, installerPackageName,
- args.user, replace, targetScanFlags, targetParseFlags, existingPackage, pkg,
- replace /* clearCodeCache */, sysPkg, renamedPackage, freezer,
+ return new PrepareResult(replace, targetScanFlags, targetParseFlags,
+ existingPackage, pkg, replace /* clearCodeCache */, sysPkg, freezer,
ps, disabledPs, childPackages);
} finally {
if (shouldCloseFreezerBeforeReturn) {
@@ -17484,7 +17435,7 @@
continue;
}
List<VersionedPackage> libClientPackages = getPackagesUsingSharedLibraryLPr(
- libraryInfo, 0, currUserId);
+ libraryInfo, MATCH_KNOWN_PACKAGES, currUserId);
if (!ArrayUtils.isEmpty(libClientPackages)) {
Slog.w(TAG, "Not removing package " + pkg.manifestPackageName
+ " hosting lib " + libraryInfo.getName() + " version "
@@ -19652,7 +19603,7 @@
set, comp, userId);
}
- private @Nullable String getSetupWizardPackageName() {
+ private @Nullable String getSetupWizardPackageNameImpl() {
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_SETUP_WIZARD);
@@ -19759,11 +19710,29 @@
return systemCaptionsServiceComponentName.getPackageName();
}
+ @Override
+ public String getSetupWizardPackageName() {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ throw new SecurityException("Non-system caller");
+ }
+ return mPmInternal.getSetupWizardPackageName();
+ }
+
public String getIncidentReportApproverPackageName() {
return mContext.getString(R.string.config_incidentReportApproverPackage);
}
@Override
+ public String[] getTelephonyPackageNames() {
+ String names = mContext.getString(R.string.config_telephonyPackages);
+ String[] telephonyPackageNames = null;
+ if (!TextUtils.isEmpty(names)) {
+ telephonyPackageNames = names.trim().split(",");
+ }
+ return telephonyPackageNames;
+ }
+
+ @Override
public void setApplicationEnabledSetting(String appPackageName,
int newState, int flags, int userId, String callingPackage) {
if (!mUserManager.exists(userId)) return;
@@ -20006,7 +19975,7 @@
if (sendNow) {
int packageUid = UserHandle.getUid(userId, pkgSetting.appId);
sendPackageChangedBroadcast(packageName,
- (flags&PackageManager.DONT_KILL_APP) != 0, components, packageUid);
+ (flags & PackageManager.DONT_KILL_APP) != 0, components, packageUid, null);
}
} finally {
Binder.restoreCallingIdentity(callingId);
@@ -20038,7 +20007,8 @@
}
private void sendPackageChangedBroadcast(String packageName,
- boolean killFlag, ArrayList<String> componentNames, int packageUid) {
+ boolean killFlag, ArrayList<String> componentNames, int packageUid,
+ String reason) {
if (DEBUG_INSTALL)
Log.v(TAG, "Sending package changed: package=" + packageName + " components="
+ componentNames);
@@ -20049,6 +20019,9 @@
extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList);
extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag);
extras.putInt(Intent.EXTRA_UID, packageUid);
+ if (reason != null) {
+ extras.putString(Intent.EXTRA_REASON, reason);
+ }
// If this is not reporting a change of the overall package, then only send it
// to registered receivers. We don't want to launch a swath of apps for every
// little component state change.
@@ -20296,6 +20269,34 @@
}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
}
+ IntentFilter overlayFilter = new IntentFilter(Intent.ACTION_OVERLAY_CHANGED);
+ overlayFilter.addDataScheme("package");
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent == null) {
+ return;
+ }
+ Uri data = intent.getData();
+ if (data == null) {
+ return;
+ }
+ String packageName = data.getSchemeSpecificPart();
+ if (packageName == null) {
+ return;
+ }
+ PackageParser.Package pkg = mPackages.get(packageName);
+ if (pkg == null) {
+ return;
+ }
+ sendPackageChangedBroadcast(pkg.packageName,
+ false /* killFlag */,
+ new ArrayList<>(Collections.singletonList(pkg.packageName)),
+ pkg.applicationInfo.uid,
+ Intent.ACTION_OVERLAY_CHANGED);
+ }
+ }, overlayFilter);
+
mModuleInfoProvider.systemReady();
// Installer service might attempt to install some packages that have been staged for
@@ -21876,6 +21877,7 @@
final String currentVolumeUuid;
final File codeFile;
final String installerPackageName;
+ final InstallSource installSource;
final String packageAbiOverride;
final int appId;
final String seinfo;
@@ -21933,6 +21935,7 @@
isCurrentLocationExternal = isExternal(pkg);
codeFile = new File(pkg.codePath);
installerPackageName = ps.installerPackageName;
+ installSource = ps.installSource;
packageAbiOverride = ps.cpuAbiOverrideString;
appId = UserHandle.getAppId(pkg.applicationInfo.uid);
seinfo = pkg.applicationInfo.seInfo;
@@ -22078,7 +22081,7 @@
final Message msg = mHandler.obtainMessage(INIT_COPY);
final OriginInfo origin = OriginInfo.fromExistingFile(codeFile);
final InstallParams params = new InstallParams(origin, move, installObserver, installFlags,
- installerPackageName, volumeUuid, null /*verificationInfo*/, user,
+ installerPackageName, installSource, volumeUuid, null /*verificationInfo*/, user,
packageAbiOverride, null /*grantedPermissions*/,
null /*whitelistedRestrictedPermissions*/, PackageParser.SigningDetails.UNKNOWN,
PackageManager.INSTALL_REASON_UNKNOWN, PackageManager.VERSION_CODE_HIGHEST);
@@ -22836,34 +22839,38 @@
}
@Override
- public String getKnownPackageName(int knownPackage, int userId) {
+ public @NonNull String[] getKnownPackageNames(int knownPackage, int userId) {
switch(knownPackage) {
case PackageManagerInternal.PACKAGE_BROWSER:
- return mPermissionManager.getDefaultBrowser(userId);
+ return new String[]{mPermissionManager.getDefaultBrowser(userId)};
case PackageManagerInternal.PACKAGE_INSTALLER:
- return mRequiredInstallerPackage;
+ return new String[]{mRequiredInstallerPackage};
case PackageManagerInternal.PACKAGE_SETUP_WIZARD:
- return mSetupWizardPackage;
+ return new String[]{mSetupWizardPackage};
case PackageManagerInternal.PACKAGE_SYSTEM:
- return "android";
+ return new String[]{"android"};
case PackageManagerInternal.PACKAGE_VERIFIER:
- return mRequiredVerifierPackage;
+ return new String[]{mRequiredVerifierPackage};
case PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER:
- return mSystemTextClassifierPackage;
+ return new String[]{mSystemTextClassifierPackage};
case PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER:
- return mRequiredPermissionControllerPackage;
+ return new String[]{mRequiredPermissionControllerPackage};
case PackageManagerInternal.PACKAGE_WELLBEING:
- return mWellbeingPackage;
+ return new String[]{mWellbeingPackage};
case PackageManagerInternal.PACKAGE_DOCUMENTER:
- return mDocumenterPackage;
+ return new String[]{mDocumenterPackage};
case PackageManagerInternal.PACKAGE_CONFIGURATOR:
- return mConfiguratorPackage;
+ return new String[]{mConfiguratorPackage};
case PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER:
- return mIncidentReportApproverPackage;
+ return new String[]{mIncidentReportApproverPackage};
case PackageManagerInternal.PACKAGE_APP_PREDICTOR:
- return mAppPredictionServicePackage;
+ return new String[]{mAppPredictionServicePackage};
+ case PackageManagerInternal.PACKAGE_TELEPHONY:
+ return mTelephonyPackages;
+ case PackageManagerInternal.PACKAGE_WIFI:
+ return new String[]{mWifiPackage};
}
- return null;
+ return ArrayUtils.emptyArray(String.class);
}
@Override
@@ -23961,18 +23968,21 @@
private final PackageInstaller.SessionParams mSessionParams;
private final String mInstallerPackageName;
private final int mInstallerUid;
+ private final InstallSource mInstallSource;
private final UserHandle mUser;
private final SigningDetails mSigningDetails;
ActiveInstallSession(String packageName, File stagedDir, IPackageInstallObserver2 observer,
PackageInstaller.SessionParams sessionParams, String installerPackageName,
- int installerUid, UserHandle user, SigningDetails signingDetails) {
+ int installerUid, InstallSource installSource,
+ UserHandle user, SigningDetails signingDetails) {
mPackageName = packageName;
mStagedDir = stagedDir;
mObserver = observer;
mSessionParams = sessionParams;
mInstallerPackageName = installerPackageName;
mInstallerUid = installerUid;
+ mInstallSource = installSource;
mUser = user;
mSigningDetails = signingDetails;
}
@@ -24001,6 +24011,10 @@
return mInstallerUid;
}
+ public InstallSource getInstallSource() {
+ return mInstallSource;
+ }
+
public UserHandle getUser() {
return mUser;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 1c1c947..52a7c6e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -468,7 +468,7 @@
* @param pckg
*/
private int displayPackageFilePath(String pckg, int userId) throws RemoteException {
- PackageInfo info = mInterface.getPackageInfo(pckg, 0, userId);
+ PackageInfo info = mInterface.getPackageInfo(pckg, PackageManager.MATCH_APEX, userId);
if (info != null && info.applicationInfo != null) {
final PrintWriter pw = getOutPrintWriter();
pw.print("package:");
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 4ea8a30..be0621b 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -205,6 +205,11 @@
proto.end(splitToken);
}
}
+
+ long sourceToken = proto.start(PackageProto.INSTALL_SOURCE);
+ proto.write(PackageProto.InstallSourceProto.INITIATING_PACKAGE_NAME,
+ installSource.initiatingPackageName);
+ proto.end(sourceToken);
}
writeUsersInfoToProto(proto, PackageProto.USERS);
proto.end(packageToken);
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 0da6b54..f0857dd 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -20,6 +20,7 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import android.annotation.NonNull;
import android.content.pm.ApplicationInfo;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.PackageManager;
@@ -125,6 +126,9 @@
String installerPackageName;
/** Indicates if the package that installed this app has been uninstalled */
boolean isOrphaned;
+ /** Information about the initial install of this package. */
+ @NonNull
+ InstallSource installSource;
/** UUID of {@link VolumeInfo} hosting this app */
String volumeUuid;
/** The category of this app, as hinted by the installer */
@@ -148,8 +152,17 @@
? new ArrayList<>(childPackageNames) : null;
this.usesStaticLibraries = usesStaticLibraries;
this.usesStaticLibrariesVersions = usesStaticLibrariesVersions;
- init(codePath, resourcePath, legacyNativeLibraryPathString, primaryCpuAbiString,
- secondaryCpuAbiString, cpuAbiOverrideString, pVersionCode);
+ this.codePath = codePath;
+ this.codePathString = codePath.toString();
+ this.resourcePath = resourcePath;
+ this.resourcePathString = resourcePath.toString();
+ this.legacyNativeLibraryPathString = legacyNativeLibraryPathString;
+ this.primaryCpuAbiString = primaryCpuAbiString;
+ this.secondaryCpuAbiString = secondaryCpuAbiString;
+ this.cpuAbiOverrideString = cpuAbiOverrideString;
+ this.versionCode = pVersionCode;
+ this.signatures = new PackageSignatures();
+ this.installSource = InstallSource.create(null);
}
/**
@@ -166,21 +179,6 @@
doCopy(base);
}
- void init(File codePath, File resourcePath, String legacyNativeLibraryPathString,
- String primaryCpuAbiString, String secondaryCpuAbiString,
- String cpuAbiOverrideString, long pVersionCode) {
- this.codePath = codePath;
- this.codePathString = codePath.toString();
- this.resourcePath = resourcePath;
- this.resourcePathString = resourcePath.toString();
- this.legacyNativeLibraryPathString = legacyNativeLibraryPathString;
- this.primaryCpuAbiString = primaryCpuAbiString;
- this.secondaryCpuAbiString = secondaryCpuAbiString;
- this.cpuAbiOverrideString = cpuAbiOverrideString;
- this.versionCode = pVersionCode;
- this.signatures = new PackageSignatures();
- }
-
public void setInstallerPackageName(String packageName) {
installerPackageName = packageName;
}
@@ -189,6 +187,21 @@
return installerPackageName;
}
+ public void setInstallSource(InstallSource installSource) {
+ this.installSource = installSource == null ? InstallSource.create(null) : installSource;
+ }
+
+ void removeInstallerPackage(String packageName) {
+ if (packageName == null) {
+ return;
+ }
+ if (packageName.equals(installerPackageName)) {
+ installerPackageName = null;
+ isOrphaned = true;
+ }
+ installSource = installSource.removeInstallerPackage(packageName);
+ }
+
public void setVolumeUuid(String volumeUuid) {
this.volumeUuid = volumeUuid;
}
@@ -241,6 +254,7 @@
firstInstallTime = orig.firstInstallTime;
installPermissionsFixed = orig.installPermissionsFixed;
installerPackageName = orig.installerPackageName;
+ installSource = orig.installSource;
isOrphaned = orig.isOrphaned;
keySetData = orig.keySetData;
lastUpdateTime = orig.lastUpdateTime;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 0db6e79..5e209965 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -554,10 +554,6 @@
return null;
}
- void addAppOpPackage(String permName, String packageName) {
- mPermissions.addAppOpPackage(permName, packageName);
- }
-
SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
SharedUserSetting s = mSharedUsers.get(name);
if (s != null) {
@@ -1052,13 +1048,7 @@
return;
}
for (int i = 0; i < mPackages.size(); i++) {
- final PackageSetting ps = mPackages.valueAt(i);
- final String installerPackageName = ps.getInstallerPackageName();
- if (installerPackageName != null
- && installerPackageName.equals(packageName)) {
- ps.setInstallerPackageName(null);
- ps.isOrphaned = true;
- }
+ mPackages.valueAt(i).removeInstallerPackage(packageName);
}
mInstallerPackages.remove(packageName);
}
@@ -2854,12 +2844,15 @@
if (pkg.isOrphaned) {
serializer.attribute(null, "isOrphaned", "true");
}
+ InstallSource installSource = pkg.installSource;
+ if (installSource.initiatingPackageName != null) {
+ serializer.attribute(null, "installInitiator", installSource.initiatingPackageName);
+ }
if (pkg.volumeUuid != null) {
serializer.attribute(null, "volumeUuid", pkg.volumeUuid);
}
if (pkg.categoryHint != ApplicationInfo.CATEGORY_UNDEFINED) {
- serializer.attribute(null, "categoryHint",
- Integer.toString(pkg.categoryHint));
+ serializer.attribute(null, "categoryHint", Integer.toString(pkg.categoryHint));
}
if (pkg.parentPackageName != null) {
serializer.attribute(null, "parentPackageName", pkg.parentPackageName);
@@ -2874,8 +2867,7 @@
pkg.signatures.writeXml(serializer, "sigs", mPastSignatures);
- writePermissionsLPr(serializer, pkg.getPermissionsState()
- .getInstallPermissionStates());
+ writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissionStates());
writeSigningKeySetLPr(serializer, pkg.keySetData);
writeUpgradeKeySetsLPr(serializer, pkg.keySetData);
@@ -3613,6 +3605,7 @@
String systemStr = null;
String installerPackageName = null;
String isOrphaned = null;
+ String installInitiatingPackageName = null;
String volumeUuid = null;
String categoryHintString = null;
String updateAvailable = null;
@@ -3659,6 +3652,7 @@
}
installerPackageName = parser.getAttributeValue(null, "installer");
isOrphaned = parser.getAttributeValue(null, "isOrphaned");
+ installInitiatingPackageName = parser.getAttributeValue(null, "installInitiator");
volumeUuid = parser.getAttributeValue(null, "volumeUuid");
categoryHintString = parser.getAttributeValue(null, "categoryHint");
if (categoryHintString != null) {
@@ -3815,6 +3809,7 @@
packageSetting.uidError = "true".equals(uidError);
packageSetting.installerPackageName = installerPackageName;
packageSetting.isOrphaned = "true".equals(isOrphaned);
+ packageSetting.installSource = InstallSource.create(installInitiatingPackageName);
packageSetting.volumeUuid = volumeUuid;
packageSetting.categoryHint = categoryHint;
packageSetting.legacyNativeLibraryPathString = legacyNativeLibraryPathStr;
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index 036d1e8..323c957 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -449,7 +449,14 @@
}
void dump(PrintWriter pw) {
- for (int i = 0; i < mWhitelitsedPackagesForUserTypes.size(); i++) {
+ pw.print("Whitelisted packages per user type");
+ final int size = mWhitelitsedPackagesForUserTypes.size();
+ if (size == 0) {
+ pw.println(": N/A");
+ return;
+ }
+ pw.println(" (" + size + " packages)");
+ for (int i = 0; i < size; i++) {
final String pkgName = mWhitelitsedPackagesForUserTypes.keyAt(i);
final String whitelistedUserTypes =
UserInfo.flagsToString(mWhitelitsedPackagesForUserTypes.valueAt(i));
diff --git a/services/core/java/com/android/server/pm/permission/BasePermission.java b/services/core/java/com/android/server/pm/permission/BasePermission.java
index 6d22faa..037912a 100644
--- a/services/core/java/com/android/server/pm/permission/BasePermission.java
+++ b/services/core/java/com/android/server/pm/permission/BasePermission.java
@@ -276,6 +276,12 @@
public boolean isAppPredictor() {
return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR) != 0;
}
+ public boolean isTelephony() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_TELEPHONY) != 0;
+ }
+ public boolean isWifi() {
+ return (protectionLevel & PermissionInfo.PROTECTION_FLAG_WIFI) != 0;
+ }
public void transfer(@NonNull String origPackageName, @NonNull String newPackageName) {
if (!origPackageName.equals(sourcePackageName)) {
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 793cdd2..f247037 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -437,17 +437,20 @@
// Installer
grantSystemFixedPermissionsToSystemPackage(
- getKnownPackage(PackageManagerInternal.PACKAGE_INSTALLER, userId),
+ ArrayUtils.firstOrNull(getKnownPackages(
+ PackageManagerInternal.PACKAGE_INSTALLER, userId)),
userId, STORAGE_PERMISSIONS);
// Verifier
- final String verifier = getKnownPackage(PackageManagerInternal.PACKAGE_VERIFIER, userId);
+ final String verifier = ArrayUtils.firstOrNull(getKnownPackages(
+ PackageManagerInternal.PACKAGE_VERIFIER, userId));
grantSystemFixedPermissionsToSystemPackage(verifier, userId, STORAGE_PERMISSIONS);
grantPermissionsToSystemPackage(verifier, userId, PHONE_PERMISSIONS, SMS_PERMISSIONS);
// SetupWizard
grantPermissionsToSystemPackage(
- getKnownPackage(PackageManagerInternal.PACKAGE_SETUP_WIZARD, userId), userId,
+ ArrayUtils.firstOrNull(getKnownPackages(
+ PackageManagerInternal.PACKAGE_SETUP_WIZARD, userId)), userId,
PHONE_PERMISSIONS, CONTACTS_PERMISSIONS, ALWAYS_LOCATION_PERMISSIONS,
CAMERA_PERMISSIONS);
@@ -596,7 +599,8 @@
userId, CONTACTS_PERMISSIONS, CALENDAR_PERMISSIONS);
// Browser
- String browserPackage = getKnownPackage(PackageManagerInternal.PACKAGE_BROWSER, userId);
+ String browserPackage = ArrayUtils.firstOrNull(getKnownPackages(
+ PackageManagerInternal.PACKAGE_BROWSER, userId));
if (browserPackage == null) {
browserPackage = getDefaultSystemHandlerActivityPackageForCategory(
Intent.CATEGORY_APP_BROWSER, userId);
@@ -761,8 +765,8 @@
}
}
- private String getKnownPackage(int knownPkgId, int userId) {
- return mServiceInternal.getKnownPackageName(knownPkgId, userId);
+ private @NonNull String[] getKnownPackages(int knownPkgId, int userId) {
+ return mServiceInternal.getKnownPackageNames(knownPkgId, userId);
}
private void grantDefaultPermissionsToDefaultSystemDialerApp(
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 89908f0..5c65752 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -297,7 +297,7 @@
// Critical; after this call the application should never have the permission
mPackageManagerInt.writeSettings(false);
final int appId = UserHandle.getAppId(uid);
- killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
+ mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED));
}
@Override
public void onInstallPermissionRevoked() {
@@ -1902,7 +1902,7 @@
private void restoreRuntimePermissions(@NonNull byte[] backup, @NonNull UserHandle user) {
synchronized (mLock) {
mHasNoDelayedPermBackup.delete(user.getIdentifier());
- mPermissionControllerManager.restoreRuntimePermissionBackup(backup, user);
+ mPermissionControllerManager.stageAndApplyRuntimePermissionsBackup(backup, user);
}
}
@@ -1923,7 +1923,7 @@
return;
}
- mPermissionControllerManager.restoreDelayedRuntimePermissionBackup(packageName, user,
+ mPermissionControllerManager.applyStagedRuntimePermissionBackup(packageName, user,
mContext.getMainExecutor(), (hasMoreBackup) -> {
if (hasMoreBackup) {
return;
@@ -3078,8 +3078,9 @@
}
}
}
- final String systemPackageName = mPackageManagerInt.getKnownPackageName(
- PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
+ // expect single system package
+ String systemPackageName = ArrayUtils.firstOrNull(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM));
final PackageParser.Package systemPackage =
mPackageManagerInt.getPackage(systemPackageName);
@@ -3195,18 +3196,19 @@
// need a separate flag anymore. Hence we need to check which
// permissions are needed by the permission controller
if (!allowed && bp.isInstaller()
- && (pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
- PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM))
- || pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_INSTALLER, UserHandle.USER_SYSTEM),
+ pkg.packageName) || ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
PackageManagerInternal.PACKAGE_PERMISSION_CONTROLLER,
- UserHandle.USER_SYSTEM)))) {
+ UserHandle.USER_SYSTEM), pkg.packageName)) {
// If this permission is to be granted to the system installer and
// this app is an installer, then it gets the permission.
allowed = true;
}
if (!allowed && bp.isVerifier()
- && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
- PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM))) {
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_VERIFIER, UserHandle.USER_SYSTEM),
+ pkg.packageName)) {
// If this permission is to be granted to the system verifier and
// this app is a verifier, then it gets the permission.
allowed = true;
@@ -3222,53 +3224,71 @@
allowed = origPermissions.hasInstallPermission(perm);
}
if (!allowed && bp.isSetup()
- && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
- PackageManagerInternal.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM))) {
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_SETUP_WIZARD, UserHandle.USER_SYSTEM),
+ pkg.packageName)) {
// If this permission is to be granted to the system setup wizard and
// this app is a setup wizard, then it gets the permission.
allowed = true;
}
if (!allowed && bp.isSystemTextClassifier()
- && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
PackageManagerInternal.PACKAGE_SYSTEM_TEXT_CLASSIFIER,
- UserHandle.USER_SYSTEM))) {
+ UserHandle.USER_SYSTEM), pkg.packageName)) {
// Special permissions for the system default text classifier.
allowed = true;
}
if (!allowed && bp.isConfigurator()
- && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
- PackageManagerInternal.PACKAGE_CONFIGURATOR,
- UserHandle.USER_SYSTEM))) {
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_CONFIGURATOR,
+ UserHandle.USER_SYSTEM), pkg.packageName)) {
// Special permissions for the device configurator.
allowed = true;
}
if (!allowed && bp.isWellbeing()
- && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
- PackageManagerInternal.PACKAGE_WELLBEING, UserHandle.USER_SYSTEM))) {
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_WELLBEING, UserHandle.USER_SYSTEM),
+ pkg.packageName)) {
// Special permission granted only to the OEM specified wellbeing app
allowed = true;
}
if (!allowed && bp.isDocumenter()
- && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
- PackageManagerInternal.PACKAGE_DOCUMENTER, UserHandle.USER_SYSTEM))) {
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_DOCUMENTER, UserHandle.USER_SYSTEM),
+ pkg.packageName)) {
// If this permission is to be granted to the documenter and
// this app is the documenter, then it gets the permission.
allowed = true;
}
if (!allowed && bp.isIncidentReportApprover()
- && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
PackageManagerInternal.PACKAGE_INCIDENT_REPORT_APPROVER,
- UserHandle.USER_SYSTEM))) {
+ UserHandle.USER_SYSTEM), pkg.packageName)) {
// If this permission is to be granted to the incident report approver and
// this app is the incident report approver, then it gets the permission.
allowed = true;
}
if (!allowed && bp.isAppPredictor()
- && pkg.packageName.equals(mPackageManagerInt.getKnownPackageName(
- PackageManagerInternal.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM))) {
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_APP_PREDICTOR, UserHandle.USER_SYSTEM),
+ pkg.packageName)) {
// Special permissions for the system app predictor.
allowed = true;
}
+ if (!allowed && bp.isTelephony()
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_TELEPHONY, UserHandle.USER_SYSTEM),
+ pkg.packageName)) {
+ // Special permissions for the system telephony apps.
+ allowed = true;
+ }
+ if (!allowed && bp.isWifi()
+ && ArrayUtils.contains(mPackageManagerInt.getKnownPackageNames(
+ PackageManagerInternal.PACKAGE_WIFI, UserHandle.USER_SYSTEM),
+ pkg.packageName)) {
+ // Special permissions for the system wifi.
+ allowed = true;
+ }
}
return allowed;
}
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 77c16e3..df2b3ca 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -152,10 +152,10 @@
for (int i = 0; i < numDangerousPerms; i++) {
PermissionInfo perm = dangerousPerms.get(i);
- if (perm.isHardRestricted() || perm.backgroundPermission != null) {
+ if (perm.isRuntime()) {
appOpsService.startWatchingMode(getSwitchOp(perm.name), null, appOpsListener);
- } else if (perm.isSoftRestricted()) {
- appOpsService.startWatchingMode(getSwitchOp(perm.name), null, appOpsListener);
+ }
+ if (perm.isSoftRestricted()) {
SoftRestrictedPermissionPolicy policy =
SoftRestrictedPermissionPolicy.forPermission(null, null, null,
perm.name);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index ef31ef15..2593c38 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -47,8 +47,8 @@
import static android.view.WindowManager.LayoutParams.LAST_SYSTEM_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -2189,10 +2189,10 @@
default:
// These are the windows that by default are shown only to the user that created
// them. If this needs to be overridden, set
- // {@link WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS} in
+ // {@link WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS} in
// {@link WindowManager.LayoutParams}. Note that permission
// {@link android.Manifest.permission.INTERNAL_SYSTEM_WINDOW} is required as well.
- if ((attrs.privateFlags & PRIVATE_FLAG_SHOW_FOR_ALL_USERS) == 0) {
+ if ((attrs.privateFlags & SYSTEM_FLAG_SHOW_FOR_ALL_USERS) == 0) {
return true;
}
break;
@@ -2446,7 +2446,7 @@
com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
params.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
- params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
if (!compatInfo.supportsScreen()) {
params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
@@ -4543,6 +4543,12 @@
private void wakeUpFromPowerKey(long eventTime) {
wakeUp(eventTime, mAllowTheaterModeWakeFromPowerKey,
PowerManager.WAKE_REASON_POWER_BUTTON, "android.policy:POWER");
+
+ // Turn on the connected TV and switch HDMI input if we're a HDMI playback device.
+ final HdmiControl hdmiControl = getHdmiControl();
+ if (hdmiControl != null) {
+ hdmiControl.turnOnTv();
+ }
}
private boolean wakeUp(long wakeTime, boolean wakeInTheaterMode, @WakeReason int reason,
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 3439d38..65bb2342 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -29,6 +29,7 @@
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
+import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -1334,6 +1335,18 @@
}
@Override
+ public void grantInlineReplyUriPermission(String key, Uri uri) {
+ enforceStatusBarService();
+ int callingUid = Binder.getCallingUid();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mNotificationDelegate.grantInlineReplyUriPermission(key, uri, callingUid);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
(new StatusBarShellCommand(this, mContext)).exec(
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 3cdb59b..3663f46 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1228,6 +1228,7 @@
saveSettingsLocked(mWallpaper.userId);
}
FgThread.getHandler().removeCallbacks(mResetRunnable);
+ mContext.getMainThreadHandler().removeCallbacks(this::tryToRebind);
}
}
}
@@ -1270,6 +1271,34 @@
}
}
+ private void tryToRebind() {
+ synchronized (mLock) {
+ if (mWallpaper.wallpaperUpdating) {
+ return;
+ }
+ final ComponentName wpService = mWallpaper.wallpaperComponent;
+ // The broadcast of package update could be delayed after service disconnected. Try
+ // to re-bind the service for 10 seconds.
+ if (bindWallpaperComponentLocked(
+ wpService, true, false, mWallpaper, null)) {
+ mWallpaper.connection.scheduleTimeoutLocked();
+ } else if (SystemClock.uptimeMillis() - mWallpaper.lastDiedTime
+ < WALLPAPER_RECONNECT_TIMEOUT_MS) {
+ // Bind fail without timeout, schedule rebind
+ Slog.w(TAG, "Rebind fail! Try again later");
+ mContext.getMainThreadHandler().postDelayed(this::tryToRebind, 1000);
+ } else {
+ // Timeout
+ Slog.w(TAG, "Reverting to built-in wallpaper!");
+ clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, null);
+ final String flattened = wpService.flattenToString();
+ EventLog.writeEvent(EventLogTags.WP_WALLPAPER_CRASHED,
+ flattened.substring(0, Math.min(flattened.length(),
+ MAX_WALLPAPER_COMPONENT_LOG_LENGTH)));
+ }
+ }
+ }
+
private void processDisconnect(final ServiceConnection connection) {
synchronized (mLock) {
// The wallpaper disappeared. If this isn't a system-default one, track
@@ -1293,20 +1322,8 @@
clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, null);
} else {
mWallpaper.lastDiedTime = SystemClock.uptimeMillis();
-
- clearWallpaperComponentLocked(mWallpaper);
- if (bindWallpaperComponentLocked(
- wpService, false, false, mWallpaper, null)) {
- mWallpaper.connection.scheduleTimeoutLocked();
- } else {
- Slog.w(TAG, "Reverting to built-in wallpaper!");
- clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, null);
- }
+ tryToRebind();
}
- final String flattened = wpService.flattenToString();
- EventLog.writeEvent(EventLogTags.WP_WALLPAPER_CRASHED,
- flattened.substring(0, Math.min(flattened.length(),
- MAX_WALLPAPER_COMPONENT_LOG_LENGTH)));
}
} else {
if (DEBUG_LIVE) {
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 59f051b..10415f5 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -271,11 +271,11 @@
}
}
- /** NOTE: This has to be called within a surface transaction. */
- public void drawMagnifiedRegionBorderIfNeededLocked(int displayId) {
+ public void drawMagnifiedRegionBorderIfNeededLocked(int displayId,
+ SurfaceControl.Transaction t) {
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
- displayMagnifier.drawMagnifiedRegionBorderIfNeededLocked();
+ displayMagnifier.drawMagnifiedRegionBorderIfNeededLocked(t);
}
// Not relevant for the window observer.
}
@@ -431,7 +431,7 @@
Slog.i(LOG_TAG, "Rotation: " + Surface.rotationToString(rotation)
+ " displayId: " + displayContent.getDisplayId());
}
- mMagnifedViewport.onRotationChangedLocked();
+ mMagnifedViewport.onRotationChangedLocked(displayContent.getPendingTransaction());
mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED);
}
@@ -536,9 +536,8 @@
.sendToTarget();
}
- /** NOTE: This has to be called within a surface transaction. */
- public void drawMagnifiedRegionBorderIfNeededLocked() {
- mMagnifedViewport.drawWindowIfNeededLocked();
+ public void drawMagnifiedRegionBorderIfNeededLocked(SurfaceControl.Transaction t) {
+ mMagnifedViewport.drawWindowIfNeededLocked(t);
}
private final class MagnifiedViewport {
@@ -744,7 +743,7 @@
return letterboxBounds;
}
- public void onRotationChangedLocked() {
+ public void onRotationChangedLocked(SurfaceControl.Transaction t) {
// If we are showing the magnification border, hide it immediately so
// the user does not see strange artifacts during rotation. The screenshot
// used for rotation already has the border. After the rotation is complete
@@ -758,7 +757,7 @@
mHandler.sendMessageDelayed(message, delay);
}
recomputeBoundsLocked();
- mWindow.updateSize();
+ mWindow.updateSize(t);
}
public void setMagnifiedRegionBorderShownLocked(boolean shown, boolean animate) {
@@ -784,10 +783,9 @@
return mMagnificationSpec;
}
- /** NOTE: This has to be called within a surface transaction. */
- public void drawWindowIfNeededLocked() {
+ public void drawWindowIfNeededLocked(SurfaceControl.Transaction t) {
recomputeBoundsLocked();
- mWindow.drawIfNeeded();
+ mWindow.drawIfNeeded(t);
}
public void destroyWindow() {
@@ -837,10 +835,11 @@
/* ignore */
}
mSurfaceControl = surfaceControl;
- mSurfaceControl.setLayer(mService.mPolicy.getWindowLayerFromTypeLw(
- TYPE_MAGNIFICATION_OVERLAY)
- * WindowManagerService.TYPE_LAYER_MULTIPLIER);
- mSurfaceControl.setPosition(0, 0);
+ mService.mTransactionFactory.get().setLayer(mSurfaceControl,
+ mService.mPolicy.getWindowLayerFromTypeLw(TYPE_MAGNIFICATION_OVERLAY)
+ * WindowManagerService.TYPE_LAYER_MULTIPLIER)
+ .setPosition(mSurfaceControl, 0, 0)
+ .apply();
mSurface.copyFrom(mSurfaceControl);
mAnimationController = new AnimationController(context,
@@ -905,10 +904,10 @@
}
}
- public void updateSize() {
+ public void updateSize(SurfaceControl.Transaction t) {
synchronized (mService.mGlobalLock) {
mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
- mSurfaceControl.setBufferSize(mTempPoint.x, mTempPoint.y);
+ t.setBufferSize(mSurfaceControl, mTempPoint.x, mTempPoint.y);
invalidate(mDirtyRect);
}
}
@@ -923,8 +922,7 @@
mService.scheduleAnimationLocked();
}
- /** NOTE: This has to be called within a surface transaction. */
- public void drawIfNeeded() {
+ public void drawIfNeeded(SurfaceControl.Transaction t) {
synchronized (mService.mGlobalLock) {
if (!mInvalidated) {
return;
@@ -959,9 +957,9 @@
canvas.drawPath(path, mPaint);
mSurface.unlockCanvasAndPost(canvas);
- mSurfaceControl.show();
+ t.show(mSurfaceControl);
} else {
- mSurfaceControl.hide();
+ t.hide(mSurfaceControl);
}
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java
index 9d9a37c..673366f 100644
--- a/services/core/java/com/android/server/wm/ActivityDisplay.java
+++ b/services/core/java/com/android/server/wm/ActivityDisplay.java
@@ -1430,9 +1430,8 @@
continue;
}
- final ArrayList<ActivityRecord> activities = task.mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (r.isActivityTypeHome()
&& ((userId == UserHandle.USER_ALL) || (r.mUserId == userId))) {
return r;
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
index eff0f75..c6b17e2 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
@@ -39,8 +39,13 @@
* If an activity is successfully started, the launch sequence's state will transition into
* {@code STARTED} via {@link #onActivityLaunched}. This is a transient state.
*
- * It must then transition to either {@code CANCELLED} with {@link #onActivityLaunchCancelled}
- * or into {@code FINISHED} with {@link #onActivityLaunchFinished}. These are terminal states.
+ * It must then transition to either {@code CANCELLED} with {@link #onActivityLaunchCancelled},
+ * which is a terminal state or into {@code FINISHED} with {@link #onActivityLaunchFinished}.
+ *
+ * The {@code FINISHED} with {@link #onActivityLaunchFinished} then may transition to
+ * {@code FULLY_DRAWN} with {@link #onReportFullyDrawn}, which is a terminal state.
+ * Note this transition may not happen if the reportFullyDrawn event is not receivied,
+ * in which case {@code FINISHED} is terminal.
*
* Note that the {@code ActivityRecordProto} provided as a parameter to some state transitions isn't
* necessarily the same within a single launch sequence: it is only the top-most activity at the
@@ -51,15 +56,15 @@
* until a subsequent transition into {@code INTENT_STARTED} initiates a new launch sequence.
*
* <pre>
- * ┌⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┐ ┌⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┐ ╔══════════════════════════╗
- * ╴╴▶ ⋮ INTENT_STARTED ⋮ ──▶ ⋮ ACTIVITY_LAUNCHED ⋮ ──▶ ║ ACTIVITY_LAUNCH_FINISHED ║
- * └⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┘ └⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┘ ╚══════════════════════════╝
- * : :
- * : :
- * ▼ ▼
- * ╔════════════════╗ ╔═══════════════════════════╗
- * ║ INTENT_FAILED ║ ║ ACTIVITY_LAUNCH_CANCELLED ║
- * ╚════════════════╝ ╚═══════════════════════════╝
+ * ┌⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┐ ┌⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┐ ┌--------------------------┐
+ * ╴╴▶ INTENT_STARTED ──▶ ACTIVITY_LAUNCHED ──▶ ACTIVITY_LAUNCH_FINISHED
+ * └⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┘ └⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯⋯┘ └--------------------------┘
+ * : : :
+ * : : :
+ * ▼ ▼ ▼
+ * ╔════════════════╗ ╔═══════════════════════════╗ ╔═══════════════════════════╗
+ * ║ INTENT_FAILED ║ ║ ACTIVITY_LAUNCH_CANCELLED ║ ║ REPORT_FULLY_DRAWN ║
+ * ╚════════════════╝ ╚═══════════════════════════╝ ╚═══════════════════════════╝
* </pre>
*/
public interface ActivityMetricsLaunchObserver {
@@ -111,7 +116,7 @@
* Multiple calls to this method cannot occur without first terminating the current
* launch sequence.
*/
- public void onIntentStarted(@NonNull Intent intent);
+ public void onIntentStarted(@NonNull Intent intent, long timestampNanos);
/**
* Notifies the observer that the current launch sequence has failed to launch an activity.
@@ -177,6 +182,9 @@
* drawn for the first time: the top-most activity at the time is what's reported here.
*
* @param finalActivity the top-most activity whose windows were first to fully draw
+ * @param timestampNanos the timestamp of ActivityLaunchFinished event in nanoseconds.
+ * To compute the TotalTime duration, deduct the timestamp {@link #onIntentStarted}
+ * from {@code timestampNanos}.
*
* Multiple calls to this method cannot occur without first terminating the current
* launch sequence.
@@ -186,5 +194,22 @@
* and only the latest activity that was top-most during first-frame drawn
* is reported here.
*/
- public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] finalActivity);
+ public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] finalActivity,
+ long timestampNanos);
+
+ /**
+ * Notifies the observer that the application self-reported itself as being fully drawn.
+ *
+ * @param activity the activity that triggers the ReportFullyDrawn event.
+ * @param timestampNanos the timestamp of ReportFullyDrawn event in nanoseconds.
+ * To compute the duration, deduct the deduct the timestamp {@link #onIntentStarted}
+ * from {@code timestampNanos}.
+ *
+ * @apiNote The behavior of ReportFullyDrawn mostly depends on the app.
+ * It is used as an accurate estimate of meanfully app startup time.
+ * This event may be missing for many apps.
+ */
+ public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity,
+ long timestampNanos);
+
}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index a0a2967..e6c6b12e 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -92,6 +92,7 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.SomeArgs;
import com.android.server.LocalServices;
+import java.util.concurrent.TimeUnit;
/**
* Listens to activity launches, transitions, visibility changes and window drawn callbacks to
@@ -132,8 +133,8 @@
// set to INVALID_START_TIME in reset.
// set to valid value in notifyActivityLaunching
- private long mCurrentTransitionStartTime = INVALID_START_TIME;
- private long mLastTransitionStartTime = INVALID_START_TIME;
+ private long mCurrentTransitionStartTimeNs = INVALID_START_TIME;
+ private long mLastTransitionStartTimeNs = INVALID_START_TIME;
private int mCurrentTransitionDeviceUptime;
private int mCurrentTransitionDelayMs;
@@ -326,12 +327,12 @@
intent));
}
- if (mCurrentTransitionStartTime == INVALID_START_TIME) {
+ if (mCurrentTransitionStartTimeNs == INVALID_START_TIME) {
- mCurrentTransitionStartTime = SystemClock.uptimeMillis();
- mLastTransitionStartTime = mCurrentTransitionStartTime;
+ mCurrentTransitionStartTimeNs = SystemClock.elapsedRealtimeNanos();
+ mLastTransitionStartTimeNs = mCurrentTransitionStartTimeNs;
- launchObserverNotifyIntentStarted(intent);
+ launchObserverNotifyIntentStarted(intent, mCurrentTransitionStartTimeNs);
}
}
@@ -382,14 +383,15 @@
? launchedActivity.getWindowingMode()
: WINDOWING_MODE_UNDEFINED;
final WindowingModeTransitionInfo info = mWindowingModeTransitionInfo.get(windowingMode);
- if (mCurrentTransitionStartTime == INVALID_START_TIME) {
+ if (mCurrentTransitionStartTimeNs == INVALID_START_TIME) {
// No transition is active ignore this launch.
return;
}
if (launchedActivity != null && launchedActivity.mDrawn) {
// Launched activity is already visible. We cannot measure windows drawn delay.
- reset(true /* abort */, info, "launched activity already visible");
+ reset(true /* abort */, info, "launched activity already visible",
+ 0L /* timestampNs */);
return;
}
@@ -407,7 +409,8 @@
if ((!isLoggableResultCode(resultCode) || launchedActivity == null || !processSwitch
|| windowingMode == WINDOWING_MODE_UNDEFINED) && !otherWindowModesLaunching) {
// Failed to launch or it was not a process switch, so we don't care about the timing.
- reset(true /* abort */, info, "failed to launch or not a process switch");
+ reset(true /* abort */, info, "failed to launch or not a process switch",
+ 0L /* timestampNs */);
return;
} else if (otherWindowModesLaunching) {
// Don't log this windowing mode but continue with the other windowing modes.
@@ -441,19 +444,20 @@
* Notifies the tracker that all windows of the app have been drawn.
*/
WindowingModeTransitionInfoSnapshot notifyWindowsDrawn(@WindowingMode int windowingMode,
- long timestamp) {
+ long timestampNs) {
if (DEBUG_METRICS) Slog.i(TAG, "notifyWindowsDrawn windowingMode=" + windowingMode);
final WindowingModeTransitionInfo info = mWindowingModeTransitionInfo.get(windowingMode);
if (info == null || info.loggedWindowsDrawn) {
return null;
}
- info.windowsDrawnDelayMs = calculateDelay(timestamp);
+ info.windowsDrawnDelayMs = calculateDelay(timestampNs);
info.loggedWindowsDrawn = true;
final WindowingModeTransitionInfoSnapshot infoSnapshot =
new WindowingModeTransitionInfoSnapshot(info);
if (allWindowsDrawn() && mLoggedTransitionStarting) {
- reset(false /* abort */, info, "notifyWindowsDrawn - all windows drawn");
+ reset(false /* abort */, info, "notifyWindowsDrawn - all windows drawn",
+ timestampNs /* timestampNs */);
}
return infoSnapshot;
}
@@ -476,7 +480,7 @@
* @param windowingModeToReason A map from windowing mode to a reason integer, which must be on
* of ActivityTaskManagerInternal.APP_TRANSITION_* reasons.
*/
- void notifyTransitionStarting(SparseIntArray windowingModeToReason, long timestamp) {
+ void notifyTransitionStarting(SparseIntArray windowingModeToReason, long timestampNs) {
if (!isAnyTransitionActive() || mLoggedTransitionStarting) {
// Ignore calls to this made after a reset and prior to notifyActivityLaunching.
@@ -484,7 +488,7 @@
return;
}
if (DEBUG_METRICS) Slog.i(TAG, "notifyTransitionStarting");
- mCurrentTransitionDelayMs = calculateDelay(timestamp);
+ mCurrentTransitionDelayMs = calculateDelay(timestampNs);
mLoggedTransitionStarting = true;
WindowingModeTransitionInfo foundInfo = null;
@@ -501,7 +505,8 @@
if (allWindowsDrawn()) {
// abort metrics collection if we cannot find a matching transition.
final boolean abortMetrics = foundInfo == null;
- reset(abortMetrics, foundInfo, "notifyTransitionStarting - all windows drawn");
+ reset(abortMetrics, foundInfo, "notifyTransitionStarting - all windows drawn",
+ timestampNs /* timestampNs */);
}
}
@@ -527,8 +532,8 @@
}
private boolean hasVisibleNonFinishingActivity(TaskRecord t) {
- for (int i = t.mActivities.size() - 1; i >= 0; --i) {
- final ActivityRecord r = t.mActivities.get(i);
+ for (int i = t.getChildCount() - 1; i >= 0; --i) {
+ final ActivityRecord r = t.getChildAt(i);
if (r.visible && !r.finishing) {
return true;
}
@@ -567,7 +572,8 @@
logAppTransitionCancel(info);
mWindowingModeTransitionInfo.remove(r.getWindowingMode());
if (mWindowingModeTransitionInfo.size() == 0) {
- reset(true /* abort */, info, "notifyVisibilityChanged to invisible");
+ reset(true /* abort */, info, "notifyVisibilityChanged to invisible",
+ 0L /* timestampNs */);
}
}
}
@@ -598,12 +604,16 @@
}
private boolean isAnyTransitionActive() {
- return mCurrentTransitionStartTime != INVALID_START_TIME
+ return mCurrentTransitionStartTimeNs != INVALID_START_TIME
&& mWindowingModeTransitionInfo.size() > 0;
}
- private void reset(boolean abort, WindowingModeTransitionInfo info, String cause) {
- if (DEBUG_METRICS) Slog.i(TAG, "reset abort=" + abort + ",cause=" + cause);
+ private void reset(boolean abort, WindowingModeTransitionInfo info, String cause,
+ long timestampNs) {
+ if (DEBUG_METRICS) {
+ Slog.i(TAG,
+ "reset abort=" + abort + ",cause=" + cause + ",timestamp=" + timestampNs);
+ }
if (!abort && isAnyTransitionActive()) {
logAppTransitionMultiEvents();
}
@@ -615,13 +625,13 @@
if (abort) {
launchObserverNotifyActivityLaunchCancelled(info);
} else {
- launchObserverNotifyActivityLaunchFinished(info);
+ launchObserverNotifyActivityLaunchFinished(info, timestampNs);
}
} else {
launchObserverNotifyIntentFailed();
}
- mCurrentTransitionStartTime = INVALID_START_TIME;
+ mCurrentTransitionStartTimeNs = INVALID_START_TIME;
mCurrentTransitionDelayMs = INVALID_DELAY;
mLoggedTransitionStarting = false;
mWindowingModeTransitionInfo.clear();
@@ -629,12 +639,14 @@
private int calculateCurrentDelay() {
// Shouldn't take more than 25 days to launch an app, so int is fine here.
- return (int) (SystemClock.uptimeMillis() - mCurrentTransitionStartTime);
+ return (int) TimeUnit.NANOSECONDS
+ .toMillis(SystemClock.elapsedRealtimeNanos() - mCurrentTransitionStartTimeNs);
}
- private int calculateDelay(long timestamp) {
+ private int calculateDelay(long timestampNs) {
// Shouldn't take more than 25 days to launch an app, so int is fine here.
- return (int) (timestamp - mCurrentTransitionStartTime);
+ return (int) TimeUnit.NANOSECONDS.toMillis(timestampNs -
+ mCurrentTransitionStartTimeNs);
}
private void logAppTransitionCancel(WindowingModeTransitionInfo info) {
@@ -679,7 +691,7 @@
// Take a snapshot of the transition info before sending it to the handler for logging.
// This will avoid any races with other operations that modify the ActivityRecord.
final WindowingModeTransitionInfoSnapshot infoSnapshot =
- new WindowingModeTransitionInfoSnapshot(info);
+ new WindowingModeTransitionInfoSnapshot(info);
final int currentTransitionDeviceUptime = mCurrentTransitionDeviceUptime;
final int currentTransitionDelayMs = mCurrentTransitionDelayMs;
BackgroundThread.getHandler().post(() -> logAppTransition(
@@ -811,7 +823,9 @@
final LogMaker builder = new LogMaker(APP_TRANSITION_REPORTED_DRAWN);
builder.setPackageName(r.packageName);
builder.addTaggedData(FIELD_CLASS_NAME, r.info.name);
- long startupTimeMs = SystemClock.uptimeMillis() - mLastTransitionStartTime;
+ long currentTimestampNs = SystemClock.elapsedRealtimeNanos();
+ long startupTimeMs =
+ TimeUnit.NANOSECONDS.toMillis(currentTimestampNs - mLastTransitionStartTimeNs);
builder.addTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS, startupTimeMs);
builder.setType(restoredFromBundle
? TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE
@@ -837,6 +851,10 @@
final WindowingModeTransitionInfoSnapshot infoSnapshot =
new WindowingModeTransitionInfoSnapshot(info, r, (int) startupTimeMs);
BackgroundThread.getHandler().post(() -> logAppFullyDrawn(infoSnapshot));
+
+ // Notify reportFullyDrawn event.
+ launchObserverNotifyReportFullyDrawn(r, currentTimestampNs);
+
return infoSnapshot;
}
@@ -1006,12 +1024,12 @@
}
/** Notify the {@link ActivityMetricsLaunchObserver} that a new launch sequence has begun. */
- private void launchObserverNotifyIntentStarted(Intent intent) {
+ private void launchObserverNotifyIntentStarted(Intent intent, long timestampNs) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"MetricsLogger:launchObserverNotifyIntentStarted");
// Beginning a launch is timing sensitive and so should be observed as soon as possible.
- mLaunchObserver.onIntentStarted(intent);
+ mLaunchObserver.onIntentStarted(intent, timestampNs);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -1049,6 +1067,16 @@
}
/**
+ * Notifies the {@link ActivityMetricsLaunchObserver} the reportFullDrawn event.
+ */
+ private void launchObserverNotifyReportFullyDrawn(ActivityRecord r, long timestampNs) {
+ Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ "MetricsLogger:launchObserverNotifyReportFullyDrawn");
+ mLaunchObserver.onReportFullyDrawn(convertActivityRecordToProto(r), timestampNs);
+ Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ /**
* Notify the {@link ActivityMetricsLaunchObserver} that the current launch sequence is
* cancelled.
*/
@@ -1068,12 +1096,14 @@
* Notify the {@link ActivityMetricsLaunchObserver} that the current launch sequence's activity
* has fully finished (successfully).
*/
- private void launchObserverNotifyActivityLaunchFinished(WindowingModeTransitionInfo info) {
+ private void launchObserverNotifyActivityLaunchFinished(WindowingModeTransitionInfo info,
+ long timestampNs) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"MetricsLogger:launchObserverNotifyActivityLaunchFinished");
- mLaunchObserver.onActivityLaunchFinished(
- convertActivityRecordToProto(info.launchedActivity));
+ mLaunchObserver
+ .onActivityLaunchFinished(convertActivityRecordToProto(info.launchedActivity),
+ timestampNs);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 976fd52..fb4de01 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -144,10 +144,10 @@
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
+import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutLocked;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
-import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutLocked;
import static com.android.server.wm.TaskPersister.DEBUG;
import static com.android.server.wm.TaskPersister.IMAGE_EXTENSION;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -318,6 +318,7 @@
ArrayList<ResultInfo> results; // pending ActivityResult objs we have received
HashSet<WeakReference<PendingIntentRecord>> pendingResults; // all pending intents for this act
ArrayList<ReferrerIntent> newIntents; // any pending new intents for single-top mode
+ Intent mLastNewIntent; // the last new intent we delivered to client
ActivityOptions pendingOptions; // most recently given options
ActivityOptions returningOptions; // options that are coming back via convertToTranslucent
AppTimeTracker appTimeTracker; // set if we are tracking the time in this app/task/activity
@@ -1253,7 +1254,7 @@
boolean setOccludesParent(boolean occludesParent) {
final boolean changed = super.setOccludesParent(occludesParent);
- if (changed) {
+ if (changed && task != null) {
if (!occludesParent) {
getActivityStack().convertActivityToTranslucent(this);
}
@@ -1577,12 +1578,12 @@
task.mTaskId, shortComponentName, reason);
final ArrayList<ActivityRecord> activities = task.mActivities;
final int index = activities.indexOf(this);
- if (index < (activities.size() - 1)) {
+ if (index < (task.getChildCount() - 1)) {
if ((intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
// If the caller asked that this activity (and all above it)
// be cleared when the task is reset, don't lose that information,
// but propagate it up to the next activity.
- final ActivityRecord next = activities.get(index + 1);
+ final ActivityRecord next = task.getChildAt(index + 1);
next.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
}
}
@@ -1723,25 +1724,27 @@
// implied that the current finishing activity should be added into stopping list rather
// than destroy immediately.
final boolean isNextNotYetVisible = next != null && (!next.nowVisible || !next.visible);
- final ActivityStack stack = getActivityStack();
- final boolean notFocusedStack = stack != mRootActivityContainer.getTopDisplayFocusedStack();
+ final boolean notGlobalFocusedStack =
+ getActivityStack() != mRootActivityContainer.getTopDisplayFocusedStack();
if (isVisible && isNextNotYetVisible) {
+ // Add this activity to the list of stopping activities. It will be processed and
+ // destroyed when the next activity reports idle.
addToStopping(false /* scheduleIdle */, false /* idleDelayed */,
"completeFinishing");
- if (DEBUG_STATES) {
- Slog.v(TAG_STATES, "Moving to STOPPING: " + this + " (finish requested)");
- }
setState(STOPPING, "completeFinishing");
- if (notFocusedStack) {
+ if (notGlobalFocusedStack) {
+ // Ensuring visibility and configuration only for non-focused stacks since this
+ // method call is relatively expensive and not necessary for focused stacks.
mRootActivityContainer.ensureVisibilityAndConfig(next, getDisplayId(),
false /* markFrozenIfConfigChanged */, true /* deferResume */);
}
- } else if (isVisible && isState(PAUSED) && getActivityStack().isFocusedStackOnDisplay()
- && !inPinnedWindowingMode()) {
- // TODO(b/137329632): Currently non-focused stack is handled differently.
- addToFinishingAndWaitForIdle();
+ } else if (addToFinishingAndWaitForIdle()) {
+ // We added this activity to the finishing list and something else is becoming resumed.
+ // The activity will complete finishing when the next activity reports idle. No need to
+ // do anything else here.
} else {
- // Not waiting for the next one to become visible - finish right away.
+ // Not waiting for the next one to become visible, and nothing else will be resumed in
+ // place of this activity - requesting destruction right away.
activityRemoved = destroyIfPossible(reason);
}
@@ -1798,13 +1801,20 @@
return activityRemoved;
}
+ /**
+ * Add this activity to the list of finishing and trigger resuming of activities in focused
+ * stacks.
+ * @return {@code true} if some other activity is being resumed as a result of this call.
+ */
@VisibleForTesting
- void addToFinishingAndWaitForIdle() {
+ boolean addToFinishingAndWaitForIdle() {
if (DEBUG_STATES) Slog.v(TAG, "Enqueueing pending finish: " + this);
setState(FINISHING, "addToFinishingAndWaitForIdle");
- mStackSupervisor.mFinishingActivities.add(this);
+ if (!mStackSupervisor.mFinishingActivities.contains(this)) {
+ mStackSupervisor.mFinishingActivities.add(this);
+ }
resumeKeyDispatchingLocked();
- mRootActivityContainer.resumeFocusedStacksTopActivities();
+ return mRootActivityContainer.resumeFocusedStacksTopActivities();
}
/**
@@ -1995,10 +2005,6 @@
if (stopped) {
clearOptionsLocked();
}
-
- if (mAtmService != null) {
- mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged();
- }
}
/**
@@ -2779,12 +2785,12 @@
if (positionInTask == -1) {
throw new IllegalStateException("Activity not found in its task");
}
- if (positionInTask == task.mActivities.size() - 1) {
+ if (positionInTask == task.getChildCount() - 1) {
// It's the topmost activity in the task - should become resumed now
return true;
}
// Check if activity above is finishing now and this one becomes the topmost in task.
- final ActivityRecord activityAbove = task.mActivities.get(positionInTask + 1);
+ final ActivityRecord activityAbove = task.getChildAt(positionInTask + 1);
if (activityAbove.finishing && results == null) {
// We will only allow making active if activity above wasn't launched for result.
// Otherwise it will cause this activity to resume before getting result.
@@ -2835,11 +2841,14 @@
}
idle = false;
results = null;
+ if (newIntents != null && newIntents.size() > 0) {
+ mLastNewIntent = newIntents.get(newIntents.size() - 1);
+ }
newIntents = null;
stopped = false;
if (isActivityTypeHome()) {
- mStackSupervisor.updateHomeProcess(task.mActivities.get(0).app);
+ mStackSupervisor.updateHomeProcess(task.getChildAt(0).app);
}
if (nowVisible) {
@@ -3093,7 +3102,7 @@
void reportFullyDrawnLocked(boolean restoredFromBundle) {
final WindowingModeTransitionInfoSnapshot info = mStackSupervisor
- .getActivityMetricsLogger().logAppTransitionReportedDrawn(this, restoredFromBundle);
+ .getActivityMetricsLogger().logAppTransitionReportedDrawn(this, restoredFromBundle);
if (info != null) {
mStackSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
info.windowsFullyDrawnDelayMs, info.getLaunchState());
@@ -3101,13 +3110,13 @@
}
/** Called when the windows associated app window container are drawn. */
- void onWindowsDrawn(boolean drawn, long timestamp) {
+ void onWindowsDrawn(boolean drawn, long timestampNs) {
mDrawn = drawn;
if (!drawn) {
return;
}
final WindowingModeTransitionInfoSnapshot info = mStackSupervisor
- .getActivityMetricsLogger().notifyWindowsDrawn(getWindowingMode(), timestamp);
+ .getActivityMetricsLogger().notifyWindowsDrawn(getWindowingMode(), timestampNs);
final int windowsDrawnDelayMs = info != null ? info.windowsDrawnDelayMs : INVALID_DELAY;
final @LaunchState int launchState = info != null ? info.getLaunchState() : -1;
mStackSupervisor.reportActivityLaunchedLocked(false /* timeout */, this,
@@ -3533,7 +3542,7 @@
super.resolveOverrideConfiguration(newParentConfiguration);
// If the activity has override bounds, the relative configuration (e.g. screen size,
// layout) needs to be resolved according to the bounds.
- if (!matchParentBounds()) {
+ if (task != null && !matchParentBounds()) {
task.computeConfigResourceOverrides(getResolvedOverrideConfiguration(),
newParentConfiguration);
}
@@ -4189,12 +4198,19 @@
return true;
}
- // Restrict task snapshot starting window to launcher start, or there is no intent at all
- // (eg. task being brought to front). If the intent is something else, likely the app is
- // going to show some specific page or view, instead of what's left last time.
+ // Restrict task snapshot starting window to launcher start, or is same as the last
+ // delivered intent, or there is no intent at all (eg. task being brought to front). If
+ // the intent is something else, likely the app is going to show some specific page or
+ // view, instead of what's left last time.
for (int i = newIntents.size() - 1; i >= 0; i--) {
final Intent intent = newIntents.get(i);
- if (intent != null && !ActivityRecord.isMainIntent(intent)) {
+ if (intent == null || ActivityRecord.isMainIntent(intent)) {
+ continue;
+ }
+
+ final boolean sameIntent = mLastNewIntent != null ? mLastNewIntent.filterEquals(intent)
+ : this.intent.filterEquals(intent);
+ if (!sameIntent || intent.getExtras() != null) {
return false;
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 5959254..41c1e4e 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -33,7 +33,7 @@
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
import static android.view.Display.INVALID_DISPLAY;
@@ -488,7 +488,7 @@
int numActivities() {
int count = 0;
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- count += mTaskHistory.get(taskNdx).mActivities.size();
+ count += mTaskHistory.get(taskNdx).getChildCount();
}
return count;
}
@@ -1077,9 +1077,8 @@
ActivityRecord topRunningNonOverlayTaskActivity() {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
- final ArrayList<ActivityRecord> activities = task.mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (!r.finishing && !r.mTaskOverlay) {
return r;
}
@@ -1091,9 +1090,8 @@
ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
- final ArrayList<ActivityRecord> activities = task.mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = activities.get(activityNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (!r.finishing && !r.delayedResume && r != notTop && r.okToShowLocked()) {
return r;
}
@@ -1117,9 +1115,8 @@
if (task.mTaskId == taskId) {
continue;
}
- ArrayList<ActivityRecord> activities = task.mActivities;
- for (int i = activities.size() - 1; i >= 0; --i) {
- final ActivityRecord r = activities.get(i);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
// Note: the taskId check depends on real taskId fields being non-zero
if (!r.finishing && (token != r.appToken) && r.okToShowLocked()) {
return r;
@@ -1431,10 +1428,8 @@
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
- final ArrayList<ActivityRecord> activities = task.mActivities;
-
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = activities.get(activityNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (!r.okToShowLocked()) {
continue;
}
@@ -1500,9 +1495,10 @@
void awakeFromSleepingLocked() {
// Ensure activities are no longer sleeping.
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- activities.get(activityNdx).setSleeping(false);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
+ r.setSleeping(false);
}
}
if (mPausingActivity != null) {
@@ -1516,9 +1512,9 @@
final int userId = UserHandle.getUserId(aInfo.uid);
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final List<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord ar = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord ar = task.getChildAt(activityNdx);
if ((userId == ar.mUserId) && packageName.equals(ar.packageName)) {
ar.updateApplicationInfo(aInfo);
@@ -1594,9 +1590,9 @@
// Make sure any paused or stopped but visible activities are now sleeping.
// This ensures that the activity's onStop() is called.
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (r.isState(STARTED, STOPPING, STOPPED, PAUSED, PAUSING)) {
r.setSleeping(true);
}
@@ -1880,9 +1876,8 @@
}
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
- final ArrayList<ActivityRecord> activities = task.mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (r.finishing) {
// We don't factor in finishing activities when determining translucency since
@@ -2113,9 +2108,8 @@
&& top != null && !top.mLaunchTaskBehind;
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
- final ArrayList<ActivityRecord> activities = task.mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
final boolean isTop = r == top;
if (aboveTop && !isTop) {
continue;
@@ -2363,9 +2357,8 @@
void clearOtherAppTimeTrackers(AppTimeTracker except) {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
- final ArrayList<ActivityRecord> activities = task.mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if ( r.appTimeTracker != except) {
r.appTimeTracker = null;
}
@@ -2423,9 +2416,9 @@
}
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (aboveTop) {
if (r == topActivity) {
aboveTop = false;
@@ -3210,14 +3203,13 @@
// We only do this for activities that are not the root of the task (since if we finish
// the root, we may no longer have the task!).
- final ArrayList<ActivityRecord> activities = task.mActivities;
- final int numActivities = activities.size();
+ final int numActivities = task.getChildCount();
int lastActivityNdx = task.findRootIndex(true /* effectiveRoot */);
if (lastActivityNdx == -1) {
lastActivityNdx = 0;
}
for (int i = numActivities - 1; i > lastActivityNdx; --i) {
- ActivityRecord target = activities.get(i);
+ ActivityRecord target = task.getChildAt(i);
// TODO: Why is this needed? Looks like we're breaking the loop before we reach the root
if (target.isRootOfTask()) break;
@@ -3257,7 +3249,7 @@
final TaskRecord targetTask;
final ActivityRecord bottom =
!mTaskHistory.isEmpty() && !mTaskHistory.get(0).mActivities.isEmpty() ?
- mTaskHistory.get(0).mActivities.get(0) : null;
+ mTaskHistory.get(0).getChildAt(0) : null;
if (bottom != null && target.taskAffinity.equals(bottom.getTaskRecord().affinity)) {
// If the activity currently at the bottom has the
// same task affinity as the one we are moving,
@@ -3279,7 +3271,7 @@
boolean noOptions = canMoveOptions;
final int start = replyChainEnd < 0 ? i : replyChainEnd;
for (int srcPos = start; srcPos >= i; --srcPos) {
- final ActivityRecord p = activities.get(srcPos);
+ final ActivityRecord p = task.getChildAt(srcPos);
if (p.finishing) {
continue;
}
@@ -3312,7 +3304,7 @@
// In this case, we want to finish this activity
// and everything above it, so be sneaky and pretend
// like these are all in the reply chain.
- end = activities.size() - 1;
+ end = task.getChildCount() - 1;
} else if (replyChainEnd < 0) {
end = i;
} else {
@@ -3320,7 +3312,7 @@
}
boolean noOptions = canMoveOptions;
for (int srcPos = i; srcPos <= end; srcPos++) {
- ActivityRecord p = activities.get(srcPos);
+ ActivityRecord p = task.getChildAt(srcPos);
if (p.finishing) {
continue;
}
@@ -3389,8 +3381,7 @@
int replyChainEnd = -1;
final String taskAffinity = task.affinity;
- final ArrayList<ActivityRecord> activities = affinityTask.mActivities;
- final int numActivities = activities.size();
+ final int numActivities = task.getChildCount();
// Do not operate on or below the effective root Activity.
int lastActivityNdx = affinityTask.findRootIndex(true /* effectiveRoot */);
@@ -3398,7 +3389,7 @@
lastActivityNdx = 0;
}
for (int i = numActivities - 1; i > lastActivityNdx; --i) {
- ActivityRecord target = activities.get(i);
+ ActivityRecord target = task.getChildAt(i);
// TODO: Why is this needed? Looks like we're breaking the loop before we reach the root
if (target.isRootOfTask()) break;
@@ -3435,7 +3426,7 @@
if (DEBUG_TASKS) Slog.v(TAG_TASKS,
"Finishing task at index " + start + " to " + i);
for (int srcPos = start; srcPos >= i; --srcPos) {
- final ActivityRecord p = activities.get(srcPos);
+ final ActivityRecord p = task.getChildAt(srcPos);
if (p.finishing) {
continue;
}
@@ -3443,7 +3434,7 @@
}
} else {
if (taskInsertionPoint < 0) {
- taskInsertionPoint = task.mActivities.size();
+ taskInsertionPoint = task.getChildCount();
}
@@ -3452,7 +3443,7 @@
"Reparenting from task=" + affinityTask + ":" + start + "-" + i
+ " to task=" + task + ":" + taskInsertionPoint);
for (int srcPos = start; srcPos >= i; --srcPos) {
- final ActivityRecord p = activities.get(srcPos);
+ final ActivityRecord p = task.getChildAt(srcPos);
p.reparent(task, taskInsertionPoint, "resetAffinityTaskIfNeededLocked");
if (DEBUG_ADD_REMOVE) Slog.i(TAG_ADD_REMOVE,
@@ -3589,9 +3580,9 @@
/** Finish all activities that were started for result from the specified activity. */
final void finishSubActivityLocked(ActivityRecord self, String resultWho, int requestCode) {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (r.resultTo == self && r.requestCode == requestCode) {
if ((r.resultWho == null && resultWho == null) ||
(r.resultWho != null && r.resultWho.equals(resultWho))) {
@@ -3637,11 +3628,11 @@
if (taskNdx < 0) {
break;
}
- activityNdx = mTaskHistory.get(taskNdx).mActivities.size() - 1;
+ activityNdx = mTaskHistory.get(taskNdx).getChildCount() - 1;
} while (activityNdx < 0);
}
if (activityNdx >= 0) {
- r = mTaskHistory.get(taskNdx).mActivities.get(activityNdx);
+ r = mTaskHistory.get(taskNdx).getChildAt(activityNdx);
if (r.isState(STARTED, RESUMED, PAUSING, PAUSED)) {
if (!r.isActivityTypeHome() || mService.mHomeProcess != r.app) {
Slog.w(TAG, " Force finishing activity "
@@ -3659,8 +3650,8 @@
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
TaskRecord tr = mTaskHistory.get(taskNdx);
if (tr.voiceSession != null && tr.voiceSession.asBinder() == sessionBinder) {
- for (int activityNdx = tr.mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = tr.mActivities.get(activityNdx);
+ for (int activityNdx = tr.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ ActivityRecord r = tr.getChildAt(activityNdx);
if (!r.finishing) {
r.finishIfPossible("finish-voice", false /* oomAdj */);
didOne = true;
@@ -3668,8 +3659,8 @@
}
} else {
// Check if any of the activities are using voice
- for (int activityNdx = tr.mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = tr.mActivities.get(activityNdx);
+ for (int activityNdx = tr.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ ActivityRecord r = tr.getChildAt(activityNdx);
if (r.voiceSession != null && r.voiceSession.asBinder() == sessionBinder) {
// Inform of cancellation
r.clearVoiceSessionLocked();
@@ -3695,9 +3686,9 @@
void finishAllActivitiesImmediately() {
boolean noActivitiesInStack = true;
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
noActivitiesInStack = false;
Slog.d(TAG, "finishAllActivitiesImmediatelyLocked: finishing " + r);
r.destroyIfPossible("finishAllActivitiesImmediatelyLocked");
@@ -3765,12 +3756,12 @@
return false;
}
int finishTo = start - 1;
- ActivityRecord parent = finishTo < 0 ? null : activities.get(finishTo);
+ ActivityRecord parent = finishTo < 0 ? null : task.getChildAt(finishTo);
boolean foundParentInTask = false;
final ComponentName dest = destIntent.getComponent();
if (start > 0 && dest != null) {
for (int i = finishTo; i >= 0; i--) {
- ActivityRecord r = activities.get(i);
+ ActivityRecord r = task.getChildAt(i);
if (r.info.packageName.equals(dest.getPackageName()) &&
r.info.name.equals(dest.getClassName())) {
finishTo = i;
@@ -3931,9 +3922,9 @@
boolean lastIsOpaque = false;
boolean activityRemoved = false;
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (r.finishing) {
continue;
}
@@ -3978,15 +3969,14 @@
}
if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Looking for activities to release in " + task);
int curNum = 0;
- final ArrayList<ActivityRecord> activities = task.mActivities;
- for (int actNdx = 0; actNdx < activities.size(); actNdx++) {
- final ActivityRecord activity = activities.get(actNdx);
+ for (int actNdx = 0; actNdx < task.getChildCount(); actNdx++) {
+ final ActivityRecord activity = task.getChildAt(actNdx);
if (activity.app == app && activity.isDestroyable()) {
if (DEBUG_RELEASE) Slog.v(TAG_RELEASE, "Destroying " + activity
+ " in state " + activity.getState() + " resumed=" + mResumedActivity
+ " pausing=" + mPausingActivity + " for reason " + reason);
activity.destroyImmediately(true /* removeFromApp */, reason);
- if (activities.get(actNdx) != activity) {
+ if (task.getChildAt(actNdx) != activity) {
// Was removed from list, back up so we don't miss the next one.
actNdx--;
}
@@ -4170,8 +4160,8 @@
if (timeTracker != null) {
// The caller wants a time tracker associated with this task.
- for (int i = tr.mActivities.size() - 1; i >= 0; i--) {
- tr.mActivities.get(i).appTimeTracker = timeTracker;
+ for (int i = tr.getChildCount() - 1; i >= 0; i--) {
+ tr.getChildAt(i).appTimeTracker = timeTracker;
}
}
@@ -4352,7 +4342,7 @@
return;
}
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "stack.resize_" + mStackId);
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "stack.resize_" + mStackId);
mService.deferWindowLayout();
try {
// Update override configurations of all tasks in the stack.
@@ -4378,7 +4368,7 @@
}
} finally {
mService.continueWindowLayout();
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -4425,9 +4415,9 @@
boolean willActivityBeVisibleLocked(IBinder token) {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (r.appToken == token) {
return true;
}
@@ -4447,9 +4437,9 @@
void closeSystemDialogsLocked() {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if ((r.info.flags&ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS) != 0) {
r.finishIfPossible("close-sys", true /* oomAdj */);
}
@@ -4556,10 +4546,10 @@
final int top = mTaskHistory.size() - 1;
if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Performing unhandledBack(): top activity at " + top);
if (top >= 0) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(top).mActivities;
- int activityTop = activities.size() - 1;
+ final TaskRecord task = mTaskHistory.get(top);
+ int activityTop = task.getChildCount() - 1;
if (activityTop >= 0) {
- activities.get(activityTop).finishIfPossible("unhandled-back", true /* oomAdj */);
+ task.getChildAt(activityTop).finishIfPossible("unhandled-back", true /* oomAdj */);
}
}
}
@@ -4585,9 +4575,9 @@
void handleAppCrash(WindowProcessController app) {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
if (r.app == app) {
Slog.w(TAG, " Force finishing activity "
+ r.intent.getComponent().flattenToShortString());
@@ -4705,9 +4695,9 @@
// All activities that came from the package must be
// restarted as if there was a config change.
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord a = activities.get(activityNdx);
+ final TaskRecord task = mTaskHistory.get(taskNdx);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord a = task.getChildAt(activityNdx);
if (a.info.packageName.equals(packageName)) {
a.forceNewConfig = true;
if (starting != null && a == starting && a.visible) {
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index f1284d81..ca74196 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -44,7 +44,7 @@
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.Display.TYPE_VIRTUAL;
@@ -690,7 +690,7 @@
ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId, int flags,
int filterCallingUid) {
try {
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "resolveIntent");
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "resolveIntent");
int modifiedFlags = flags
| PackageManager.MATCH_DEFAULT_ONLY | ActivityManagerService.STOCK_PM_FLAGS;
if (intent.isWebIntent()
@@ -711,7 +711,7 @@
Binder.restoreCallingIdentity(token);
}
} finally {
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -824,7 +824,7 @@
System.identityHashCode(r), task.mTaskId, r.shortComponentName);
if (r.isActivityTypeHome()) {
// Home process is the root process of the task.
- updateHomeProcess(task.mActivities.get(0).app);
+ updateHomeProcess(task.getChildAt(0).app);
}
mService.getPackageManagerInternalLocked().notifyPackageUse(
r.intent.getComponent().getPackageName(), NOTIFY_PACKAGE_USE_ACTIVITY);
@@ -1629,7 +1629,7 @@
mPendingTempOtherTaskInsetBounds = copyOrNull(tempOtherTaskInsetBounds);
}
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeDockedStack");
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "resizeDockedStack");
mService.deferWindowLayout();
try {
// Don't allow re-entry while resizing. E.g. due to docked stack detaching.
@@ -1695,7 +1695,7 @@
} finally {
mAllowDockedStackResize = true;
mService.continueWindowLayout();
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -1717,7 +1717,7 @@
return;
}
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizePinnedStack");
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "resizePinnedStack");
mService.deferWindowLayout();
try {
Rect insetBounds = null;
@@ -1739,7 +1739,7 @@
!DEFER_RESUME);
} finally {
mService.continueWindowLayout();
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -1796,6 +1796,7 @@
tr.removeTaskActivitiesLocked(reason);
cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
mService.getLockTaskController().clearLockedTask(tr);
+ mService.getTaskChangeNotificationController().notifyTaskStackChanged();
if (tr.isPersistable) {
mService.notifyTaskPersisterLocked(null, true);
}
@@ -1899,9 +1900,9 @@
task.createTask(onTop, true /* showForAllUsers */);
if (DEBUG_RECENTS) Slog.v(TAG_RECENTS,
"Added restored task=" + task + " to stack=" + stack);
- final ArrayList<ActivityRecord> activities = task.mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- activities.get(activityNdx).setTask(task);
+ for (int activityNdx = task.getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = task.getChildAt(activityNdx);
+ r.setTask(task);
}
return true;
}
@@ -2501,8 +2502,8 @@
return;
}
- for (int i = task.mActivities.size() - 1; i >= 0; i--) {
- final ActivityRecord r = task.mActivities.get(i);
+ for (int i = task.getChildCount() - 1; i >= 0; i--) {
+ final ActivityRecord r = task.getChildAt(i);
if (r.attachedToProcess()) {
mMultiWindowModeChangedActivities.add(r);
}
@@ -2524,8 +2525,8 @@
}
void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, Rect targetStackBounds) {
- for (int i = task.mActivities.size() - 1; i >= 0; i--) {
- final ActivityRecord r = task.mActivities.get(i);
+ for (int i = task.getChildCount() - 1; i >= 0; i--) {
+ final ActivityRecord r = task.getChildAt(i);
if (r.attachedToProcess()) {
mPipModeChangedActivities.add(r);
// If we are scheduling pip change, then remove this activity from multi-window
@@ -2543,8 +2544,8 @@
void updatePictureInPictureMode(TaskRecord task, Rect targetStackBounds, boolean forceUpdate) {
mHandler.removeMessages(REPORT_PIP_MODE_CHANGED_MSG);
- for (int i = task.mActivities.size() - 1; i >= 0; i--) {
- final ActivityRecord r = task.mActivities.get(i);
+ for (int i = task.getChildCount() - 1; i >= 0; i--) {
+ final ActivityRecord r = task.getChildAt(i);
if (r.attachedToProcess()) {
r.updatePictureInPictureMode(targetStackBounds, forceUpdate);
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 9397893..1a80006 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -770,13 +770,13 @@
boolean restrictedBgActivity = false;
if (!abort) {
try {
- Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
"shouldAbortBackgroundActivityStart");
restrictedBgActivity = shouldAbortBackgroundActivityStart(callingUid,
callingPid, callingPackage, realCallingUid, realCallingPid, callerApp,
originatingPendingIntent, allowBackgroundActivityStart, intent);
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -1401,41 +1401,12 @@
final ActivityStack startedActivityStack;
try {
mService.deferWindowLayout();
- result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
+ result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
startFlags, doResume, options, inTask, outActivity, restrictedBgActivity);
} finally {
- final ActivityStack currentStack = r.getActivityStack();
- startedActivityStack = currentStack != null ? currentStack : mTargetStack;
-
- if (ActivityManager.isStartResultSuccessful(result)) {
- if (startedActivityStack != null) {
- // If there is no state change (e.g. a resumed activity is reparented to
- // top of another display) to trigger a visibility/configuration checking,
- // we have to update the configuration for changing to different display.
- final ActivityRecord currentTop =
- startedActivityStack.topRunningActivityLocked();
- if (currentTop != null && currentTop.shouldUpdateConfigForDisplayChanged()) {
- mRootActivityContainer.ensureVisibilityAndConfig(
- currentTop, currentTop.getDisplayId(),
- true /* markFrozenIfConfigChanged */, false /* deferResume */);
- }
- }
- } else {
- // If we are not able to proceed, disassociate the activity from the task.
- // Leaving an activity in an incomplete state can lead to issues, such as
- // performing operations without a window container.
- final ActivityStack stack = mStartActivity.getActivityStack();
- if (stack != null) {
- mStartActivity.finishIfPossible("startActivity", true /* oomAdj */);
- }
-
- // Stack should also be detached from display and be removed if it's empty.
- if (startedActivityStack != null && startedActivityStack.isAttached()
- && startedActivityStack.numActivities() == 0
- && !startedActivityStack.isActivityTypeHome()) {
- startedActivityStack.remove();
- }
- }
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ startedActivityStack = handleStartResult(r, result);
mService.continueWindowLayout();
}
@@ -1445,6 +1416,49 @@
}
/**
+ * If the start result is success, ensure that the configuration of the started activity matches
+ * the current display. Otherwise clean up unassociated containers to avoid leakage.
+ *
+ * @return the stack where the successful started activity resides.
+ */
+ private @Nullable ActivityStack handleStartResult(@NonNull ActivityRecord started, int result) {
+ final ActivityStack currentStack = started.getActivityStack();
+ ActivityStack startedActivityStack = currentStack != null ? currentStack : mTargetStack;
+
+ if (ActivityManager.isStartResultSuccessful(result)) {
+ if (startedActivityStack != null) {
+ // If there is no state change (e.g. a resumed activity is reparented to top of
+ // another display) to trigger a visibility/configuration checking, we have to
+ // update the configuration for changing to different display.
+ final ActivityRecord currentTop = startedActivityStack.topRunningActivityLocked();
+ if (currentTop != null && currentTop.shouldUpdateConfigForDisplayChanged()) {
+ mRootActivityContainer.ensureVisibilityAndConfig(
+ currentTop, currentTop.getDisplayId(),
+ true /* markFrozenIfConfigChanged */, false /* deferResume */);
+ }
+ }
+ return startedActivityStack;
+ }
+
+ // If we are not able to proceed, disassociate the activity from the task. Leaving an
+ // activity in an incomplete state can lead to issues, such as performing operations
+ // without a window container.
+ final ActivityStack stack = mStartActivity.getActivityStack();
+ if (stack != null) {
+ mStartActivity.finishIfPossible("startActivity", true /* oomAdj */);
+ }
+
+ // Stack should also be detached from display and be removed if it's empty.
+ if (startedActivityStack != null && startedActivityStack.isAttached()
+ && startedActivityStack.numActivities() == 0
+ && !startedActivityStack.isActivityTypeHome()) {
+ startedActivityStack.remove();
+ startedActivityStack = null;
+ }
+ return startedActivityStack;
+ }
+
+ /**
* Return true if background activity is really aborted.
*
* TODO(b/131748165): Refactor the logic so we don't need to call this method everywhere.
@@ -1469,7 +1483,7 @@
}
// Note: This method should only be called from {@link startActivity}.
- private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
+ private int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
ActivityRecord[] outActivity, boolean restrictedBgActivity) {
@@ -1592,7 +1606,7 @@
// accordingly.
if (mTargetStack.isFocusable()
&& !mRootActivityContainer.isTopDisplayFocusedStack(mTargetStack)) {
- mTargetStack.moveToFront("startActivityUnchecked");
+ mTargetStack.moveToFront("startActivityInner");
}
mRootActivityContainer.resumeFocusedStacksTopActivities(
mTargetStack, mStartActivity, mOptions);
@@ -1873,7 +1887,7 @@
mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */,
mLaunchFlags, mOptions);
mTargetStack.addTask(targetTask,
- !mLaunchTaskBehind /* toTop */, "startActivityUnchecked");
+ !mLaunchTaskBehind /* toTop */, "complyActivityFlags");
}
}
} else if ((mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) == 0 && !mAddingToTask
@@ -2430,7 +2444,7 @@
if (mStartActivity.getTaskRecord() == null || mStartActivity.getTaskRecord() == parent) {
parent.addActivityToTop(mStartActivity);
} else {
- mStartActivity.reparent(parent, parent.mActivities.size() /* top */, reason);
+ mStartActivity.reparent(parent, parent.getChildCount() /* top */, reason);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index ab35652..0488a3b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -159,10 +159,10 @@
*
* @param reasons A map from windowing mode to a reason integer why the transition was started,
* which must be one of the APP_TRANSITION_* values.
- * @param timestamp The time at which the app transition started in
- * {@link SystemClock#uptimeMillis()} timebase.
+ * @param timestampNs The time at which the app transition started in
+ * {@link SystemClock#elapsedRealtimeNs()} ()} timebase.
*/
- public abstract void notifyAppTransitionStarting(SparseIntArray reasons, long timestamp);
+ public abstract void notifyAppTransitionStarting(SparseIntArray reasons, long timestampNs);
/**
* Callback for window manager to let activity manager know that the app transition was
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 750fc68..20113a6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -52,7 +52,7 @@
import static android.os.FactoryTest.FACTORY_TEST_OFF;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.SYSTEM_UID;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RTL;
@@ -63,7 +63,6 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
import static com.android.server.am.ActivityManagerService.ANR_TRACE_DIR;
import static com.android.server.am.ActivityManagerService.MY_PID;
@@ -1607,6 +1606,7 @@
}
final long origId = Binder.clearCallingIdentity();
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "finishActivity");
try {
boolean res;
final boolean finishWithRootActivity =
@@ -1634,6 +1634,7 @@
}
return res;
} finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
Binder.restoreCallingIdentity(origId);
}
}
@@ -1668,6 +1669,7 @@
try {
WindowProcessController proc = null;
synchronized (mGlobalLock) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityIdle");
ActivityStack stack = ActivityRecord.getStackLocked(token);
if (stack == null) {
return;
@@ -1682,6 +1684,7 @@
}
}
} finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
Binder.restoreCallingIdentity(origId);
}
}
@@ -1708,10 +1711,12 @@
public final void activityPaused(IBinder token) {
final long origId = Binder.clearCallingIdentity();
synchronized (mGlobalLock) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityPaused");
ActivityStack stack = ActivityRecord.getStackLocked(token);
if (stack != null) {
stack.activityPausedLocked(token, false);
}
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
Binder.restoreCallingIdentity(origId);
}
@@ -1732,6 +1737,7 @@
int restartingUid = 0;
final ActivityRecord r;
synchronized (mGlobalLock) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityStopped");
r = ActivityRecord.isInStackLocked(token);
if (r != null) {
if (r.attachedToProcess()
@@ -1743,6 +1749,7 @@
}
r.activityStoppedLocked(icicle, persistentState, description);
}
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
if (restartingName != null) {
@@ -1764,12 +1771,14 @@
if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "ACTIVITY DESTROYED: " + token);
synchronized (mGlobalLock) {
final long origId = Binder.clearCallingIdentity();
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityDestroyed");
try {
final ActivityRecord activity = ActivityRecord.forTokenLocked(token);
if (activity != null) {
activity.destroyed("activityDestroyed");
}
} finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
Binder.restoreCallingIdentity(origId);
}
}
@@ -1982,7 +1991,7 @@
final TaskRecord task = r.getTaskRecord();
int index = task.mActivities.lastIndexOf(r);
if (index > 0) {
- ActivityRecord under = task.mActivities.get(index - 1);
+ ActivityRecord under = task.getChildAt(index - 1);
under.returningOptions = safeOptions != null ? safeOptions.getOptions(r) : null;
}
return r.setOccludesParent(false);
@@ -3321,29 +3330,6 @@
}
@Override
- public void startInPlaceAnimationOnFrontMostApplication(Bundle opts) {
- final SafeActivityOptions safeOptions = SafeActivityOptions.fromBundle(opts);
- final ActivityOptions activityOptions = safeOptions != null
- ? safeOptions.getOptions(mStackSupervisor)
- : null;
- if (activityOptions == null
- || activityOptions.getAnimationType() != ActivityOptions.ANIM_CUSTOM_IN_PLACE
- || activityOptions.getCustomInPlaceResId() == 0) {
- throw new IllegalArgumentException("Expected in-place ActivityOption " +
- "with valid animation");
- }
- // Get top display of front most application.
- final ActivityStack focusedStack = getTopDisplayFocusedStack();
- if (focusedStack != null) {
- final DisplayContent dc = focusedStack.getDisplay().mDisplayContent;
- dc.prepareAppTransition(TRANSIT_TASK_IN_PLACE, false);
- dc.mAppTransition.overrideInPlaceAppTransition(activityOptions.getPackageName(),
- activityOptions.getCustomInPlaceResId());
- dc.executeAppTransition();
- }
- }
-
- @Override
public void removeStack(int stackId) {
enforceCallerIsRecentsOrHasPermission(MANAGE_ACTIVITY_STACKS, "removeStack()");
synchronized (mGlobalLock) {
@@ -5530,8 +5516,8 @@
void startProcessAsync(ActivityRecord activity, boolean knownToBeDead, boolean isTop,
String hostingType) {
try {
- if (Trace.isTagEnabled(TRACE_TAG_ACTIVITY_MANAGER)) {
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "dispatchingStartProcess:"
+ if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "dispatchingStartProcess:"
+ activity.processName);
}
// Post message to start process to avoid possible deadlock of calling into AMS with the
@@ -5541,7 +5527,7 @@
isTop, hostingType, activity.intent.getComponent());
mH.sendMessage(m);
} finally {
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -5694,11 +5680,11 @@
private void updateResumedAppTrace(@Nullable ActivityRecord resumed) {
if (mTracedResumedActivity != null) {
- Trace.asyncTraceEnd(TRACE_TAG_ACTIVITY_MANAGER,
+ Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER,
constructResumedTraceName(mTracedResumedActivity.packageName), 0);
}
if (resumed != null) {
- Trace.asyncTraceBegin(TRACE_TAG_ACTIVITY_MANAGER,
+ Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER,
constructResumedTraceName(resumed.packageName), 0);
}
mTracedResumedActivity = resumed;
@@ -6026,10 +6012,10 @@
@Override
public void notifyAppTransitionStarting(SparseIntArray reasons,
- long timestamp) {
+ long timestampNs) {
synchronized (mGlobalLock) {
mStackSupervisor.getActivityMetricsLogger().notifyTransitionStarting(
- reasons, timestamp);
+ reasons, timestampNs);
}
}
@@ -6797,7 +6783,14 @@
@Override
public boolean attachApplication(WindowProcessController wpc) throws RemoteException {
synchronized (mGlobalLockWithoutBoost) {
- return mRootActivityContainer.attachApplication(wpc);
+ if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "attachApplication:" + wpc.mName);
+ }
+ try {
+ return mRootActivityContainer.attachApplication(wpc);
+ } finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 93c461f..394b475 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -33,7 +33,6 @@
import static android.view.WindowManager.TRANSIT_SHOW_SINGLE_TASK_DISPLAY;
import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE;
import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
import static android.view.WindowManager.TRANSIT_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_TASK_OPEN_BEHIND;
import static android.view.WindowManager.TRANSIT_TASK_TO_BACK;
@@ -2257,8 +2256,7 @@
static boolean isTaskTransit(int transit) {
return isTaskOpenTransit(transit)
|| transit == TRANSIT_TASK_CLOSE
- || transit == TRANSIT_TASK_TO_BACK
- || transit == TRANSIT_TASK_IN_PLACE;
+ || transit == TRANSIT_TASK_TO_BACK;
}
private static boolean isTaskOpenTransit(int transit) {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 20a871b..0c07e15 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -31,7 +31,6 @@
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_SHOW_SINGLE_TASK_DISPLAY;
import static android.view.WindowManager.TRANSIT_TASK_CLOSE;
-import static android.view.WindowManager.TRANSIT_TASK_IN_PLACE;
import static android.view.WindowManager.TRANSIT_TASK_OPEN;
import static android.view.WindowManager.TRANSIT_TASK_TO_BACK;
import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
@@ -179,8 +178,6 @@
final int layoutRedo;
mService.mSurfaceAnimationRunner.deferStartingAnimations();
try {
- processApplicationsAnimatingInPlace(transit);
-
handleClosingApps(transit, animLp, voiceInteraction);
handleOpeningApps(transit, animLp, voiceInteraction);
handleChangingApps(transit, animLp, voiceInteraction);
@@ -212,7 +209,7 @@
mDisplayContent.computeImeTarget(true /* updateImeTarget */);
mService.mAtmInternal.notifyAppTransitionStarting(mTempTransitionReasons.clone(),
- SystemClock.uptimeMillis());
+ SystemClock.elapsedRealtimeNanos());
if (transit == TRANSIT_SHOW_SINGLE_TASK_DISPLAY) {
mService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
@@ -710,19 +707,4 @@
}
return topApp;
}
-
- private void processApplicationsAnimatingInPlace(int transit) {
- if (transit == TRANSIT_TASK_IN_PLACE) {
- // Find the focused window
- final WindowState win = mDisplayContent.findFocusedWindow();
- if (win != null) {
- final AppWindowToken wtoken = win.mAppToken;
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Now animating app in place %s", wtoken);
- wtoken.cancelAnimation();
- wtoken.applyAnimationLocked(null, transit, false, false);
- wtoken.updateReportedVisibilityLocked();
- wtoken.showAllWindowsLocked();
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index b1ef601..e56fdd2 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -512,7 +512,7 @@
if (DEBUG_VISIBILITY) Slog.v(TAG, "VIS " + this + ": interesting="
+ numInteresting + " visible=" + numVisible);
if (nowDrawn != reportedDrawn) {
- onWindowsDrawn(nowDrawn, SystemClock.uptimeMillis());
+ onWindowsDrawn(nowDrawn, SystemClock.elapsedRealtimeNanos());
reportedDrawn = nowDrawn;
}
if (nowVisible != reportedVisible) {
@@ -1420,7 +1420,7 @@
mReparenting = true;
getParent().removeChild(this);
- task.addChild(this, position);
+ task.addChild((ActivityRecord) this, position);
mReparenting = false;
@@ -2283,7 +2283,7 @@
}
}
- private final Runnable mAddStartingWindow = new Runnable() {
+ private class AddStartingWindow implements Runnable {
@Override
public void run() {
@@ -2343,7 +2343,9 @@
AppWindowToken.this);
}
}
- };
+ }
+
+ private final AddStartingWindow mAddStartingWindow = new AddStartingWindow();
private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning,
boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents,
@@ -2930,7 +2932,7 @@
layer += Z_BOOST_BASE;
}
if (!mNeedsAnimationBoundsLayer) {
- leash.setLayer(layer);
+ t.setLayer(leash, layer);
}
final DisplayContent dc = getDisplayContent();
diff --git a/services/core/java/com/android/server/wm/CircularDisplayMask.java b/services/core/java/com/android/server/wm/CircularDisplayMask.java
index c1ca816..b73b481 100644
--- a/services/core/java/com/android/server/wm/CircularDisplayMask.java
+++ b/services/core/java/com/android/server/wm/CircularDisplayMask.java
@@ -56,7 +56,7 @@
private int mMaskThickness;
CircularDisplayMask(Supplier<Surface> surfaceFactory, DisplayContent dc, int zOrder,
- int screenOffset, int maskThickness) {
+ int screenOffset, int maskThickness, SurfaceControl.Transaction t) {
final Display display = dc.getDisplay();
mSurface = surfaceFactory.get();
mScreenSize = new Point();
@@ -75,10 +75,10 @@
.setFormat(PixelFormat.TRANSLUCENT)
.build();
- ctrl.setLayerStack(display.getLayerStack());
- ctrl.setLayer(zOrder);
- ctrl.setPosition(0, 0);
- ctrl.show();
+ t.setLayerStack(ctrl, display.getLayerStack());
+ t.setLayer(ctrl, zOrder);
+ t.setPosition(ctrl, 0, 0);
+ t.show(ctrl);
mSurface.copyFrom(ctrl);
} catch (OutOfResourcesException e) {
}
@@ -91,7 +91,7 @@
mMaskThickness = maskThickness;
}
- private void drawIfNeeded() {
+ private void drawIfNeeded(SurfaceControl.Transaction t) {
if (!mDrawNeeded || !mVisible || mDimensionsUnequal) {
return;
}
@@ -108,45 +108,46 @@
return;
}
switch (mRotation) {
- case Surface.ROTATION_0:
- case Surface.ROTATION_90:
- // chin bottom or right
- mSurfaceControl.setPosition(0, 0);
- break;
- case Surface.ROTATION_180:
- // chin top
- mSurfaceControl.setPosition(0, -mScreenOffset);
- break;
- case Surface.ROTATION_270:
- // chin left
- mSurfaceControl.setPosition(-mScreenOffset, 0);
- break;
+ case Surface.ROTATION_0:
+ case Surface.ROTATION_90:
+ // chin bottom or right
+ t.setPosition(mSurfaceControl, 0, 0);
+ break;
+ case Surface.ROTATION_180:
+ // chin top
+ t.setPosition(mSurfaceControl, 0, -mScreenOffset);
+ break;
+ case Surface.ROTATION_270:
+ // chin left
+ t.setPosition(mSurfaceControl, -mScreenOffset, 0);
+ break;
}
int circleRadius = mScreenSize.x / 2;
c.drawColor(Color.BLACK);
- // The radius is reduced by mMaskThickness to provide an anti aliasing effect on the display edges.
+ // The radius is reduced by mMaskThickness to provide an anti aliasing effect on the
+ // display edges.
c.drawCircle(circleRadius, circleRadius, circleRadius - mMaskThickness, mPaint);
mSurface.unlockCanvasAndPost(c);
}
// Note: caller responsible for being inside
// Surface.openTransaction() / closeTransaction()
- public void setVisibility(boolean on) {
+ public void setVisibility(boolean on, SurfaceControl.Transaction t) {
if (mSurfaceControl == null) {
return;
}
mVisible = on;
- drawIfNeeded();
+ drawIfNeeded(t);
if (on) {
- mSurfaceControl.show();
+ t.show(mSurfaceControl);
} else {
- mSurfaceControl.hide();
+ t.hide(mSurfaceControl);
}
}
- void positionSurface(int dw, int dh, int rotation) {
+ void positionSurface(int dw, int dh, int rotation, SurfaceControl.Transaction t) {
if (mLastDW == dw && mLastDH == dh && mRotation == rotation) {
return;
}
@@ -154,7 +155,7 @@
mLastDH = dh;
mDrawNeeded = true;
mRotation = rotation;
- drawIfNeeded();
+ drawIfNeeded(t);
}
}
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 8afbbdf..300ee1d 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -626,6 +626,10 @@
return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
}
+ boolean hasChild() {
+ return getChildCount() > 0;
+ }
+
abstract protected int getChildCount();
abstract protected E getChildAt(int index);
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index f592ac6..4c3611e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -331,7 +331,7 @@
/**
* For default display it contains real metrics, empty for others.
- * @see WindowManagerService#createWatermarkInTransaction()
+ * @see WindowManagerService#createWatermark()
*/
final DisplayMetrics mRealDisplayMetrics = new DisplayMetrics();
@@ -5152,7 +5152,7 @@
// Let surface flinger to set the display ID of this input window handle because we don't
// know which display the parent surface control is on.
final InputWindowHandle portalWindowHandle = new InputWindowHandle(
- null /* inputApplicationHandle */, null /* clientWindow */, INVALID_DISPLAY);
+ null /* inputApplicationHandle */, INVALID_DISPLAY);
portalWindowHandle.name = name;
portalWindowHandle.token = new Binder();
portalWindowHandle.layoutParamsFlags =
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 7be4dbd..60e9819 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -16,13 +16,12 @@
package com.android.server.wm;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
import static android.view.Display.TYPE_BUILT_IN;
@@ -197,6 +196,11 @@
// Nav bar is never forced opaque.
private static final int NAV_BAR_FORCE_TRANSPARENT = 2;
+ /** Don't apply window animation (see {@link #selectAnimation}). */
+ static final int ANIMATION_NONE = -1;
+ /** Use the transit animation in style resource (see {@link #selectAnimation}). */
+ static final int ANIMATION_STYLEABLE = 0;
+
/**
* These are the system UI flags that, when changing, can cause the layout
* of the screen to change.
@@ -1037,9 +1041,9 @@
}
/**
- * Control the animation to run when a window's state changes. Return a
- * non-0 number to force the animation to a specific resource ID, or 0
- * to use the default animation.
+ * Control the animation to run when a window's state changes. Return a positive number to
+ * force the animation to a specific resource ID, {@link #ANIMATION_STYLEABLE} to use the
+ * style resource defining the animation, or {@link #ANIMATION_NONE} for no animation.
*
* @param win The window that is changing.
* @param transit What is happening to the window:
@@ -1048,9 +1052,9 @@
* {@link com.android.server.policy.WindowManagerPolicy#TRANSIT_SHOW}, or
* {@link com.android.server.policy.WindowManagerPolicy#TRANSIT_HIDE}.
*
- * @return Resource ID of the actual animation to use, or 0 for none.
+ * @return Resource ID of the actual animation to use, or {@link #ANIMATION_NONE} for none.
*/
- public int selectAnimationLw(WindowState win, int transit) {
+ int selectAnimation(WindowState win, int transit) {
if (DEBUG_ANIM) Slog.i(TAG, "selectAnimation in " + win
+ ": transit=" + transit);
if (win == mStatusBar) {
@@ -1058,7 +1062,7 @@
final boolean expanded = win.getAttrs().height == MATCH_PARENT
&& win.getAttrs().width == MATCH_PARENT;
if (isKeyguard || expanded) {
- return -1;
+ return ANIMATION_NONE;
}
if (transit == TRANSIT_EXIT
|| transit == TRANSIT_HIDE) {
@@ -1069,7 +1073,7 @@
}
} else if (win == mNavigationBar) {
if (win.getAttrs().windowAnimations != 0) {
- return 0;
+ return ANIMATION_STYLEABLE;
}
// This can be on either the bottom or the right or the left.
if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
@@ -1102,7 +1106,7 @@
}
}
} else if (win.getAttrs().type == TYPE_DOCK_DIVIDER) {
- return selectDockedDividerAnimationLw(win, transit);
+ return selectDockedDividerAnimation(win, transit);
}
if (transit == TRANSIT_PREVIEW_DONE) {
@@ -1116,13 +1120,13 @@
// is shown. We don't want an animation on the dream, because
// we need it shown immediately with the keyguard animating away
// to reveal it.
- return -1;
+ return ANIMATION_NONE;
}
- return 0;
+ return ANIMATION_STYLEABLE;
}
- private int selectDockedDividerAnimationLw(WindowState win, int transit) {
+ private int selectDockedDividerAnimation(WindowState win, int transit) {
int insets = mDisplayContent.getDockedDividerController().getContentInsets();
// If the divider is behind the navigation bar, don't animate.
@@ -1141,14 +1145,14 @@
|| frame.bottom + insets >= win.getDisplayFrameLw().bottom);
final boolean offscreen = offscreenLandscape || offscreenPortrait;
if (behindNavBar || offscreen) {
- return 0;
+ return ANIMATION_STYLEABLE;
}
if (transit == TRANSIT_ENTER || transit == TRANSIT_SHOW) {
return R.anim.fade_in;
} else if (transit == TRANSIT_EXIT) {
return R.anim.fade_out;
} else {
- return 0;
+ return ANIMATION_STYLEABLE;
}
}
@@ -3130,9 +3134,10 @@
final int dockedVisibility = updateLightStatusBarLw(0 /* vis */,
mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState);
mService.getStackBounds(
- WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mNonDockedStackBounds);
- mService.getStackBounds(
WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, mDockedStackBounds);
+ mService.getStackBounds(mDockedStackBounds.isEmpty()
+ ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+ ACTIVITY_TYPE_UNDEFINED, mNonDockedStackBounds);
final Pair<Integer, Boolean> result =
updateSystemBarsLw(win, mLastSystemUiFlags, tmpVisibility);
final int visibility = result.first;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 34820ac..abd7222 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -263,7 +263,7 @@
InputChannel[] channels = InputChannel.openInputChannelPair("drag");
mServerChannel = channels[0];
mClientChannel = channels[1];
- mService.mInputManager.registerInputChannel(mServerChannel, null);
+ mService.mInputManager.registerInputChannel(mServerChannel);
mInputEventReceiver = new DragInputEventReceiver(mClientChannel,
mService.mH.getLooper(), mDragDropController);
@@ -272,7 +272,7 @@
mDragApplicationHandle.dispatchingTimeoutNanos =
WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
- mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
+ mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle,
display.getDisplayId());
mDragWindowHandle.name = "drag";
mDragWindowHandle.token = mServerChannel.getToken();
diff --git a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
index f64592f..2165b0e 100644
--- a/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
+++ b/services/core/java/com/android/server/wm/EmulatorDisplayOverlay.java
@@ -50,7 +50,7 @@
private boolean mVisible;
EmulatorDisplayOverlay(Supplier<Surface> surfaceFactory, Context context, DisplayContent dc,
- int zOrder) {
+ int zOrder, SurfaceControl.Transaction t) {
mSurface = surfaceFactory.get();
final Display display = dc.getDisplay();
mScreenSize = new Point();
@@ -63,9 +63,9 @@
.setBufferSize(mScreenSize.x, mScreenSize.y)
.setFormat(PixelFormat.TRANSLUCENT)
.build();
- ctrl.setLayer(zOrder);
- ctrl.setPosition(0, 0);
- ctrl.show();
+ t.setLayer(ctrl, zOrder);
+ t.setPosition(ctrl, 0, 0);
+ t.show(ctrl);
mSurface.copyFrom(ctrl);
} catch (OutOfResourcesException e) {
}
@@ -75,7 +75,7 @@
com.android.internal.R.drawable.emulator_circular_window_overlay);
}
- private void drawIfNeeded() {
+ private void drawIfNeeded(SurfaceControl.Transaction t) {
if (!mDrawNeeded || !mVisible) {
return;
}
@@ -92,7 +92,7 @@
return;
}
c.drawColor(Color.TRANSPARENT, PorterDuff.Mode.SRC);
- mSurfaceControl.setPosition(0, 0);
+ t.setPosition(mSurfaceControl, 0, 0);
// Always draw the overlay with square dimensions
int size = Math.max(mScreenSize.x, mScreenSize.y);
mOverlay.setBounds(0, 0, size, size);
@@ -102,20 +102,20 @@
// Note: caller responsible for being inside
// Surface.openTransaction() / closeTransaction()
- public void setVisibility(boolean on) {
+ public void setVisibility(boolean on, SurfaceControl.Transaction t) {
if (mSurfaceControl == null) {
return;
}
mVisible = on;
- drawIfNeeded();
+ drawIfNeeded(t);
if (on) {
- mSurfaceControl.show();
+ t.show(mSurfaceControl);
} else {
- mSurfaceControl.hide();
+ t.hide(mSurfaceControl);
}
}
- void positionSurface(int dw, int dh, int rotation) {
+ void positionSurface(int dw, int dh, int rotation, SurfaceControl.Transaction t) {
if (mLastDW == dw && mLastDH == dh && mRotation == rotation) {
return;
}
@@ -123,7 +123,7 @@
mLastDH = dh;
mDrawNeeded = true;
mRotation = rotation;
- drawIfNeeded();
+ drawIfNeeded(t);
}
}
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 7e085f6..bc95481 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -25,7 +25,7 @@
*/
class ImeInsetsSourceProvider extends InsetsSourceProvider {
- private WindowState mCurImeTarget;
+ private WindowState mImeTargetFromIme;
private Runnable mShowImeRunner;
private boolean mIsImeLayoutDrawn;
@@ -40,8 +40,8 @@
void onPostLayout() {
super.onPostLayout();
- if (mCurImeTarget != null
- && mCurImeTarget == mDisplayContent.mInputMethodTarget
+ if (mImeTargetFromIme != null
+ && isImeTargetFromDisplayContentAndImeSame()
&& mWin != null
&& mWin.isDrawnLw()
&& !mWin.mGivenInsetsPending) {
@@ -64,18 +64,33 @@
/**
* Called from {@link WindowManagerInternal#showImePostLayout} when {@link InputMethodService}
* requests to show IME on {@param imeTarget}.
- * @param imeTarget imeTarget on which IME is displayed.
+ * @param imeTarget imeTarget on which IME request is coming from.
*/
void scheduleShowImePostLayout(WindowState imeTarget) {
- mCurImeTarget = imeTarget;
+ mImeTargetFromIme = imeTarget;
mShowImeRunner = () -> {
// Target should still be the same.
- if (mCurImeTarget == mDisplayContent.mInputMethodTarget) {
+ if (isImeTargetFromDisplayContentAndImeSame()) {
mDisplayContent.mInputMethodTarget.showInsets(
WindowInsets.Type.ime(), true /* fromIme */);
}
- mCurImeTarget = null;
+ mImeTargetFromIme = null;
};
}
+ private boolean isImeTargetFromDisplayContentAndImeSame() {
+ // IMMS#mLastImeTargetWindow always considers focused window as
+ // IME target, however DisplayContent#computeImeTarget() can compute
+ // a different IME target.
+ // Refer to WindowManagerService#applyImeVisibility(token, false).
+ // If IMMS's imeTarget is child of DisplayContent's imeTarget and child window
+ // is above the parent, we will consider it as the same target for now.
+ // TODO(b/139861270): Remove the child & sublayer check once IMMS is aware of
+ // actual IME target.
+ return mImeTargetFromIme == mDisplayContent.mInputMethodTarget
+ || (mDisplayContent.mInputMethodTarget.getParentWindow() == mImeTargetFromIme
+ && mDisplayContent.mInputMethodTarget.mSubLayer
+ > mImeTargetFromIme.mSubLayer);
+ }
+
}
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
index d774dc3..82ac6b8 100644
--- a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -189,7 +189,7 @@
| WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT);
- lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
lp.setTitle("ImmersiveModeConfirmation");
lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation;
lp.token = getWindowToken();
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index 8dae016..ebe9f08 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -65,14 +65,14 @@
} else {
mClientChannel = channels[1];
}
- mService.mInputManager.registerInputChannel(mServerChannel, null);
+ mService.mInputManager.registerInputChannel(mServerChannel);
mApplicationHandle = new InputApplicationHandle(new Binder());
mApplicationHandle.name = name;
mApplicationHandle.dispatchingTimeoutNanos =
WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
- mWindowHandle = new InputWindowHandle(mApplicationHandle, null, displayId);
+ mWindowHandle = new InputWindowHandle(mApplicationHandle, displayId);
mWindowHandle.name = name;
mWindowHandle.token = mServerChannel.getToken();
mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index ec36a82..9973e11 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -5,20 +5,25 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS;
import android.os.Debug;
import android.os.IBinder;
+import android.os.RemoteException;
import android.util.Slog;
+import android.view.IWindow;
import android.view.KeyEvent;
import android.view.WindowManager;
import com.android.server.input.InputManagerService;
import java.io.PrintWriter;
+import java.util.concurrent.atomic.AtomicReference;
final class InputManagerCallback implements InputManagerService.WindowManagerCallbacks {
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "InputManagerCallback" : TAG_WM;
private final WindowManagerService mService;
// Set to true when the first input device configuration change notification
@@ -37,6 +42,13 @@
// which point the ActivityManager will enable dispatching.
private boolean mInputDispatchEnabled;
+ // TODO(b/141749603)) investigate if this can be part of client focus change dispatch
+ // Tracks the currently focused window used to update pointer capture state in clients
+ private AtomicReference<IWindow> mFocusedWindow = new AtomicReference<>();
+
+ // Tracks focused window pointer capture state
+ private boolean mFocusedWindowHasCapture;
+
public InputManagerCallback(WindowManagerService service) {
mService = service;
}
@@ -53,7 +65,7 @@
}
synchronized (mService.mGlobalLock) {
- WindowState windowState = mService.windowForClientLocked(null, token, false);
+ WindowState windowState = mService.mInputToWindowMap.get(token);
if (windowState != null) {
Slog.i(TAG_WM, "WINDOW DIED " + windowState);
windowState.removeIfPossible();
@@ -72,9 +84,10 @@
AppWindowToken appWindowToken = null;
WindowState windowState = null;
boolean aboveSystem = false;
+ //TODO(b/141764879) Limit scope of wm lock when input calls notifyANR
synchronized (mService.mGlobalLock) {
if (token != null) {
- windowState = mService.windowForClientLocked(null, token, false);
+ windowState = mService.mInputToWindowMap.get(token);
if (windowState != null) {
appWindowToken = windowState.mAppToken;
}
@@ -109,7 +122,7 @@
// Notify the activity manager about the timeout and let it decide whether
// to abort dispatching or keep waiting.
final boolean abort = appWindowToken.keyDispatchingTimedOut(reason,
- (windowState != null) ? windowState.mSession.mPid : -1);
+ windowState.mSession.mPid);
if (!abort) {
// The activity manager declined to abort dispatching.
// Wait a bit longer and timeout again later.
@@ -239,6 +252,60 @@
mService.mH.obtainMessage(ON_POINTER_DOWN_OUTSIDE_FOCUS, touchedToken).sendToTarget();
}
+ @Override
+ public boolean notifyFocusChanged(IBinder oldToken, IBinder newToken) {
+ boolean requestRefreshConfiguration = false;
+ final IWindow newFocusedWindow;
+ final WindowState win;
+
+ // TODO(b/141749603) investigate if this can be part of client focus change dispatch
+ synchronized (mService.mGlobalLock) {
+ win = mService.mInputToWindowMap.get(newToken);
+ }
+ newFocusedWindow = (win != null) ? win.mClient : null;
+
+ final IWindow focusedWindow = mFocusedWindow.get();
+ if (focusedWindow != null) {
+ if (newFocusedWindow != null
+ && newFocusedWindow.asBinder() == focusedWindow.asBinder()) {
+ Slog.w(TAG, "notifyFocusChanged called with unchanged mFocusedWindow="
+ + focusedWindow);
+ return false;
+ }
+ requestRefreshConfiguration = dispatchPointerCaptureChanged(focusedWindow, false);
+ }
+ mFocusedWindow.set(newFocusedWindow);
+ return requestRefreshConfiguration;
+ }
+
+ @Override
+ public boolean requestPointerCapture(IBinder windowToken, boolean enabled) {
+ final IWindow focusedWindow = mFocusedWindow.get();
+ if (focusedWindow == null || focusedWindow.asBinder() != windowToken) {
+ Slog.e(TAG, "requestPointerCapture called for a window that has no focus: "
+ + windowToken);
+ return false;
+ }
+ if (mFocusedWindowHasCapture == enabled) {
+ Slog.i(TAG, "requestPointerCapture: already " + (enabled ? "enabled" : "disabled"));
+ return false;
+ }
+ return dispatchPointerCaptureChanged(focusedWindow, enabled);
+ }
+
+ private boolean dispatchPointerCaptureChanged(IWindow focusedWindow, boolean enabled) {
+ if (mFocusedWindowHasCapture != enabled) {
+ mFocusedWindowHasCapture = enabled;
+ try {
+ focusedWindow.dispatchPointerCaptureChanged(enabled);
+ } catch (RemoteException ex) {
+ /* ignore */
+ }
+ return true;
+ }
+ return false;
+ }
+
/** Waits until the built-in input devices have been configured. */
public boolean waitForInputDevicesReady(long timeoutMillis) {
synchronized (mInputDevicesReadyMonitor) {
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 932b4fa..584c6e1 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -112,7 +112,7 @@
}
}
- private final Runnable mUpdateInputWindows = new Runnable() {
+ private class UpdateInputWindows implements Runnable {
@Override
public void run() {
synchronized (mService.mGlobalLock) {
@@ -148,7 +148,9 @@
mUpdateInputForAllWindowsConsumer.updateInputWindows(inDrag);
}
}
- };
+ }
+
+ private final UpdateInputWindows mUpdateInputWindows = new UpdateInputWindows();
public InputMonitor(WindowManagerService service, int displayId) {
mService = service;
@@ -411,7 +413,7 @@
WallpaperController wallpaperController;
// An invalid window handle that tells SurfaceFlinger not update the input info.
- final InputWindowHandle mInvalidInputWindow = new InputWindowHandle(null, null, mDisplayId);
+ final InputWindowHandle mInvalidInputWindow = new InputWindowHandle(null, mDisplayId);
private void updateInputWindows(boolean inDrag) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java
index b7184a5..22ba82a 100644
--- a/services/core/java/com/android/server/wm/InsetsControlTarget.java
+++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java
@@ -33,4 +33,13 @@
*/
default void showInsets(@InsetType int types, boolean fromIme) {
}
+
+ /**
+ * Instructs the control target to hide inset sources.
+ *
+ * @param types to specify which types of insets source window should be hidden.
+ * @param fromIme {@code true} if IME hide request originated from {@link InputMethodService}.
+ */
+ default void hideInsets(@InsetType int types, boolean fromIme) {
+ }
}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index cc55e01..3731d3f 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -241,6 +241,10 @@
@Override
public void startAnimation(SurfaceControl animationLeash, Transaction t,
OnAnimationFinishedCallback finishCallback) {
+ // TODO: use 0 alpha and remove t.hide() once b/138459974 is fixed.
+ t.setAlpha(animationLeash, 1 /* alpha */);
+ t.hide(animationLeash);
+
mCapturedLeash = animationLeash;
final Rect frame = mWin.getWindowFrames().mFrame;
t.setPosition(mCapturedLeash, frame.left, frame.top);
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 2b5eb3a..52cc422 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
@@ -180,7 +180,7 @@
if (!mKeyguardShowing) {
return;
}
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "keyguardGoingAway");
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "keyguardGoingAway");
mService.deferWindowLayout();
try {
setKeyguardGoingAway(true);
@@ -202,11 +202,8 @@
true /* taskSwitch */);
mWindowManager.executeAppTransition();
} finally {
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "keyguardGoingAway: surfaceLayout");
mService.continueWindowLayout();
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
-
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
diff --git a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
index 93e2d8d..362ed3c 100644
--- a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
+++ b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
@@ -70,9 +70,12 @@
}
@Override
- public void onIntentStarted(Intent intent) {
+ public void onIntentStarted(Intent intent, long timestampNs) {
mHandler.sendMessage(PooledLambda.obtainMessage(
- LaunchObserverRegistryImpl::handleOnIntentStarted, this, intent));
+ LaunchObserverRegistryImpl::handleOnIntentStarted,
+ this,
+ intent,
+ timestampNs));
}
@Override
@@ -99,9 +102,22 @@
@Override
public void onActivityLaunchFinished(
- @ActivityRecordProto byte[] activity) {
+ @ActivityRecordProto byte[] activity,
+ long timestampNs) {
mHandler.sendMessage(PooledLambda.obtainMessage(
- LaunchObserverRegistryImpl::handleOnActivityLaunchFinished, this, activity));
+ LaunchObserverRegistryImpl::handleOnActivityLaunchFinished,
+ this,
+ activity,
+ timestampNs));
+ }
+
+ @Override
+ public void onReportFullyDrawn(@ActivityRecordProto byte[] activity, long timestampNs) {
+ mHandler.sendMessage(PooledLambda.obtainMessage(
+ LaunchObserverRegistryImpl::handleOnReportFullyDrawn,
+ this,
+ activity,
+ timestampNs));
}
// Use PooledLambda.obtainMessage to invoke below methods. Every method reference must be
@@ -116,11 +132,11 @@
mList.remove(observer);
}
- private void handleOnIntentStarted(Intent intent) {
+ private void handleOnIntentStarted(Intent intent, long timestampNs) {
// Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
for (int i = 0; i < mList.size(); i++) {
ActivityMetricsLaunchObserver o = mList.get(i);
- o.onIntentStarted(intent);
+ o.onIntentStarted(intent, timestampNs);
}
}
@@ -152,11 +168,20 @@
}
private void handleOnActivityLaunchFinished(
- @ActivityRecordProto byte[] activity) {
+ @ActivityRecordProto byte[] activity, long timestampNs) {
// Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
for (int i = 0; i < mList.size(); i++) {
ActivityMetricsLaunchObserver o = mList.get(i);
- o.onActivityLaunchFinished(activity);
+ o.onActivityLaunchFinished(activity, timestampNs);
+ }
+ }
+
+ private void handleOnReportFullyDrawn(
+ @ActivityRecordProto byte[] activity, long timestampNs) {
+ // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
+ for (int i = 0; i < mList.size(); i++) {
+ ActivityMetricsLaunchObserver o = mList.get(i);
+ o.onReportFullyDrawn(activity, timestampNs);
}
}
}
diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
index 013607e..5d27390 100644
--- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java
+++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java
@@ -17,7 +17,6 @@
package com.android.server.wm;
import android.content.ComponentName;
-import android.content.pm.PackageList;
import android.content.pm.PackageManagerInternal;
import android.graphics.Rect;
import android.os.Environment;
@@ -32,6 +31,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastXmlSerializer;
import com.android.server.LocalServices;
+import com.android.server.pm.PackageList;
import com.android.server.wm.LaunchParamsController.LaunchParams;
import libcore.io.IoUtils;
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 1bd2493..c59a73b 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -20,7 +20,7 @@
import android.graphics.Point;
import android.graphics.Rect;
-import android.os.Binder;
+import android.os.IBinder;
import android.os.Process;
import android.view.InputChannel;
import android.view.InputEventReceiver;
@@ -170,7 +170,7 @@
final InputWindowHandle mWindowHandle;
final InputEventReceiver mInputEventReceiver;
final WindowManagerService mWmService;
- final Binder mToken = new Binder();
+ final IBinder mToken;
InputInterceptor(String namePrefix, WindowState win) {
mWmService = win.mWmService;
@@ -180,10 +180,11 @@
mClientChannel = channels[1];
mInputEventReceiver = new SimpleInputReceiver(mClientChannel);
- mWmService.mInputManager.registerInputChannel(mServerChannel, mToken);
+ mWmService.mInputManager.registerInputChannel(mServerChannel);
+ mToken = mServerChannel.getToken();
mWindowHandle = new InputWindowHandle(null /* inputApplicationHandle */,
- null /* clientWindow */, win.getDisplayId());
+ win.getDisplayId());
mWindowHandle.name = name;
mWindowHandle.token = mToken;
mWindowHandle.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
@@ -247,12 +248,12 @@
mLayoutFrameRelative.offset(-surfaceOrigin.x, -surfaceOrigin.y);
}
- private void createSurface() {
+ private void createSurface(SurfaceControl.Transaction t) {
mSurface = mSurfaceControlFactory.get().setName("Letterbox - " + mType)
.setFlags(HIDDEN).setColorLayer().build();
- mSurface.setLayer(-1);
- mSurface.setColor(new float[]{0, 0, 0});
- mSurface.setColorSpaceAgnostic(true);
+ t.setLayer(mSurface, -1)
+ .setColor(mSurface, new float[]{0, 0, 0})
+ .setColorSpaceAgnostic(mSurface, true);
}
void attachInput(WindowState win) {
@@ -300,7 +301,7 @@
mSurfaceFrameRelative.set(mLayoutFrameRelative);
if (!mSurfaceFrameRelative.isEmpty()) {
if (mSurface == null) {
- createSurface();
+ createSurface(t);
}
t.setPosition(mSurface, mSurfaceFrameRelative.left, mSurfaceFrameRelative.top);
t.setWindowCrop(mSurface, mSurfaceFrameRelative.width(),
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index 062cdc5..12579e6 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -22,7 +22,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.WindowManager.TRANSIT_NONE;
import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
@@ -158,7 +158,7 @@
void startRecentsActivity(IRecentsAnimationRunner recentsAnimationRunner) {
ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "startRecentsActivity(): intent=%s", mTargetIntent);
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "RecentsAnimation#startRecentsActivity");
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RecentsAnimation#startRecentsActivity");
// TODO(multi-display) currently only support recents animation in default display.
final DisplayContent dc =
@@ -263,7 +263,7 @@
throw e;
} finally {
mService.continueWindowLayout();
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
@@ -297,7 +297,7 @@
}
mWindowManager.inSurfaceTransaction(() -> {
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER,
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"RecentsAnimation#onAnimationFinished_inSurfaceTransaction");
mService.deferWindowLayout();
try {
@@ -394,7 +394,7 @@
throw e;
} finally {
mService.continueWindowLayout();
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
});
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index bd27905..d606e5d 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -98,8 +98,10 @@
private final ArrayList<WallpaperAnimationAdapter> mPendingWallpaperAnimations =
new ArrayList<>();
private final int mDisplayId;
- private final Runnable mFailsafeRunnable = () ->
- cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "failSafeRunnable");
+ private boolean mWillFinishToHome = false;
+ private final Runnable mFailsafeRunnable = () -> cancelAnimation(
+ mWillFinishToHome ? REORDER_MOVE_TO_TOP : REORDER_MOVE_TO_ORIGINAL_POSITION,
+ "failSafeRunnable");
// The recents component app token that is shown behind the visibile tasks
private AppWindowToken mTargetAppToken;
@@ -326,6 +328,13 @@
}
}
}
+
+ @Override
+ public void setWillFinishToHome(boolean willFinishToHome) {
+ synchronized (mService.getWindowManagerLock()) {
+ mWillFinishToHome = willFinishToHome;
+ }
+ }
};
/**
@@ -494,7 +503,8 @@
}
final SparseIntArray reasons = new SparseIntArray();
reasons.put(WINDOWING_MODE_FULLSCREEN, APP_TRANSITION_RECENTS_ANIM);
- mService.mAtmInternal.notifyAppTransitionStarting(reasons, SystemClock.uptimeMillis());
+ mService.mAtmInternal
+ .notifyAppTransitionStarting(reasons, SystemClock.elapsedRealtimeNanos());
}
private RemoteAnimationTarget[] createAppAnimations() {
diff --git a/services/core/java/com/android/server/wm/RootActivityContainer.java b/services/core/java/com/android/server/wm/RootActivityContainer.java
index d78d517..60833c3 100644
--- a/services/core/java/com/android/server/wm/RootActivityContainer.java
+++ b/services/core/java/com/android/server/wm/RootActivityContainer.java
@@ -983,7 +983,7 @@
stack.resize(task.getRequestedOverrideBounds(), null /* tempTaskBounds */,
null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS, !DEFER_RESUME);
- if (task.mActivities.size() == 1) {
+ if (task.getChildCount() == 1) {
// Defer resume until below, and do not schedule PiP changes until we animate below
task.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE, DEFER_RESUME,
false /* schedulePictureInPictureModeChange */, reason);
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 6f10d3d..b6a05d1 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -813,18 +813,18 @@
final int defaultDw = defaultInfo.logicalWidth;
final int defaultDh = defaultInfo.logicalHeight;
if (mWmService.mWatermark != null) {
- mWmService.mWatermark.positionSurface(defaultDw, defaultDh);
+ mWmService.mWatermark.positionSurface(defaultDw, defaultDh, mDisplayTransaction);
}
if (mWmService.mStrictModeFlash != null) {
- mWmService.mStrictModeFlash.positionSurface(defaultDw, defaultDh);
+ mWmService.mStrictModeFlash.positionSurface(defaultDw, defaultDh, mDisplayTransaction);
}
if (mWmService.mCircularDisplayMask != null) {
mWmService.mCircularDisplayMask.positionSurface(defaultDw, defaultDh,
- mWmService.getDefaultDisplayRotation());
+ mWmService.getDefaultDisplayRotation(), mDisplayTransaction);
}
if (mWmService.mEmulatorDisplayOverlay != null) {
mWmService.mEmulatorDisplayOverlay.positionSurface(defaultDw, defaultDh,
- mWmService.getDefaultDisplayRotation());
+ mWmService.getDefaultDisplayRotation(), mDisplayTransaction);
}
final int count = mChildren.size();
diff --git a/services/core/java/com/android/server/wm/SeamlessRotator.java b/services/core/java/com/android/server/wm/SeamlessRotator.java
index bcd90a1..ba31818 100644
--- a/services/core/java/com/android/server/wm/SeamlessRotator.java
+++ b/services/core/java/com/android/server/wm/SeamlessRotator.java
@@ -20,9 +20,10 @@
import static android.view.Surface.ROTATION_90;
import android.graphics.Matrix;
+import android.os.IBinder;
import android.view.DisplayInfo;
-import android.view.Surface;
import android.view.Surface.Rotation;
+import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import com.android.server.wm.utils.CoordinateTransforms;
@@ -35,7 +36,7 @@
*
* Works by transforming the {@link WindowState} back into the old display rotation.
*
- * Uses {@link android.view.SurfaceControl#deferTransactionUntil(Surface, long)} instead of
+ * Uses {@link Transaction#deferTransactionUntil(SurfaceControl, IBinder, long)} instead of
* latching on the buffer size to allow for seamless 180 degree rotations.
*/
public class SeamlessRotator {
diff --git a/services/core/java/com/android/server/wm/StrictModeFlash.java b/services/core/java/com/android/server/wm/StrictModeFlash.java
index 9e5d9ca..f537005 100644
--- a/services/core/java/com/android/server/wm/StrictModeFlash.java
+++ b/services/core/java/com/android/server/wm/StrictModeFlash.java
@@ -39,7 +39,8 @@
private boolean mDrawNeeded;
private final int mThickness = 20;
- StrictModeFlash(Supplier<Surface> surfaceFactory, DisplayContent dc) {
+ StrictModeFlash(Supplier<Surface> surfaceFactory, DisplayContent dc,
+ SurfaceControl.Transaction t) {
mSurface = surfaceFactory.get();
SurfaceControl ctrl = null;
try {
@@ -48,9 +49,11 @@
.setBufferSize(1, 1)
.setFormat(PixelFormat.TRANSLUCENT)
.build();
- ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101); // one more than Watermark? arbitrary.
- ctrl.setPosition(0, 0);
- ctrl.show();
+
+ // one more than Watermark? arbitrary.
+ t.setLayer(ctrl, WindowManagerService.TYPE_LAYER_MULTIPLIER * 101);
+ t.setPosition(ctrl, 0, 0);
+ t.show(ctrl);
mSurface.copyFrom(ctrl);
} catch (OutOfResourcesException e) {
}
@@ -103,25 +106,25 @@
// Note: caller responsible for being inside
// Surface.openTransaction() / closeTransaction()
- public void setVisibility(boolean on) {
+ public void setVisibility(boolean on, SurfaceControl.Transaction t) {
if (mSurfaceControl == null) {
return;
}
drawIfNeeded();
if (on) {
- mSurfaceControl.show();
+ t.show(mSurfaceControl);
} else {
- mSurfaceControl.hide();
+ t.hide(mSurfaceControl);
}
}
- void positionSurface(int dw, int dh) {
+ void positionSurface(int dw, int dh, SurfaceControl.Transaction t) {
if (mLastDW == dw && mLastDH == dh) {
return;
}
mLastDW = dw;
mLastDH = dh;
- mSurfaceControl.setBufferSize(dw, dh);
+ t.setBufferSize(mSurfaceControl, dw, dh);
mDrawNeeded = true;
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a181c18..2657826 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -58,7 +58,7 @@
import java.io.PrintWriter;
import java.util.function.Consumer;
-class Task extends WindowContainer<AppWindowToken> implements ConfigurationContainerListener{
+class Task extends WindowContainer<ActivityRecord> implements ConfigurationContainerListener{
static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_WM;
// TODO: Track parent marks like this in WindowContainer.
@@ -120,9 +120,12 @@
// TODO: Remove after unification.
@Override
- public void onConfigurationChanged(Configuration newParentConfig) {
- // Only forward configuration changes in cases where children won't get it from TaskRecord.
- onConfigurationChanged(newParentConfig, mTaskRecord == null /*forwardToChildren*/);
+ public void onConfigurationChanged(Configuration newParentConfig, boolean forwardToChildren) {
+ // Forward configuration changes in cases
+ // - children won't get it from TaskRecord
+ // - it's a pinned task
+ forwardToChildren &= (mTaskRecord == null) || inPinnedWindowingMode();
+ super.onConfigurationChanged(newParentConfig, forwardToChildren);
}
Task(int taskId, TaskStack stack, int userId, WindowManagerService service, int resizeMode,
@@ -170,14 +173,14 @@
}
@Override
- void addChild(AppWindowToken wtoken, int position) {
+ void addChild(ActivityRecord child, int position) {
position = getAdjustedAddPosition(position);
- super.addChild(wtoken, position);
+ super.addChild(child, position);
mDeferRemoval = false;
}
@Override
- void positionChildAt(int position, AppWindowToken child, boolean includingParents) {
+ void positionChildAt(int position, ActivityRecord child, boolean includingParents) {
position = getAdjustedAddPosition(position);
super.positionChildAt(position, child, includingParents);
mDeferRemoval = false;
@@ -279,13 +282,13 @@
}
@Override
- void removeChild(AppWindowToken token) {
- if (!mChildren.contains(token)) {
+ void removeChild(ActivityRecord child) {
+ if (!mChildren.contains(child)) {
Slog.e(TAG, "removeChild: token=" + this + " not found.");
return;
}
- super.removeChild(token);
+ super.removeChild(child);
if (mChildren.isEmpty()) {
EventLog.writeEvent(WM_TASK_REMOVED, mTaskId, "removeAppToken: last token");
@@ -674,18 +677,18 @@
return null;
}
- void positionChildAtTop(AppWindowToken aToken) {
- positionChildAt(aToken, POSITION_TOP);
+ void positionChildAtTop(ActivityRecord child) {
+ positionChildAt(child, POSITION_TOP);
}
- void positionChildAt(AppWindowToken aToken, int position) {
- if (aToken == null) {
+ void positionChildAt(ActivityRecord child, int position) {
+ if (child == null) {
Slog.w(TAG_WM,
"Attempted to position of non-existing app");
return;
}
- positionChildAt(position, aToken, false /* includeParents */);
+ positionChildAt(position, child, false /* includeParents */);
}
void forceWindowsScaleable(boolean force) {
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index b680fa4..478b1b5 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -256,7 +256,7 @@
final InputChannel[] channels = InputChannel.openInputChannelPair(TAG);
mServerChannel = channels[0];
mClientChannel = channels[1];
- mService.mInputManager.registerInputChannel(mServerChannel, null);
+ mService.mInputManager.registerInputChannel(mServerChannel);
mInputEventReceiver = new WindowPositionerEventReceiver(
mClientChannel, mService.mAnimationHandler.getLooper(),
@@ -267,8 +267,7 @@
mDragApplicationHandle.dispatchingTimeoutNanos =
WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
- mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null,
- display.getDisplayId());
+ mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, display.getDisplayId());
mDragWindowHandle.name = TAG;
mDragWindowHandle.token = mServerChannel.getToken();
mDragWindowHandle.layer = mService.getDragLayerLocked();
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index 75333c7..299b32c 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -48,7 +48,7 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -447,7 +447,7 @@
}
void cleanUpResourcesForDestroy() {
- if (!mActivities.isEmpty()) {
+ if (hasChild()) {
return;
}
@@ -553,7 +553,7 @@
// This method assumes that the task is already placed in the right stack.
// we do not mess with that decision and we only do the resize!
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeTask_" + mTaskId);
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "resizeTask_" + mTaskId);
boolean updatedConfig = false;
mTmpConfig.setTo(getResolvedOverrideConfiguration());
@@ -587,7 +587,7 @@
saveLaunchingStateIfNeeded();
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return kept;
} finally {
mAtmService.continueWindowLayout();
@@ -1095,7 +1095,7 @@
// There are no non-finishing activities in the task.
return null;
}
- return mActivities.get(rootActivityIndex);
+ return getChildAt(rootActivityIndex);
}
ActivityRecord getTopActivity() {
@@ -1103,8 +1103,8 @@
}
ActivityRecord getTopActivity(boolean includeOverlays) {
- for (int i = mActivities.size() - 1; i >= 0; --i) {
- final ActivityRecord r = mActivities.get(i);
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ActivityRecord r = getChildAt(i);
if (r.finishing || (!includeOverlays && r.mTaskOverlay)) {
continue;
}
@@ -1115,8 +1115,8 @@
ActivityRecord topRunningActivityLocked() {
if (mStack != null) {
- for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = mActivities.get(activityNdx);
+ for (int activityNdx = getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ ActivityRecord r = getChildAt(activityNdx);
if (!r.finishing && r.okToShowLocked()) {
return r;
}
@@ -1126,8 +1126,8 @@
}
boolean isVisible() {
- for (int i = mActivities.size() - 1; i >= 0; --i) {
- final ActivityRecord r = mActivities.get(i);
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ActivityRecord r = getChildAt(i);
if (r.visible) {
return true;
}
@@ -1139,8 +1139,8 @@
* Return true if any activities in this task belongs to input uid.
*/
boolean containsAppUid(int uid) {
- for (int i = mActivities.size() - 1; i >= 0; --i) {
- final ActivityRecord r = mActivities.get(i);
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ActivityRecord r = getChildAt(i);
if (r.getUid() == uid) {
return true;
}
@@ -1150,8 +1150,8 @@
void getAllRunningVisibleActivitiesLocked(ArrayList<ActivityRecord> outActivities) {
if (mStack != null) {
- for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = mActivities.get(activityNdx);
+ for (int activityNdx = getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ ActivityRecord r = getChildAt(activityNdx);
if (!r.finishing && r.okToShowLocked() && r.visibleIgnoringKeyguard) {
outActivities.add(r);
}
@@ -1161,8 +1161,8 @@
ActivityRecord topRunningActivityWithStartingWindowLocked() {
if (mStack != null) {
- for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = mActivities.get(activityNdx);
+ for (int activityNdx = getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ ActivityRecord r = getChildAt(activityNdx);
if (r.mStartingWindowState != STARTING_WINDOW_SHOWN
|| r.finishing || !r.okToShowLocked()) {
continue;
@@ -1179,8 +1179,8 @@
*/
void getNumRunningActivities(TaskActivitiesReport reportOut) {
reportOut.reset();
- for (int i = mActivities.size() - 1; i >= 0; --i) {
- final ActivityRecord r = mActivities.get(i);
+ for (int i = getChildCount() - 1; i >= 0; --i) {
+ final ActivityRecord r = getChildAt(i);
if (r.finishing) {
continue;
}
@@ -1227,17 +1227,17 @@
}
void addActivityToTop(ActivityRecord r) {
- addActivityAtIndex(mActivities.size(), r);
+ addActivityAtIndex(getChildCount(), r);
}
@Override
/*@WindowConfiguration.ActivityType*/
public int getActivityType() {
final int applicationType = super.getActivityType();
- if (applicationType != ACTIVITY_TYPE_UNDEFINED || mActivities.isEmpty()) {
+ if (applicationType != ACTIVITY_TYPE_UNDEFINED || !hasChild()) {
return applicationType;
}
- return mActivities.get(0).getActivityType();
+ return getChildAt(0).getActivityType();
}
/**
@@ -1259,7 +1259,7 @@
numFullscreen++;
}
// Only set this based on the first activity
- if (mActivities.isEmpty()) {
+ if (!hasChild()) {
if (r.getActivityType() == ACTIVITY_TYPE_UNDEFINED) {
// Normally non-standard activity type for the activity record will be set when the
// object is created, however we delay setting the standard application type until
@@ -1279,10 +1279,10 @@
r.setActivityType(getActivityType());
}
- final int size = mActivities.size();
+ final int size = getChildCount();
if (index == size && size > 0) {
- final ActivityRecord top = mActivities.get(size - 1);
+ final ActivityRecord top = getChildAt(size - 1);
if (top.mTaskOverlay) {
// Place below the task overlay activity since the overlay activity should always
// be on top.
@@ -1341,7 +1341,7 @@
mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged();
}
- if (mActivities.isEmpty()) {
+ if (!hasChild()) {
return !mReuseTask;
}
updateEffectiveIntent();
@@ -1355,8 +1355,8 @@
*/
boolean onlyHasTaskOverlayActivities(boolean excludeFinishing) {
int count = 0;
- for (int i = mActivities.size() - 1; i >= 0; i--) {
- final ActivityRecord r = mActivities.get(i);
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ final ActivityRecord r = getChildAt(i);
if (excludeFinishing && r.finishing) {
continue;
}
@@ -1372,7 +1372,7 @@
// We will automatically remove the task either if it has explicitly asked for
// this, or it is empty and has never contained an activity that got shown to
// the user.
- return autoRemoveRecents || (mActivities.isEmpty() && !hasBeenVisible);
+ return autoRemoveRecents || (!hasChild() && !hasBeenVisible);
}
/**
@@ -1380,9 +1380,9 @@
* task starting at a specified index.
*/
final void performClearTaskAtIndexLocked(int activityNdx, String reason) {
- int numActivities = mActivities.size();
+ int numActivities = getChildCount();
for ( ; activityNdx < numActivities; ++activityNdx) {
- final ActivityRecord r = mActivities.get(activityNdx);
+ final ActivityRecord r = getChildAt(activityNdx);
if (r.finishing) {
continue;
}
@@ -1429,9 +1429,9 @@
* or null if none was found.
*/
final ActivityRecord performClearTaskLocked(ActivityRecord newR, int launchFlags) {
- int numActivities = mActivities.size();
+ int numActivities = getChildCount();
for (int activityNdx = numActivities - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = mActivities.get(activityNdx);
+ ActivityRecord r = getChildAt(activityNdx);
if (r.finishing) {
continue;
}
@@ -1440,7 +1440,7 @@
final ActivityRecord ret = r;
for (++activityNdx; activityNdx < numActivities; ++activityNdx) {
- r = mActivities.get(activityNdx);
+ r = getChildAt(activityNdx);
if (r.finishing) {
continue;
}
@@ -1591,8 +1591,8 @@
*/
final ActivityRecord findActivityInHistoryLocked(ActivityRecord r) {
final ComponentName realActivity = r.mActivityComponent;
- for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord candidate = mActivities.get(activityNdx);
+ for (int activityNdx = getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ ActivityRecord candidate = getChildAt(activityNdx);
if (candidate.finishing) {
continue;
}
@@ -1609,12 +1609,12 @@
// Traverse upwards looking for any break between main task activities and
// utility activities.
int activityNdx;
- final int numActivities = mActivities.size();
+ final int numActivities = getChildCount();
final boolean relinquish = numActivities != 0 &&
- (mActivities.get(0).info.flags & FLAG_RELINQUISH_TASK_IDENTITY) != 0;
+ (getChildAt(0).info.flags & FLAG_RELINQUISH_TASK_IDENTITY) != 0;
for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities;
++activityNdx) {
- final ActivityRecord r = mActivities.get(activityNdx);
+ final ActivityRecord r = getChildAt(activityNdx);
if (relinquish && (r.info.flags & FLAG_RELINQUISH_TASK_IDENTITY) == 0) {
// This will be the top activity for determining taskDescription. Pre-inc to
// overcome initial decrement below.
@@ -1642,7 +1642,7 @@
boolean navigationBarContrastWhenTransparent = false;
boolean topActivity = true;
for (--activityNdx; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = mActivities.get(activityNdx);
+ final ActivityRecord r = getChildAt(activityNdx);
if (r.mTaskOverlay) {
continue;
}
@@ -1697,9 +1697,9 @@
*/
int findRootIndex(boolean effectiveRoot) {
int effectiveNdx = -1;
- final int topActivityNdx = mActivities.size() - 1;
+ final int topActivityNdx = getChildCount() - 1;
for (int activityNdx = 0; activityNdx <= topActivityNdx; ++activityNdx) {
- final ActivityRecord r = mActivities.get(activityNdx);
+ final ActivityRecord r = getChildAt(activityNdx);
if (r.finishing) {
continue;
}
@@ -1720,7 +1720,7 @@
// But we still want to update the intent, so let's use the bottom activity.
effectiveRootIndex = 0;
}
- final ActivityRecord r = mActivities.get(effectiveRootIndex);
+ final ActivityRecord r = getChildAt(effectiveRootIndex);
setIntent(r);
// Update the task description when the activities change
@@ -2289,8 +2289,8 @@
}
void addStartingWindowsForVisibleActivities(boolean taskSwitch) {
- for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord r = mActivities.get(activityNdx);
+ for (int activityNdx = getChildCount() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = getChildAt(activityNdx);
if (r.visible) {
r.showStartingWindow(null /* prev */, false /* newTask */, taskSwitch);
}
@@ -2462,7 +2462,7 @@
sb.append(" StackId=");
sb.append(getStackId());
sb.append(" sz=");
- sb.append(mActivities.size());
+ sb.append(getChildCount());
sb.append('}');
return sb.toString();
}
@@ -2495,8 +2495,8 @@
final long token = proto.start(fieldId);
super.writeToProto(proto, CONFIGURATION_CONTAINER, logLevel);
proto.write(ID, mTaskId);
- for (int i = mActivities.size() - 1; i >= 0; i--) {
- ActivityRecord activity = mActivities.get(i);
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ ActivityRecord activity = getChildAt(i);
activity.writeToProto(proto, ACTIVITIES);
}
proto.write(STACK_ID, mStack.mStackId);
@@ -2607,10 +2607,9 @@
out.endTag(null, TAG_INTENT);
}
- final ArrayList<ActivityRecord> activities = mActivities;
- final int numActivities = activities.size();
+ final int numActivities = getChildCount();
for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
- final ActivityRecord r = activities.get(activityNdx);
+ final ActivityRecord r = getChildAt(activityNdx);
if (r.info.persistableMode == ActivityInfo.PERSIST_ROOT_ONLY || !r.isPersistable() ||
((r.intent.getFlags() & FLAG_ACTIVITY_NEW_DOCUMENT
| FLAG_ACTIVITY_RETAIN_IN_RECENTS) == FLAG_ACTIVITY_NEW_DOCUMENT) &&
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index d070850..172ebce 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -134,6 +134,7 @@
private final int mStatusBarColor;
@VisibleForTesting final SystemBarBackgroundPainter mSystemBarBackgroundPainter;
private final int mOrientationOnCreation;
+ private final SurfaceControl.Transaction mTransaction;
static TaskSnapshotSurface create(WindowManagerService service, AppWindowToken token,
TaskSnapshot snapshot) {
@@ -252,6 +253,7 @@
windowPrivateFlags, sysUiVis, taskDescription, 1f);
mStatusBarColor = taskDescription.getStatusBarColor();
mOrientationOnCreation = currentOrientation;
+ mTransaction = mService.mTransactionFactory.get();
}
@Override
@@ -336,27 +338,23 @@
surface.copyFrom(mChildSurfaceControl);
final Rect frame;
- SurfaceControl.openTransaction();
- try {
- // We can just show the surface here as it will still be hidden as the parent is
- // still hidden.
- mChildSurfaceControl.show();
- if (aspectRatioMismatch) {
- // Clip off ugly navigation bar.
- final Rect crop = calculateSnapshotCrop();
- frame = calculateSnapshotFrame(crop);
- mChildSurfaceControl.setWindowCrop(crop);
- mChildSurfaceControl.setPosition(frame.left, frame.top);
- } else {
- frame = null;
- }
-
- // Scale the mismatch dimensions to fill the task bounds
- final float scale = 1 / mSnapshot.getScale();
- mChildSurfaceControl.setMatrix(scale, 0, 0, scale);
- } finally {
- SurfaceControl.closeTransaction();
+ // We can just show the surface here as it will still be hidden as the parent is
+ // still hidden.
+ mTransaction.show(mChildSurfaceControl);
+ if (aspectRatioMismatch) {
+ // Clip off ugly navigation bar.
+ final Rect crop = calculateSnapshotCrop();
+ frame = calculateSnapshotFrame(crop);
+ mTransaction.setWindowCrop(mChildSurfaceControl, crop);
+ mTransaction.setPosition(mChildSurfaceControl, frame.left, frame.top);
+ } else {
+ frame = null;
}
+
+ // Scale the mismatch dimensions to fill the task bounds
+ final float scale = 1 / mSnapshot.getScale();
+ mTransaction.setMatrix(mChildSurfaceControl, scale, 0, 0, scale);
+ mTransaction.apply();
surface.attachAndQueueBufferWithColorSpace(buffer, mSnapshot.getColorSpace());
surface.release();
diff --git a/services/core/java/com/android/server/wm/Watermark.java b/services/core/java/com/android/server/wm/Watermark.java
index 729cfc0..725aaa48 100644
--- a/services/core/java/com/android/server/wm/Watermark.java
+++ b/services/core/java/com/android/server/wm/Watermark.java
@@ -55,7 +55,7 @@
private boolean mDrawNeeded;
Watermark(Supplier<Surface> surfaceFactory, DisplayContent dc, DisplayMetrics dm,
- String[] tokens) {
+ String[] tokens, SurfaceControl.Transaction t) {
if (false) {
Log.i(TAG_WM, "*********************** WATERMARK");
for (int i=0; i<tokens.length; i++) {
@@ -121,21 +121,21 @@
.setBufferSize(1, 1)
.setFormat(PixelFormat.TRANSLUCENT)
.build();
- ctrl.setLayerStack(mDisplay.getLayerStack());
- ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER*100);
- ctrl.setPosition(0, 0);
- ctrl.show();
+ t.setLayerStack(ctrl, mDisplay.getLayerStack())
+ .setLayer(ctrl, WindowManagerService.TYPE_LAYER_MULTIPLIER * 100)
+ .setPosition(ctrl, 0, 0)
+ .show(ctrl);
mSurface.copyFrom(ctrl);
} catch (OutOfResourcesException e) {
}
mSurfaceControl = ctrl;
}
- void positionSurface(int dw, int dh) {
+ void positionSurface(int dw, int dh, SurfaceControl.Transaction t) {
if (mLastDW != dw || mLastDH != dh) {
mLastDW = dw;
mLastDH = dh;
- mSurfaceControl.setBufferSize(dw, dh);
+ t.setBufferSize(mSurfaceControl, dw, dh);
mDrawNeeded = true;
}
}
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index f437b28..3a1d6e0 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -161,7 +161,8 @@
dc.checkAppWindowsReadyToShow();
orAnimating(dc.getDockedDividerController().animate(mCurrentTime));
if (accessibilityController != null) {
- accessibilityController.drawMagnifiedRegionBorderIfNeededLocked(displayId);
+ accessibilityController.drawMagnifiedRegionBorderIfNeededLocked(displayId,
+ mTransaction);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowHashMap.java b/services/core/java/com/android/server/wm/WindowHashMap.java
deleted file mode 100644
index 49bba41..0000000
--- a/services/core/java/com/android/server/wm/WindowHashMap.java
+++ /dev/null
@@ -1,28 +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 android.os.IBinder;
-
-import java.util.HashMap;
-
-/**
- * Subclass of HashMap such that we can instruct the compiler to boost our thread priority when
- * locking this class. See makefile.
- */
-class WindowHashMap extends HashMap<IBinder, WindowState> {
-}
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index f4b7672..0cb4826 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -514,6 +514,13 @@
public abstract void showImePostLayout(IBinder imeTargetWindowToken);
/**
+ * Hide IME using imeTargetWindow when requested.
+ *
+ * @param displayId on which IME is shown
+ */
+ public abstract void hideIme(int displayId);
+
+ /**
* Tell window manager about a package that should not be running with high refresh rate
* setting until removeNonHighRefreshRatePackage is called for the same package.
*
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0f4d0a8..c485280 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -236,6 +236,7 @@
import android.view.SurfaceSession;
import android.view.View;
import android.view.WindowContentFrameStats;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManager.RemoveContentMode;
@@ -518,7 +519,10 @@
final ArraySet<Session> mSessions = new ArraySet<>();
/** Mapping from an IWindow IBinder to the server's Window object. */
- final WindowHashMap mWindowMap = new WindowHashMap();
+ final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();
+
+ /** Mapping from an InputWindowHandle token to the server's Window object. */
+ final HashMap<IBinder, WindowState> mInputToWindowMap = new HashMap<>();
/** Global service lock used by the package the owns this service. */
final WindowManagerGlobalLock mGlobalLock;
@@ -1269,14 +1273,7 @@
// Add ourself to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
-
- openSurfaceTransaction();
- try {
- createWatermarkInTransaction();
- } finally {
- closeSurfaceTransaction("createWatermarkInTransaction");
- }
-
+ createWatermark();
showEmulatorDisplayOverlayIfNeeded();
}
@@ -1585,7 +1582,6 @@
win.attach();
mWindowMap.put(client.asBinder(), win);
-
win.initAppOpsState();
final boolean suspended = mPmInternal.isPackageSuspended(win.getOwningPackage(),
@@ -3435,60 +3431,45 @@
public void showCircularMask(boolean visible) {
synchronized (mGlobalLock) {
+ if (visible) {
+ // TODO(multi-display): support multiple displays
+ if (mCircularDisplayMask == null) {
+ int screenOffset = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_windowOutsetBottom);
+ int maskThickness = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.circular_display_mask_thickness);
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
- ">>> OPEN TRANSACTION showCircularMask(visible=" + visible + ")");
- openSurfaceTransaction();
- try {
- if (visible) {
- // TODO(multi-display): support multiple displays
- if (mCircularDisplayMask == null) {
- int screenOffset = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_windowOutsetBottom);
- int maskThickness = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.circular_display_mask_thickness);
- mCircularDisplayMask = new CircularDisplayMask(mSurfaceFactory,
- getDefaultDisplayContentLocked(),
- mPolicy.getWindowLayerFromTypeLw(
- WindowManager.LayoutParams.TYPE_POINTER)
- * TYPE_LAYER_MULTIPLIER + 10, screenOffset, maskThickness);
+ if (SHOW_LIGHT_TRANSACTIONS) {
+ Slog.i(TAG_WM,
+ ">>> showCircularMask(visible=" + visible + ")");
}
- mCircularDisplayMask.setVisibility(true);
- } else if (mCircularDisplayMask != null) {
- mCircularDisplayMask.setVisibility(false);
- mCircularDisplayMask = null;
+ mCircularDisplayMask = new CircularDisplayMask(mSurfaceFactory,
+ getDefaultDisplayContentLocked(), mPolicy.getWindowLayerFromTypeLw(
+ WindowManager.LayoutParams.TYPE_POINTER) * TYPE_LAYER_MULTIPLIER
+ + 10, screenOffset, maskThickness, mTransaction);
}
- } finally {
- closeSurfaceTransaction("showCircularMask");
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
- "<<< CLOSE TRANSACTION showCircularMask(visible=" + visible + ")");
+ mCircularDisplayMask.setVisibility(true, mTransaction);
+ } else if (mCircularDisplayMask != null) {
+ mCircularDisplayMask.setVisibility(false, mTransaction);
+ mCircularDisplayMask = null;
}
+ mTransaction.apply();
}
}
public void showEmulatorDisplayOverlay() {
synchronized (mGlobalLock) {
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
- ">>> OPEN TRANSACTION showEmulatorDisplayOverlay");
- openSurfaceTransaction();
- try {
- if (mEmulatorDisplayOverlay == null) {
- mEmulatorDisplayOverlay = new EmulatorDisplayOverlay(
- mSurfaceFactory,
- mContext,
- getDefaultDisplayContentLocked(),
- mPolicy.getWindowLayerFromTypeLw(
- WindowManager.LayoutParams.TYPE_POINTER)
- * TYPE_LAYER_MULTIPLIER + 10);
- }
- mEmulatorDisplayOverlay.setVisibility(true);
- } finally {
- closeSurfaceTransaction("showEmulatorDisplayOverlay");
- if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM,
- "<<< CLOSE TRANSACTION showEmulatorDisplayOverlay");
+ if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, ">>> showEmulatorDisplayOverlay");
+ if (mEmulatorDisplayOverlay == null) {
+ mEmulatorDisplayOverlay = new EmulatorDisplayOverlay(mSurfaceFactory, mContext,
+ getDefaultDisplayContentLocked(),
+ mPolicy.getWindowLayerFromTypeLw(WindowManager.LayoutParams.TYPE_POINTER)
+ * TYPE_LAYER_MULTIPLIER + 10, mTransaction);
}
+ mEmulatorDisplayOverlay.setVisibility(true, mTransaction);
+ mTransaction.apply();
}
}
@@ -3517,23 +3498,16 @@
return;
}
- if (SHOW_VERBOSE_TRANSACTIONS) Slog.i(TAG_WM,
- ">>> OPEN TRANSACTION showStrictModeViolation");
+ if (SHOW_VERBOSE_TRANSACTIONS) Slog.i(TAG_WM, ">>> showStrictModeViolation");
// TODO: Modify this to use the surface trace once it is not going crazy.
// b/31532461
- SurfaceControl.openTransaction();
- try {
- // TODO(multi-display): support multiple displays
- if (mStrictModeFlash == null) {
- mStrictModeFlash = new StrictModeFlash(mSurfaceFactory,
- getDefaultDisplayContentLocked());
- }
- mStrictModeFlash.setVisibility(on);
- } finally {
- SurfaceControl.closeTransaction();
- if (SHOW_VERBOSE_TRANSACTIONS) Slog.i(TAG_WM,
- "<<< CLOSE TRANSACTION showStrictModeViolation");
+ // TODO(multi-display): support multiple displays
+ if (mStrictModeFlash == null) {
+ mStrictModeFlash = new StrictModeFlash(mSurfaceFactory,
+ getDefaultDisplayContentLocked(), mTransaction);
}
+ mStrictModeFlash.setVisibility(on, mTransaction);
+ mTransaction.apply();
}
}
@@ -5520,7 +5494,7 @@
return val;
}
- void createWatermarkInTransaction() {
+ void createWatermark() {
if (mWatermark != null) {
return;
}
@@ -5538,8 +5512,8 @@
// TODO(multi-display): Show watermarks on secondary displays.
final DisplayContent displayContent = getDefaultDisplayContentLocked();
mWatermark = new Watermark(mSurfaceFactory, displayContent,
- displayContent.mRealDisplayMetrics,
- toks);
+ displayContent.mRealDisplayMetrics, toks, mTransaction);
+ mTransaction.apply();
}
}
} catch (FileNotFoundException e) {
@@ -7339,6 +7313,16 @@
}
@Override
+ public void hideIme(int displayId) {
+ synchronized (mGlobalLock) {
+ final DisplayContent dc = mRoot.getDisplayContent(displayId);
+ if (dc != null && dc.mInputMethodTarget != null) {
+ dc.mInputMethodTarget.hideInsets(WindowInsets.Type.ime(), true /* fromIme */);
+ }
+ }
+ }
+
+ @Override
public boolean isUidAllowedOnDisplay(int displayId, int uid) {
if (displayId == Display.DEFAULT_DISPLAY) {
return true;
@@ -7621,7 +7605,7 @@
}
private void onPointerDownOutsideFocusLocked(IBinder touchedToken) {
- final WindowState touchedWindow = windowForClientLocked(null, touchedToken, false);
+ final WindowState touchedWindow = mInputToWindowMap.get(touchedToken);
if (touchedWindow == null || !touchedWindow.canReceiveKeys()) {
return;
}
@@ -7694,9 +7678,9 @@
clientChannel.transferTo(outInputChannel);
clientChannel.dispose();
- mInputManager.registerInputChannel(inputChannel, null /* generate new token */);
+ mInputManager.registerInputChannel(inputChannel);
- InputWindowHandle h = new InputWindowHandle(null, null, displayId);
+ InputWindowHandle h = new InputWindowHandle(null, displayId);
h.token = inputChannel.getToken();
h.name = name;
h.layoutParamsFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 56e08b2..7ff9b70 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -820,8 +820,7 @@
mLastRequestedHeight = 0;
mLayer = 0;
mInputWindowHandle = new InputWindowHandle(
- mAppToken != null ? mAppToken.mInputApplicationHandle : null, c,
- getDisplayId());
+ mAppToken != null ? mAppToken.mInputApplicationHandle : null, getDisplayId());
}
void attach() {
@@ -2191,7 +2190,9 @@
InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
mInputChannel = inputChannels[0];
mClientChannel = inputChannels[1];
- mInputWindowHandle.token = mClient.asBinder();
+ mWmService.mInputManager.registerInputChannel(mInputChannel);
+ mClientChannel.setToken(mInputChannel.getToken());
+ mInputWindowHandle.token = mInputChannel.getToken();
if (outInputChannel != null) {
mClientChannel.transferTo(outInputChannel);
mClientChannel.dispose();
@@ -2202,7 +2203,7 @@
// Create dummy event receiver that simply reports all events as handled.
mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
}
- mWmService.mInputManager.registerInputChannel(mInputChannel, mClient.asBinder());
+ mWmService.mInputToWindowMap.put(mInputWindowHandle.token, this);
}
void disposeInputChannel() {
@@ -2223,6 +2224,7 @@
mClientChannel = null;
}
mWmService.mKeyInterceptionInfoForToken.remove(mInputWindowHandle.token);
+ mWmService.mInputToWindowMap.remove(mInputWindowHandle.token);
mInputWindowHandle.token = null;
}
@@ -3375,6 +3377,15 @@
}
}
+ @Override
+ public void hideInsets(@InsetType int types, boolean fromIme) {
+ try {
+ mClient.hideInsets(types, fromIme);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to deliver showInsets", e);
+ }
+ }
+
Rect getBackdropFrame(Rect frame) {
// When the task is docked, we send fullscreen sized backDropFrame as soon as resizing
// start even if we haven't received the relayout window, so that the client requests
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 3dcf6ec..eac372f 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -506,9 +506,8 @@
flags |= SurfaceControl.OPAQUE;
}
- mSurfaceController = new WindowSurfaceController(mSession.mSurfaceSession,
- attrs.getTitle().toString(), width, height, format, flags, this,
- windowType, ownerUid);
+ mSurfaceController = new WindowSurfaceController(attrs.getTitle().toString(), width,
+ height, format, flags, this, windowType, ownerUid);
mSurfaceController.setColorSpaceAgnostic((attrs.privateFlags
& WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC) != 0);
@@ -1349,13 +1348,16 @@
// frozen, there is no reason to animate and it can cause strange
// artifacts when we unfreeze the display if some different animation
// is running.
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#applyAnimationLocked");
if (mWin.mToken.okToAnimate()) {
- int anim = mWin.getDisplayContent().getDisplayPolicy().selectAnimationLw(mWin, transit);
+ int anim = mWin.getDisplayContent().getDisplayPolicy().selectAnimation(mWin, transit);
int attr = -1;
Animation a = null;
- if (anim != 0) {
- a = anim != -1 ? AnimationUtils.loadAnimation(mContext, anim) : null;
+ if (anim != DisplayPolicy.ANIMATION_STYLEABLE) {
+ if (anim != DisplayPolicy.ANIMATION_NONE) {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#loadAnimation");
+ a = AnimationUtils.loadAnimation(mContext, anim);
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ }
} else {
switch (transit) {
case WindowManagerPolicy.TRANSIT_ENTER:
@@ -1385,7 +1387,9 @@
+ " isEntrance=" + isEntrance + " Callers " + Debug.getCallers(3));
if (a != null) {
if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + this);
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#startAnimation");
mWin.startAnimation(a);
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
mAnimationIsEntrance = isEntrance;
}
} else {
@@ -1396,7 +1400,6 @@
mWin.getDisplayContent().adjustForImeIfNeeded();
}
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
return mWin.isAnimating();
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 49f27a1..0b4ea99 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -37,8 +37,8 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl;
-import android.view.SurfaceSession;
import android.view.WindowContentFrameStats;
+import android.view.WindowManager;
import com.android.server.protolog.common.ProtoLog;
@@ -85,7 +85,7 @@
private final SurfaceControl.Transaction mTmpTransaction;
- public WindowSurfaceController(SurfaceSession s, String name, int w, int h, int format,
+ WindowSurfaceController(String name, int w, int h, int format,
int flags, WindowStateAnimator animator, int windowType, int ownerUid) {
mAnimator = animator;
@@ -109,6 +109,13 @@
.setFlags(flags)
.setMetadata(METADATA_WINDOW_TYPE, windowType)
.setMetadata(METADATA_OWNER_UID, ownerUid);
+
+ if ((win.getAttrs().privateFlags &
+ WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST) != 0) {
+ b.setContainerLayer();
+ }
+
+
mSurfaceControl = b.build();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 56f6d4b..0cfdebc 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -23,7 +23,6 @@
import static com.android.server.wm.WindowManagerService.LAYOUT_REPEAT_THRESHOLD;
import android.os.Debug;
-import android.os.Trace;
import android.util.Slog;
import java.io.PrintWriter;
@@ -52,15 +51,19 @@
/** The number of layout requests when deferring. */
private int mDeferredRequests;
- private final Runnable mPerformSurfacePlacement;
-
- public WindowSurfacePlacer(WindowManagerService service) {
- mService = service;
- mPerformSurfacePlacement = () -> {
+ private class Traverser implements Runnable {
+ @Override
+ public void run() {
synchronized (mService.mGlobalLock) {
performSurfacePlacement();
}
- };
+ }
+ }
+
+ private final Traverser mPerformSurfacePlacement = new Traverser();
+
+ WindowSurfacePlacer(WindowManagerService service) {
+ mService = service;
}
/**
@@ -152,7 +155,6 @@
return;
}
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmLayout");
mInLayout = true;
boolean recoveringMemory = false;
@@ -198,8 +200,6 @@
mInLayout = false;
Slog.wtf(TAG, "Unhandled exception while laying out windows", e);
}
-
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
void debugLayoutRepeats(final String msg, int pendingLayoutChanges) {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index fb2fdab..dd2629d 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -201,8 +201,7 @@
void setDisplayViewports(JNIEnv* env, jobjectArray viewportObjArray);
- status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel,
- int32_t displayId);
+ status_t registerInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel);
status_t registerInputMonitor(JNIEnv* env, const sp<InputChannel>& inputChannel,
int32_t displayId, bool isGestureMonitor);
status_t unregisterInputChannel(JNIEnv* env, const sp<InputChannel>& inputChannel);
@@ -435,10 +434,9 @@
}
status_t NativeInputManager::registerInputChannel(JNIEnv* /* env */,
- const sp<InputChannel>& inputChannel, int32_t displayId) {
+ const sp<InputChannel>& inputChannel) {
ATRACE_CALL();
- return mInputManager->getDispatcher()->registerInputChannel(
- inputChannel, displayId);
+ return mInputManager->getDispatcher()->registerInputChannel(inputChannel);
}
status_t NativeInputManager::registerInputMonitor(JNIEnv* /* env */,
@@ -1405,7 +1403,7 @@
}
static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */,
- jlong ptr, jobject inputChannelObj, jint displayId) {
+ jlong ptr, jobject inputChannelObj) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
@@ -1415,7 +1413,7 @@
return;
}
- status_t status = im->registerInputChannel(env, inputChannel, displayId);
+ status_t status = im->registerInputChannel(env, inputChannel);
if (status) {
std::string message;
@@ -1757,7 +1755,7 @@
{ "nativeHasKeys", "(JII[I[Z)Z",
(void*) nativeHasKeys },
{ "nativeRegisterInputChannel",
- "(JLandroid/view/InputChannel;I)V",
+ "(JLandroid/view/InputChannel;)V",
(void*) nativeRegisterInputChannel },
{ "nativeRegisterInputMonitor",
"(JLandroid/view/InputChannel;IZ)V",
diff --git a/services/core/jni/com_android_server_security_VerityUtils.cpp b/services/core/jni/com_android_server_security_VerityUtils.cpp
index 9ceb770..906b568 100644
--- a/services/core/jni/com_android_server_security_VerityUtils.cpp
+++ b/services/core/jni/com_android_server_security_VerityUtils.cpp
@@ -17,6 +17,8 @@
#define LOG_TAG "VerityUtils"
#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
+#include <nativehelper/ScopedUtfChars.h>
#include "jni.h"
#include <utils/Log.h>
@@ -72,11 +74,17 @@
namespace {
int enableFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath, jbyteArray signature) {
- const char* path = env->GetStringUTFChars(filePath, nullptr);
- ::android::base::unique_fd rfd(open(path, O_RDONLY | O_CLOEXEC));
- env->ReleaseStringUTFChars(filePath, path);
+ ScopedUtfChars path(env, filePath);
+ if (path.c_str() == nullptr) {
+ return EINVAL;
+ }
+ ::android::base::unique_fd rfd(open(path.c_str(), O_RDONLY | O_CLOEXEC));
if (rfd.get() < 0) {
- return errno;
+ return errno;
+ }
+ ScopedByteArrayRO signature_bytes(env, signature);
+ if (signature_bytes.get() == nullptr) {
+ return EINVAL;
}
fsverity_enable_arg arg = {};
@@ -85,11 +93,11 @@
arg.block_size = 4096;
arg.salt_size = 0;
arg.salt_ptr = reinterpret_cast<uintptr_t>(nullptr);
- arg.sig_size = env->GetArrayLength(signature);
- arg.sig_ptr = reinterpret_cast<uintptr_t>(signature);
+ arg.sig_size = signature_bytes.size();
+ arg.sig_ptr = reinterpret_cast<uintptr_t>(signature_bytes.get());
if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, &arg) < 0) {
- return errno;
+ return errno;
}
return 0;
}
@@ -101,14 +109,16 @@
fsverity_digest *data = reinterpret_cast<fsverity_digest *>(&bytes);
data->digest_size = kSha256Bytes; // the only input/output parameter
- const char* path = env->GetStringUTFChars(filePath, nullptr);
- ::android::base::unique_fd rfd(open(path, O_RDONLY | O_CLOEXEC));
- env->ReleaseStringUTFChars(filePath, path);
+ ScopedUtfChars path(env, filePath);
+ if (path.c_str() == nullptr) {
+ return EINVAL;
+ }
+ ::android::base::unique_fd rfd(open(path.c_str(), O_RDONLY | O_CLOEXEC));
if (rfd.get() < 0) {
- return errno;
+ return errno;
}
if (ioctl(rfd.get(), FS_IOC_MEASURE_VERITY, data) < 0) {
- return errno;
+ return errno;
}
return 0;
}
diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp
index 47790ce..91c05a8 100644
--- a/services/devicepolicy/Android.bp
+++ b/services/devicepolicy/Android.bp
@@ -5,4 +5,13 @@
libs: [
"services.core",
],
+
+ plugins: [
+ "compat-changeid-annotation-processor",
+ ],
}
+
+platform_compat_config {
+ name: "services-devicepolicy-platform-compat-config",
+ src: ":services.devicepolicy",
+}
\ No newline at end of file
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 47291cb..6f643c9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -129,6 +129,7 @@
import android.app.admin.DeviceStateCache;
import android.app.admin.NetworkEvent;
import android.app.admin.PasswordMetrics;
+import android.app.admin.PasswordPolicy;
import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
import android.app.admin.StartInstallingUpdateCallback;
@@ -137,6 +138,8 @@
import android.app.backup.IBackupManager;
import android.app.trust.TrustManager;
import android.app.usage.UsageStatsManagerInternal;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -235,6 +238,7 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.IPlatformCompat;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
@@ -251,6 +255,8 @@
import com.android.internal.util.XmlUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.PasswordValidationError;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
import com.android.server.SystemServerInitThreadPool;
@@ -494,6 +500,22 @@
private static final String LOG_TAG_PROFILE_OWNER = "profile-owner";
private static final String LOG_TAG_DEVICE_OWNER = "device-owner";
+ /**
+ * For admin apps targeting R+, throw when the app sets password requirement
+ * that is not taken into account at given quality. For example when quality is set
+ * to {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, it doesn't make sense to
+ * require certain password length. If the intent is to require a password of certain length
+ * having at least NUMERIC quality, the admin should first call
+ * {@link #setPasswordQuality(ComponentName, int, boolean)} and only then call
+ * {@link #setPasswordMinimumLength(ComponentName, int, boolean)}.
+ *
+ * <p>Conversely when an admin app targeting R+ lowers password quality, those
+ * requirements that stop making sense are reset to default values.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long ADMIN_APP_PASSWORD_COMPLEXITY = 123562444L;
+
final Context mContext;
final Injector mInjector;
final IPackageManager mIPackageManager;
@@ -506,6 +528,7 @@
private final LockSettingsInternal mLockSettingsInternal;
private final DeviceAdminServiceController mDeviceAdminServiceController;
private final OverlayPackagesProvider mOverlayPackagesProvider;
+ private final IPlatformCompat mIPlatformCompat;
private final DevicePolicyCacheImpl mPolicyCache = new DevicePolicyCacheImpl();
private final DeviceStateCacheImpl mStateCache = new DeviceStateCacheImpl();
@@ -968,19 +991,8 @@
static final int DEF_PASSWORD_HISTORY_LENGTH = 0;
int passwordHistoryLength = DEF_PASSWORD_HISTORY_LENGTH;
- static final int DEF_MINIMUM_PASSWORD_LENGTH = 0;
- static final int DEF_MINIMUM_PASSWORD_LETTERS = 1;
- static final int DEF_MINIMUM_PASSWORD_UPPER_CASE = 0;
- static final int DEF_MINIMUM_PASSWORD_LOWER_CASE = 0;
- static final int DEF_MINIMUM_PASSWORD_NUMERIC = 1;
- static final int DEF_MINIMUM_PASSWORD_SYMBOLS = 1;
- static final int DEF_MINIMUM_PASSWORD_NON_LETTER = 0;
@NonNull
- PasswordMetrics minimumPasswordMetrics = new PasswordMetrics(
- PASSWORD_QUALITY_UNSPECIFIED, DEF_MINIMUM_PASSWORD_LENGTH,
- DEF_MINIMUM_PASSWORD_LETTERS, DEF_MINIMUM_PASSWORD_UPPER_CASE,
- DEF_MINIMUM_PASSWORD_LOWER_CASE, DEF_MINIMUM_PASSWORD_NUMERIC,
- DEF_MINIMUM_PASSWORD_SYMBOLS, DEF_MINIMUM_PASSWORD_NON_LETTER);
+ PasswordPolicy mPasswordPolicy = new PasswordPolicy();
static final long DEF_MAXIMUM_TIME_TO_UNLOCK = 0;
long maximumTimeToUnlock = DEF_MAXIMUM_TIME_TO_UNLOCK;
@@ -1115,36 +1127,36 @@
out.startTag(null, TAG_POLICIES);
info.writePoliciesToXml(out);
out.endTag(null, TAG_POLICIES);
- if (minimumPasswordMetrics.quality != PASSWORD_QUALITY_UNSPECIFIED) {
+ if (mPasswordPolicy.quality != PASSWORD_QUALITY_UNSPECIFIED) {
writeAttributeValueToXml(
- out, TAG_PASSWORD_QUALITY, minimumPasswordMetrics.quality);
- if (minimumPasswordMetrics.length != DEF_MINIMUM_PASSWORD_LENGTH) {
+ out, TAG_PASSWORD_QUALITY, mPasswordPolicy.quality);
+ if (mPasswordPolicy.length != PasswordPolicy.DEF_MINIMUM_LENGTH) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_LENGTH, minimumPasswordMetrics.length);
+ out, TAG_MIN_PASSWORD_LENGTH, mPasswordPolicy.length);
}
- if (minimumPasswordMetrics.upperCase != DEF_MINIMUM_PASSWORD_UPPER_CASE) {
+ if (mPasswordPolicy.upperCase != PasswordPolicy.DEF_MINIMUM_UPPER_CASE) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_UPPERCASE, minimumPasswordMetrics.upperCase);
+ out, TAG_MIN_PASSWORD_UPPERCASE, mPasswordPolicy.upperCase);
}
- if (minimumPasswordMetrics.lowerCase != DEF_MINIMUM_PASSWORD_LOWER_CASE) {
+ if (mPasswordPolicy.lowerCase != PasswordPolicy.DEF_MINIMUM_LOWER_CASE) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_LOWERCASE, minimumPasswordMetrics.lowerCase);
+ out, TAG_MIN_PASSWORD_LOWERCASE, mPasswordPolicy.lowerCase);
}
- if (minimumPasswordMetrics.letters != DEF_MINIMUM_PASSWORD_LETTERS) {
+ if (mPasswordPolicy.letters != PasswordPolicy.DEF_MINIMUM_LETTERS) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_LETTERS, minimumPasswordMetrics.letters);
+ out, TAG_MIN_PASSWORD_LETTERS, mPasswordPolicy.letters);
}
- if (minimumPasswordMetrics.numeric != DEF_MINIMUM_PASSWORD_NUMERIC) {
+ if (mPasswordPolicy.numeric != PasswordPolicy.DEF_MINIMUM_NUMERIC) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_NUMERIC, minimumPasswordMetrics.numeric);
+ out, TAG_MIN_PASSWORD_NUMERIC, mPasswordPolicy.numeric);
}
- if (minimumPasswordMetrics.symbols != DEF_MINIMUM_PASSWORD_SYMBOLS) {
+ if (mPasswordPolicy.symbols != PasswordPolicy.DEF_MINIMUM_SYMBOLS) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_SYMBOLS, minimumPasswordMetrics.symbols);
+ out, TAG_MIN_PASSWORD_SYMBOLS, mPasswordPolicy.symbols);
}
- if (minimumPasswordMetrics.nonLetter > DEF_MINIMUM_PASSWORD_NON_LETTER) {
+ if (mPasswordPolicy.nonLetter > PasswordPolicy.DEF_MINIMUM_NON_LETTER) {
writeAttributeValueToXml(
- out, TAG_MIN_PASSWORD_NONLETTER, minimumPasswordMetrics.nonLetter);
+ out, TAG_MIN_PASSWORD_NONLETTER, mPasswordPolicy.nonLetter);
}
}
if (passwordHistoryLength != DEF_PASSWORD_HISTORY_LENGTH) {
@@ -1383,31 +1395,31 @@
info.readPoliciesFromXml(parser);
}
} else if (TAG_PASSWORD_QUALITY.equals(tag)) {
- minimumPasswordMetrics.quality = Integer.parseInt(
+ mPasswordPolicy.quality = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_LENGTH.equals(tag)) {
- minimumPasswordMetrics.length = Integer.parseInt(
+ mPasswordPolicy.length = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_PASSWORD_HISTORY_LENGTH.equals(tag)) {
passwordHistoryLength = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_UPPERCASE.equals(tag)) {
- minimumPasswordMetrics.upperCase = Integer.parseInt(
+ mPasswordPolicy.upperCase = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_LOWERCASE.equals(tag)) {
- minimumPasswordMetrics.lowerCase = Integer.parseInt(
+ mPasswordPolicy.lowerCase = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_LETTERS.equals(tag)) {
- minimumPasswordMetrics.letters = Integer.parseInt(
+ mPasswordPolicy.letters = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_NUMERIC.equals(tag)) {
- minimumPasswordMetrics.numeric = Integer.parseInt(
+ mPasswordPolicy.numeric = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_SYMBOLS.equals(tag)) {
- minimumPasswordMetrics.symbols = Integer.parseInt(
+ mPasswordPolicy.symbols = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_MIN_PASSWORD_NONLETTER.equals(tag)) {
- minimumPasswordMetrics.nonLetter = Integer.parseInt(
+ mPasswordPolicy.nonLetter = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
}else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
maximumTimeToUnlock = Long.parseLong(
@@ -1668,23 +1680,23 @@
pw.decreaseIndent();
}
pw.print("passwordQuality=0x");
- pw.println(Integer.toHexString(minimumPasswordMetrics.quality));
+ pw.println(Integer.toHexString(mPasswordPolicy.quality));
pw.print("minimumPasswordLength=");
- pw.println(minimumPasswordMetrics.length);
+ pw.println(mPasswordPolicy.length);
pw.print("passwordHistoryLength=");
pw.println(passwordHistoryLength);
pw.print("minimumPasswordUpperCase=");
- pw.println(minimumPasswordMetrics.upperCase);
+ pw.println(mPasswordPolicy.upperCase);
pw.print("minimumPasswordLowerCase=");
- pw.println(minimumPasswordMetrics.lowerCase);
+ pw.println(mPasswordPolicy.lowerCase);
pw.print("minimumPasswordLetters=");
- pw.println(minimumPasswordMetrics.letters);
+ pw.println(mPasswordPolicy.letters);
pw.print("minimumPasswordNumeric=");
- pw.println(minimumPasswordMetrics.numeric);
+ pw.println(mPasswordPolicy.numeric);
pw.print("minimumPasswordSymbols=");
- pw.println(minimumPasswordMetrics.symbols);
+ pw.println(mPasswordPolicy.symbols);
pw.print("minimumPasswordNonLetter=");
- pw.println(minimumPasswordMetrics.nonLetter);
+ pw.println(mPasswordPolicy.nonLetter);
pw.print("maximumTimeToUnlock=");
pw.println(maximumTimeToUnlock);
pw.print("strongAuthUnlockTimeout=");
@@ -1984,6 +1996,11 @@
return LocalServices.getService(LockSettingsInternal.class);
}
+ IPlatformCompat getIPlatformCompat() {
+ return IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+ }
+
boolean hasUserSetupCompleted(DevicePolicyData userData) {
return userData.mUserSetupComplete;
}
@@ -2186,7 +2203,7 @@
}
void postOnSystemServerInitThreadPool(Runnable runnable) {
- SystemServerInitThreadPool.get().submit(runnable, LOG_TAG);
+ SystemServerInitThreadPool.submit(runnable, LOG_TAG);
}
public TransferOwnershipMetadataManager newTransferOwnershipMetadataManager() {
@@ -2222,6 +2239,7 @@
mUsageStatsManagerInternal = Preconditions.checkNotNull(
injector.getUsageStatsManagerInternal());
mIPackageManager = Preconditions.checkNotNull(injector.getIPackageManager());
+ mIPlatformCompat = Preconditions.checkNotNull(injector.getIPlatformCompat());
mIPermissionManager = Preconditions.checkNotNull(injector.getIPermissionManager());
mTelephonyManager = Preconditions.checkNotNull(injector.getTelephonyManager());
@@ -4135,15 +4153,15 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
final long ident = mInjector.binderClearCallingIdentity();
try {
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.quality != quality) {
- metrics.quality = quality;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.quality != quality) {
+ passwordPolicy.quality = quality;
resetInactivePasswordRequirementsIfRPlus(userId, ap);
updatePasswordValidityCheckpointLocked(userId, parent);
updatePasswordQualityCacheForUserGroup(userId);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
} finally {
mInjector.binderRestoreCallingIdentity(ident);
}
@@ -4156,23 +4174,33 @@
.write();
}
+ private boolean passwordQualityInvocationOrderCheckEnabled(String packageName, int userId) {
+ try {
+ return mIPlatformCompat.isChangeEnabledByPackageName(ADMIN_APP_PASSWORD_COMPLEXITY,
+ packageName);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Failed to get a response from PLATFORM_COMPAT_SERVICE", e);
+ }
+ return getTargetSdk(packageName, userId) > Build.VERSION_CODES.Q;
+ }
+
/**
* For admins targeting R+ reset various password constraints to default values when quality is
* set to a value that makes those constraints that have no effect.
*/
private void resetInactivePasswordRequirementsIfRPlus(int userId, ActiveAdmin admin) {
- if (getTargetSdk(admin.info.getPackageName(), userId) > Build.VERSION_CODES.Q) {
- final PasswordMetrics metrics = admin.minimumPasswordMetrics;
- if (metrics.quality < PASSWORD_QUALITY_NUMERIC) {
- metrics.length = ActiveAdmin.DEF_MINIMUM_PASSWORD_LENGTH;
+ if (passwordQualityInvocationOrderCheckEnabled(admin.info.getPackageName(), userId)) {
+ final PasswordPolicy policy = admin.mPasswordPolicy;
+ if (policy.quality < PASSWORD_QUALITY_NUMERIC) {
+ policy.length = PasswordPolicy.DEF_MINIMUM_LENGTH;
}
- if (metrics.quality < PASSWORD_QUALITY_COMPLEX) {
- metrics.letters = ActiveAdmin.DEF_MINIMUM_PASSWORD_LETTERS;
- metrics.upperCase = ActiveAdmin.DEF_MINIMUM_PASSWORD_UPPER_CASE;
- metrics.lowerCase = ActiveAdmin.DEF_MINIMUM_PASSWORD_LOWER_CASE;
- metrics.numeric = ActiveAdmin.DEF_MINIMUM_PASSWORD_NUMERIC;
- metrics.symbols = ActiveAdmin.DEF_MINIMUM_PASSWORD_SYMBOLS;
- metrics.nonLetter = ActiveAdmin.DEF_MINIMUM_PASSWORD_NON_LETTER;
+ if (policy.quality < PASSWORD_QUALITY_COMPLEX) {
+ policy.letters = PasswordPolicy.DEF_MINIMUM_LETTERS;
+ policy.upperCase = PasswordPolicy.DEF_MINIMUM_UPPER_CASE;
+ policy.lowerCase = PasswordPolicy.DEF_MINIMUM_LOWER_CASE;
+ policy.numeric = PasswordPolicy.DEF_MINIMUM_NUMERIC;
+ policy.symbols = PasswordPolicy.DEF_MINIMUM_SYMBOLS;
+ policy.nonLetter = PasswordPolicy.DEF_MINIMUM_NON_LETTER;
}
}
}
@@ -4237,7 +4265,7 @@
if (who != null) {
ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle, parent);
- return admin != null ? admin.minimumPasswordMetrics.quality : mode;
+ return admin != null ? admin.mPasswordPolicy.quality : mode;
}
// Return the strictest policy across all participating admins.
@@ -4246,8 +4274,8 @@
final int N = admins.size();
for (int i = 0; i < N; i++) {
ActiveAdmin admin = admins.get(i);
- if (mode < admin.minimumPasswordMetrics.quality) {
- mode = admin.minimumPasswordMetrics.quality;
+ if (mode < admin.mPasswordPolicy.quality) {
+ mode = admin.mPasswordPolicy.quality;
}
}
return mode;
@@ -4307,14 +4335,14 @@
synchronized (getLockObject()) {
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
ensureMinimumQuality(userId, ap, PASSWORD_QUALITY_NUMERIC, "setPasswordMinimumLength");
- if (metrics.length != length) {
- metrics.length = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.length != length) {
+ passwordPolicy.length = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_LENGTH)
@@ -4325,8 +4353,9 @@
private void ensureMinimumQuality(
int userId, ActiveAdmin admin, int minimumQuality, String operation) {
- if (admin.minimumPasswordMetrics.quality < minimumQuality
- && getTargetSdk(admin.info.getPackageName(), userId) > Build.VERSION_CODES.Q) {
+ if (admin.mPasswordPolicy.quality < minimumQuality
+ && passwordQualityInvocationOrderCheckEnabled(admin.info.getPackageName(),
+ userId)) {
throw new IllegalStateException(String.format(
"password quality should be at least %d for %s", minimumQuality, operation));
}
@@ -4335,7 +4364,7 @@
@Override
public int getPasswordMinimumLength(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.length, PASSWORD_QUALITY_NUMERIC);
+ admin -> admin.mPasswordPolicy.length, PASSWORD_QUALITY_NUMERIC);
}
@Override
@@ -4564,13 +4593,13 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
ensureMinimumQuality(
userId, ap, PASSWORD_QUALITY_COMPLEX, "setPasswordMinimumUpperCase");
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.upperCase != length) {
- metrics.upperCase = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.upperCase != length) {
+ passwordPolicy.upperCase = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_UPPER_CASE)
@@ -4582,7 +4611,7 @@
@Override
public int getPasswordMinimumUpperCase(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.upperCase, PASSWORD_QUALITY_COMPLEX);
+ admin -> admin.mPasswordPolicy.upperCase, PASSWORD_QUALITY_COMPLEX);
}
@Override
@@ -4594,13 +4623,13 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
ensureMinimumQuality(
userId, ap, PASSWORD_QUALITY_COMPLEX, "setPasswordMinimumLowerCase");
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.lowerCase != length) {
- metrics.lowerCase = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.lowerCase != length) {
+ passwordPolicy.lowerCase = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_LOWER_CASE)
@@ -4612,7 +4641,7 @@
@Override
public int getPasswordMinimumLowerCase(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.lowerCase, PASSWORD_QUALITY_COMPLEX);
+ admin -> admin.mPasswordPolicy.lowerCase, PASSWORD_QUALITY_COMPLEX);
}
@Override
@@ -4626,13 +4655,13 @@
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
ensureMinimumQuality(userId, ap, PASSWORD_QUALITY_COMPLEX, "setPasswordMinimumLetters");
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.letters != length) {
- metrics.letters = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.letters != length) {
+ passwordPolicy.letters = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_LETTERS)
@@ -4644,7 +4673,7 @@
@Override
public int getPasswordMinimumLetters(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.letters, PASSWORD_QUALITY_COMPLEX);
+ admin -> admin.mPasswordPolicy.letters, PASSWORD_QUALITY_COMPLEX);
}
@Override
@@ -4658,13 +4687,13 @@
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
ensureMinimumQuality(userId, ap, PASSWORD_QUALITY_COMPLEX, "setPasswordMinimumNumeric");
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.numeric != length) {
- metrics.numeric = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.numeric != length) {
+ passwordPolicy.numeric = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_NUMERIC)
@@ -4676,7 +4705,7 @@
@Override
public int getPasswordMinimumNumeric(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.numeric, PASSWORD_QUALITY_COMPLEX);
+ admin -> admin.mPasswordPolicy.numeric, PASSWORD_QUALITY_COMPLEX);
}
@Override
@@ -4690,13 +4719,13 @@
ActiveAdmin ap = getActiveAdminForCallerLocked(
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
ensureMinimumQuality(userId, ap, PASSWORD_QUALITY_COMPLEX, "setPasswordMinimumSymbols");
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.symbols != length) {
- ap.minimumPasswordMetrics.symbols = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.symbols != length) {
+ ap.mPasswordPolicy.symbols = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_SYMBOLS)
@@ -4708,7 +4737,7 @@
@Override
public int getPasswordMinimumSymbols(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.symbols, PASSWORD_QUALITY_COMPLEX);
+ admin -> admin.mPasswordPolicy.symbols, PASSWORD_QUALITY_COMPLEX);
}
@Override
@@ -4723,13 +4752,13 @@
who, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
ensureMinimumQuality(
userId, ap, PASSWORD_QUALITY_COMPLEX, "setPasswordMinimumNonLetter");
- final PasswordMetrics metrics = ap.minimumPasswordMetrics;
- if (metrics.nonLetter != length) {
- ap.minimumPasswordMetrics.nonLetter = length;
+ final PasswordPolicy passwordPolicy = ap.mPasswordPolicy;
+ if (passwordPolicy.nonLetter != length) {
+ ap.mPasswordPolicy.nonLetter = length;
updatePasswordValidityCheckpointLocked(userId, parent);
saveSettingsLocked(userId);
}
- maybeLogPasswordComplexitySet(who, userId, parent, metrics);
+ maybeLogPasswordComplexitySet(who, userId, parent, passwordPolicy);
}
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_PASSWORD_MINIMUM_NON_LETTER)
@@ -4741,7 +4770,7 @@
@Override
public int getPasswordMinimumNonLetter(ComponentName who, int userHandle, boolean parent) {
return getStrictestPasswordRequirement(who, userHandle, parent,
- admin -> admin.minimumPasswordMetrics.nonLetter, PASSWORD_QUALITY_COMPLEX);
+ admin -> admin.mPasswordPolicy.nonLetter, PASSWORD_QUALITY_COMPLEX);
}
/**
@@ -4777,6 +4806,33 @@
}
}
+ /**
+ * Calculates strictest (maximum) value for a given password property enforced by admin[s].
+ */
+ @Override
+ public PasswordMetrics getPasswordMinimumMetrics(@UserIdInt int userHandle) {
+ return getPasswordMinimumMetrics(userHandle, false /* parent */);
+ }
+
+ /**
+ * Calculates strictest (maximum) value for a given password property enforced by admin[s].
+ */
+ private PasswordMetrics getPasswordMinimumMetrics(@UserIdInt int userHandle, boolean parent) {
+ if (!mHasFeature) {
+ new PasswordMetrics(LockPatternUtils.CREDENTIAL_TYPE_NONE);
+ }
+ enforceFullCrossUsersPermission(userHandle);
+ ArrayList<PasswordMetrics> adminMetrics = new ArrayList<>();
+ synchronized (getLockObject()) {
+ List<ActiveAdmin> admins =
+ getActiveAdminsForLockscreenPoliciesLocked(userHandle, parent);
+ for (ActiveAdmin admin : admins) {
+ adminMetrics.add(admin.mPasswordPolicy.getMinMetrics());
+ }
+ }
+ return PasswordMetrics.merge(adminMetrics);
+ }
+
@Override
public boolean isActivePasswordSufficient(int userHandle, boolean parent) {
if (!mHasFeature) {
@@ -4792,8 +4848,9 @@
int credentialOwner = getCredentialOwner(userHandle, parent);
DevicePolicyData policy = getUserDataUnchecked(credentialOwner);
PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(credentialOwner);
- return isActivePasswordSufficientForUserLocked(
+ boolean activePasswordSufficientForUserLocked = isActivePasswordSufficientForUserLocked(
policy.mPasswordValidAtLastCheckpoint, metrics, userHandle, parent);
+ return activePasswordSufficientForUserLocked;
}
}
@@ -4856,25 +4913,11 @@
*/
private boolean isPasswordSufficientForUserWithoutCheckpointLocked(
@NonNull PasswordMetrics metrics, @UserIdInt int userId, boolean parent) {
- final int requiredQuality = getPasswordQuality(null, userId, parent);
-
- if (requiredQuality >= PASSWORD_QUALITY_NUMERIC
- && metrics.length < getPasswordMinimumLength(null, userId, parent)) {
- return false;
- }
-
- // PASSWORD_QUALITY_COMPLEX doesn't represent actual password quality, it means that number
- // of characters of each class should be checked instead of quality itself.
- if (requiredQuality == PASSWORD_QUALITY_COMPLEX) {
- return metrics.upperCase >= getPasswordMinimumUpperCase(null, userId, parent)
- && metrics.lowerCase >= getPasswordMinimumLowerCase(null, userId, parent)
- && metrics.letters >= getPasswordMinimumLetters(null, userId, parent)
- && metrics.numeric >= getPasswordMinimumNumeric(null, userId, parent)
- && metrics.symbols >= getPasswordMinimumSymbols(null, userId, parent)
- && metrics.nonLetter >= getPasswordMinimumNonLetter(null, userId, parent);
- } else {
- return metrics.quality >= requiredQuality;
- }
+ PasswordMetrics minMetrics = getPasswordMinimumMetrics(userId, parent);
+ final List<PasswordValidationError> passwordValidationErrors =
+ PasswordMetrics.validatePasswordMetrics(
+ minMetrics, PASSWORD_COMPLEXITY_NONE, false, metrics);
+ return passwordValidationErrors.isEmpty();
}
@Override
@@ -5132,77 +5175,17 @@
private boolean resetPasswordInternal(String password, long tokenHandle, byte[] token,
int flags, int callingUid, int userHandle) {
- int quality;
synchronized (getLockObject()) {
- quality = getPasswordQuality(null, userHandle, /* parent */ false);
- if (quality == PASSWORD_QUALITY_MANAGED) {
- quality = PASSWORD_QUALITY_UNSPECIFIED;
- }
// TODO(b/120484642): remove getBytes() below
- final PasswordMetrics metrics = PasswordMetrics.computeForPassword(password.getBytes());
- final int realQuality = metrics.quality;
- if (realQuality < quality && quality != PASSWORD_QUALITY_COMPLEX) {
- Slog.w(LOG_TAG, "resetPassword: password quality 0x"
- + Integer.toHexString(realQuality)
- + " does not meet required quality 0x"
- + Integer.toHexString(quality));
+ final PasswordMetrics minMetrics = getPasswordMinimumMetrics(userHandle);
+ final List<PasswordValidationError> validationErrors =
+ PasswordMetrics.validatePassword(
+ minMetrics, PASSWORD_COMPLEXITY_NONE, false, password.getBytes());
+ if (!validationErrors.isEmpty()) {
+ Log.w(LOG_TAG, "Failed to reset password due to constraint violation: "
+ + validationErrors.get(0));
return false;
}
- quality = Math.max(realQuality, quality);
- int length = getPasswordMinimumLength(null, userHandle, /* parent */ false);
- if (password.length() < length) {
- Slog.w(LOG_TAG, "resetPassword: password length " + password.length()
- + " does not meet required length " + length);
- return false;
- }
- if (quality == PASSWORD_QUALITY_COMPLEX) {
- int neededLetters = getPasswordMinimumLetters(null, userHandle, /* parent */ false);
- if(metrics.letters < neededLetters) {
- Slog.w(LOG_TAG, "resetPassword: number of letters " + metrics.letters
- + " does not meet required number of letters " + neededLetters);
- return false;
- }
- int neededNumeric = getPasswordMinimumNumeric(null, userHandle, /* parent */ false);
- if (metrics.numeric < neededNumeric) {
- Slog.w(LOG_TAG, "resetPassword: number of numerical digits " + metrics.numeric
- + " does not meet required number of numerical digits "
- + neededNumeric);
- return false;
- }
- int neededLowerCase = getPasswordMinimumLowerCase(
- null, userHandle, /* parent */ false);
- if (metrics.lowerCase < neededLowerCase) {
- Slog.w(LOG_TAG, "resetPassword: number of lowercase letters "
- + metrics.lowerCase
- + " does not meet required number of lowercase letters "
- + neededLowerCase);
- return false;
- }
- int neededUpperCase = getPasswordMinimumUpperCase(
- null, userHandle, /* parent */ false);
- if (metrics.upperCase < neededUpperCase) {
- Slog.w(LOG_TAG, "resetPassword: number of uppercase letters "
- + metrics.upperCase
- + " does not meet required number of uppercase letters "
- + neededUpperCase);
- return false;
- }
- int neededSymbols = getPasswordMinimumSymbols(null, userHandle, /* parent */ false);
- if (metrics.symbols < neededSymbols) {
- Slog.w(LOG_TAG, "resetPassword: number of special symbols " + metrics.symbols
- + " does not meet required number of special symbols " + neededSymbols);
- return false;
- }
- int neededNonLetter = getPasswordMinimumNonLetter(
- null, userHandle, /* parent */ false);
- if (metrics.nonLetter < neededNonLetter) {
- Slog.w(LOG_TAG, "resetPassword: number of non-letter characters "
- + metrics.nonLetter
- + " does not meet required number of non-letter characters "
- + neededNonLetter);
- return false;
- }
- }
}
DevicePolicyData policy = getUserData(userHandle);
@@ -5222,28 +5205,20 @@
// back in to the service.
final long ident = mInjector.binderClearCallingIdentity();
final boolean result;
+ final LockscreenCredential newCredential =
+ LockscreenCredential.createPasswordOrNone(password);
try {
if (token == null) {
// This is the legacy reset password for DPM. Here we want to be able to override
// the old device password without necessarily knowing it.
- if (!TextUtils.isEmpty(password)) {
- mLockPatternUtils.saveLockPassword(password.getBytes(), null, quality,
- userHandle, /*allowUntrustedChange */true);
- } else {
- mLockPatternUtils.clearLock(null, userHandle,
- /*allowUntrustedChange */ true);
- }
+ mLockPatternUtils.setLockCredential(
+ newCredential,
+ LockscreenCredential.createNone(),
+ userHandle, /*allowUntrustedChange */true);
result = true;
} else {
- if (!TextUtils.isEmpty(password)) {
- result = mLockPatternUtils.setLockCredentialWithToken(password.getBytes(),
- LockPatternUtils.CREDENTIAL_TYPE_PASSWORD,
- quality, tokenHandle, token, userHandle);
- } else {
- result = mLockPatternUtils.setLockCredentialWithToken(null,
- LockPatternUtils.CREDENTIAL_TYPE_NONE,
- quality, tokenHandle, token, userHandle);
- }
+ result = mLockPatternUtils.setLockCredentialWithToken(newCredential, tokenHandle,
+ token, userHandle);
}
boolean requireEntry = (flags & DevicePolicyManager.RESET_PASSWORD_REQUIRE_ENTRY) != 0;
if (requireEntry) {
@@ -11574,10 +11549,10 @@
/**
* Returns true if specified admin is allowed to limit passwords and has a
- * {@code minimumPasswordMetrics.quality} of at least {@code minPasswordQuality}
+ * {@code mPasswordPolicy.quality} of at least {@code minPasswordQuality}
*/
private static boolean isLimitPasswordAllowed(ActiveAdmin admin, int minPasswordQuality) {
- if (admin.minimumPasswordMetrics.quality < minPasswordQuality) {
+ if (admin.mPasswordPolicy.quality < minPasswordQuality) {
return false;
}
return admin.info.usesPolicy(DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD);
@@ -14196,13 +14171,13 @@
}
private void maybeLogPasswordComplexitySet(ComponentName who, int userId, boolean parent,
- PasswordMetrics metrics) {
+ PasswordPolicy passwordPolicy) {
if (SecurityLog.isLoggingEnabled()) {
final int affectedUserId = parent ? getProfileParentId(userId) : userId;
SecurityLog.writeEvent(SecurityLog.TAG_PASSWORD_COMPLEXITY_SET, who.getPackageName(),
- userId, affectedUserId, metrics.length, metrics.quality, metrics.letters,
- metrics.nonLetter, metrics.numeric, metrics.upperCase, metrics.lowerCase,
- metrics.symbols);
+ userId, affectedUserId, passwordPolicy.length, passwordPolicy.quality,
+ passwordPolicy.letters, passwordPolicy.nonLetter, passwordPolicy.numeric,
+ passwordPolicy.upperCase, passwordPolicy.lowerCase, passwordPolicy.symbols);
}
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
index 0838fbc..7cfbcc8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/RemoteBugreportUtils.java
@@ -51,7 +51,7 @@
static final long REMOTE_BUGREPORT_TIMEOUT_MILLIS = 10 * DateUtils.MINUTE_IN_MILLIS;
static final String CTL_STOP = "ctl.stop";
- static final String REMOTE_BUGREPORT_SERVICE = "bugreportremote";
+ static final String REMOTE_BUGREPORT_SERVICE = "bugreportd";
static final String BUGREPORT_MIMETYPE = "application/vnd.android.bugreport";
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index de65002..88859a7 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -501,7 +501,7 @@
mRuntimeStartElapsedTime, mRuntimeStartUptime);
LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);
// Prepare the thread pool for init tasks that can be parallelized
- SystemServerInitThreadPool.get();
+ SystemServerInitThreadPool.start();
} finally {
t.traceEnd(); // InitBeforeStartServices
}
@@ -635,7 +635,7 @@
Slog.i(TAG, "Reading configuration...");
final String TAG_SYSTEM_CONFIG = "ReadingSystemConfig";
t.traceBegin(TAG_SYSTEM_CONFIG);
- SystemServerInitThreadPool.get().submit(SystemConfig::getInstance, TAG_SYSTEM_CONFIG);
+ SystemServerInitThreadPool.submit(SystemConfig::getInstance, TAG_SYSTEM_CONFIG);
t.traceEnd();
// Platform compat service is used by ActivityManagerService, PackageManagerService, and
@@ -821,7 +821,7 @@
// service, and permissions service, therefore we start it after them.
// Start sensor service in a separate thread. Completion should be checked
// before using it.
- mSensorServiceStart = SystemServerInitThreadPool.get().submit(() -> {
+ mSensorServiceStart = SystemServerInitThreadPool.submit(() -> {
TimingsTraceAndSlog traceLog = TimingsTraceAndSlog.newAsyncLog();
traceLog.traceBegin(START_SENSOR_SERVICE);
startSensorService();
@@ -921,7 +921,6 @@
false);
boolean disableCameraService = SystemProperties.getBoolean("config.disable_cameraservice",
false);
- boolean disableSlices = SystemProperties.getBoolean("config.disable_slices", false);
boolean enableLeftyService = SystemProperties.getBoolean("config.enable_lefty", false);
boolean isEmulator = SystemProperties.get("ro.kernel.qemu").equals("1");
@@ -946,7 +945,7 @@
// ensure that it completes before the 32 bit relro process is forked
// from the zygote. In the event that it takes too long, the webview
// RELRO process will block, but it will do so without holding any locks.
- mZygotePreload = SystemServerInitThreadPool.get().submit(() -> {
+ mZygotePreload = SystemServerInitThreadPool.submit(() -> {
try {
Slog.i(TAG, SECONDARY_ZYGOTE_PRELOAD);
TimingsTraceAndSlog traceLog = TimingsTraceAndSlog.newAsyncLog();
@@ -1058,7 +1057,7 @@
// Start receiving calls from HIDL services. Start in in a separate thread
// because it need to connect to SensorManager. This have to start
// after START_SENSOR_SERVICE is done.
- SystemServerInitThreadPool.get().submit(() -> {
+ SystemServerInitThreadPool.submit(() -> {
TimingsTraceAndSlog traceLog = TimingsTraceAndSlog.newAsyncLog();
traceLog.traceBegin(START_HIDL_SERVICES);
startHidlServices();
@@ -1856,7 +1855,7 @@
t.traceEnd();
}
- if (!disableSlices) {
+ if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_SLICES_DISABLED)) {
t.traceBegin("StartSliceManagerService");
mSystemServiceManager.startService(SLICE_MANAGER_SERVICE_CLASS);
t.traceEnd();
@@ -2050,7 +2049,7 @@
final String WEBVIEW_PREPARATION = "WebViewFactoryPreparation";
Future<?> webviewPrep = null;
if (!mOnlyCore && mWebViewUpdateService != null) {
- webviewPrep = SystemServerInitThreadPool.get().submit(() -> {
+ webviewPrep = SystemServerInitThreadPool.submit(() -> {
Slog.i(TAG, WEBVIEW_PREPARATION);
TimingsTraceAndSlog traceLog = TimingsTraceAndSlog.newAsyncLog();
traceLog.traceBegin(WEBVIEW_PREPARATION);
diff --git a/services/print/java/com/android/server/print/TEST_MAPPING b/services/print/java/com/android/server/print/TEST_MAPPING
new file mode 100644
index 0000000..4fa8822
--- /dev/null
+++ b/services/print/java/com/android/server/print/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsPrintTestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
index 6e8b86a..7b7b8e6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -34,7 +34,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.AlarmManagerService.ACTIVE_INDEX;
import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_BUCKET_CHANGED;
-import static com.android.server.AlarmManagerService.AlarmHandler.APP_STANDBY_PAROLE_CHANGED;
+import static com.android.server.AlarmManagerService.AlarmHandler.CHARGING_STATUS_CHANGED;
import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_LONG_TIME;
import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_SHORT_TIME;
import static com.android.server.AlarmManagerService.Constants.KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION;
@@ -53,6 +53,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeastOnce;
@@ -68,6 +69,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.os.BatteryManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -107,6 +109,7 @@
private long mAppStandbyWindow;
private AlarmManagerService mService;
private UsageStatsManagerInternal.AppIdleStateChangeListener mAppStandbyListener;
+ private AlarmManagerService.ChargingReceiver mChargingReceiver;
@Mock
private ContentResolver mMockResolver;
@Mock
@@ -290,6 +293,13 @@
ArgumentCaptor.forClass(UsageStatsManagerInternal.AppIdleStateChangeListener.class);
verify(mUsageStatsManagerInternal).addAppIdleStateChangeListener(captor.capture());
mAppStandbyListener = captor.getValue();
+
+ ArgumentCaptor<AlarmManagerService.ChargingReceiver> chargingReceiverCaptor =
+ ArgumentCaptor.forClass(AlarmManagerService.ChargingReceiver.class);
+ verify(mMockContext).registerReceiver(chargingReceiverCaptor.capture(),
+ argThat((filter) -> filter.hasAction(BatteryManager.ACTION_CHARGING)
+ && filter.hasAction(BatteryManager.ACTION_DISCHARGING)));
+ mChargingReceiver = chargingReceiverCaptor.getValue();
}
private void setTestAlarm(int type, long triggerTime, PendingIntent operation) {
@@ -724,17 +734,19 @@
}
private void assertAndHandleParoleChanged(boolean parole) {
- mAppStandbyListener.onParoleStateChanged(parole);
+ mChargingReceiver.onReceive(mMockContext,
+ new Intent(parole ? BatteryManager.ACTION_CHARGING
+ : BatteryManager.ACTION_DISCHARGING));
final ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
verify(mService.mHandler, atLeastOnce()).sendMessage(messageCaptor.capture());
final Message lastMessage = messageCaptor.getValue();
assertEquals("Unexpected message send to handler", lastMessage.what,
- APP_STANDBY_PAROLE_CHANGED);
+ CHARGING_STATUS_CHANGED);
mService.mHandler.handleMessage(lastMessage);
}
@Test
- public void testParole() throws Exception {
+ public void testCharging() throws Exception {
setQuotasEnabled(true);
final int workingQuota = mService.getQuotaForBucketLocked(STANDBY_BUCKET_WORKING_SET);
when(mUsageStatsManagerInternal.getAppStandbyBucket(eq(TEST_CALLING_PACKAGE), anyInt(),
diff --git a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
index d0158e0..247a358 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
@@ -64,6 +64,9 @@
import android.util.ArraySet;
import android.util.Pair;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
import com.android.server.AppStateTracker.Listener;
@@ -85,14 +88,10 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
/**
* Tests for {@link AppStateTracker}
*
- * Run with:
- atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/mockingservicestests/src/com/android/server/AppStateTrackerTest.java
+ * Run with: atest com.android.server.AppStateTrackerTest
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 2aa625a..9c97305 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -17,6 +17,7 @@
import static androidx.test.InstrumentationRegistry.getContext;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
@@ -31,6 +32,7 @@
import static com.android.server.DeviceIdleController.LIGHT_STATE_OVERRIDE;
import static com.android.server.DeviceIdleController.LIGHT_STATE_PRE_IDLE;
import static com.android.server.DeviceIdleController.LIGHT_STATE_WAITING_FOR_NETWORK;
+import static com.android.server.DeviceIdleController.MSG_REPORT_STATIONARY_STATUS;
import static com.android.server.DeviceIdleController.STATE_ACTIVE;
import static com.android.server.DeviceIdleController.STATE_IDLE;
import static com.android.server.DeviceIdleController.STATE_IDLE_MAINTENANCE;
@@ -51,6 +53,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.longThat;
import static org.mockito.Mockito.atLeastOnce;
@@ -72,6 +75,7 @@
import android.net.NetworkInfo;
import android.os.Handler;
import android.os.Looper;
+import android.os.Message;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
@@ -87,11 +91,13 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Answers;
+import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoSession;
+import org.mockito.invocation.InvocationOnMock;
import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
/**
* Tests for {@link com.android.server.DeviceIdleController}.
@@ -99,6 +105,7 @@
@RunWith(AndroidJUnit4.class)
public class DeviceIdleControllerTest {
private DeviceIdleController mDeviceIdleController;
+ private DeviceIdleController.MyHandler mHandler;
private AnyMotionDetectorForTest mAnyMotionDetector;
private AppStateTrackerForTest mAppStateTracker;
private DeviceIdleController.Constants mConstants;
@@ -112,8 +119,6 @@
@Mock
private ContentResolver mContentResolver;
@Mock
- private DeviceIdleController.MyHandler mHandler;
- @Mock
private IActivityManager mIActivityManager;
@Mock
private LocationManager mLocationManager;
@@ -171,6 +176,23 @@
@Override
DeviceIdleController.MyHandler getHandler(DeviceIdleController controller) {
+ if (mHandler == null) {
+ mHandler = controller.new MyHandler(getContext().getMainLooper());
+ spyOn(mHandler);
+ doNothing().when(mHandler).handleMessage(argThat((message) ->
+ message.what != MSG_REPORT_STATIONARY_STATUS));
+ doAnswer(new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock invocation) throws Throwable {
+ Message msg = invocation.getArgument(0);
+ mHandler.handleMessage(msg);
+ return true;
+ }
+ }).when(mHandler).sendMessageDelayed(
+ argThat((message) -> message.what == MSG_REPORT_STATIONARY_STATUS),
+ anyLong());
+ }
+
return mHandler;
}
@@ -236,6 +258,19 @@
}
}
+ private class StationaryListenerForTest implements DeviceIdleInternal.StationaryListener {
+ boolean motionExpected = false;
+ boolean isStationary = false;
+
+ @Override
+ public void onDeviceStationaryChanged(boolean isStationary) {
+ if (isStationary == motionExpected) {
+ fail("Unexpected device stationary status: " + isStationary);
+ }
+ this.isStationary = isStationary;
+ }
+ }
+
@Before
public void setUp() {
mMockingSession = mockitoSession()
@@ -265,8 +300,6 @@
doReturn(true).when(mSensorManager).registerListener(any(), any(), anyInt());
mAppStateTracker = new AppStateTrackerForTest(getContext(), Looper.getMainLooper());
mAnyMotionDetector = new AnyMotionDetectorForTest();
- mHandler = mock(DeviceIdleController.MyHandler.class, Answers.RETURNS_DEEP_STUBS);
- doNothing().when(mHandler).handleMessage(any());
mInjector = new InjectorForTest(getContext());
doNothing().when(mContentResolver).registerContentObserver(any(), anyBoolean(), any());
@@ -1724,6 +1757,86 @@
1.0f, curfactor, delta);
}
+ @Test
+ public void testStationaryDetection_QuickDozeOff() {
+ setQuickDozeEnabled(false);
+ enterDeepState(STATE_IDLE);
+ // Regular progression through states, so time should have increased appropriately.
+ mInjector.nowElapsed += mConstants.IDLE_AFTER_INACTIVE_TIMEOUT + mConstants.SENSING_TIMEOUT
+ + mConstants.LOCATING_TIMEOUT;
+
+ StationaryListenerForTest stationaryListener = new StationaryListenerForTest();
+
+ mDeviceIdleController.registerStationaryListener(stationaryListener);
+
+ // Go to IDLE_MAINTENANCE
+ mDeviceIdleController.stepIdleStateLocked("testing");
+
+ // Back to IDLE
+ mDeviceIdleController.stepIdleStateLocked("testing");
+ assertTrue(stationaryListener.isStationary);
+
+ // Test motion
+ stationaryListener.motionExpected = true;
+ mDeviceIdleController.mMotionListener.onTrigger(null);
+ assertFalse(stationaryListener.isStationary);
+ }
+
+ @Test
+ public void testStationaryDetection_QuickDozeOn() {
+ setAlarmSoon(false);
+ enterDeepState(STATE_QUICK_DOZE_DELAY);
+ mDeviceIdleController.stepIdleStateLocked("testing");
+ verifyStateConditions(STATE_IDLE);
+ // Quick doze progression through states, so time should have increased appropriately.
+ mInjector.nowElapsed += mConstants.QUICK_DOZE_DELAY_TIMEOUT;
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListener = ArgumentCaptor
+ .forClass(AlarmManager.OnAlarmListener.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), eq("DeviceIdleController.motion"),
+ alarmListener.capture(), any());
+
+ StationaryListenerForTest stationaryListener = new StationaryListenerForTest();
+
+ stationaryListener.motionExpected = true;
+ mDeviceIdleController.registerStationaryListener(stationaryListener);
+ assertFalse(stationaryListener.isStationary);
+
+ // Go to IDLE_MAINTENANCE
+ mDeviceIdleController.stepIdleStateLocked("testing");
+
+ mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT / 2;
+
+ // Back to IDLE
+ mDeviceIdleController.stepIdleStateLocked("testing");
+
+ // Now enough time has passed.
+ mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT / 2;
+ stationaryListener.motionExpected = false;
+ alarmListener.getValue().onAlarm();
+ assertTrue(stationaryListener.isStationary);
+
+ stationaryListener.motionExpected = true;
+ mDeviceIdleController.mMotionListener.onSensorChanged(null);
+ assertFalse(stationaryListener.isStationary);
+
+ // Since we're in quick doze, the device shouldn't stop idling.
+ verifyStateConditions(STATE_IDLE);
+
+ // Go to IDLE_MAINTENANCE
+ mDeviceIdleController.stepIdleStateLocked("testing");
+
+ mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT / 2;
+
+ // Back to IDLE
+ mDeviceIdleController.stepIdleStateLocked("testing");
+
+ // Now enough time has passed.
+ mInjector.nowElapsed += mConstants.MOTION_INACTIVE_TIMEOUT / 2;
+ stationaryListener.motionExpected = false;
+ alarmListener.getValue().onAlarm();
+ assertTrue(stationaryListener.isStationary);
+ }
+
private void enterDeepState(int state) {
switch (state) {
case STATE_ACTIVE:
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 43f251a..d11d987 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -156,7 +156,7 @@
@Mock private AccessibilityWindowManager mMockA11yWindowManager;
@Mock private AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
@Mock private WindowManagerInternal mMockWindowManagerInternal;
- @Mock private GlobalActionPerformer mMockGlobalActionPerformer;
+ @Mock private SystemActionPerformer mMockSystemActionPerformer;
@Mock private IBinder mMockService;
@Mock private IAccessibilityServiceClient mMockServiceInterface;
@Mock private KeyEventDispatcher mMockKeyEventDispatcher;
@@ -221,7 +221,7 @@
mServiceConnection = new TestAccessibilityServiceConnection(mMockContext, COMPONENT_NAME,
mSpyServiceInfo, SERVICE_ID, mHandler, new Object(), mMockSecurityPolicy,
- mMockSystemSupport, mMockWindowManagerInternal, mMockGlobalActionPerformer,
+ mMockSystemSupport, mMockWindowManagerInternal, mMockSystemActionPerformer,
mMockA11yWindowManager);
// Assume that the service is connected
mServiceConnection.mService = mMockService;
@@ -489,7 +489,7 @@
@Test
public void performGlobalAction() {
mServiceConnection.performGlobalAction(GLOBAL_ACTION_HOME);
- verify(mMockGlobalActionPerformer).performGlobalAction(GLOBAL_ACTION_HOME);
+ verify(mMockSystemActionPerformer).performSystemAction(GLOBAL_ACTION_HOME);
}
@Test
@@ -776,10 +776,10 @@
AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
Object lock, AccessibilitySecurityPolicy securityPolicy,
SystemSupport systemSupport, WindowManagerInternal windowManagerInternal,
- GlobalActionPerformer globalActionPerfomer,
+ SystemActionPerformer systemActionPerfomer,
AccessibilityWindowManager a11yWindowManager) {
super(context, componentName, accessibilityServiceInfo, id, mainHandler, lock,
- securityPolicy, systemSupport, windowManagerInternal, globalActionPerfomer,
+ securityPolicy, systemSupport, windowManagerInternal, systemActionPerfomer,
a11yWindowManager);
mResolvedUserId = USER_ID;
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index 6be5a37..597d337 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -41,12 +41,14 @@
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.testing.DexmakerShareClassLoaderRule;
import android.view.Display;
import com.android.server.wm.WindowManagerInternal;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -65,9 +67,14 @@
"com.android.server.accessibility", "AccessibilityServiceConnectionTest");
static final int SERVICE_ID = 42;
+ // Mock package-private AccessibilityUserState class
+ @Rule
+ public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
+ new DexmakerShareClassLoaderRule();
+
AccessibilityServiceConnection mConnection;
- @Mock AccessibilityManagerService.UserState mMockUserState;
+ @Mock AccessibilityUserState mMockUserState;
@Mock Context mMockContext;
@Mock AccessibilityServiceInfo mMockServiceInfo;
@Mock ResolveInfo mMockResolveInfo;
@@ -75,7 +82,7 @@
@Mock AccessibilityWindowManager mMockA11yWindowManager;
@Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
@Mock WindowManagerInternal mMockWindowManagerInternal;
- @Mock GlobalActionPerformer mMockGlobalActionPerformer;
+ @Mock SystemActionPerformer mMockSystemActionPerformer;
@Mock KeyEventDispatcher mMockKeyEventDispatcher;
@Mock MagnificationController mMockMagnificationController;
@Mock IBinder mMockIBinder;
@@ -104,7 +111,7 @@
mConnection = new AccessibilityServiceConnection(mMockUserState, mMockContext,
COMPONENT_NAME, mMockServiceInfo, SERVICE_ID, mHandler, new Object(),
mMockSecurityPolicy, mMockSystemSupport, mMockWindowManagerInternal,
- mMockGlobalActionPerformer, mMockA11yWindowManager);
+ mMockSystemActionPerformer, mMockA11yWindowManager);
when(mMockSecurityPolicy.canPerformGestures(mConnection)).thenReturn(true);
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
new file mode 100644
index 0000000..9180054
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.accessibility;
+
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_AUTO;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
+import static android.accessibilityservice.AccessibilityService.SHOW_MODE_IGNORE_HARD_KEYBOARD;
+import static android.view.accessibility.AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
+import static android.view.accessibility.AccessibilityManager.STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED;
+import static android.view.accessibility.AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
+import android.testing.DexmakerShareClassLoaderRule;
+
+import com.android.internal.util.test.FakeSettingsProvider;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Tests for AccessibilityUserState */
+public class AccessibilityUserStateTest {
+
+ private static final ComponentName COMPONENT_NAME =
+ new ComponentName("com.android.server.accessibility", "AccessibilityUserStateTest");
+
+ // Values of setting key SHOW_IME_WITH_HARD_KEYBOARD
+ private static final int STATE_HIDE_IME = 0;
+ private static final int STATE_SHOW_IME = 1;
+
+ private static final int USER_ID = 42;
+
+ // Mock package-private class AccessibilityServiceConnection
+ @Rule public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
+ new DexmakerShareClassLoaderRule();
+
+ @Mock private AccessibilityServiceInfo mMockServiceInfo;
+
+ @Mock private AccessibilityServiceConnection mMockConnection;
+
+ @Mock private AccessibilityUserState.ServiceInfoChangeListener mMockListener;
+
+ @Mock private Context mContext;
+
+ private MockContentResolver mMockResolver;
+
+ private AccessibilityUserState mUserState;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ FakeSettingsProvider.clearSettingsProvider();
+ mMockResolver = new MockContentResolver();
+ mMockResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(mContext.getContentResolver()).thenReturn(mMockResolver);
+ when(mMockServiceInfo.getComponentName()).thenReturn(COMPONENT_NAME);
+ when(mMockConnection.getServiceInfo()).thenReturn(mMockServiceInfo);
+
+ mUserState = new AccessibilityUserState(USER_ID, mContext, mMockListener);
+ }
+
+ @After
+ public void tearDown() {
+ FakeSettingsProvider.clearSettingsProvider();
+ }
+
+ @Test
+ public void onSwitchToAnotherUser_userStateClearedNonDefaultValues() {
+ mUserState.getBoundServicesLocked().add(mMockConnection);
+ mUserState.getBindingServicesLocked().add(COMPONENT_NAME);
+ mUserState.setLastSentClientStateLocked(
+ STATE_FLAG_ACCESSIBILITY_ENABLED
+ | STATE_FLAG_TOUCH_EXPLORATION_ENABLED
+ | STATE_FLAG_HIGH_TEXT_CONTRAST_ENABLED);
+ mUserState.setNonInteractiveUiTimeoutLocked(30);
+ mUserState.setInteractiveUiTimeoutLocked(30);
+ mUserState.mEnabledServices.add(COMPONENT_NAME);
+ mUserState.mTouchExplorationGrantedServices.add(COMPONENT_NAME);
+ mUserState.setTouchExplorationEnabledLocked(true);
+ mUserState.setDisplayMagnificationEnabledLocked(true);
+ mUserState.setNavBarMagnificationEnabledLocked(true);
+ mUserState.setServiceAssignedToAccessibilityButtonLocked(COMPONENT_NAME);
+ mUserState.setNavBarMagnificationAssignedToAccessibilityButtonLocked(true);
+ mUserState.setAutoclickEnabledLocked(true);
+ mUserState.setUserNonInteractiveUiTimeoutLocked(30);
+ mUserState.setUserInteractiveUiTimeoutLocked(30);
+
+ mUserState.onSwitchToAnotherUserLocked();
+
+ verify(mMockConnection).unbindLocked();
+ assertTrue(mUserState.getBoundServicesLocked().isEmpty());
+ assertTrue(mUserState.getBindingServicesLocked().isEmpty());
+ assertEquals(-1, mUserState.getLastSentClientStateLocked());
+ assertEquals(0, mUserState.getNonInteractiveUiTimeoutLocked());
+ assertEquals(0, mUserState.getInteractiveUiTimeoutLocked());
+ assertTrue(mUserState.mEnabledServices.isEmpty());
+ assertTrue(mUserState.mTouchExplorationGrantedServices.isEmpty());
+ assertFalse(mUserState.isTouchExplorationEnabledLocked());
+ assertFalse(mUserState.isDisplayMagnificationEnabledLocked());
+ assertFalse(mUserState.isNavBarMagnificationEnabledLocked());
+ assertNull(mUserState.getServiceAssignedToAccessibilityButtonLocked());
+ assertFalse(mUserState.isNavBarMagnificationAssignedToAccessibilityButtonLocked());
+ assertFalse(mUserState.isAutoclickEnabledLocked());
+ assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked());
+ assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked());
+ }
+
+ @Test
+ public void addService_connectionAlreadyAdded_notAddAgain() {
+ mUserState.getBoundServicesLocked().add(mMockConnection);
+
+ mUserState.addServiceLocked(mMockConnection);
+
+ verify(mMockConnection, never()).onAdded();
+ }
+
+ @Test
+ public void addService_connectionNotYetAddedToBoundService_addAndNotifyServices() {
+ when(mMockConnection.getComponentName()).thenReturn(COMPONENT_NAME);
+
+ mUserState.addServiceLocked(mMockConnection);
+
+ verify(mMockConnection).onAdded();
+ assertTrue(mUserState.getBoundServicesLocked().contains(mMockConnection));
+ assertEquals(mMockConnection, mUserState.mComponentNameToServiceMap.get(COMPONENT_NAME));
+ verify(mMockListener).onServiceInfoChangedLocked(eq(mUserState));
+ }
+
+ @Test
+ public void reconcileSoftKeyboardMode_whenStateNotMatchSettings_setBothDefault() {
+ // When soft kb show mode is hidden in settings and is auto in state.
+ putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ SHOW_MODE_HIDDEN, USER_ID);
+
+ mUserState.reconcileSoftKeyboardModeWithSettingsLocked();
+
+ assertEquals(SHOW_MODE_AUTO, mUserState.getSoftKeyboardShowModeLocked());
+ assertEquals(SHOW_MODE_AUTO, getSecureIntForUser(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, USER_ID));
+ assertNull(mUserState.getServiceChangingSoftKeyboardModeLocked());
+ }
+
+ @Test
+ public void
+ reconcileSoftKeyboardMode_stateIgnoreHardKb_settingsShowImeHardKb_setAutoOverride() {
+ // When show mode is ignore hard kb without original hard kb value
+ // and show ime with hard kb is hide
+ putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ SHOW_MODE_IGNORE_HARD_KEYBOARD, USER_ID);
+ mUserState.setSoftKeyboardModeLocked(SHOW_MODE_IGNORE_HARD_KEYBOARD, COMPONENT_NAME);
+ putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
+ STATE_HIDE_IME, USER_ID);
+
+ mUserState.reconcileSoftKeyboardModeWithSettingsLocked();
+
+ assertEquals(SHOW_MODE_AUTO | SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN,
+ getSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, USER_ID));
+ assertNull(mUserState.getServiceChangingSoftKeyboardModeLocked());
+ }
+
+ @Test
+ public void removeService_serviceChangingSoftKeyboardMode_removeAndSetSoftKbModeAuto() {
+ mUserState.setServiceChangingSoftKeyboardModeLocked(COMPONENT_NAME);
+ mUserState.mComponentNameToServiceMap.put(COMPONENT_NAME, mMockConnection);
+ mUserState.setSoftKeyboardModeLocked(SHOW_MODE_HIDDEN, COMPONENT_NAME);
+
+ mUserState.removeServiceLocked(mMockConnection);
+
+ assertFalse(mUserState.getBoundServicesLocked().contains(mMockConnection));
+ verify(mMockConnection).onRemoved();
+ assertEquals(SHOW_MODE_AUTO, mUserState.getSoftKeyboardShowModeLocked());
+ assertNull(mUserState.mComponentNameToServiceMap.get(COMPONENT_NAME));
+ verify(mMockListener).onServiceInfoChangedLocked(eq(mUserState));
+ }
+
+ @Test
+ public void serviceDisconnected_removeServiceAndAddToBinding() {
+ when(mMockConnection.getComponentName()).thenReturn(COMPONENT_NAME);
+ mUserState.addServiceLocked(mMockConnection);
+
+ mUserState.serviceDisconnectedLocked(mMockConnection);
+
+ assertFalse(mUserState.getBoundServicesLocked().contains(mMockConnection));
+ assertTrue(mUserState.getBindingServicesLocked().contains(COMPONENT_NAME));
+ }
+
+ @Test
+ public void setSoftKeyboardMode_withInvalidShowMode_shouldKeepDefaultAuto() {
+ final int invalidShowMode = SHOW_MODE_HIDDEN | SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE;
+
+ assertFalse(mUserState.setSoftKeyboardModeLocked(invalidShowMode, null));
+
+ assertEquals(SHOW_MODE_AUTO, mUserState.getSoftKeyboardShowModeLocked());
+ }
+
+ @Test
+ public void setSoftKeyboardMode_newModeSameWithCurrentState_returnTrue() {
+ when(mMockConnection.getComponentName()).thenReturn(COMPONENT_NAME);
+ mUserState.addServiceLocked(mMockConnection);
+
+ assertTrue(mUserState.setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null));
+ }
+
+ @Test
+ public void setSoftKeyboardMode_withIgnoreHardKb_whenHardKbOverridden_returnFalseAdNoChange() {
+ putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ SHOW_MODE_AUTO | SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN, USER_ID);
+
+ assertFalse(mUserState.setSoftKeyboardModeLocked(SHOW_MODE_IGNORE_HARD_KEYBOARD, null));
+
+ assertEquals(SHOW_MODE_AUTO, mUserState.getSoftKeyboardShowModeLocked());
+ }
+
+ @Test
+ public void
+ setSoftKeyboardMode_withIgnoreHardKb_whenShowImeWithHardKb_setOriginalHardKbValue() {
+ putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, STATE_SHOW_IME, USER_ID);
+
+ assertTrue(mUserState.setSoftKeyboardModeLocked(SHOW_MODE_IGNORE_HARD_KEYBOARD, null));
+
+ assertEquals(SHOW_MODE_IGNORE_HARD_KEYBOARD | SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE,
+ getSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, USER_ID));
+ }
+
+ @Test
+ public void setSoftKeyboardMode_whenCurrentIgnoreHardKb_shouldSetShowImeWithHardKbValue() {
+ mUserState.setSoftKeyboardModeLocked(SHOW_MODE_IGNORE_HARD_KEYBOARD, COMPONENT_NAME);
+ putSecureIntForUser(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, STATE_HIDE_IME, USER_ID);
+ putSecureIntForUser(Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
+ SHOW_MODE_IGNORE_HARD_KEYBOARD | SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE, USER_ID);
+
+ assertTrue(mUserState.setSoftKeyboardModeLocked(SHOW_MODE_AUTO, null));
+
+ assertEquals(STATE_SHOW_IME, getSecureIntForUser(
+ Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, USER_ID));
+ }
+
+ @Test
+ public void setSoftKeyboardMode_withRequester_shouldUpdateInternalStateAndSettingsAsIs() {
+ assertTrue(mUserState.setSoftKeyboardModeLocked(SHOW_MODE_HIDDEN, COMPONENT_NAME));
+
+ assertEquals(SHOW_MODE_HIDDEN, mUserState.getSoftKeyboardShowModeLocked());
+ assertEquals(SHOW_MODE_HIDDEN, getSecureIntForUser(
+ Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, USER_ID));
+ assertEquals(COMPONENT_NAME, mUserState.getServiceChangingSoftKeyboardModeLocked());
+ }
+
+ @Test
+ public void setSoftKeyboardMode_shouldNotifyBoundService() {
+ mUserState.addServiceLocked(mMockConnection);
+
+ assertTrue(mUserState.setSoftKeyboardModeLocked(SHOW_MODE_HIDDEN, COMPONENT_NAME));
+
+ verify(mMockConnection).notifySoftKeyboardShowModeChangedLocked(eq(SHOW_MODE_HIDDEN));
+ }
+
+ private int getSecureIntForUser(String key, int userId) {
+ return Settings.Secure.getIntForUser(mMockResolver, key, -1, userId);
+ }
+
+ private void putSecureIntForUser(String key, int value, int userId) {
+ Settings.Secure.putIntForUser(mMockResolver, key, value, userId);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/GlobalActionPerformerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
similarity index 63%
rename from services/tests/servicestests/src/com/android/server/accessibility/GlobalActionPerformerTest.java
rename to services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
index c73be6f..37f5b87 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/GlobalActionPerformerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/SystemActionPerformerTest.java
@@ -1,17 +1,17 @@
/*
- ** 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.
+ * 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.accessibility;
@@ -35,13 +35,11 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.function.Consumer;
-
/**
- * Tests for GlobalActionPerformer
+ * Tests for SystemActionPerformer
*/
-public class GlobalActionPerformerTest {
- GlobalActionPerformer mGlobalActionPerformer;
+public class SystemActionPerformerTest {
+ SystemActionPerformer mSystemActionPerformer;
@Mock Context mMockContext;
@Mock WindowManagerInternal mMockWindowManagerInternal;
@@ -55,34 +53,34 @@
when(mMockContext.getSystemService(android.app.Service.STATUS_BAR_SERVICE))
.thenReturn(mMockStatusBarManager);
- mGlobalActionPerformer =
- new GlobalActionPerformer(mMockContext, mMockWindowManagerInternal,
+ mSystemActionPerformer =
+ new SystemActionPerformer(mMockContext, mMockWindowManagerInternal,
() -> mMockScreenshotHelper);
}
@Test
public void testNotifications_expandsNotificationPanel() {
- mGlobalActionPerformer
- .performGlobalAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS);
+ mSystemActionPerformer
+ .performSystemAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS);
verify(mMockStatusBarManager).expandNotificationsPanel();
}
@Test
public void testQuickSettings_requestsQuickSettingsPanel() {
- mGlobalActionPerformer
- .performGlobalAction(AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS);
+ mSystemActionPerformer
+ .performSystemAction(AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS);
verify(mMockStatusBarManager).expandSettingsPanel();
}
@Test
public void testPowerDialog_requestsFromWindowManager() {
- mGlobalActionPerformer.performGlobalAction(AccessibilityService.GLOBAL_ACTION_POWER_DIALOG);
+ mSystemActionPerformer.performSystemAction(AccessibilityService.GLOBAL_ACTION_POWER_DIALOG);
verify(mMockWindowManagerInternal).showGlobalActions();
}
@Test
public void testScreenshot_requestsFromScreenshotHelper() {
- mGlobalActionPerformer.performGlobalAction(
+ mSystemActionPerformer.performSystemAction(
AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);
verify(mMockScreenshotHelper).takeScreenshot(
eq(android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN), anyBoolean(),
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
index 210de53..8da927d 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -56,7 +56,6 @@
MessageCapturingHandler mMessageCapturingHandler;
- @Mock AccessibilityManagerService.UserState mMockUserState;
@Mock Context mMockContext;
@Mock AccessibilityServiceInfo mMockServiceInfo;
@Mock ResolveInfo mMockResolveInfo;
@@ -64,7 +63,7 @@
@Mock AccessibilityWindowManager mMockA11yWindowManager;
@Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
@Mock WindowManagerInternal mMockWindowManagerInternal;
- @Mock GlobalActionPerformer mMockGlobalActionPerformer;
+ @Mock SystemActionPerformer mMockSystemActionPerformer;
@Mock IBinder mMockOwner;
@Mock IAccessibilityServiceClient mMockAccessibilityServiceClient;
@Mock IBinder mMockServiceAsBinder;
@@ -174,7 +173,7 @@
mUiAutomationManager.registerUiTestAutomationServiceLocked(mMockOwner,
mMockAccessibilityServiceClient, mMockContext, mMockServiceInfo, SERVICE_ID,
mMessageCapturingHandler, mMockSecurityPolicy, mMockSystemSupport,
- mMockWindowManagerInternal, mMockGlobalActionPerformer,
+ mMockWindowManagerInternal, mMockSystemActionPerformer,
mMockA11yWindowManager, flags);
}
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 9a1fd9c..aeccfc5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -25,10 +25,12 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
+import static android.app.admin.PasswordMetrics.computeForPassword;
import static android.os.UserManagerInternal.CAMERA_DISABLED_GLOBALLY;
import static android.os.UserManagerInternal.CAMERA_DISABLED_LOCALLY;
import static android.os.UserManagerInternal.CAMERA_NOT_DISABLED;
+import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
import static com.android.server.testutils.TestUtils.assertExpectException;
@@ -93,7 +95,7 @@
import androidx.test.filters.SmallTest;
import com.android.internal.R;
-import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.devicepolicy.DevicePolicyManagerService.RestrictionsListener;
@@ -4266,9 +4268,9 @@
assertTrue(dpm.isResetPasswordTokenActive(admin1));
// test reset password with token
- when(getServices().lockPatternUtils.setLockCredentialWithToken(eq(password.getBytes()),
- eq(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD),
- eq(DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC), eq(handle), eq(token),
+ when(getServices().lockPatternUtils.setLockCredentialWithToken(
+ eq(LockscreenCredential.createPassword(password)),
+ eq(handle), eq(token),
eq(UserHandle.USER_SYSTEM)))
.thenReturn(true);
assertTrue(dpm.resetPasswordWithToken(admin1, password, token, 0));
@@ -4295,11 +4297,7 @@
reset(mContext.spiedContext);
- PasswordMetrics passwordMetricsNoSymbols = new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9,
- 8, 2,
- 6, 1,
- 0, 1);
+ PasswordMetrics passwordMetricsNoSymbols = computeForPassword("abcdXYZ5".getBytes());
setActivePasswordState(passwordMetricsNoSymbols);
assertTrue(dpm.isActivePasswordSufficient());
@@ -4326,11 +4324,7 @@
reset(mContext.spiedContext);
assertFalse(dpm.isActivePasswordSufficient());
- PasswordMetrics passwordMetricsWithSymbols = new PasswordMetrics(
- DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9,
- 7, 2,
- 5, 1,
- 1, 2);
+ PasswordMetrics passwordMetricsWithSymbols = computeForPassword("abcd.XY5".getBytes());
setActivePasswordState(passwordMetricsWithSymbols);
assertTrue(dpm.isActivePasswordSufficient());
@@ -4347,7 +4341,7 @@
final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
// When there is no lockscreen, user password metrics is always empty.
when(getServices().lockSettingsInternal.getUserPasswordMetrics(userHandle))
- .thenReturn(new PasswordMetrics());
+ .thenReturn(new PasswordMetrics(CREDENTIAL_TYPE_NONE));
// If no password requirements are set, isActivePasswordSufficient should succeed.
assertTrue(dpm.isActivePasswordSufficient());
@@ -5314,7 +5308,7 @@
.thenReturn(DpmMockContext.CALLER_USER_HANDLE);
when(getServices().lockSettingsInternal
.getUserPasswordMetrics(DpmMockContext.CALLER_USER_HANDLE))
- .thenReturn(PasswordMetrics.computeForPassword("asdf".getBytes()));
+ .thenReturn(computeForPassword("asdf".getBytes()));
assertEquals(PASSWORD_COMPLEXITY_MEDIUM, dpm.getPasswordComplexity());
}
@@ -5331,10 +5325,10 @@
when(getServices().lockSettingsInternal
.getUserPasswordMetrics(DpmMockContext.CALLER_USER_HANDLE))
- .thenReturn(PasswordMetrics.computeForPassword("asdf".getBytes()));
+ .thenReturn(computeForPassword("asdf".getBytes()));
when(getServices().lockSettingsInternal
.getUserPasswordMetrics(parentUser.id))
- .thenReturn(PasswordMetrics.computeForPassword("parentUser".getBytes()));
+ .thenReturn(computeForPassword("parentUser".getBytes()));
assertEquals(PASSWORD_COMPLEXITY_HIGH, dpm.getPasswordComplexity());
}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
new file mode 100644
index 0000000..baf1ed0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.integrity.engine;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.server.integrity.model.AppInstallMetadata;
+import com.android.server.integrity.model.AtomicFormula;
+import com.android.server.integrity.model.OpenFormula;
+import com.android.server.integrity.model.Rule;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class RuleEvaluatorTest {
+
+ private static final String PACKAGE_NAME_1 = "com.test.app";
+ private static final String PACKAGE_NAME_2 = "com.test.app2";
+ private static final String APP_CERTIFICATE = "test_cert";
+ private static final AppInstallMetadata APP_INSTALL_METADATA =
+ new AppInstallMetadata.Builder()
+ .setPackageName(PACKAGE_NAME_1)
+ .setAppCertificate(APP_CERTIFICATE)
+ .build();
+
+ @Test
+ public void testMatchRules_emptyRules() {
+ List<Rule> rules = new ArrayList<>();
+
+ Rule matchedRule = RuleEvaluator.evaluateRules(rules, APP_INSTALL_METADATA);
+
+ assertEquals(Rule.EMPTY, matchedRule);
+ }
+
+ @Test
+ public void testMatchRules_emptyMatch() {
+ Rule rule1 = new Rule(
+ new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
+ PACKAGE_NAME_2), Rule.Effect.DENY);
+
+ Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule1),
+ APP_INSTALL_METADATA);
+
+ assertEquals(Rule.EMPTY, matchedRule);
+ }
+
+
+ @Test
+ public void testMatchRules_oneMatch() {
+ Rule rule1 = new Rule(
+ new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
+ PACKAGE_NAME_1), Rule.Effect.DENY);
+ Rule rule2 = new Rule(
+ new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
+ PACKAGE_NAME_2), Rule.Effect.DENY);
+
+ Rule matchedRule = RuleEvaluator.evaluateRules(Arrays.asList(rule1, rule2),
+ APP_INSTALL_METADATA);
+
+ assertEquals(rule1, matchedRule);
+ }
+
+ @Test
+ public void testMatchRules_multipleMatches() {
+ Rule rule1 = new Rule(
+ new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
+ PACKAGE_NAME_1), Rule.Effect.DENY);
+ OpenFormula openFormula2 = new OpenFormula(OpenFormula.Connector.AND, Arrays.asList(
+ new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
+ PACKAGE_NAME_1),
+ new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE,
+ AtomicFormula.Operator.EQ,
+ APP_CERTIFICATE)));
+ Rule rule2 = new Rule(
+ openFormula2, Rule.Effect.DENY);
+
+ Rule matchedRule = RuleEvaluator.evaluateRules(Arrays.asList(rule1, rule2),
+ APP_INSTALL_METADATA);
+
+ assertNotEquals(Rule.EMPTY, matchedRule);
+ }
+
+ @Test
+ public void testMatchRules_ruleWithNot() {
+ OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.NOT,
+ Collections.singletonList(
+ new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
+ PACKAGE_NAME_2)));
+ Rule rule = new Rule(openFormula, Rule.Effect.DENY);
+
+ Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule),
+ APP_INSTALL_METADATA);
+
+ assertEquals(rule, matchedRule);
+ }
+
+ @Test
+ public void testMatchRules_ruleWithIntegerOperators() {
+ Rule rule1 = new Rule(
+ new AtomicFormula(AtomicFormula.Key.VERSION_CODE, AtomicFormula.Operator.GT,
+ 1), Rule.Effect.DENY);
+
+ Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule1),
+ APP_INSTALL_METADATA);
+
+ assertEquals(rule1, matchedRule);
+ }
+
+ @Test
+ public void testMatchRules_validForm() {
+ OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.AND, Arrays.asList(
+ new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
+ PACKAGE_NAME_1),
+ new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE,
+ AtomicFormula.Operator.EQ,
+ APP_CERTIFICATE)));
+ Rule rule = new Rule(
+ openFormula, Rule.Effect.DENY);
+
+ Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule),
+ APP_INSTALL_METADATA);
+
+ assertEquals(rule, matchedRule);
+ }
+
+ @Test
+ public void testMatchRules_ruleNotInDNF() {
+ OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.OR, Arrays.asList(
+ new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
+ PACKAGE_NAME_1),
+ new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE,
+ AtomicFormula.Operator.EQ,
+ APP_CERTIFICATE)));
+ Rule rule = new Rule(
+ openFormula, Rule.Effect.DENY);
+
+ Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule),
+ APP_INSTALL_METADATA);
+
+ assertEquals(Rule.EMPTY, matchedRule);
+ }
+
+ @Test
+ public void testMatchRules_openFormulaWithNot() {
+ OpenFormula openSubFormula = new OpenFormula(OpenFormula.Connector.AND, Arrays.asList(
+ new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
+ PACKAGE_NAME_2),
+ new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE,
+ AtomicFormula.Operator.EQ,
+ APP_CERTIFICATE)));
+ OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.NOT,
+ Collections.singletonList(openSubFormula));
+ Rule rule = new Rule(
+ openFormula, Rule.Effect.DENY);
+
+ Rule matchedRule = RuleEvaluator.evaluateRules(Collections.singletonList(rule),
+ APP_INSTALL_METADATA);
+
+ assertEquals(Rule.EMPTY, matchedRule);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/OpenFormulaTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/OpenFormulaTest.java
index 1a3dde0..2133a7d 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/model/OpenFormulaTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/model/OpenFormulaTest.java
@@ -24,6 +24,9 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.Arrays;
+import java.util.Collections;
+
@RunWith(JUnit4.class)
public class OpenFormulaTest {
@@ -34,12 +37,11 @@
@Test
public void testValidOpenFormula() {
- OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.AND, ATOMIC_FORMULA_1,
- ATOMIC_FORMULA_2);
+ OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.AND,
+ Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2));
assertEquals(OpenFormula.Connector.AND, openFormula.getConnector());
- assertEquals(ATOMIC_FORMULA_1, openFormula.getMainFormula());
- assertEquals(ATOMIC_FORMULA_2, openFormula.getAuxiliaryFormula());
+ assertEquals(Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2), openFormula.getFormulas());
}
@Test
@@ -47,9 +49,10 @@
assertExpectException(
IllegalArgumentException.class,
/* expectedExceptionMessageRegex */
- String.format("Invalid formulas used for connector %s", OpenFormula.Connector.AND),
- () -> new OpenFormula(OpenFormula.Connector.AND, ATOMIC_FORMULA_1,
- null));
+ String.format("Connector %s must have at least 2 formulas",
+ OpenFormula.Connector.AND),
+ () -> new OpenFormula(OpenFormula.Connector.AND,
+ Collections.singletonList(ATOMIC_FORMULA_1)));
}
@Test
@@ -57,8 +60,8 @@
assertExpectException(
IllegalArgumentException.class,
/* expectedExceptionMessageRegex */
- String.format("Invalid formulas used for connector %s", OpenFormula.Connector.NOT),
- () -> new OpenFormula(OpenFormula.Connector.NOT, ATOMIC_FORMULA_1,
- ATOMIC_FORMULA_2));
+ String.format("Connector %s must have 1 formula only", OpenFormula.Connector.NOT),
+ () -> new OpenFormula(OpenFormula.Connector.NOT,
+ Arrays.asList(ATOMIC_FORMULA_1, ATOMIC_FORMULA_2)));
}
}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java
index cf001be..048ee70 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java
@@ -19,19 +19,27 @@
import static com.android.server.testutils.TestUtils.assertExpectException;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNull;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.Arrays;
+
@RunWith(JUnit4.class)
public class RuleTest {
private static final Rule.Effect DENY_EFFECT = Rule.Effect.DENY;
- private static final Formula SIMPLE_FORMULA =
+ private static final String PACKAGE_NAME = "com.test.app";
+ private static final String APP_CERTIFICATE = "test_cert";
+ private static final Formula PACKAGE_NAME_ATOMIC_FORMULA =
new AtomicFormula(AtomicFormula.Key.PACKAGE_NAME, AtomicFormula.Operator.EQ,
- "com.test.app");
+ PACKAGE_NAME);
+ private static final Formula APP_CERTIFICATE_ATOMIC_FORMULA =
+ new AtomicFormula(AtomicFormula.Key.APP_CERTIFICATE, AtomicFormula.Operator.EQ,
+ APP_CERTIFICATE);
@Test
public void testEmptyRule() {
@@ -43,9 +51,9 @@
@Test
public void testValidRule() {
- Rule validRule = new Rule(SIMPLE_FORMULA, DENY_EFFECT);
+ Rule validRule = new Rule(PACKAGE_NAME_ATOMIC_FORMULA, DENY_EFFECT);
- assertEquals(SIMPLE_FORMULA, validRule.getFormula());
+ assertEquals(PACKAGE_NAME_ATOMIC_FORMULA, validRule.getFormula());
assertEquals(DENY_EFFECT, validRule.getEffect());
}
@@ -54,7 +62,7 @@
assertExpectException(
NullPointerException.class,
/* expectedExceptionMessageRegex */ null,
- () -> new Rule(SIMPLE_FORMULA, null));
+ () -> new Rule(PACKAGE_NAME_ATOMIC_FORMULA, null));
}
@Test
@@ -64,4 +72,32 @@
/* expectedExceptionMessageRegex */ null,
() -> new Rule(null, DENY_EFFECT));
}
+
+ @Test
+ public void testToString() {
+ OpenFormula openFormula = new OpenFormula(OpenFormula.Connector.AND,
+ Arrays.asList(PACKAGE_NAME_ATOMIC_FORMULA, APP_CERTIFICATE_ATOMIC_FORMULA));
+ Rule rule = new Rule(openFormula, Rule.Effect.DENY);
+
+ String toString = rule.toString();
+
+ assertEquals(String.format("Rule: PACKAGE_NAME EQ %s AND APP_CERTIFICATE EQ %s, DENY",
+ PACKAGE_NAME, APP_CERTIFICATE), toString);
+ }
+
+ @Test
+ public void testEquals_trueCase() {
+ Rule rule1 = new Rule(PACKAGE_NAME_ATOMIC_FORMULA, DENY_EFFECT);
+ Rule rule2 = new Rule(PACKAGE_NAME_ATOMIC_FORMULA, DENY_EFFECT);
+
+ assertEquals(rule1, rule2);
+ }
+
+ @Test
+ public void testEquals_falseCase() {
+ Rule rule1 = new Rule(PACKAGE_NAME_ATOMIC_FORMULA, DENY_EFFECT);
+ Rule rule2 = new Rule(APP_CERTIFICATE_ATOMIC_FORMULA, DENY_EFFECT);
+
+ assertNotEquals(rule1, rule2);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index f9ac022..537287d 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -35,7 +35,9 @@
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.hardware.authsecret.V1_0.IAuthSecret;
+import android.hardware.face.Face;
import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.os.FileUtils;
import android.os.IProgressListener;
@@ -249,11 +251,32 @@
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)).thenReturn(true);
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.hasEnrolledFingerprints(userId)).thenReturn(true);
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Fingerprint fp = (Fingerprint) invocation.getArguments()[0];
+ FingerprintManager.RemovalCallback callback =
+ (FingerprintManager.RemovalCallback) invocation.getArguments()[2];
+ callback.onRemovalSucceeded(fp, 0);
+ return null;
+ }
+ }).when(mFingerprintManager).remove(any(), eq(userId), any());
+
// Hardware must be detected and templates must be enrolled
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_FACE)).thenReturn(true);
when(mFaceManager.isHardwareDetected()).thenReturn(true);
when(mFaceManager.hasEnrolledTemplates(userId)).thenReturn(true);
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ Face face = (Face) invocation.getArguments()[0];
+ FaceManager.RemovalCallback callback =
+ (FaceManager.RemovalCallback) invocation.getArguments()[2];
+ callback.onRemovalSucceeded(face, 0);
+ return null;
+ }
+ }).when(mFaceManager).remove(any(), eq(userId), any());
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
index c00d33b..b60111e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
@@ -19,10 +19,9 @@
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
-import static com.android.internal.widget.LockPatternUtils.stringToPattern;
-
import static junit.framework.Assert.assertEquals;
+import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.never;
@@ -48,6 +47,8 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+import com.android.internal.widget.LockscreenCredential;
import org.junit.Before;
import org.junit.Test;
@@ -55,6 +56,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
+
/**
* Test class for {@link LockSettingsShellCommand}.
*
@@ -87,24 +90,30 @@
public void testWrongPassword() throws Exception {
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
- when(mLockPatternUtils.checkPassword("1234".getBytes(), mUserId)).thenReturn(false);
+ when(mLockPatternUtils.checkCredential(
+ LockscreenCredential.createPassword("1234"), mUserId, null)).thenReturn(false);
assertEquals(-1, mCommand.exec(mBinder, in, out, err,
new String[] { "set-pin", "--old", "1234" },
mShellCallback, mResultReceiver));
- verify(mLockPatternUtils, never()).saveLockPassword(any(byte[].class), any(byte[].class),
- anyInt(), anyInt());
+ verify(mLockPatternUtils, never()).setLockCredential(any(), any(),
+ anyInt(), anyBoolean());
}
@Test
public void testChangePin() throws Exception {
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
- when(mLockPatternUtils.checkPassword("1234".getBytes(), mUserId)).thenReturn(true);
+ when(mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId)).thenReturn(
+ PASSWORD_QUALITY_NUMERIC);
+ when(mLockPatternUtils.checkCredential(
+ LockscreenCredential.createPin("1234"), mUserId, null)).thenReturn(true);
assertEquals(0, mCommand.exec(new Binder(), in, out, err,
new String[] { "set-pin", "--old", "1234", "4321" },
mShellCallback, mResultReceiver));
- verify(mLockPatternUtils).saveLockPassword("4321".getBytes(), "1234".getBytes(),
- PASSWORD_QUALITY_NUMERIC, mUserId);
+ verify(mLockPatternUtils).setLockCredential(
+ LockscreenCredential.createPin("4321"),
+ LockscreenCredential.createPin("1234"),
+ mUserId);
}
@Test
@@ -121,12 +130,17 @@
public void testChangePassword() throws Exception {
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
- when(mLockPatternUtils.checkPassword("1234".getBytes(), mUserId)).thenReturn(true);
+ when(mLockPatternUtils.getKeyguardStoredPasswordQuality(mUserId)).thenReturn(
+ PASSWORD_QUALITY_ALPHABETIC);
+ when(mLockPatternUtils.checkCredential(
+ LockscreenCredential.createPassword("1234"), mUserId, null)).thenReturn(true);
assertEquals(0, mCommand.exec(new Binder(), in, out, err,
new String[] { "set-password", "--old", "1234", "4321" },
mShellCallback, mResultReceiver));
- verify(mLockPatternUtils).saveLockPassword("4321".getBytes(), "1234".getBytes(),
- PASSWORD_QUALITY_ALPHABETIC, mUserId);
+ verify(mLockPatternUtils).setLockCredential(
+ LockscreenCredential.createPassword("4321"),
+ LockscreenCredential.createPassword("1234"),
+ mUserId);
}
@Test
@@ -143,11 +157,15 @@
public void testChangePattern() throws Exception {
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
- when(mLockPatternUtils.checkPattern(stringToPattern("1234"), mUserId)).thenReturn(true);
+ when(mLockPatternUtils.checkCredential(
+ LockscreenCredential.createPattern(stringToPattern("1234")),
+ mUserId, null)).thenReturn(true);
assertEquals(0, mCommand.exec(new Binder(), in, out, err,
new String[] { "set-pattern", "--old", "1234", "4321" },
mShellCallback, mResultReceiver));
- verify(mLockPatternUtils).saveLockPattern(stringToPattern("4321"), "1234".getBytes(),
+ verify(mLockPatternUtils).setLockCredential(
+ LockscreenCredential.createPattern(stringToPattern("4321")),
+ LockscreenCredential.createPattern(stringToPattern("1234")),
mUserId);
}
@@ -165,10 +183,19 @@
public void testClear() throws Exception {
when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
- when(mLockPatternUtils.checkPattern(stringToPattern("1234"), mUserId)).thenReturn(true);
+ when(mLockPatternUtils.checkCredential(
+ LockscreenCredential.createPattern(stringToPattern("1234")),
+ mUserId, null)).thenReturn(true);
assertEquals(0, mCommand.exec(new Binder(), in, out, err,
new String[] { "clear", "--old", "1234" },
mShellCallback, mResultReceiver));
- verify(mLockPatternUtils).clearLock("1234".getBytes(), mUserId);
+ verify(mLockPatternUtils).setLockCredential(
+ LockscreenCredential.createNone(),
+ LockscreenCredential.createPattern(stringToPattern("1234")),
+ mUserId);
+ }
+
+ private List<LockPatternView.Cell> stringToPattern(String str) {
+ return LockPatternUtils.byteArrayToPattern(str.getBytes());
}
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 2a169b7..cb51897 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -434,35 +434,6 @@
assertEquals(2, PersistentData.TYPE_SP_WEAVER);
}
- public void testCredentialHash_serializeUnserialize() {
- byte[] serialized = CredentialHash.create(
- PAYLOAD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD).toBytes();
- CredentialHash deserialized = CredentialHash.fromBytes(serialized);
-
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, deserialized.type);
- assertArrayEquals(PAYLOAD, deserialized.hash);
- }
-
- public void testCredentialHash_unserialize_versionGatekeeper() {
- // This test ensures that we can read serialized VERSION_GATEKEEPER CredentialHashes
- // even if we change the wire format in the future.
- byte[] serialized = new byte[] {
- 1, /* VERSION_GATEKEEPER */
- 2, /* CREDENTIAL_TYPE_PASSWORD */
- 0, 0, 0, 5, /* hash length */
- 1, 2, -1, -2, 33, /* hash */
- };
- CredentialHash deserialized = CredentialHash.fromBytes(serialized);
-
- assertEquals(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, deserialized.type);
- assertArrayEquals(PAYLOAD, deserialized.hash);
-
- // Make sure the constants we use on the wire do not change.
- assertEquals(-1, LockPatternUtils.CREDENTIAL_TYPE_NONE);
- assertEquals(1, LockPatternUtils.CREDENTIAL_TYPE_PATTERN);
- assertEquals(2, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
- }
-
private static void assertArrayEquals(byte[] expected, byte[] actual) {
if (!Arrays.equals(expected, actual)) {
fail("expected:<" + Arrays.toString(expected) +
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 0776589..42ca42a 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -39,6 +39,7 @@
import androidx.test.filters.SmallTest;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockscreenCredential;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationResult;
import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
@@ -364,7 +365,7 @@
// Verify DPM gets notified about new device lock
flushHandlerTasks();
final PasswordMetrics metric = PasswordMetrics.computeForCredential(
- LockPatternUtils.CREDENTIAL_TYPE_PATTERN, pattern);
+ LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(pattern)));
assertEquals(metric, mService.getUserPasswordMetrics(PRIMARY_USER_ID));
verify(mDevicePolicyManager).reportPasswordChanged(PRIMARY_USER_ID);
@@ -512,7 +513,7 @@
assertFalse(mService.havePattern(PRIMARY_USER_ID));
}
- public void testgetHashFactorPrimaryUser() throws RemoteException {
+ public void testGetHashFactorPrimaryUser() throws RemoteException {
final byte[] password = "password".getBytes();
mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID, false);
@@ -527,7 +528,7 @@
assertArrayEquals(hashFactor, newHashFactor);
}
- public void testgetHashFactorManagedProfileUnifiedChallenge() throws RemoteException {
+ public void testGetHashFactorManagedProfileUnifiedChallenge() throws RemoteException {
final byte[] pattern = "1236".getBytes();
mService.setLockCredential(pattern, LockPatternUtils.CREDENTIAL_TYPE_PATTERN,
null, PASSWORD_QUALITY_SOMETHING, PRIMARY_USER_ID, false);
@@ -535,7 +536,7 @@
assertNotNull(mService.getHashFactor(null, MANAGED_PROFILE_USER_ID));
}
- public void testgetHashFactorManagedProfileSeparateChallenge() throws RemoteException {
+ public void testGetHashFactorManagedProfileSeparateChallenge() throws RemoteException {
final byte[] primaryPassword = "primary".getBytes();
final byte[] profilePassword = "profile".getBytes();
mService.setLockCredential(primaryPassword, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,
diff --git a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
index fe7a376..25b41db 100644
--- a/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/ConnOnActivityStartTest.java
@@ -29,7 +29,6 @@
import android.os.Bundle;
import android.os.IBinder;
import android.os.SystemClock;
-import android.provider.Settings;
import android.support.test.uiautomator.UiDevice;
import android.text.TextUtils;
import android.util.Log;
@@ -88,17 +87,11 @@
private static final int REPEAT_TEST_COUNT = 5;
- private static final String KEY_PAROLE_DURATION = "parole_duration";
- private static final String DESIRED_PAROLE_DURATION = "0";
-
private static Context mContext;
private static UiDevice mUiDevice;
private static int mTestPkgUid;
private static BatteryManager mBatteryManager;
- private static boolean mAppIdleConstsUpdated;
- private static String mOriginalAppIdleConsts;
-
private static ServiceConnection mServiceConnection;
private static ICmdReceiverService mCmdReceiverService;
@@ -107,7 +100,6 @@
mContext = InstrumentationRegistry.getContext();
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
- setDesiredParoleDuration();
mContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
mTestPkgUid = mContext.getPackageManager().getPackageUid(TEST_PKG, 0);
@@ -119,10 +111,6 @@
@AfterClass
public static void tearDownOnce() throws Exception {
batteryReset();
- if (mAppIdleConstsUpdated) {
- Settings.Global.putString(mContext.getContentResolver(),
- Settings.Global.APP_IDLE_CONSTANTS, mOriginalAppIdleConsts);
- }
unbindService();
}
@@ -160,27 +148,6 @@
}
}
- private static void setDesiredParoleDuration() {
- mOriginalAppIdleConsts = Settings.Global.getString(mContext.getContentResolver(),
- Settings.Global.APP_IDLE_CONSTANTS);
- String newAppIdleConstants;
- final String newConstant = KEY_PAROLE_DURATION + "=" + DESIRED_PAROLE_DURATION;
- if (mOriginalAppIdleConsts == null || "null".equals(mOriginalAppIdleConsts)) {
- // app_idle_constants is initially empty, so just assign the desired value.
- newAppIdleConstants = newConstant;
- } else if (mOriginalAppIdleConsts.contains(KEY_PAROLE_DURATION)) {
- // app_idle_constants contains parole_duration, so replace it with the desired value.
- newAppIdleConstants = mOriginalAppIdleConsts.replaceAll(
- KEY_PAROLE_DURATION + "=\\d+", newConstant);
- } else {
- // app_idle_constants didn't have parole_duration, so append the desired value.
- newAppIdleConstants = mOriginalAppIdleConsts + "," + newConstant;
- }
- Settings.Global.putString(mContext.getContentResolver(),
- Settings.Global.APP_IDLE_CONSTANTS, newAppIdleConstants);
- mAppIdleConstsUpdated = true;
- }
-
@Test
public void testStartActivity_batterySaver() throws Exception {
setBatterySaverMode(true);
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index ba12b73..8a48904 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -113,6 +113,7 @@
import android.net.NetworkTemplate;
import android.net.StringNetworkSpecifier;
import android.os.Binder;
+import android.os.Handler;
import android.os.INetworkManagementService;
import android.os.PersistableBundle;
import android.os.PowerManagerInternal;
@@ -1117,7 +1118,7 @@
// Define simple data plan
final SubscriptionPlan plan = buildMonthlyDataPlan(
ZonedDateTime.parse("2015-11-01T00:00:00.00Z"), DataUnit.MEGABYTES.toBytes(1800));
- mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[] { plan },
+ setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[] { plan },
mServiceContext.getOpPackageName());
// We're 20% through the month (6 days)
@@ -1241,7 +1242,7 @@
// Define simple data plan which gives us effectively 60MB/day
final SubscriptionPlan plan = buildMonthlyDataPlan(
ZonedDateTime.parse("2015-11-01T00:00:00.00Z"), DataUnit.MEGABYTES.toBytes(1800));
- mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[] { plan },
+ setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[] { plan },
mServiceContext.getOpPackageName());
// We're 20% through the month (6 days)
@@ -1457,6 +1458,8 @@
when(mConnManager.getAllNetworkState()).thenReturn(new NetworkState[0]);
when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{FAKE_SUB_ID});
when(mTelephonyManager.getSubscriberId(FAKE_SUB_ID)).thenReturn(FAKE_SUBSCRIBER_ID);
+ when(mTelephonyManager.createForSubscriptionId(FAKE_SUB_ID))
+ .thenReturn(mock(TelephonyManager.class));
PersistableBundle bundle = CarrierConfigManager.getDefaultConfig();
when(mCarrierConfigManager.getConfigForSubId(FAKE_SUB_ID)).thenReturn(bundle);
setNetworkPolicies(buildDefaultFakeMobilePolicy());
@@ -1468,6 +1471,8 @@
when(mConnManager.getAllNetworkState()).thenReturn(new NetworkState[0]);
when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(new int[]{FAKE_SUB_ID});
when(mTelephonyManager.getSubscriberId(FAKE_SUB_ID)).thenReturn(FAKE_SUBSCRIBER_ID);
+ when(mTelephonyManager.createForSubscriptionId(FAKE_SUB_ID))
+ .thenReturn(mock(TelephonyManager.class));
when(mCarrierConfigManager.getConfigForSubId(FAKE_SUB_ID)).thenReturn(null);
setNetworkPolicies(buildDefaultFakeMobilePolicy());
// smoke test to make sure no errors are raised
@@ -1653,7 +1658,7 @@
final SubscriptionPlan plan = buildMonthlyDataPlan(
ZonedDateTime.parse("2015-11-01T00:00:00.00Z"),
DataUnit.MEGABYTES.toBytes(1800));
- mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
+ setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
mServiceContext.getOpPackageName());
reset(mTelephonyManager, mNetworkManager, mNotifManager);
@@ -1674,7 +1679,7 @@
final SubscriptionPlan plan = buildMonthlyDataPlan(
ZonedDateTime.parse("2015-11-01T00:00:00.00Z"),
DataUnit.MEGABYTES.toBytes(100));
- mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
+ setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
mServiceContext.getOpPackageName());
reset(mTelephonyManager, mNetworkManager, mNotifManager);
@@ -1690,7 +1695,7 @@
{
final SubscriptionPlan plan = buildMonthlyDataPlan(
ZonedDateTime.parse("2015-11-01T00:00:00.00Z"), BYTES_UNLIMITED);
- mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
+ setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
mServiceContext.getOpPackageName());
reset(mTelephonyManager, mNetworkManager, mNotifManager);
@@ -1707,7 +1712,7 @@
{
final SubscriptionPlan plan = buildMonthlyDataPlan(
ZonedDateTime.parse("2015-11-01T00:00:00.00Z"), BYTES_UNLIMITED);
- mService.setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
+ setSubscriptionPlans(TEST_SUB_ID, new SubscriptionPlan[]{plan},
mServiceContext.getOpPackageName());
reset(mTelephonyManager, mNetworkManager, mNotifManager);
@@ -1923,6 +1928,8 @@
when(mSubscriptionManager.getActiveSubscriptionIdList()).thenReturn(
new int[] { TEST_SUB_ID });
when(mTelephonyManager.getSubscriberId(TEST_SUB_ID)).thenReturn(TEST_IMSI);
+ when(mTelephonyManager.createForSubscriptionId(TEST_SUB_ID))
+ .thenReturn(mock(TelephonyManager.class));
doNothing().when(mTelephonyManager).setPolicyDataEnabled(anyBoolean(), anyInt());
expectNetworkState(false /* roaming */);
}
@@ -2049,6 +2056,23 @@
private FutureIntent mRestrictBackgroundChanged;
+ private void postMsgAndWaitForCompletion() throws InterruptedException {
+ final Handler handler = mService.getHandlerForTesting();
+ final CountDownLatch latch = new CountDownLatch(1);
+ mService.getHandlerForTesting().post(latch::countDown);
+ if (!latch.await(5, TimeUnit.SECONDS)) {
+ fail("Timed out waiting for the test msg to be handled");
+ }
+ }
+
+ private void setSubscriptionPlans(int subId, SubscriptionPlan[] plans, String callingPackage)
+ throws InterruptedException {
+ mService.setSubscriptionPlans(subId, plans, callingPackage);
+ // setSubscriptionPlans() triggers async events, wait for those to be completed before
+ // moving forward as they could interfere with the tests later.
+ postMsgAndWaitForCompletion();
+ }
+
private void setRestrictBackground(boolean flag) throws Exception {
mService.setRestrictBackground(flag);
// Sanity check.
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index 43bcd4f..7c2a097 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -167,6 +167,7 @@
/* userId */ 456,
/* installerPackageName */ "testInstaller",
/* installerUid */ -1,
+ InstallSource.create("testInstaller"),
/* sessionParams */ params,
/* createdMillis */ 0L,
/* stageDir */ mTmpDir,
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 4ffcf8f..12ba219 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -47,7 +47,6 @@
import android.app.usage.AppStandbyInfo;
import android.app.usage.UsageEvents;
-import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.ContextWrapper;
@@ -77,7 +76,6 @@
import java.util.Arrays;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
@@ -108,8 +106,6 @@
private static final long WORKING_SET_THRESHOLD = 12 * HOUR_MS;
private static final long FREQUENT_THRESHOLD = 24 * HOUR_MS;
private static final long RARE_THRESHOLD = 48 * HOUR_MS;
- // Short STABLE_CHARGING_THRESHOLD for testing purposes
- private static final long STABLE_CHARGING_THRESHOLD = 2000;
/** Mock variable used in {@link MyInjector#isPackageInstalled(String, int, int)} */
private static boolean isPackageInstalled = true;
@@ -132,7 +128,6 @@
static class MyInjector extends AppStandbyController.Injector {
long mElapsedRealtime;
boolean mIsAppIdleEnabled = true;
- boolean mIsCharging;
List<String> mPowerSaveWhitelistExceptIdle = new ArrayList<>();
boolean mDisplayOn;
DisplayManager.DisplayListener mDisplayListener;
@@ -167,11 +162,6 @@
}
@Override
- boolean isCharging() {
- return mIsCharging;
- }
-
- @Override
boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
return mPowerSaveWhitelistExceptIdle.contains(packageName);
}
@@ -228,8 +218,7 @@
return "screen_thresholds=0/0/0/" + HOUR_MS + ",elapsed_thresholds=0/"
+ WORKING_SET_THRESHOLD + "/"
+ FREQUENT_THRESHOLD + "/"
- + RARE_THRESHOLD + ","
- + "stable_charging_threshold=" + STABLE_CHARGING_THRESHOLD;
+ + RARE_THRESHOLD;
}
@Override
@@ -273,13 +262,6 @@
} catch (PackageManager.NameNotFoundException nnfe) {}
}
- private void setChargingState(AppStandbyController controller, boolean charging) {
- mInjector.mIsCharging = charging;
- if (controller != null) {
- controller.setChargingState(charging);
- }
- }
-
private void setAppIdleEnabled(AppStandbyController controller, boolean enabled) {
mInjector.mIsAppIdleEnabled = enabled;
if (controller != null) {
@@ -296,7 +278,6 @@
controller.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
mInjector.setDisplayOn(false);
mInjector.setDisplayOn(true);
- setChargingState(controller, false);
controller.checkIdleStates(USER_ID);
assertNotEquals(STANDBY_BUCKET_EXEMPTED,
controller.getAppStandbyBucket(PACKAGE_1, USER_ID,
@@ -314,65 +295,6 @@
MyContextWrapper myContext = new MyContextWrapper(InstrumentationRegistry.getContext());
mInjector = new MyInjector(myContext, Looper.getMainLooper());
mController = setupController();
- setChargingState(mController, false);
- }
-
- private class TestParoleListener extends UsageStatsManagerInternal.AppIdleStateChangeListener {
- private boolean mOnParole = false;
- private CountDownLatch mLatch;
- private long mLastParoleChangeTime;
- private boolean mIsExpecting = false;
- private boolean mExpectedParoleState;
-
- public boolean getParoleState() {
- synchronized (this) {
- return mOnParole;
- }
- }
-
- public void rearmLatch() {
- synchronized (this) {
- mLatch = new CountDownLatch(1);
- mIsExpecting = false;
- }
- }
-
- public void rearmLatch(boolean expectedParoleState) {
- synchronized (this) {
- mLatch = new CountDownLatch(1);
- mIsExpecting = true;
- mExpectedParoleState = expectedParoleState;
- }
- }
-
- public void awaitOnLatch(long time) throws Exception {
- mLatch.await(time, TimeUnit.MILLISECONDS);
- }
-
- public long getLastParoleChangeTime() {
- synchronized (this) {
- return mLastParoleChangeTime;
- }
- }
-
- @Override
- public void onAppIdleStateChanged(String packageName, int userId, boolean idle,
- int bucket, int reason) {
- }
-
- @Override
- public void onParoleStateChanged(boolean isParoleOn) {
- synchronized (this) {
- // Only record information if it is being looked for
- if (mLatch != null && mLatch.getCount() > 0) {
- mOnParole = isParoleOn;
- mLastParoleChangeTime = getCurrentTime();
- if (!mIsExpecting || isParoleOn == mExpectedParoleState) {
- mLatch.countDown();
- }
- }
- }
- }
}
@Test
@@ -383,133 +305,6 @@
mInjector.mElapsedRealtime, false));
}
- @Test
- public void testCharging() throws Exception {
- long startTime;
- TestParoleListener paroleListener = new TestParoleListener();
- long marginOfError = 200;
-
- // Charging
- paroleListener.rearmLatch();
- mController.addListener(paroleListener);
- startTime = getCurrentTime();
- setChargingState(mController, true);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertTrue(paroleListener.mOnParole);
- // Parole will only be granted after device has been charging for a sufficient amount of
- // time.
- assertEquals(STABLE_CHARGING_THRESHOLD,
- paroleListener.getLastParoleChangeTime() - startTime,
- marginOfError);
-
- // Discharging
- paroleListener.rearmLatch();
- startTime = getCurrentTime();
- setChargingState(mController, false);
- mController.checkIdleStates(USER_ID);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertFalse(paroleListener.getParoleState());
- // Parole should be revoked immediately
- assertEquals(0,
- paroleListener.getLastParoleChangeTime() - startTime,
- marginOfError);
-
- // Brief Charging
- paroleListener.rearmLatch();
- setChargingState(mController, true);
- setChargingState(mController, false);
- // Device stopped charging before the stable charging threshold.
- // Parole should not be granted at the end of the threshold
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertFalse(paroleListener.getParoleState());
-
- // Charging Again
- paroleListener.rearmLatch();
- startTime = getCurrentTime();
- setChargingState(mController, true);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertTrue(paroleListener.getParoleState());
- assertTrue(paroleListener.mOnParole);
- assertEquals(STABLE_CHARGING_THRESHOLD,
- paroleListener.getLastParoleChangeTime() - startTime,
- marginOfError);
- }
-
- @Test
- public void testEnabledState() throws Exception {
- TestParoleListener paroleListener = new TestParoleListener();
- paroleListener.rearmLatch(true);
- mController.addListener(paroleListener);
- long lastUpdateTime;
-
- // Test that listeners are notified if enabled changes when the device is not in parole.
- setChargingState(mController, false);
-
- // Start off not enabled. Device is effectively in permanent parole.
- setAppIdleEnabled(mController, false);
- // Since AppStandbyController uses a handler to notify listeners of a state change, there is
- // some inherent latency between changing the state and getting the notification. We need to
- // wait until the paroleListener has been notified that parole is on before continuing with
- // the test.
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertTrue(paroleListener.mOnParole);
-
- // Enable controller
- paroleListener.rearmLatch();
- setAppIdleEnabled(mController, true);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertFalse(paroleListener.mOnParole);
- lastUpdateTime = paroleListener.getLastParoleChangeTime();
-
- paroleListener.rearmLatch();
- setAppIdleEnabled(mController, true);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertFalse(paroleListener.mOnParole);
- // Make sure AppStandbyController doesn't notify listeners when there's no change.
- assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime());
-
- // Disable controller
- paroleListener.rearmLatch();
- setAppIdleEnabled(mController, false);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertTrue(paroleListener.mOnParole);
- lastUpdateTime = paroleListener.getLastParoleChangeTime();
-
- paroleListener.rearmLatch();
- setAppIdleEnabled(mController, false);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertTrue(paroleListener.mOnParole);
- // Make sure AppStandbyController doesn't notify listeners when there's no change.
- assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime());
-
-
- // Test that listeners aren't notified if enabled status changes when the device is already
- // in parole.
-
- // A device is in parole whenever it's charging.
- setChargingState(mController, true);
-
- // Start off not enabled.
- paroleListener.rearmLatch();
- setAppIdleEnabled(mController, false);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertTrue(paroleListener.mOnParole);
- lastUpdateTime = paroleListener.getLastParoleChangeTime();
-
- // Test that toggling doesn't notify the listener.
- paroleListener.rearmLatch();
- setAppIdleEnabled(mController, true);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertTrue(paroleListener.mOnParole);
- assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime());
-
- paroleListener.rearmLatch();
- setAppIdleEnabled(mController, false);
- paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
- assertTrue(paroleListener.mOnParole);
- assertEquals(lastUpdateTime, paroleListener.getLastParoleChangeTime());
- }
-
private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket) {
mInjector.mElapsedRealtime = elapsedTime;
controller.checkIdleStates(USER_ID);
@@ -804,8 +599,6 @@
@Test
public void testSystemInteractionTimeout() throws Exception {
- setChargingState(mController, false);
-
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
// Fast forward to RARE
mInjector.mElapsedRealtime = RARE_THRESHOLD + 100;
@@ -829,8 +622,6 @@
@Test
public void testInitialForegroundServiceTimeout() throws Exception {
- setChargingState(mController, false);
-
mInjector.mElapsedRealtime = 1 * RARE_THRESHOLD + 100;
// Make sure app is in NEVER bucket
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
index 8c3373f..3ae5674 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/BuzzBeepBlinkTest.java
@@ -22,6 +22,7 @@
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
+import static android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNull;
@@ -105,6 +106,7 @@
private int mUid = 1000;
private int mPid = 2000;
private android.os.UserHandle mUser = UserHandle.of(ActivityManager.getCurrentUser());
+ private NotificationChannel mChannel;
private VibrateRepeatMatcher mVibrateOnceMatcher = new VibrateRepeatMatcher(-1);
private VibrateRepeatMatcher mVibrateLoopMatcher = new VibrateRepeatMatcher(0);
@@ -158,6 +160,8 @@
mService.mScreenOn = false;
mService.mInCallStateOffHook = false;
mService.mNotificationPulseEnabled = true;
+
+ mChannel = new NotificationChannel("test", "test", IMPORTANCE_HIGH);
}
//
@@ -174,13 +178,18 @@
true /* noisy */, false /* buzzy*/, false /* lights */);
}
+ private NotificationRecord getBeepyOtherNotification() {
+ return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
+ true /* noisy */, false /* buzzy*/, false /* lights */);
+ }
+
private NotificationRecord getBeepyOnceNotification() {
return getNotificationRecord(mId, false /* insistent */, true /* once */,
true /* noisy */, false /* buzzy*/, false /* lights */);
}
private NotificationRecord getQuietNotification() {
- return getNotificationRecord(mId, false /* insistent */, false /* once */,
+ return getNotificationRecord(mId, false /* insistent */, true /* once */,
false /* noisy */, false /* buzzy*/, false /* lights */);
}
@@ -214,6 +223,11 @@
false /* noisy */, true /* buzzy*/, false /* lights */);
}
+ private NotificationRecord getBuzzyOtherNotification() {
+ return getNotificationRecord(mOtherId, false /* insistent */, false /* once */,
+ false /* noisy */, true /* buzzy*/, false /* lights */);
+ }
+
private NotificationRecord getBuzzyOnceNotification() {
return getNotificationRecord(mId, false /* insistent */, true /* once */,
false /* noisy */, true /* buzzy*/, false /* lights */);
@@ -239,22 +253,34 @@
false /* noisy */, false /* buzzy*/, true /* lights */);
}
- private NotificationRecord getCallRecord(int id, boolean insistent) {
- return getNotificationRecord(id, false, false /* once */, true /* noisy */,
- false /* buzzy */, false /* lights */, false /* default vib */,
- false /* default sound */, false /* default lights */, "",
- Notification.GROUP_ALERT_ALL, false);
+ private NotificationRecord getCallRecord(int id, NotificationChannel channel, boolean looping) {
+ final Builder builder = new Builder(getContext())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setPriority(Notification.PRIORITY_HIGH);
+ Notification n = builder.build();
+ if (looping) {
+ n.flags |= Notification.FLAG_INSISTENT;
+ }
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
+ mPid, n, mUser, null, System.currentTimeMillis());
+ NotificationRecord r = new NotificationRecord(getContext(), sbn, channel);
+ mService.addNotification(r);
+
+ return r;
}
private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
boolean noisy, boolean buzzy, boolean lights) {
- return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true, true,
- null, Notification.GROUP_ALERT_ALL, false);
+ return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, buzzy, noisy,
+ lights, null, Notification.GROUP_ALERT_ALL, false);
}
- private NotificationRecord getLeanbackNotificationRecord(int id, boolean insistent, boolean once,
+ private NotificationRecord getLeanbackNotificationRecord(int id, boolean insistent,
+ boolean once,
boolean noisy, boolean buzzy, boolean lights) {
- return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true, true,
+ return getNotificationRecord(id, insistent, once, noisy, buzzy, lights, true, true,
+ true,
null, Notification.GROUP_ALERT_ALL, true);
}
@@ -265,16 +291,16 @@
private NotificationRecord getLightsNotificationRecord(String groupKey,
int groupAlertBehavior) {
- return getNotificationRecord(mId, false, false, false, false, true /*lights*/, true, true,
- true, groupKey, groupAlertBehavior, false);
+ return getNotificationRecord(mId, false, false, false, false, true /*lights*/, true,
+ true, true, groupKey, groupAlertBehavior, false);
}
- private NotificationRecord getNotificationRecord(int id, boolean insistent, boolean once,
+ private NotificationRecord getNotificationRecord(int id,
+ boolean insistent, boolean once,
boolean noisy, boolean buzzy, boolean lights, boolean defaultVibration,
boolean defaultSound, boolean defaultLights, String groupKey, int groupAlertBehavior,
boolean isLeanback) {
- NotificationChannel channel =
- new NotificationChannel("test", "test", IMPORTANCE_HIGH);
+
final Builder builder = new Builder(getContext())
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
@@ -285,31 +311,37 @@
if (noisy) {
if (defaultSound) {
defaults |= Notification.DEFAULT_SOUND;
- channel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
+ mChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
Notification.AUDIO_ATTRIBUTES_DEFAULT);
} else {
builder.setSound(CUSTOM_SOUND);
- channel.setSound(CUSTOM_SOUND, CUSTOM_ATTRIBUTES);
+ mChannel.setSound(CUSTOM_SOUND, CUSTOM_ATTRIBUTES);
}
} else {
- channel.setSound(null, null);
+ mChannel.setSound(null, null);
}
if (buzzy) {
if (defaultVibration) {
defaults |= Notification.DEFAULT_VIBRATE;
} else {
builder.setVibrate(CUSTOM_VIBRATION);
- channel.setVibrationPattern(CUSTOM_VIBRATION);
+ mChannel.setVibrationPattern(CUSTOM_VIBRATION);
}
- channel.enableVibration(true);
+ mChannel.enableVibration(true);
+ } else {
+ mChannel.setVibrationPattern(null);
+ mChannel.enableVibration(false);
}
+
if (lights) {
if (defaultLights) {
defaults |= Notification.DEFAULT_LIGHTS;
} else {
builder.setLights(CUSTOM_LIGHT_COLOR, CUSTOM_LIGHT_ON, CUSTOM_LIGHT_OFF);
}
- channel.enableLights(true);
+ mChannel.enableLights(true);
+ } else {
+ mChannel.enableLights(false);
}
builder.setDefaults(defaults);
@@ -329,7 +361,7 @@
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, id, mTag, mUid,
mPid, n, mUser, null, System.currentTimeMillis());
- NotificationRecord r = new NotificationRecord(context, sbn, channel);
+ NotificationRecord r = new NotificationRecord(context, sbn, mChannel);
mService.addNotification(r);
return r;
}
@@ -339,18 +371,19 @@
//
private void verifyNeverBeep() throws RemoteException {
- verify(mRingtonePlayer, never()).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
- anyBoolean(), (AudioAttributes) anyObject());
+ verify(mRingtonePlayer, never()).playAsync(any(), any(), anyBoolean(), any());
}
- private void verifyBeep() throws RemoteException {
- verify(mRingtonePlayer, times(1)).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
- eq(true), (AudioAttributes) anyObject());
+ private void verifyBeepUnlooped() throws RemoteException {
+ verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(false), any());
}
- private void verifyBeepLooped() throws RemoteException {
- verify(mRingtonePlayer, times(1)).playAsync((Uri) anyObject(), (UserHandle) anyObject(),
- eq(false), (AudioAttributes) anyObject());
+ private void verifyBeepLooped() throws RemoteException {
+ verify(mRingtonePlayer, times(1)).playAsync(any(), any(), eq(true), any());
+ }
+
+ private void verifyBeep(int times) throws RemoteException {
+ verify(mRingtonePlayer, times(times)).playAsync(any(), any(), anyBoolean(), any());
}
private void verifyNeverStopAudio() throws RemoteException {
@@ -362,24 +395,31 @@
}
private void verifyNeverVibrate() {
- verify(mVibrator, never()).vibrate(anyInt(), anyString(), (VibrationEffect) anyObject(),
- anyString(), (AudioAttributes) anyObject());
+ verify(mVibrator, never()).vibrate(anyInt(), anyString(), any(), anyString(), any());
}
private void verifyVibrate() {
verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateOnceMatcher),
- anyString(), (AudioAttributes) anyObject());
+ anyString(), any());
+ }
+
+ private void verifyVibrate(int times) {
+ verify(mVibrator, times(times)).vibrate(anyInt(), anyString(), any(), anyString(), any());
}
private void verifyVibrateLooped() {
verify(mVibrator, times(1)).vibrate(anyInt(), anyString(), argThat(mVibrateLoopMatcher),
- anyString(), (AudioAttributes) anyObject());
+ anyString(), any());
}
private void verifyDelayedVibrateLooped() {
verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(),
- argThat(mVibrateLoopMatcher), anyString(),
- (AudioAttributes) anyObject());
+ argThat(mVibrateLoopMatcher), anyString(), any());
+ }
+
+ private void verifyDelayedVibrate() {
+ verify(mVibrator, timeout(MAX_VIBRATION_DELAY).times(1)).vibrate(anyInt(), anyString(),
+ argThat(mVibrateOnceMatcher), anyString(), any());
}
private void verifyStopVibrate() {
@@ -398,11 +438,6 @@
verify(mLight, times(1)).setFlashing(anyInt(), anyInt(), anyInt(), anyInt());
}
- private void verifyCustomLights() {
- verify(mLight, times(1)).setFlashing(
- eq(CUSTOM_LIGHT_COLOR), anyInt(), eq(CUSTOM_LIGHT_ON), eq(CUSTOM_LIGHT_OFF));
- }
-
//
// Tests
//
@@ -425,7 +460,7 @@
mService.buzzBeepBlinkLocked(r);
- verifyBeepLooped();
+ verifyBeepUnlooped();
verifyNeverVibrate();
verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt());
assertTrue(r.isInterruptive());
@@ -438,7 +473,7 @@
mService.buzzBeepBlinkLocked(r);
- verifyBeep();
+ verifyBeepLooped();
assertTrue(r.isInterruptive());
assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
@@ -492,7 +527,7 @@
mService.buzzBeepBlinkLocked(r);
- verifyBeepLooped();
+ verifyBeepUnlooped();
assertTrue(r.isInterruptive());
}
@@ -533,7 +568,7 @@
// update should beep
r.isUpdate = true;
mService.buzzBeepBlinkLocked(r);
- verifyBeepLooped();
+ verifyBeepUnlooped();
verify(mAccessibilityService, times(2)).sendAccessibilityEvent(any(), anyInt());
assertTrue(r.isInterruptive());
assertNotEquals(-1, r.getLastAudiblyAlertedMs());
@@ -723,7 +758,7 @@
mService.buzzBeepBlinkLocked(r);
verifyNeverVibrate();
- verifyBeepLooped();
+ verifyBeepUnlooped();
assertTrue(r.isInterruptive());
assertNotEquals(-1, r.getLastAudiblyAlertedMs());
}
@@ -821,7 +856,7 @@
mService.buzzBeepBlinkLocked(summary);
- verifyBeepLooped();
+ verifyBeepUnlooped();
// summaries are never interruptive for notification counts
assertFalse(summary.isInterruptive());
assertNotEquals(-1, summary.getLastAudiblyAlertedMs());
@@ -833,7 +868,7 @@
mService.buzzBeepBlinkLocked(nonGroup);
- verifyBeepLooped();
+ verifyBeepUnlooped();
assertTrue(nonGroup.isInterruptive());
assertNotEquals(-1, nonGroup.getLastAudiblyAlertedMs());
}
@@ -856,7 +891,7 @@
mService.buzzBeepBlinkLocked(child);
- verifyBeepLooped();
+ verifyBeepUnlooped();
assertTrue(child.isInterruptive());
assertNotEquals(-1, child.getLastAudiblyAlertedMs());
}
@@ -867,7 +902,7 @@
mService.buzzBeepBlinkLocked(nonGroup);
- verifyBeepLooped();
+ verifyBeepUnlooped();
assertTrue(nonGroup.isInterruptive());
assertNotEquals(-1, nonGroup.getLastAudiblyAlertedMs());
}
@@ -878,7 +913,7 @@
mService.buzzBeepBlinkLocked(group);
- verifyBeepLooped();
+ verifyBeepUnlooped();
assertTrue(group.isInterruptive());
assertNotEquals(-1, group.getLastAudiblyAlertedMs());
}
@@ -1293,7 +1328,11 @@
@Test
public void testListenerHintCall() throws Exception {
- NotificationRecord r = getCallRecord(1, true);
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ NotificationRecord r = getCallRecord(1, ringtoneChannel, true);
mService.setHints(NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS);
@@ -1310,7 +1349,7 @@
mService.buzzBeepBlinkLocked(r);
- verifyBeepLooped();
+ verifyBeepUnlooped();
}
@Test
@@ -1326,7 +1365,11 @@
@Test
public void testListenerHintBoth() throws Exception {
- NotificationRecord r = getCallRecord(1, true);
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ NotificationRecord r = getCallRecord(1, ringtoneChannel, true);
NotificationRecord s = getBeepyNotification();
mService.setHints(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS
@@ -1340,7 +1383,11 @@
@Test
public void testListenerHintNotification_callSound() throws Exception {
- NotificationRecord r = getCallRecord(1, true);
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ NotificationRecord r = getCallRecord(1, ringtoneChannel, true);
mService.setHints(NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS);
@@ -1349,6 +1396,167 @@
verifyBeepLooped();
}
+ @Test
+ public void testCannotInterruptRingtoneInsistentBeep() throws Exception {
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
+ mService.addNotification(ringtoneNotification);
+
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+ verifyBeepLooped();
+
+ NotificationRecord interrupter = getBeepyOtherNotification();
+ assertTrue(mService.shouldMuteNotificationLocked(interrupter));
+ mService.buzzBeepBlinkLocked(interrupter);
+
+ verifyBeep(1);
+
+ assertFalse(interrupter.isInterruptive());
+ assertEquals(-1, interrupter.getLastAudiblyAlertedMs());
+ }
+
+ @Test
+ public void testCannotInterruptRingtoneInsistentBuzz() {
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(Uri.EMPTY,
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ ringtoneChannel.enableVibration(true);
+ NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
+ assertFalse(mService.shouldMuteNotificationLocked(ringtoneNotification));
+
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+ verifyVibrateLooped();
+
+ NotificationRecord interrupter = getBuzzyOtherNotification();
+ assertTrue(mService.shouldMuteNotificationLocked(interrupter));
+ mService.buzzBeepBlinkLocked(interrupter);
+
+ verifyVibrate(1);
+
+ assertFalse(interrupter.isInterruptive());
+ assertEquals(-1, interrupter.getLastAudiblyAlertedMs());
+ }
+
+ @Test
+ public void testCanInterruptRingtoneNonInsistentBeep() throws Exception {
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, false);
+
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+ verifyBeepUnlooped();
+
+ NotificationRecord interrupter = getBeepyOtherNotification();
+ mService.buzzBeepBlinkLocked(interrupter);
+
+ verifyBeep(2);
+
+ assertTrue(interrupter.isInterruptive());
+ }
+
+ @Test
+ public void testCanInterruptRingtoneNonInsistentBuzz() {
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(null,
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ ringtoneChannel.enableVibration(true);
+ NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, false);
+
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+ verifyVibrate();
+
+ NotificationRecord interrupter = getBuzzyOtherNotification();
+ mService.buzzBeepBlinkLocked(interrupter);
+
+ verifyVibrate(2);
+
+ assertTrue(interrupter.isInterruptive());
+ }
+
+ @Test
+ public void testRingtoneInsistentBeep_doesNotBlockFutureSoundsOnceStopped() throws Exception {
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(Settings.System.DEFAULT_RINGTONE_URI,
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
+
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+ verifyBeepLooped();
+
+ mService.clearSoundLocked();
+
+ NotificationRecord interrupter = getBeepyOtherNotification();
+ mService.buzzBeepBlinkLocked(interrupter);
+
+ verifyBeep(2);
+
+ assertTrue(interrupter.isInterruptive());
+ }
+
+ @Test
+ public void testRingtoneInsistentBuzz_doesNotBlockFutureSoundsOnceStopped() {
+ NotificationChannel ringtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ ringtoneChannel.setSound(null,
+ new AudioAttributes.Builder().setUsage(USAGE_NOTIFICATION_RINGTONE).build());
+ ringtoneChannel.enableVibration(true);
+ NotificationRecord ringtoneNotification = getCallRecord(1, ringtoneChannel, true);
+
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+ verifyVibrateLooped();
+
+ mService.clearVibrateLocked();
+
+ NotificationRecord interrupter = getBuzzyOtherNotification();
+ mService.buzzBeepBlinkLocked(interrupter);
+
+ verifyVibrate(2);
+
+ assertTrue(interrupter.isInterruptive());
+ }
+
+ @Test
+ public void testCanInterruptNonRingtoneInsistentBeep() throws Exception {
+ NotificationChannel fakeRingtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ NotificationRecord ringtoneNotification = getCallRecord(1, fakeRingtoneChannel, true);
+
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+ verifyBeepLooped();
+
+ NotificationRecord interrupter = getBeepyOtherNotification();
+ mService.buzzBeepBlinkLocked(interrupter);
+
+ verifyBeep(2);
+
+ assertTrue(interrupter.isInterruptive());
+ }
+
+ @Test
+ public void testCanInterruptNonRingtoneInsistentBuzz() throws Exception {
+ NotificationChannel fakeRingtoneChannel =
+ new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
+ fakeRingtoneChannel.enableVibration(true);
+ NotificationRecord ringtoneNotification = getCallRecord(1, fakeRingtoneChannel, true);
+
+ mService.buzzBeepBlinkLocked(ringtoneNotification);
+
+ NotificationRecord interrupter = getBuzzyOtherNotification();
+ mService.buzzBeepBlinkLocked(interrupter);
+
+ verifyVibrate(2);
+
+ assertTrue(interrupter.isInterruptive());
+ }
+
static class VibrateRepeatMatcher implements ArgumentMatcher<VibrationEffect> {
private final int mRepeatIndex;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 4ea2fc0..cd0f4f1 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -537,6 +537,18 @@
return new NotificationRecord(mContext, sbn, channel);
}
+ private NotificationRecord generateNotificationRecord(NotificationChannel channel, int userId) {
+ if (channel == null) {
+ channel = mTestNotificationChannel;
+ }
+ Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0,
+ nb.build(), new UserHandle(userId), null, 0);
+ return new NotificationRecord(mContext, sbn, channel);
+ }
+
private Map<String, Answer> getSignalExtractorSideEffects() {
Map<String, Answer> answers = new ArrayMap<>();
@@ -5451,6 +5463,112 @@
}
@Test
+ public void testGrantInlineReplyUriPermission_recordExists() throws Exception {
+ NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, 0);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+ nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+ waitForIdle();
+
+ // A notification exists for the given record
+ StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifsBefore.length);
+
+ reset(mPackageManager);
+
+ Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
+
+ mService.mNotificationDelegate.grantInlineReplyUriPermission(
+ nr.getKey(), uri, nr.sbn.getUid());
+
+ // Grant permission called for the UID of SystemUI under the target user ID
+ verify(mUgm, times(1)).grantUriPermissionFromOwner(any(),
+ eq(nr.sbn.getUid()), eq(nr.sbn.getPackageName()), eq(uri), anyInt(), anyInt(),
+ eq(nr.sbn.getUserId()));
+ }
+
+ @Test
+ public void testGrantInlineReplyUriPermission_userAll() throws Exception {
+ // generate a NotificationRecord for USER_ALL to make sure it's converted into USER_SYSTEM
+ NotificationRecord nr =
+ generateNotificationRecord(mTestNotificationChannel, UserHandle.USER_ALL);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+ nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+ waitForIdle();
+
+ // A notification exists for the given record
+ StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifsBefore.length);
+
+ reset(mPackageManager);
+
+ Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
+
+ mService.mNotificationDelegate.grantInlineReplyUriPermission(
+ nr.getKey(), uri, nr.sbn.getUid());
+
+ // Target user for the grant is USER_ALL instead of USER_SYSTEM
+ verify(mUgm, times(1)).grantUriPermissionFromOwner(any(),
+ eq(nr.sbn.getUid()), eq(nr.sbn.getPackageName()), eq(uri), anyInt(), anyInt(),
+ eq(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void testGrantInlineReplyUriPermission_acrossUsers() throws Exception {
+ // generate a NotificationRecord for USER_ALL to make sure it's converted into USER_SYSTEM
+ int otherUserId = 11;
+ NotificationRecord nr =
+ generateNotificationRecord(mTestNotificationChannel, otherUserId);
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+ nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+ waitForIdle();
+
+ // A notification exists for the given record
+ StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
+ assertEquals(1, notifsBefore.length);
+
+ reset(mPackageManager);
+
+ Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
+
+ int uid = 0; // sysui on primary user
+ int otherUserUid = (otherUserId * 100000) + 1; // SystemUI as a different user
+ String sysuiPackage = "sysui";
+ final String[] sysuiPackages = new String[] { sysuiPackage };
+ when(mPackageManager.getPackagesForUid(uid)).thenReturn(sysuiPackages);
+
+ // Make sure to mock call for USER_SYSTEM and not USER_ALL, since it's been replaced by the
+ // time this is called
+ when(mPackageManager.getPackageUid(sysuiPackage, 0, otherUserId))
+ .thenReturn(otherUserUid);
+
+ mService.mNotificationDelegate.grantInlineReplyUriPermission(nr.getKey(), uri, uid);
+
+ // Target user for the grant is USER_ALL instead of USER_SYSTEM
+ verify(mUgm, times(1)).grantUriPermissionFromOwner(any(),
+ eq(otherUserUid), eq(nr.sbn.getPackageName()), eq(uri), anyInt(), anyInt(),
+ eq(otherUserId));
+ }
+
+ @Test
+ public void testGrantInlineReplyUriPermission_noRecordExists() throws Exception {
+ NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+ waitForIdle();
+
+ // No notifications exist for the given record
+ StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
+ assertEquals(0, notifsBefore.length);
+
+ Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
+ int uid = 0; // sysui on primary user
+
+ mService.mNotificationDelegate.grantInlineReplyUriPermission(nr.getKey(), uri, uid);
+
+ // Grant permission not called if no record exists for the given key
+ verify(mUgm, times(0)).grantUriPermissionFromOwner(any(), anyInt(), any(),
+ eq(uri), anyInt(), anyInt(), anyInt());
+ }
+
+ @Test
public void testNotificationBubbles_disabled_lowRamDevice() throws Exception {
// Bubbles are allowed!
setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index aaaa7a5..2836e69 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -28,6 +28,7 @@
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.timeout;
@@ -128,7 +129,7 @@
mActivityMetricsLogger.notifyActivityLaunching(intent);
- verifyAsync(mLaunchObserver).onIntentStarted(eq(intent));
+ verifyAsync(mLaunchObserver).onIntentStarted(eq(intent), anyLong());
verifyNoMoreInteractions(mLaunchObserver);
}
@@ -163,12 +164,12 @@
testOnActivityLaunched();
mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
- SystemClock.uptimeMillis());
+ SystemClock.elapsedRealtimeNanos());
mActivityMetricsLogger.notifyWindowsDrawn(mActivityRecord.getWindowingMode(),
- SystemClock.uptimeMillis());
+ SystemClock.elapsedRealtimeNanos());
- verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecord));
+ verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecord), anyLong());
verifyNoMoreInteractions(mLaunchObserver);
}
@@ -186,6 +187,16 @@
}
@Test
+ public void testOnReportFullyDrawn() throws Exception {
+ testOnActivityLaunched();
+
+ mActivityMetricsLogger.logAppTransitionReportedDrawn(mActivityRecord, false);
+
+ verifyAsync(mLaunchObserver).onReportFullyDrawn(eqProto(mActivityRecord), anyLong());
+ verifyNoMoreInteractions(mLaunchObserver);
+ }
+
+ @Test
public void testOnActivityLaunchedTrampoline() throws Exception {
testOnIntentStarted();
@@ -206,12 +217,13 @@
testOnActivityLaunchedTrampoline();
mActivityMetricsLogger.notifyTransitionStarting(new SparseIntArray(),
- SystemClock.uptimeMillis());
+ SystemClock.elapsedRealtimeNanos());
mActivityMetricsLogger.notifyWindowsDrawn(mActivityRecordTrampoline.getWindowingMode(),
- SystemClock.uptimeMillis());
+ SystemClock.elapsedRealtimeNanos());
- verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecordTrampoline));
+ verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(mActivityRecordTrampoline),
+ anyLong());
verifyNoMoreInteractions(mLaunchObserver);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
index c2a05c24..2835c1f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java
@@ -891,7 +891,7 @@
activity.app = null;
overlayActivity.app = null;
- assertEquals(2, mTask.mActivities.size());
+ assertEquals(2, mTask.getChildCount());
mStack.finishDisabledPackageActivitiesLocked(activity.packageName,
null /* filterByClasses */, true /* doit */, true /* evenPersistent */,
@@ -900,7 +900,7 @@
// Although the overlay activity is in another package, the non-overlay activities are
// removed from the task. Since the overlay activity should be removed as well, the task
// should be empty.
- assertThat(mTask.mActivities).isEmpty();
+ assertFalse(mTask.hasChild());
assertThat(mStack.getAllTasks()).isEmpty();
}
@@ -918,11 +918,11 @@
// second activity will be immediately removed as it has no state.
secondActivity.setSavedState(null /* savedState */);
- assertEquals(2, mTask.mActivities.size());
+ assertEquals(2, mTask.getChildCount());
mStack.handleAppDiedLocked(secondActivity.app);
- assertThat(mTask.mActivities).isEmpty();
+ assertFalse(mTask.hasChild());
assertThat(mStack.getAllTasks()).isEmpty();
}
@@ -936,7 +936,7 @@
mStack.handleAppDiedLocked(activity.app);
- assertEquals(1, mTask.mActivities.size());
+ assertEquals(1, mTask.getChildCount());
assertEquals(1, mStack.getAllTasks().size());
}
@@ -950,7 +950,7 @@
mStack.handleAppDiedLocked(activity.app);
- assertThat(mTask.mActivities).isEmpty();
+ assertFalse(mTask.hasChild());
assertThat(mStack.getAllTasks()).isEmpty();
}
@@ -964,7 +964,7 @@
mStack.handleAppDiedLocked(activity.app);
- assertEquals(1, mTask.mActivities.size());
+ assertEquals(1, mTask.getChildCount());
assertEquals(1, mStack.getAllTasks().size());
}
@@ -978,7 +978,7 @@
mStack.handleAppDiedLocked(activity.app);
- assertThat(mTask.mActivities).isEmpty();
+ assertFalse(mTask.hasChild());
assertThat(mStack.getAllTasks()).isEmpty();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
index 77fbdcf..d43fe63 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTestsBase.java
@@ -215,23 +215,6 @@
return this;
}
- static Pair<Intent, ActivityInfo> createIntentAndActivityInfo() {
- // TODO: Look into consolidating with dup. code in build() method below.
- final int id = sCurrentActivityId++;
- final ComponentName component = ComponentName.createRelative(
- DEFAULT_COMPONENT_PACKAGE_NAME, DEFAULT_COMPONENT_CLASS_NAME + id);
-
- final Intent intent = new Intent();
- intent.setComponent(component);
-
- final ActivityInfo aInfo = new ActivityInfo();
- aInfo.applicationInfo = new ApplicationInfo();
- aInfo.applicationInfo.packageName = component.getPackageName();
- aInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
- aInfo.packageName = component.getPackageName();
- return new Pair<>(intent, aInfo);
- }
-
ActivityRecord build() {
if (mComponent == null) {
final int id = sCurrentActivityId++;
@@ -249,6 +232,7 @@
intent.setComponent(mComponent);
final ActivityInfo aInfo = new ActivityInfo();
aInfo.applicationInfo = new ApplicationInfo();
+ aInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
aInfo.applicationInfo.packageName = mComponent.getPackageName();
aInfo.applicationInfo.uid = mUid;
aInfo.packageName = mComponent.getPackageName();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index 650a911..b174251 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -57,7 +57,7 @@
private TaskStack mStack;
private Task mTask;
- private AppWindowToken mToken;
+ private ActivityRecord mToken;
public void setUpOnDisplay(DisplayContent dc) {
mStack = createTaskStackOnDisplay(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, dc);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 605d520..14939cc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -101,10 +101,10 @@
@Test
@FlakyTest(bugId = 131005232)
public void testTransitWithinTask() {
- final AppWindowToken opening = createAppWindowToken(mDisplayContent,
+ final ActivityRecord opening = createAppWindowToken(mDisplayContent,
WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
opening.setOccludesParent(false);
- final AppWindowToken closing = createAppWindowToken(mDisplayContent,
+ final ActivityRecord closing = createAppWindowToken(mDisplayContent,
WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
closing.setOccludesParent(false);
final Task task = opening.getTask();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 72d9bd0..9d53676 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -159,7 +159,7 @@
final TaskStack stack1 = createTaskStackOnDisplay(dc1);
final Task task1 = createTaskInStack(stack1, 0 /* userId */);
- final AppWindowToken token1 =
+ final ActivityRecord token1 =
WindowTestUtils.createTestAppWindowToken(dc1);
task1.addChild(token1, 0);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
index 2661735..b4c978f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -77,7 +77,7 @@
TaskStack mStack;
Task mTask;
- AppWindowToken mToken;
+ ActivityRecord mToken;
private final String mPackageName = getInstrumentation().getTargetContext().getPackageName();
@@ -410,7 +410,7 @@
}
private AppWindowToken createTestAppWindowTokenForGivenTask(Task task) {
- final AppWindowToken appToken =
+ final ActivityRecord appToken =
WindowTestUtils.createTestAppWindowToken(mDisplayContent);
task.addChild(appToken, 0);
waitUntilHandlersIdle();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index a98f79c..73420a0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -46,7 +46,7 @@
private static class TestWindowContainer extends WindowContainer<TestWindowContainer> {
final SurfaceControl mControl = mock(SurfaceControl.class);
- final SurfaceControl.Transaction mTransaction = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction mTransaction = spy(StubTransaction.class);
TestWindowContainer(WindowManagerService wm) {
super(wm);
@@ -66,7 +66,7 @@
private static class MockSurfaceBuildingContainer extends WindowContainer<TestWindowContainer> {
final SurfaceSession mSession = new SurfaceSession();
final SurfaceControl mHostControl = mock(SurfaceControl.class);
- final SurfaceControl.Transaction mHostTransaction = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction mHostTransaction = spy(StubTransaction.class);
MockSurfaceBuildingContainer(WindowManagerService wm) {
super(wm);
@@ -118,7 +118,7 @@
public void setUp() throws Exception {
mHost = new MockSurfaceBuildingContainer(mWm);
mSurfaceAnimatorStarter = spy(new SurfaceAnimatorStarterImpl());
- mTransaction = mock(SurfaceControl.Transaction.class);
+ mTransaction = spy(StubTransaction.class);
mDimmer = new Dimmer(mHost, mSurfaceAnimatorStarter);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 2ba3cbd..f12c349 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -241,7 +241,7 @@
assertEquals(dc, stack.getDisplayContent());
final Task task = createTaskInStack(stack, 0 /* userId */);
- final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(dc);
+ final ActivityRecord token = WindowTestUtils.createTestAppWindowToken(dc);
task.addChild(token, 0);
assertEquals(dc, task.getDisplayContent());
assertEquals(dc, token.getDisplayContent());
@@ -313,7 +313,7 @@
// Add stack with activity.
final TaskStack stack0 = createTaskStackOnDisplay(dc0);
final Task task0 = createTaskInStack(stack0, 0 /* userId */);
- final AppWindowToken token =
+ final ActivityRecord token =
WindowTestUtils.createTestAppWindowToken(dc0);
task0.addChild(token, 0);
dc0.configureDisplayPolicy();
@@ -321,7 +321,7 @@
final TaskStack stack1 = createTaskStackOnDisplay(dc1);
final Task task1 = createTaskInStack(stack1, 0 /* userId */);
- final AppWindowToken token1 =
+ final ActivityRecord token1 =
WindowTestUtils.createTestAppWindowToken(dc0);
task1.addChild(token1, 0);
dc1.configureDisplayPolicy();
@@ -682,16 +682,15 @@
// is appWin & null on the other display.
mDisplayContent.setInputMethodWindowLocked(mImeWindow);
newDisplay.setInputMethodWindowLocked(null);
- assertTrue("appWin should be IME target window",
- appWin.equals(mDisplayContent.mInputMethodTarget));
+ assertEquals("appWin should be IME target window",
+ appWin, mDisplayContent.mInputMethodTarget);
assertNull("newDisplay Ime target: ", newDisplay.mInputMethodTarget);
// Switch input method window on new display & make sure the input method target also
// switched as expected.
newDisplay.setInputMethodWindowLocked(mImeWindow);
mDisplayContent.setInputMethodWindowLocked(null);
- assertTrue("appWin1 should be IME target window",
- appWin1.equals(newDisplay.mInputMethodTarget));
+ assertEquals("appWin1 should be IME target window", appWin1, newDisplay.mInputMethodTarget);
assertNull("default display Ime target: ", mDisplayContent.mInputMethodTarget);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 304df22..452e06f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -96,7 +96,7 @@
* Creates a window state which can be used as a drop target.
*/
private WindowState createDropTargetWindow(String name, int ownerId) {
- final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(
+ final ActivityRecord token = WindowTestUtils.createTestAppWindowToken(
mDisplayContent);
final TaskStack stack = createTaskStackOnDisplay(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mDisplayContent);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
index 7115af9..b9fef4b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java
@@ -32,7 +32,6 @@
import static org.mockito.Matchers.any;
import android.content.ComponentName;
-import android.content.pm.PackageList;
import android.content.pm.PackageManagerInternal;
import android.graphics.Rect;
import android.os.UserHandle;
@@ -43,6 +42,7 @@
import androidx.test.filters.MediumTest;
import com.android.server.LocalServices;
+import com.android.server.pm.PackageList;
import com.android.server.wm.LaunchParamsController.LaunchParams;
import org.junit.Before;
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index 2d0416d..15417d7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -21,6 +21,7 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -48,8 +49,8 @@
@Before
public void setUp() throws Exception {
mSurfaces = new SurfaceControlMocker();
- mLetterbox = new Letterbox(mSurfaces, () -> mock(SurfaceControl.Transaction.class));
- mTransaction = mock(SurfaceControl.Transaction.class);
+ mLetterbox = new Letterbox(mSurfaces, StubTransaction::new);
+ mTransaction = spy(StubTransaction.class);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index 2ad40f2..f5d08dc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -239,4 +239,15 @@
public SurfaceControl.Transaction remove(SurfaceControl sc) {
return this;
}
+
+ @Override
+ public SurfaceControl.Transaction syncInputWindows() {
+ return this;
+ }
+
+ @Override
+ public SurfaceControl.Transaction setColorSpaceAgnostic(SurfaceControl sc, boolean agnostic) {
+ return this;
+ }
+
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java
index 92ddb35..eef680b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java
@@ -54,7 +54,7 @@
// Stack should contain visible app window to be considered visible.
final Task pinnedTask = createTaskInStack(mPinnedStack, 0 /* userId */);
assertFalse(mPinnedStack.isVisible());
- final AppWindowToken pinnedApp =
+ final ActivityRecord pinnedApp =
WindowTestUtils.createTestAppWindowToken(mDisplayContent);
pinnedTask.addChild(pinnedApp, 0 /* addPos */);
assertTrue(mPinnedStack.isVisible());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
index 2eb6ea4..d045073 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskStackTests.java
@@ -68,13 +68,13 @@
public void testClosingAppDifferentStackOrientation() {
final TaskStack stack = createTaskStackOnDisplay(mDisplayContent);
final Task task1 = createTaskInStack(stack, 0 /* userId */);
- AppWindowToken appWindowToken1 =
+ ActivityRecord appWindowToken1 =
WindowTestUtils.createTestAppWindowToken(mDisplayContent);
task1.addChild(appWindowToken1, 0);
appWindowToken1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
final Task task2 = createTaskInStack(stack, 1 /* userId */);
- AppWindowToken appWindowToken2 =
+ ActivityRecord appWindowToken2 =
WindowTestUtils.createTestAppWindowToken(mDisplayContent);
task2.addChild(appWindowToken2, 0);
appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
@@ -88,13 +88,13 @@
public void testMoveTaskToBackDifferentStackOrientation() {
final TaskStack stack = createTaskStackOnDisplay(mDisplayContent);
final Task task1 = createTaskInStack(stack, 0 /* userId */);
- AppWindowToken appWindowToken1 =
+ ActivityRecord appWindowToken1 =
WindowTestUtils.createTestAppWindowToken(mDisplayContent);
task1.addChild(appWindowToken1, 0);
appWindowToken1.setOrientation(SCREEN_ORIENTATION_LANDSCAPE);
final Task task2 = createTaskInStack(stack, 1 /* userId */);
- AppWindowToken appWindowToken2 =
+ ActivityRecord appWindowToken2 =
WindowTestUtils.createTestAppWindowToken(mDisplayContent);
task2.addChild(appWindowToken2, 0);
appWindowToken2.setOrientation(SCREEN_ORIENTATION_PORTRAIT);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java b/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java
index 778f0ca..9c3ff65 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestActivityDisplay.java
@@ -22,8 +22,11 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static org.mockito.ArgumentMatchers.any;
+
import android.hardware.display.DisplayManagerGlobal;
import android.view.Display;
import android.view.DisplayInfo;
@@ -85,6 +88,10 @@
displayRotation.setRotation(rotation);
return true;
}).when(displayRotation).updateRotationUnchecked(anyBoolean());
+
+ final InputMonitor inputMonitor = mDisplayContent.getInputMonitor();
+ spyOn(inputMonitor);
+ doNothing().when(inputMonitor).resumeDispatchingLw(any());
}
@SuppressWarnings("TypeParameterUnusedInFormals")
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index 09e5027..6a94137 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -115,4 +115,8 @@
@Override
public void showInsets(int types, boolean fromIme) throws RemoteException {
}
+
+ @Override
+ public void hideInsets(int types, boolean fromIme) throws RemoteException {
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java
index 0330de8..bfc0741 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowAnimationSpecTest.java
@@ -27,6 +27,7 @@
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.graphics.Point;
@@ -50,7 +51,7 @@
@Presubmit
public class WindowAnimationSpecTest {
private final SurfaceControl mSurfaceControl = mock(SurfaceControl.class);
- private final SurfaceControl.Transaction mTransaction = mock(SurfaceControl.Transaction.class);
+ private final SurfaceControl.Transaction mTransaction = spy(StubTransaction.class);
private final Animation mAnimation = mock(Animation.class);
private final Rect mStackBounds = new Rect(0, 0, 10, 10);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index e5fb28d..a09253a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -452,7 +452,7 @@
@Test
public void testSeamlesslyRotateWindow() {
final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction t = spy(StubTransaction.class);
app.mHasSurface = true;
app.mSurfaceControl = mock(SurfaceControl.class);
@@ -536,7 +536,7 @@
final float[] values = new float[9];
final Matrix matrix = new Matrix();
- final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ final SurfaceControl.Transaction t = spy(StubTransaction.class);
final WindowState win1 = createWindow(null, TYPE_APPLICATION, dc, "win1");
win1.mHasSurface = true;
win1.mSurfaceControl = mock(SurfaceControl.class);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
index c627c19..f44c969 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java
@@ -19,17 +19,11 @@
import static android.app.AppOpsManager.OP_NONE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.server.wm.ActivityTestsBase.ActivityBuilder.createIntentAndActivityInfo;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import android.app.ActivityManager;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
import android.os.IBinder;
-import android.util.Pair;
import android.view.IWindow;
import android.view.WindowManager;
@@ -51,23 +45,22 @@
}
/** Creates an {@link AppWindowToken} and adds it to the specified {@link Task}. */
- static AppWindowToken createAppWindowTokenInTask(DisplayContent dc, Task task) {
- final AppWindowToken newToken = createTestAppWindowToken(dc);
+ static ActivityRecord createAppWindowTokenInTask(DisplayContent dc, Task task) {
+ final ActivityRecord newToken = createTestAppWindowToken(dc);
task.addChild(newToken, POSITION_TOP);
return newToken;
}
- static AppWindowToken createTestAppWindowToken(DisplayContent dc) {
+ static ActivityRecord createTestAppWindowToken(DisplayContent dc) {
synchronized (dc.mWmService.mGlobalLock) {
- Pair<Intent, ActivityInfo> pair = createIntentAndActivityInfo();
- final AppWindowToken token = new AppWindowToken(dc.mWmService,
- dc.mWmService.mAtmService, new ActivityRecord.Token(pair.first), pair.second,
- null, pair.first, dc);
- token.setOccludesParent(true);
- token.setHidden(false);
- token.hiddenRequested = false;
- spyOn(token);
- return token;
+ final ActivityRecord r =
+ new ActivityTestsBase.ActivityBuilder(dc.mWmService.mAtmService)
+ .build();
+ r.onDisplayChanged(dc);
+ r.setOccludesParent(true);
+ r.setHidden(false);
+ r.hiddenRequested = false;
+ return r;
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 4c4b21e..1fce46c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -204,15 +204,15 @@
}
}
- AppWindowToken createAppWindowToken(DisplayContent dc, int windowingMode, int activityType) {
+ ActivityRecord createAppWindowToken(DisplayContent dc, int windowingMode, int activityType) {
return createTestAppWindowToken(dc, windowingMode, activityType);
}
- AppWindowToken createTestAppWindowToken(DisplayContent dc, int
+ ActivityRecord createTestAppWindowToken(DisplayContent dc, int
windowingMode, int activityType) {
final TaskStack stack = createTaskStackOnDisplay(windowingMode, activityType, dc);
final Task task = createTaskInStack(stack, 0 /* userId */);
- final AppWindowToken appWindowToken =
+ final ActivityRecord appWindowToken =
WindowTestUtils.createTestAppWindowToken(dc);
task.addChild(appWindowToken, 0);
return appWindowToken;
@@ -244,7 +244,7 @@
WindowState createAppWindow(Task task, int type, String name) {
synchronized (mWm.mGlobalLock) {
- final AppWindowToken token = WindowTestUtils.createTestAppWindowToken(mDisplayContent);
+ final ActivityRecord token = WindowTestUtils.createTestAppWindowToken(mDisplayContent);
task.addChild(token, 0);
return createWindow(null, type, token, name);
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index ecee709..2cd207f 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -97,8 +97,6 @@
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
@@ -191,11 +189,6 @@
event.mPackage = packageName;
reportEventOrAddToQueue(userId, event);
}
-
- @Override
- public void onParoleStateChanged(boolean isParoleOn) {
-
- }
};
public UsageStatsService(Context context) {
@@ -1426,7 +1419,7 @@
Binder.getCallingUid(), userId);
final long token = Binder.clearCallingIdentity();
try {
- return mAppStandby.isAppIdleFilteredOrParoled(
+ return mAppStandby.isAppIdleFiltered(
packageName, userId,
SystemClock.elapsedRealtime(), obfuscateInstantApps);
} finally {
@@ -1995,11 +1988,6 @@
}
@Override
- public boolean isAppIdleParoleOn() {
- return mAppStandby.isParoledOrCharging();
- }
-
- @Override
public void prepareShutdown() {
// This method *WILL* do IO work, but we must block until it is finished or else
// we might not shutdown cleanly. This is ok to do with the 'am' lock held, because
@@ -2015,7 +2003,6 @@
@Override
public void addAppIdleStateChangeListener(AppIdleStateChangeListener listener) {
mAppStandby.addListener(listener);
- listener.onParoleStateChanged(isAppIdleParoleOn());
}
@Override
diff --git a/startop/apps/test/Android.bp b/startop/apps/test/Android.bp
index a806320..2ff26b8 100644
--- a/startop/apps/test/Android.bp
+++ b/startop/apps/test/Android.bp
@@ -17,12 +17,14 @@
android_app {
name: "startop_test_app",
srcs: [
+ "src/ComplexLayoutInflationActivity.java",
"src/CPUIntensive.java",
"src/EmptyActivity.java",
- "src/LayoutInflationActivity.java",
- "src/ComplexLayoutInflationActivity.java",
"src/FrameLayoutInflationActivity.java",
+ "src/LayoutInflationActivity.java",
+ "src/NonInteractiveSystemServerBenchmarkActivity.java",
"src/SystemServerBenchmarkActivity.java",
+ "src/SystemServerBenchmarks.java",
"src/TextViewInflationActivity.java",
],
sdk_version: "26", // Android O (8.0) and higher
diff --git a/startop/apps/test/AndroidManifest.xml b/startop/apps/test/AndroidManifest.xml
index 15785d4..ebe2584 100644
--- a/startop/apps/test/AndroidManifest.xml
+++ b/startop/apps/test/AndroidManifest.xml
@@ -84,6 +84,14 @@
</intent-filter>
</activity>
+ <activity
+ android:label="Non-interactive SystemServer Benchmark"
+ android:name=".NonInteractiveSystemServerBenchmarkActivity"
+ android:exported="true" />
+
</application>
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+
</manifest>
diff --git a/startop/apps/test/README.md b/startop/apps/test/README.md
index dadc66a..949dff7 100644
--- a/startop/apps/test/README.md
+++ b/startop/apps/test/README.md
@@ -24,3 +24,14 @@
inflation.
adb shell am start -n com.android.startop.test/.ComplexLayoutInflationActivity
+
+## NonInteractiveSystemServerBenchmark
+
+This activity is for running microbenchmarks from the command line. Run as follows:
+
+ adb shell am start -W -n com.android.startop.test .NonInteractiveSystemServerBenchmarkActivity
+
+It takes awhile (and there's currently no automated way to make sure it's done),
+but when it finishes, you can get the results like this:
+
+ adb shell cat /sdcard/Android/data/com.android.startop.test/files/benchmark.csv
diff --git a/startop/apps/test/src/NonInteractiveSystemServerBenchmarkActivity.java b/startop/apps/test/src/NonInteractiveSystemServerBenchmarkActivity.java
new file mode 100644
index 0000000..a2dc2cf
--- /dev/null
+++ b/startop/apps/test/src/NonInteractiveSystemServerBenchmarkActivity.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.startop.test;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.GridLayout;
+import android.widget.TextView;
+
+public class NonInteractiveSystemServerBenchmarkActivity extends Activity {
+ ArrayList<CharSequence> benchmarkNames = new ArrayList();
+ ArrayList<Runnable> benchmarkThunks = new ArrayList();
+
+ PrintStream out;
+
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ SystemServerBenchmarks.initializeBenchmarks(this, (name, thunk) -> {
+ benchmarkNames.add(name);
+ benchmarkThunks.add(thunk);
+ });
+
+ try {
+ out = new PrintStream(new File(getExternalFilesDir(null), "benchmark.csv"));
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ out.println("Name,Mean,Stdev");
+ runBenchmarks(0);
+ }
+
+ void runBenchmarks(int i) {
+ if (i < benchmarkNames.size()) {
+ SystemServerBenchmarks.runBenchmarkInBackground(benchmarkThunks.get(i),
+ (mean, stdev) -> {
+ out.printf("%s,%.0f,%.0f\n", benchmarkNames.get(i), mean, stdev);
+ runBenchmarks(i + 1);
+ });
+ }
+ }
+}
diff --git a/startop/apps/test/src/SystemServerBenchmarkActivity.java b/startop/apps/test/src/SystemServerBenchmarkActivity.java
index c8d9fde..75ea69b 100644
--- a/startop/apps/test/src/SystemServerBenchmarkActivity.java
+++ b/startop/apps/test/src/SystemServerBenchmarkActivity.java
@@ -31,13 +31,25 @@
import android.widget.GridLayout;
import android.widget.TextView;
+public class SystemServerBenchmarkActivity extends Activity implements BenchmarkRunner {
+ private GridLayout benchmarkList;
-class Benchmark {
- // Time limit to run benchmarks in seconds
- public static final int TIME_LIMIT = 5;
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.system_server_benchmark_page);
- public Benchmark(ViewGroup parent, CharSequence name, Runnable thunk) {
- Context context = parent.getContext();
+ benchmarkList = findViewById(R.id.benchmark_list);
+
+ SystemServerBenchmarks.initializeBenchmarks(this, this);
+ }
+
+ /**
+ * Adds a benchmark to the set to run.
+ *
+ * @param name A short name that shows up in the UI or benchmark results
+ */
+ public void addBenchmark(CharSequence name, Runnable thunk) {
+ Context context = benchmarkList.getContext();
Button button = new Button(context);
TextView mean = new TextView(context);
TextView stdev = new TextView(context);
@@ -50,165 +62,14 @@
mean.setText("Running...");
stdev.setText("");
- new AsyncTask() {
- double resultMean = 0;
- double resultStdev = 0;
-
- @Override
- protected Object doInBackground(Object... _args) {
- long startTime = System.nanoTime();
- int count = 0;
-
- // Run benchmark
- while (true) {
- long elapsed = -System.nanoTime();
- thunk.run();
- elapsed += System.nanoTime();
-
- count++;
- double elapsedVariance = (double) elapsed - resultMean;
- resultMean += elapsedVariance / count;
- resultStdev += elapsedVariance * ((double) elapsed - resultMean);
-
- if (System.nanoTime() - startTime > TIME_LIMIT * 1e9) {
- break;
- }
- }
- resultStdev = Math.sqrt(resultStdev / (count - 1));
-
- return null;
- }
-
- @Override
- protected void onPostExecute(Object _result) {
- mean.setText(String.format("%.3f", resultMean / 1e6));
- stdev.setText(String.format("%.3f", resultStdev / 1e6));
- }
- }.execute(new Object());
- });
-
- parent.addView(button);
- parent.addView(mean);
- parent.addView(stdev);
- }
-}
-
-public class SystemServerBenchmarkActivity extends Activity {
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.system_server_benchmark_page);
-
- GridLayout benchmarkList = findViewById(R.id.benchmark_list);
-
- new Benchmark(benchmarkList, "Empty", () -> {
- });
-
- new Benchmark(benchmarkList, "CPU Intensive (1 thread)", () -> {
- CPUIntensive.doSomeWork(1);
- });
-
- new Benchmark(benchmarkList, "CPU Intensive (2 thread)", () -> {
- CPUIntensive.doSomeWork(2);
- });
-
- new Benchmark(benchmarkList, "CPU Intensive (4 thread)", () -> {
- CPUIntensive.doSomeWork(4);
- });
-
- new Benchmark(benchmarkList, "CPU Intensive (8 thread)", () -> {
- CPUIntensive.doSomeWork(8);
- });
-
- PackageManager pm = getPackageManager();
- new Benchmark(benchmarkList, "getInstalledApplications", () -> {
- pm.getInstalledApplications(PackageManager.MATCH_SYSTEM_ONLY);
- });
-
- new Benchmark(benchmarkList, "getInstalledPackages", () -> {
- pm.getInstalledPackages(PackageManager.GET_ACTIVITIES);
- });
-
- new Benchmark(benchmarkList, "getPackageInfo", () -> {
- try {
- pm.getPackageInfo("com.android.startop.test", 0);
- } catch (NameNotFoundException e) {
- throw new RuntimeException(e);
- }
- });
-
- new Benchmark(benchmarkList, "getApplicationInfo", () -> {
- try {
- pm.getApplicationInfo("com.android.startop.test", 0);
- } catch (NameNotFoundException e) {
- throw new RuntimeException(e);
- }
- });
-
- try {
- ApplicationInfo app = pm.getApplicationInfo("com.android.startop.test", 0);
- new Benchmark(benchmarkList, "getResourcesForApplication", () -> {
- try {
- pm.getResourcesForApplication(app);
- } catch (NameNotFoundException e) {
- throw new RuntimeException(e);
- }
+ SystemServerBenchmarks.runBenchmarkInBackground(thunk, (resultMean, resultStdev) -> {
+ mean.setText(String.format("%.3f", resultMean / 1e6));
+ stdev.setText(String.format("%.3f", resultStdev / 1e6));
});
-
- new Benchmark(benchmarkList, "getPackagesForUid", () -> {
- pm.getPackagesForUid(app.uid);
- });
- } catch (NameNotFoundException e) {
- throw new RuntimeException(e);
- }
-
- ComponentName component = new ComponentName(this, this.getClass());
- new Benchmark(benchmarkList, "getActivityInfo", () -> {
- try {
- pm.getActivityInfo(component, PackageManager.GET_META_DATA);
- } catch (NameNotFoundException e) {
- throw new RuntimeException(e);
- }
});
- new Benchmark(benchmarkList, "getLaunchIntentForPackage", () -> {
- pm.getLaunchIntentForPackage("com.android.startop.test");
- });
-
- new Benchmark(benchmarkList, "getPackageUid", () -> {
- try {
- pm.getPackageUid("com.android.startop.test", 0);
- } catch (NameNotFoundException e) {
- throw new RuntimeException(e);
- }
- });
-
- new Benchmark(benchmarkList, "checkPermission", () -> {
- // Check for the first permission I could find.
- pm.checkPermission("android.permission.SEND_SMS", "com.android.startop.test");
- });
-
- new Benchmark(benchmarkList, "checkSignatures", () -> {
- // Compare with settings, since settings is on both AOSP and Master builds
- pm.checkSignatures("com.android.settings", "com.android.startop.test");
- });
-
- Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED);
- new Benchmark(benchmarkList, "queryBroadcastReceivers", () -> {
- pm.queryBroadcastReceivers(intent, 0);
- });
-
- new Benchmark(benchmarkList, "hasSystemFeature", () -> {
- pm.hasSystemFeature(PackageManager.FEATURE_CAMERA);
- });
-
- new Benchmark(benchmarkList, "resolveService", () -> {
- pm.resolveService(intent, 0);
- });
-
- ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
- new Benchmark(benchmarkList, "getRunningAppProcesses", () -> {
- am.getRunningAppProcesses();
- });
-
+ benchmarkList.addView(button);
+ benchmarkList.addView(mean);
+ benchmarkList.addView(stdev);
}
}
diff --git a/startop/apps/test/src/SystemServerBenchmarks.java b/startop/apps/test/src/SystemServerBenchmarks.java
new file mode 100644
index 0000000..5918503
--- /dev/null
+++ b/startop/apps/test/src/SystemServerBenchmarks.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.startop.test;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.ConnectivityManager;
+import android.os.AsyncTask;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.UserManager;
+
+/**
+ * An interface for running benchmarks and collecting results. Used so we can have both an
+ * interactive runner and a non-interactive runner.
+ */
+interface BenchmarkRunner {
+ void addBenchmark(CharSequence name, Runnable thunk);
+}
+
+interface ResultListener {
+ /**
+ * Called when a benchmark result is ready
+ *
+ * @param mean The average iteration time in nanoseconds
+ * @param stdev The standard deviation of iteration times in nanoseconds
+ */
+ void onResult(double mean, double stdev);
+}
+
+class SystemServerBenchmarks {
+ // Time limit to run benchmarks in seconds
+ public static final int TIME_LIMIT = 5;
+
+ static void initializeBenchmarks(Activity parent, BenchmarkRunner benchmarks) {
+ final String packageName = parent.getPackageName();
+
+ benchmarks.addBenchmark("Empty", () -> {
+ });
+
+ benchmarks.addBenchmark("CPU Intensive (1 thread)", () -> {
+ CPUIntensive.doSomeWork(1);
+ });
+
+ benchmarks.addBenchmark("CPU Intensive (2 thread)", () -> {
+ CPUIntensive.doSomeWork(2);
+ });
+
+ benchmarks.addBenchmark("CPU Intensive (4 thread)", () -> {
+ CPUIntensive.doSomeWork(4);
+ });
+
+ benchmarks.addBenchmark("CPU Intensive (8 thread)", () -> {
+ CPUIntensive.doSomeWork(8);
+ });
+
+ PackageManager pm = parent.getPackageManager();
+ benchmarks.addBenchmark("getInstalledApplications", () -> {
+ pm.getInstalledApplications(PackageManager.MATCH_SYSTEM_ONLY);
+ });
+
+ benchmarks.addBenchmark("getInstalledPackages", () -> {
+ pm.getInstalledPackages(PackageManager.GET_ACTIVITIES);
+ });
+
+ benchmarks.addBenchmark("getPackageInfo", () -> {
+ try {
+ pm.getPackageInfo(packageName, 0);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ benchmarks.addBenchmark("getApplicationInfo", () -> {
+ try {
+ pm.getApplicationInfo(packageName, 0);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ try {
+ ApplicationInfo app = pm.getApplicationInfo(packageName, 0);
+ benchmarks.addBenchmark("getResourcesForApplication", () -> {
+ try {
+ pm.getResourcesForApplication(app);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ benchmarks.addBenchmark("getPackagesForUid", () -> {
+ pm.getPackagesForUid(app.uid);
+ });
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+
+ ComponentName component = new ComponentName(parent, parent.getClass());
+ benchmarks.addBenchmark("getActivityInfo", () -> {
+ try {
+ pm.getActivityInfo(component, PackageManager.GET_META_DATA);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ benchmarks.addBenchmark("getLaunchIntentForPackage", () -> {
+ pm.getLaunchIntentForPackage(packageName);
+ });
+
+ benchmarks.addBenchmark("getPackageUid", () -> {
+ try {
+ pm.getPackageUid(packageName, 0);
+ } catch (NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ });
+
+ benchmarks.addBenchmark("checkPermission", () -> {
+ // Check for the first permission I could find.
+ pm.checkPermission("android.permission.SEND_SMS", packageName);
+ });
+
+ benchmarks.addBenchmark("checkSignatures", () -> {
+ // Compare with settings, since settings is on both AOSP and Master builds
+ pm.checkSignatures("com.android.settings", packageName);
+ });
+
+ Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED);
+ benchmarks.addBenchmark("queryBroadcastReceivers", () -> {
+ pm.queryBroadcastReceivers(intent, 0);
+ });
+
+ benchmarks.addBenchmark("hasSystemFeature", () -> {
+ pm.hasSystemFeature(PackageManager.FEATURE_CAMERA);
+ });
+
+ benchmarks.addBenchmark("resolveService", () -> {
+ pm.resolveService(intent, 0);
+ });
+
+ ActivityManager am = (ActivityManager) parent.getSystemService(Context.ACTIVITY_SERVICE);
+ benchmarks.addBenchmark("getRunningAppProcesses", () -> {
+ am.getRunningAppProcesses();
+ });
+
+ // We use PendingIntent.getCreatorPackage, since
+ // getPackageIntentForSender is not public to us, but getCreatorPackage
+ // is just a thin wrapper around it.
+ PendingIntent pi = PendingIntent.getActivity(parent, 0, new Intent(), 0);
+ benchmarks.addBenchmark("getPackageIntentForSender", () -> {
+ pi.getCreatorPackage();
+ });
+
+ PowerManager pwr = (PowerManager) parent.getSystemService(Context.POWER_SERVICE);
+ PowerManager.WakeLock wl = pwr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "benchmark tag");
+ benchmarks.addBenchmark("WakeLock Acquire/Release", () -> {
+ wl.acquire();
+ wl.release();
+ });
+
+ AppOpsManager appOps = (AppOpsManager) parent.getSystemService(Context.APP_OPS_SERVICE);
+ int uid = Process.myUid();
+ benchmarks.addBenchmark("AppOpsService.checkOperation", () -> {
+ appOps.checkOp(AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, uid, packageName);
+ });
+
+ benchmarks.addBenchmark("AppOpsService.checkPackage", () -> {
+ appOps.checkPackage(uid, packageName);
+ });
+
+ benchmarks.addBenchmark("AppOpsService.noteOperation", () -> {
+ appOps.noteOp(AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, uid, packageName);
+ });
+
+ benchmarks.addBenchmark("AppOpsService.noteProxyOperation", () -> {
+ appOps.noteProxyOp(AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, packageName);
+ });
+
+ UserManager userManager = (UserManager) parent.getSystemService(Context.USER_SERVICE);
+ benchmarks.addBenchmark("isUserUnlocked", () -> {
+ userManager.isUserUnlocked();
+ });
+
+ benchmarks.addBenchmark("getIntentSender", () -> {
+ pi.getIntentSender();
+ });
+
+ ConnectivityManager cm = (ConnectivityManager) parent
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+ benchmarks.addBenchmark("getActiveNetworkInfo", () -> {
+ cm.getActiveNetworkInfo();
+ });
+ }
+
+ /**
+ * A helper method for benchark runners to actually run the benchmark and gather stats
+ *
+ * @param thunk The code whose performance we want to measure
+ * @param reporter What to do with the results
+ */
+ static void runBenchmarkInBackground(Runnable thunk, ResultListener reporter) {
+ new AsyncTask() {
+ double resultMean = 0;
+ double resultStdev = 0;
+
+ @Override
+ protected Object doInBackground(Object... _args) {
+ long startTime = System.nanoTime();
+ int count = 0;
+
+ // Run benchmark
+ while (true) {
+ long elapsed = -System.nanoTime();
+ thunk.run();
+ elapsed += System.nanoTime();
+
+ count++;
+ double elapsedVariance = (double) elapsed - resultMean;
+ resultMean += elapsedVariance / count;
+ resultStdev += elapsedVariance * ((double) elapsed - resultMean);
+
+ if (System.nanoTime() - startTime > TIME_LIMIT * 1e9) {
+ break;
+ }
+ }
+ resultStdev = Math.sqrt(resultStdev / (count - 1));
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Object _result) {
+ reporter.onResult(resultMean, resultStdev);
+ }
+ }.execute(new Object());
+ }
+}
diff --git a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
index acf9946..59f4d56 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
@@ -33,6 +33,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
import java.util.Objects;
/**
@@ -86,10 +87,14 @@
public static final class IntentStarted extends AppLaunchEvent {
@NonNull
public final Intent intent;
+ public final long timestampNs;
- public IntentStarted(@SequenceId long sequenceId, Intent intent) {
+ public IntentStarted(@SequenceId long sequenceId,
+ Intent intent,
+ long timestampNs) {
super(sequenceId);
this.intent = intent;
+ this.timestampNs = timestampNs;
Objects.requireNonNull(intent, "intent");
}
@@ -98,14 +103,16 @@
public boolean equals(Object other) {
if (other instanceof IntentStarted) {
return intent.equals(((IntentStarted)other).intent) &&
- super.equals(other);
+ timestampNs == ((IntentStarted)other).timestampNs &&
+ super.equals(other);
}
return false;
}
@Override
protected String toStringBody() {
- return ", intent=" + intent.toString();
+ return ", intent=" + intent.toString() +
+ " , timestampNs=" + Long.toString(timestampNs);
}
@@ -113,11 +120,13 @@
protected void writeToParcelImpl(Parcel p, int flags) {
super.writeToParcelImpl(p, flags);
IntentProtoParcelable.write(p, intent, flags);
+ p.writeLong(timestampNs);
}
IntentStarted(Parcel p) {
super(p);
intent = IntentProtoParcelable.create(p);
+ timestampNs = p.readLong();
}
}
@@ -154,8 +163,8 @@
@Override
public boolean equals(Object other) {
if (other instanceof BaseWithActivityRecordData) {
- return activityRecordSnapshot.equals(
- ((BaseWithActivityRecordData)other).activityRecordSnapshot) &&
+ return (Arrays.equals(activityRecordSnapshot,
+ ((BaseWithActivityRecordData)other).activityRecordSnapshot)) &&
super.equals(other);
}
return false;
@@ -163,7 +172,7 @@
@Override
protected String toStringBody() {
- return ", " + activityRecordSnapshot.toString();
+ return ", " + new String(activityRecordSnapshot);
}
@Override
@@ -200,7 +209,7 @@
@Override
protected String toStringBody() {
- return ", temperature=" + Integer.toString(temperature);
+ return super.toStringBody() + ", temperature=" + Integer.toString(temperature);
}
@Override
@@ -216,18 +225,39 @@
}
public static final class ActivityLaunchFinished extends BaseWithActivityRecordData {
+ public final long timestampNs;
+
public ActivityLaunchFinished(@SequenceId long sequenceId,
- @NonNull @ActivityRecordProto byte[] snapshot) {
+ @NonNull @ActivityRecordProto byte[] snapshot,
+ long timestampNs) {
super(sequenceId, snapshot);
+ this.timestampNs = timestampNs;
}
@Override
public boolean equals(Object other) {
- if (other instanceof ActivityLaunched) {
- return super.equals(other);
+ if (other instanceof ActivityLaunchFinished) {
+ return timestampNs == ((ActivityLaunchFinished)other).timestampNs &&
+ super.equals(other);
}
return false;
}
+
+ @Override
+ protected String toStringBody() {
+ return super.toStringBody() + ", timestampNs=" + Long.toString(timestampNs);
+ }
+
+ @Override
+ protected void writeToParcelImpl(Parcel p, int flags) {
+ super.writeToParcelImpl(p, flags);
+ p.writeLong(timestampNs);
+ }
+
+ ActivityLaunchFinished(Parcel p) {
+ super(p);
+ timestampNs = p.readLong();
+ }
}
public static class ActivityLaunchCancelled extends AppLaunchEvent {
@@ -242,8 +272,8 @@
@Override
public boolean equals(Object other) {
if (other instanceof ActivityLaunchCancelled) {
- return Objects.equals(activityRecordSnapshot,
- ((ActivityLaunchCancelled)other).activityRecordSnapshot) &&
+ return Arrays.equals(activityRecordSnapshot,
+ ((ActivityLaunchCancelled)other).activityRecordSnapshot) &&
super.equals(other);
}
return false;
@@ -251,7 +281,7 @@
@Override
protected String toStringBody() {
- return ", " + activityRecordSnapshot.toString();
+ return super.toStringBody() + ", " + new String(activityRecordSnapshot);
}
@Override
@@ -275,6 +305,42 @@
}
}
+ public static final class ReportFullyDrawn extends BaseWithActivityRecordData {
+ public final long timestampNs;
+
+ public ReportFullyDrawn(@SequenceId long sequenceId,
+ @NonNull @ActivityRecordProto byte[] snapshot,
+ long timestampNs) {
+ super(sequenceId, snapshot);
+ this.timestampNs = timestampNs;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof ReportFullyDrawn) {
+ return timestampNs == ((ReportFullyDrawn)other).timestampNs &&
+ super.equals(other);
+ }
+ return false;
+ }
+
+ @Override
+ protected String toStringBody() {
+ return super.toStringBody() + ", timestampNs=" + Long.toString(timestampNs);
+ }
+
+ @Override
+ protected void writeToParcelImpl(Parcel p, int flags) {
+ super.writeToParcelImpl(p, flags);
+ p.writeLong(timestampNs);
+ }
+
+ ReportFullyDrawn(Parcel p) {
+ super(p);
+ timestampNs = p.readLong();
+ }
+ }
+
@Override
public @ContentsFlags int describeContents() { return 0; }
@@ -348,6 +414,7 @@
ActivityLaunched.class,
ActivityLaunchFinished.class,
ActivityLaunchCancelled.class,
+ ReportFullyDrawn.class,
};
public static class ActivityRecordProtoParcelable {
diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
index 902da4c..f753548 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
@@ -315,19 +315,19 @@
// All callbacks occur on the same background thread. Don't synchronize explicitly.
@Override
- public void onIntentStarted(@NonNull Intent intent) {
+ public void onIntentStarted(@NonNull Intent intent, long timestampNs) {
// #onIntentStarted [is the only transition that] initiates a new launch sequence.
++mSequenceId;
if (DEBUG) {
- Log.v(TAG, String.format("AppLaunchObserver#onIntentStarted(%d, %s)",
- mSequenceId, intent));
+ Log.v(TAG, String.format("AppLaunchObserver#onIntentStarted(%d, %s, %d)",
+ mSequenceId, intent, timestampNs));
}
invokeRemote(mIorapRemote,
(IIorap remote) ->
remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
- new AppLaunchEvent.IntentStarted(mSequenceId, intent))
+ new AppLaunchEvent.IntentStarted(mSequenceId, intent, timestampNs))
);
}
@@ -374,16 +374,34 @@
}
@Override
- public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity) {
+ public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity,
+ long timestampNs) {
if (DEBUG) {
- Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunchFinished(%d, %s)",
- mSequenceId, activity));
+ Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunchFinished(%d, %s, %d)",
+ mSequenceId, activity, timestampNs));
}
invokeRemote(mIorapRemote,
(IIorap remote) ->
remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
- new AppLaunchEvent.ActivityLaunchFinished(mSequenceId, activity))
+ new AppLaunchEvent.ActivityLaunchFinished(mSequenceId,
+ activity,
+ timestampNs))
+ );
+ }
+
+ @Override
+ public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity,
+ long timestampNs) {
+ if (DEBUG) {
+ Log.v(TAG, String.format("AppLaunchObserver#onReportFullyDrawn(%d, %s, %d)",
+ mSequenceId, activity, timestampNs));
+ }
+
+ invokeRemote(mIorapRemote,
+ (IIorap remote) ->
+ remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+ new AppLaunchEvent.ReportFullyDrawn(mSequenceId, activity, timestampNs))
);
}
}
diff --git a/startop/iorap/tests/src/com/google/android/startop/iorap/AppLaunchEventTest.kt b/startop/iorap/tests/src/com/google/android/startop/iorap/AppLaunchEventTest.kt
new file mode 100644
index 0000000..51e407d
--- /dev/null
+++ b/startop/iorap/tests/src/com/google/android/startop/iorap/AppLaunchEventTest.kt
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT 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.google.android.startop.iorap
+
+import android.content.Intent;
+import android.net.Uri
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.test.filters.SmallTest
+import com.google.android.startop.iorap.AppLaunchEvent;
+import com.google.android.startop.iorap.AppLaunchEvent.ActivityLaunched
+import com.google.android.startop.iorap.AppLaunchEvent.ActivityLaunchCancelled
+import com.google.android.startop.iorap.AppLaunchEvent.ActivityLaunchFinished
+import com.google.android.startop.iorap.AppLaunchEvent.IntentStarted;
+import com.google.android.startop.iorap.AppLaunchEvent.IntentFailed;
+import com.google.android.startop.iorap.AppLaunchEvent.ReportFullyDrawn
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+
+/**
+ * Basic unit tests to test all of the [AppLaunchEvent]s in [com.google.android.startop.iorap].
+ */
+@SmallTest
+class AppLaunchEventTest {
+ /**
+ * Test for IntentStarted.
+ */
+ @Test
+ fun testIntentStarted() {
+ var intent = Intent()
+ val valid = IntentStarted(/* sequenceId= */2L, intent, /* timestampNs= */ 1L)
+ val copy = IntentStarted(/* sequenceId= */2L, intent, /* timestampNs= */ 1L)
+ val noneCopy1 = IntentStarted(/* sequenceId= */1L, intent, /* timestampNs= */ 1L)
+ val noneCopy2 = IntentStarted(/* sequenceId= */2L, intent, /* timestampNs= */ 2L)
+ val noneCopy3 = IntentStarted(/* sequenceId= */2L, Intent(), /* timestampNs= */ 1L)
+
+ // equals(Object other)
+ assertThat(valid).isEqualTo(copy)
+ assertThat(valid).isNotEqualTo(noneCopy1)
+ assertThat(valid).isNotEqualTo(noneCopy2)
+ assertThat(valid).isNotEqualTo(noneCopy3)
+
+ // test toString()
+ val result = valid.toString()
+ assertThat(result).isEqualTo("IntentStarted{sequenceId=2, intent=Intent { } , timestampNs=1}")
+ }
+
+ /**
+ * Test for IntentFailed.
+ */
+ @Test
+ fun testIntentFailed() {
+ val valid = IntentFailed(/* sequenceId= */2L)
+ val copy = IntentFailed(/* sequenceId= */2L)
+ val noneCopy = IntentFailed(/* sequenceId= */1L)
+
+ // equals(Object other)
+ assertThat(valid).isEqualTo(copy)
+ assertThat(valid).isNotEqualTo(noneCopy)
+
+ // test toString()
+ val result = valid.toString()
+ assertThat(result).isEqualTo("IntentFailed{sequenceId=2}")
+ }
+
+ /**
+ * Test for ActivityLaunched.
+ */
+ @Test
+ fun testActivityLaunched() {
+ //var activityRecord =
+ val valid = ActivityLaunched(/* sequenceId= */2L, "test".toByteArray(),
+ /* temperature= */ 0)
+ val copy = ActivityLaunched(/* sequenceId= */2L, "test".toByteArray(),
+ /* temperature= */ 0)
+ val noneCopy1 = ActivityLaunched(/* sequenceId= */1L, "test".toByteArray(),
+ /* temperature= */ 0)
+ val noneCopy2 = ActivityLaunched(/* sequenceId= */1L, "test".toByteArray(),
+ /* temperature= */ 1)
+ val noneCopy3 = ActivityLaunched(/* sequenceId= */1L, "test1".toByteArray(),
+ /* temperature= */ 0)
+
+ // equals(Object other)
+ assertThat(valid).isEqualTo(copy)
+ assertThat(valid).isNotEqualTo(noneCopy1)
+ assertThat(valid).isNotEqualTo(noneCopy2)
+ assertThat(valid).isNotEqualTo(noneCopy3)
+
+ // test toString()
+ val result = valid.toString()
+ assertThat(result).isEqualTo("ActivityLaunched{sequenceId=2, test, temperature=0}")
+ }
+
+
+ /**
+ * Test for ActivityLaunchFinished.
+ */
+ @Test
+ fun testActivityLaunchFinished() {
+ val valid = ActivityLaunchFinished(/* sequenceId= */2L, "test".toByteArray(),
+ /* timestampNs= */ 1L)
+ val copy = ActivityLaunchFinished(/* sequenceId= */2L, "test".toByteArray(),
+ /* timestampNs= */ 1L)
+ val noneCopy1 = ActivityLaunchFinished(/* sequenceId= */1L, "test".toByteArray(),
+ /* timestampNs= */ 1L)
+ val noneCopy2 = ActivityLaunchFinished(/* sequenceId= */1L, "test".toByteArray(),
+ /* timestampNs= */ 2L)
+ val noneCopy3 = ActivityLaunchFinished(/* sequenceId= */2L, "test1".toByteArray(),
+ /* timestampNs= */ 1L)
+
+ // equals(Object other)
+ assertThat(valid).isEqualTo(copy)
+ assertThat(valid).isNotEqualTo(noneCopy1)
+ assertThat(valid).isNotEqualTo(noneCopy2)
+ assertThat(valid).isNotEqualTo(noneCopy3)
+
+ // test toString()
+ val result = valid.toString()
+ assertThat(result).isEqualTo("ActivityLaunchFinished{sequenceId=2, test, timestampNs=1}")
+ }
+
+ /**
+ * Test for ActivityLaunchCancelled.
+ */
+ @Test
+ fun testActivityLaunchCancelled() {
+ val valid = ActivityLaunchCancelled(/* sequenceId= */2L, "test".toByteArray())
+ val copy = ActivityLaunchCancelled(/* sequenceId= */2L, "test".toByteArray())
+ val noneCopy1 = ActivityLaunchCancelled(/* sequenceId= */1L, "test".toByteArray())
+ val noneCopy2 = ActivityLaunchCancelled(/* sequenceId= */2L, "test1".toByteArray())
+
+ // equals(Object other)
+ assertThat(valid).isEqualTo(copy)
+ assertThat(valid).isNotEqualTo(noneCopy1)
+ assertThat(valid).isNotEqualTo(noneCopy2)
+
+ // test toString()
+ val result = valid.toString()
+ assertThat(result).isEqualTo("ActivityLaunchCancelled{sequenceId=2, test}")
+ }
+
+ /**
+ * Test for ReportFullyDrawn.
+ */
+ @Test
+ fun testReportFullyDrawn() {
+ val valid = ReportFullyDrawn(/* sequenceId= */2L, "test".toByteArray(), /* timestampNs= */ 1L)
+ val copy = ReportFullyDrawn(/* sequenceId= */2L, "test".toByteArray(), /* timestampNs= */ 1L)
+ val noneCopy1 = ReportFullyDrawn(/* sequenceId= */1L, "test".toByteArray(),
+ /* timestampNs= */ 1L)
+ val noneCopy2 = ReportFullyDrawn(/* sequenceId= */1L, "test".toByteArray(),
+ /* timestampNs= */ 1L)
+ val noneCopy3 = ReportFullyDrawn(/* sequenceId= */2L, "test1".toByteArray(),
+ /* timestampNs= */ 1L)
+
+ // equals(Object other)
+ assertThat(valid).isEqualTo(copy)
+ assertThat(valid).isNotEqualTo(noneCopy1)
+ assertThat(valid).isNotEqualTo(noneCopy2)
+ assertThat(valid).isNotEqualTo(noneCopy3)
+
+ // test toString()
+ val result = valid.toString()
+ assertThat(result).isEqualTo("ReportFullyDrawn{sequenceId=2, test, timestampNs=1}")
+ }
+}
diff --git a/startop/scripts/app_startup/app_startup_runner.py b/startop/scripts/app_startup/app_startup_runner.py
index fa1c4e6..25ee6f7 100755
--- a/startop/scripts/app_startup/app_startup_runner.py
+++ b/startop/scripts/app_startup/app_startup_runner.py
@@ -233,13 +233,17 @@
simulate: bool,
inodes_path: str,
timeout: int,
- compiler_type: CompilerType) -> DataFrame:
+ compiler_type: CompilerType,
+ requires_trace_collection: bool) -> DataFrame:
""" Executes run based on perfetto trace. """
- passed, perfetto_trace_file = run_perfetto_collector(collector_info,
- timeout,
- simulate)
- if not passed:
- raise RuntimeError('Cannot run perfetto collector!')
+ if requires_trace_collection:
+ passed, perfetto_trace_file = run_perfetto_collector(collector_info,
+ timeout,
+ simulate)
+ if not passed:
+ raise RuntimeError('Cannot run perfetto collector!')
+ else:
+ perfetto_trace_file = tempfile.NamedTemporaryFile()
with perfetto_trace_file:
for combos in run_combos:
@@ -271,7 +275,8 @@
simulate: bool,
inodes_path: str,
timeout: int,
- compiler_type: CompilerType):
+ compiler_type: CompilerType,
+ requires_trace_collection: bool):
# nothing will work if the screen isn't unlocked first.
cmd_utils.execute_arbitrary_command([_UNLOCK_SCREEN_SCRIPT],
timeout,
@@ -284,7 +289,8 @@
simulate,
inodes_path,
timeout,
- compiler_type)
+ compiler_type,
+ requires_trace_collection)
def gather_results(commands: Iterable[Tuple[DataFrame]],
key_list: List[str], value_list: List[Tuple[str, ...]]):
@@ -369,11 +375,13 @@
CollectorPackageInfo)
print_utils.debug_print_gen("grouped run combinations: ", grouped_combos())
+ requires_trace_collection = any(i in _TRACING_READAHEADS for i in opts.readaheads)
exec = execute_run_combos(grouped_combos(),
opts.simulate,
opts.inodes,
opts.timeout,
- opts.compiler_type)
+ opts.compiler_type,
+ requires_trace_collection)
results = gather_results(exec, _COMBINATORIAL_OPTIONS, combos())
diff --git a/startop/scripts/iorap/common b/startop/scripts/iorap/common
index 031dabf..387e45d 100755
--- a/startop/scripts/iorap/common
+++ b/startop/scripts/iorap/common
@@ -248,6 +248,6 @@
local remote_path="$(_iorapd_path_to_data_file "$package" "$activity" "compiled_trace.pb")"
# See 'read_ahead.cc' LOG(INFO).
- local pattern="ReadAhead completed ($remote_path)"
+ local pattern="Description = $remote_path"
logcat_wait_for_pattern "$timeout" "$timestamp" "$pattern"
}
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 5e71416..3f348a4 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -728,6 +728,7 @@
}
/** {@hide} */
+ @TestApi
public String getTelecomCallId() {
return mTelecomCallId;
}
@@ -2137,6 +2138,9 @@
}
int state = parcelableCall.getState();
+ if (mTargetSdkVersion < Phone.SDK_VERSION_R && state == Call.STATE_SIMULATED_RINGING) {
+ state = Call.STATE_RINGING;
+ }
boolean stateChanged = mState != state;
if (stateChanged) {
mState = state;
diff --git a/telecomm/java/android/telecom/CallScreeningService.java b/telecomm/java/android/telecom/CallScreeningService.java
index 0d97567..ef1c790 100644
--- a/telecomm/java/android/telecom/CallScreeningService.java
+++ b/telecomm/java/android/telecom/CallScreeningService.java
@@ -374,6 +374,8 @@
new ComponentName(getPackageName(), getClass().getName()));
} else if (response.getSilenceCall()) {
mCallScreeningAdapter.silenceCall(callDetails.getTelecomCallId());
+ } else if (response.getShouldScreenCallFurther()) {
+ mCallScreeningAdapter.screenCallFurther(callDetails.getTelecomCallId());
} else {
mCallScreeningAdapter.allowCall(callDetails.getTelecomCallId());
}
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 0cc052e..2ecdb30 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -21,7 +21,6 @@
import android.bluetooth.BluetoothDevice;
import android.os.Build;
import android.os.Bundle;
-import android.os.RemoteException;
import android.util.ArrayMap;
import java.util.Collections;
@@ -111,6 +110,10 @@
public void onSilenceRinger(Phone phone) { }
}
+ // TODO: replace all usages of this with the actual R constant from Build.VERSION_CODES
+ /** @hide */
+ public static final int SDK_VERSION_R = 30;
+
// A Map allows us to track each Call by its Telecom-specified call ID
private final Map<String, Call> mCallByTelecomCallId = new ArrayMap<>();
@@ -143,6 +146,12 @@
}
final void internalAddCall(ParcelableCall parcelableCall) {
+ if (mTargetSdkVersion < SDK_VERSION_R
+ && parcelableCall.getState() == Call.STATE_AUDIO_PROCESSING) {
+ Log.i(this, "Skipping adding audio processing call for sdk compatibility");
+ return;
+ }
+
Call call = new Call(this, parcelableCall.getId(), mInCallAdapter,
parcelableCall.getState(), mCallingPackage, mTargetSdkVersion);
mCallByTelecomCallId.put(parcelableCall.getId(), call);
@@ -150,7 +159,7 @@
checkCallTree(parcelableCall);
call.internalUpdate(parcelableCall, mCallByTelecomCallId);
fireCallAdded(call);
- }
+ }
final void internalRemoveCall(Call call) {
mCallByTelecomCallId.remove(call.internalGetCallId());
@@ -164,12 +173,28 @@
}
final void internalUpdateCall(ParcelableCall parcelableCall) {
- Call call = mCallByTelecomCallId.get(parcelableCall.getId());
- if (call != null) {
- checkCallTree(parcelableCall);
- call.internalUpdate(parcelableCall, mCallByTelecomCallId);
- }
- }
+ if (mTargetSdkVersion < SDK_VERSION_R
+ && parcelableCall.getState() == Call.STATE_AUDIO_PROCESSING) {
+ Log.i(this, "removing audio processing call during update for sdk compatibility");
+ Call call = mCallByTelecomCallId.get(parcelableCall.getId());
+ if (call != null) {
+ internalRemoveCall(call);
+ }
+ return;
+ }
+
+ Call call = mCallByTelecomCallId.get(parcelableCall.getId());
+ if (call != null) {
+ checkCallTree(parcelableCall);
+ call.internalUpdate(parcelableCall, mCallByTelecomCallId);
+ } else {
+ // This call may have come out of audio processing. Try adding it if our target sdk
+ // version is low enough.
+ if (mTargetSdkVersion < SDK_VERSION_R) {
+ internalAddCall(parcelableCall);
+ }
+ }
+ }
final void internalSetPostDialWait(String telecomId, String remaining) {
Call call = mCallByTelecomCallId.get(telecomId);
diff --git a/telephony/java/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
similarity index 100%
rename from telephony/java/com/android/internal/telephony/SmsApplication.java
rename to telephony/common/com/android/internal/telephony/SmsApplication.java
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 1d5c18d..047b220 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -526,6 +526,15 @@
"default_vm_number_roaming_string";
/**
+ * Where there is no preloaded voicemail number on a SIM card, specifies the carrier's default
+ * voicemail number while the device is both roaming and not registered for IMS.
+ * When empty string, no default voicemail number is specified for roaming network and
+ * unregistered state in IMS.
+ */
+ public static final String KEY_DEFAULT_VM_NUMBER_ROAMING_AND_IMS_UNREGISTERED_STRING =
+ "default_vm_number_roaming_and_ims_unregistered_string";
+
+ /**
* Flag that specifies to use the user's own phone number as the voicemail number when there is
* no pre-loaded voicemail number on the SIM card.
* <p>
@@ -2817,7 +2826,7 @@
"ping_test_before_data_switch_bool";
/**
- * Controls time in milli seconds until DcTracker reevaluates 5G connection state.
+ * Controls time in milliseconds until DcTracker reevaluates 5G connection state.
* @hide
*/
public static final String KEY_5G_WATCHDOG_TIME_MS_LONG =
@@ -3199,6 +3208,13 @@
public static final String KEY_CARRIER_CERTIFICATE_STRING_ARRAY =
"carrier_certificate_string_array";
+ /**
+ * DisconnectCause array to play busy tone. Value should be array of
+ * {@link android.telephony.DisconnectCause}.
+ */
+ public static final String KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY =
+ "disconnect_cause_play_busytone_int_array";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -3225,6 +3241,7 @@
sDefaults.putBoolean(KEY_SUPPORT_DOWNGRADE_VT_TO_AUDIO_BOOL, true);
sDefaults.putString(KEY_DEFAULT_VM_NUMBER_STRING, "");
sDefaults.putString(KEY_DEFAULT_VM_NUMBER_ROAMING_STRING, "");
+ sDefaults.putString(KEY_DEFAULT_VM_NUMBER_ROAMING_AND_IMS_UNREGISTERED_STRING, "");
sDefaults.putBoolean(KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL, false);
sDefaults.putBoolean(KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS, true);
sDefaults.putBoolean(KEY_VILTE_DATA_IS_METERED_BOOL, true);
@@ -3628,6 +3645,8 @@
sDefaults.putBoolean(KEY_SUPPORT_WPS_OVER_IMS_BOOL, true);
sDefaults.putAll(Ims.getDefaults());
sDefaults.putStringArray(KEY_CARRIER_CERTIFICATE_STRING_ARRAY, null);
+ sDefaults.putIntArray(KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY,
+ new int[] {4 /* BUSY */});
}
/**
diff --git a/telephony/java/android/telephony/CellBroadcastService.java b/telephony/java/android/telephony/CellBroadcastService.java
index d5e447e..46eb9df 100644
--- a/telephony/java/android/telephony/CellBroadcastService.java
+++ b/telephony/java/android/telephony/CellBroadcastService.java
@@ -21,6 +21,7 @@
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
+import android.telephony.cdma.CdmaSmsCbProgramData;
/**
* A service which exposes the cell broadcast handling module to the system.
@@ -69,9 +70,11 @@
/**
* Handle a CDMA cell broadcast SMS message forwarded from the system.
* @param slotIndex the index of the slot which received the message
- * @param message the SMS PDU
+ * @param bearerData the CDMA SMS bearer data
+ * @param serviceCategory the CDMA SCPT service category
*/
- public abstract void onCdmaCellBroadcastSms(int slotIndex, byte[] message);
+ public abstract void onCdmaCellBroadcastSms(int slotIndex, byte[] bearerData,
+ @CdmaSmsCbProgramData.Category int serviceCategory);
/**
* If overriding this method, call through to the super method for any unknown actions.
@@ -102,11 +105,14 @@
/**
* Handle a CDMA cell broadcast SMS.
* @param slotIndex the index of the slot which received the broadcast
- * @param message the SMS message PDU
+ * @param bearerData the CDMA SMS bearer data
+ * @param serviceCategory the CDMA SCPT service category
*/
@Override
- public void handleCdmaCellBroadcastSms(int slotIndex, byte[] message) {
- CellBroadcastService.this.onCdmaCellBroadcastSms(slotIndex, message);
+ public void handleCdmaCellBroadcastSms(int slotIndex, byte[] bearerData,
+ int serviceCategory) {
+ CellBroadcastService.this.onCdmaCellBroadcastSms(slotIndex, bearerData,
+ serviceCategory);
}
}
}
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index 432978d..b7dab16 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -35,6 +35,15 @@
/** @hide */
public static final int INVALID_CHANNEL_NUMBER = -1;
+ /**
+ * parameters for validation
+ * @hide
+ */
+ public static final int MCC_LENGTH = 3;
+
+ private static final int MNC_MIN_LENGTH = 2;
+ private static final int MNC_MAX_LENGTH = 3;
+
// Log tag
/** @hide */
protected final String mTag;
@@ -207,6 +216,17 @@
dest.writeString(mAlphaShort);
}
+ /** Used by phone interface manager to verify if a given string is valid MccMnc
+ * @hide
+ */
+ public static boolean isValidPlmn(@NonNull String plmn) {
+ if (plmn.length() < MCC_LENGTH + MNC_MIN_LENGTH
+ || plmn.length() > MCC_LENGTH + MNC_MAX_LENGTH) {
+ return false;
+ }
+ return (isMcc(plmn.substring(0, MCC_LENGTH)) && isMnc(plmn.substring(MCC_LENGTH)));
+ }
+
/**
* Construct from Parcel
* @hide
@@ -267,10 +287,10 @@
/** @hide */
private static boolean isMcc(@NonNull String mcc) {
// ensure no out of bounds indexing
- if (mcc.length() != 3) return false;
+ if (mcc.length() != MCC_LENGTH) return false;
// Character.isDigit allows all unicode digits, not just [0-9]
- for (int i = 0; i < 3; i++) {
+ for (int i = 0; i < MCC_LENGTH; i++) {
if (mcc.charAt(i) < '0' || mcc.charAt(i) > '9') return false;
}
@@ -280,7 +300,7 @@
/** @hide */
private static boolean isMnc(@NonNull String mnc) {
// ensure no out of bounds indexing
- if (mnc.length() < 2 || mnc.length() > 3) return false;
+ if (mnc.length() < MNC_MIN_LENGTH || mnc.length() > MNC_MAX_LENGTH) return false;
// Character.isDigit allows all unicode digits, not just [0-9]
for (int i = 0; i < mnc.length(); i++) {
@@ -289,4 +309,5 @@
return true;
}
+
}
diff --git a/telephony/java/android/telephony/CellInfoNr.java b/telephony/java/android/telephony/CellInfoNr.java
index 9775abd..cea8323 100644
--- a/telephony/java/android/telephony/CellInfoNr.java
+++ b/telephony/java/android/telephony/CellInfoNr.java
@@ -19,6 +19,8 @@
import android.annotation.NonNull;
import android.os.Parcel;
+import dalvik.annotation.codegen.CovariantReturnType;
+
import java.util.Objects;
/**
@@ -46,6 +48,7 @@
/**
* @return a {@link CellIdentityNr} instance.
*/
+ @CovariantReturnType(returnType = CellIdentityNr.class, presentAfter = 29)
@Override
@NonNull
public CellIdentity getCellIdentity() {
@@ -55,6 +58,7 @@
/**
* @return a {@link CellSignalStrengthNr} instance.
*/
+ @CovariantReturnType(returnType = CellSignalStrengthNr.class, presentAfter = 29)
@Override
@NonNull
public CellSignalStrength getCellSignalStrength() {
diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
index cdf01da..4dc54f0 100644
--- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
@@ -245,9 +245,12 @@
}
/**
- * Get the Ec/No as dB
+ * Get the Ec/No (Energy per chip over the noise spectral density) as dB.
*
- * @hide
+ * Reference: TS 25.133 Section 9.1.2.3
+ *
+ * @return the Ec/No of the measured cell in the range [-24, 1] or
+ * {@link android.telephony.CellInfo#UNAVAILABLE UNAVAILABLE} if unavailable
*/
public int getEcNo() {
return mEcNo;
diff --git a/telephony/java/android/telephony/ICellBroadcastService.aidl b/telephony/java/android/telephony/ICellBroadcastService.aidl
index eff64a2..bcd6cc5 100644
--- a/telephony/java/android/telephony/ICellBroadcastService.aidl
+++ b/telephony/java/android/telephony/ICellBroadcastService.aidl
@@ -28,5 +28,5 @@
oneway void handleGsmCellBroadcastSms(int slotId, in byte[] message);
/** @see android.telephony.CellBroadcastService#onCdmaCellBroadcastSms */
- oneway void handleCdmaCellBroadcastSms(int slotId, in byte[] message);
+ oneway void handleCdmaCellBroadcastSms(int slotId, in byte[] bearerData, int serviceCategory);
}
diff --git a/telephony/java/android/telephony/ICellInfoCallback.aidl b/telephony/java/android/telephony/ICellInfoCallback.aidl
index ee3c1b1..60732a3 100644
--- a/telephony/java/android/telephony/ICellInfoCallback.aidl
+++ b/telephony/java/android/telephony/ICellInfoCallback.aidl
@@ -16,7 +16,6 @@
package android.telephony;
-import android.os.ParcelableException;
import android.telephony.CellInfo;
import java.util.List;
@@ -28,5 +27,5 @@
oneway interface ICellInfoCallback
{
void onCellInfo(in List<CellInfo> state);
- void onError(in int errorCode, in ParcelableException detail);
+ void onError(in int errorCode, in String exceptionName, in String message);
}
diff --git a/telephony/java/android/telephony/ModemActivityInfo.java b/telephony/java/android/telephony/ModemActivityInfo.java
index 43bc85c..d105fe3 100644
--- a/telephony/java/android/telephony/ModemActivityInfo.java
+++ b/telephony/java/android/telephony/ModemActivityInfo.java
@@ -20,11 +20,10 @@
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
-
import android.os.SystemClock;
import android.util.Range;
+
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
/**
@@ -64,17 +63,20 @@
mTimestamp = timestamp;
mSleepTimeMs = sleepTimeMs;
mIdleTimeMs = idleTimeMs;
- if (txTimeMs != null) {
- populateTransmitPowerRange(txTimeMs);
- }
+ populateTransmitPowerRange(txTimeMs);
mRxTimeMs = rxTimeMs;
}
/** helper API to populate tx power range for each bucket **/
private void populateTransmitPowerRange(@NonNull int[] transmitPowerMs) {
- for (int i = 0; i < Math.min(transmitPowerMs.length, TX_POWER_LEVELS); i++) {
+ int i = 0;
+ for ( ; i < Math.min(transmitPowerMs.length, TX_POWER_LEVELS); i++) {
mTransmitPowerInfo.add(i, new TransmitPower(TX_POWER_RANGES[i], transmitPowerMs[i]));
}
+ // Make sure that mTransmitPowerInfo is fully initialized.
+ for ( ; i < TX_POWER_LEVELS; i++) {
+ mTransmitPowerInfo.add(i, new TransmitPower(TX_POWER_RANGES[i], 0));
+ }
}
@Override
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index f4330fa..2d35f8e 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -1814,6 +1814,36 @@
// SMS send failure result codes
+ /** @hide */
+ @IntDef(prefix = { "RESULT" }, value = {
+ RESULT_ERROR_NONE,
+ RESULT_ERROR_GENERIC_FAILURE,
+ RESULT_ERROR_RADIO_OFF,
+ RESULT_ERROR_NULL_PDU,
+ RESULT_ERROR_NO_SERVICE,
+ RESULT_ERROR_LIMIT_EXCEEDED,
+ RESULT_ERROR_FDN_CHECK_FAILURE,
+ RESULT_ERROR_SHORT_CODE_NOT_ALLOWED,
+ RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED,
+ RESULT_RADIO_NOT_AVAILABLE,
+ RESULT_NETWORK_REJECT,
+ RESULT_INVALID_ARGUMENTS,
+ RESULT_INVALID_STATE,
+ RESULT_NO_MEMORY,
+ RESULT_INVALID_SMS_FORMAT,
+ RESULT_SYSTEM_ERROR,
+ RESULT_MODEM_ERROR,
+ RESULT_NETWORK_ERROR,
+ RESULT_INVALID_SMSC_ADDRESS,
+ RESULT_OPERATION_NOT_ALLOWED,
+ RESULT_INTERNAL_ERROR,
+ RESULT_NO_RESOURCES,
+ RESULT_CANCELLED,
+ RESULT_REQUEST_NOT_SUPPORTED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Result {}
+
/**
* No error.
* @hide
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 1b87657..40d057f 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -53,7 +53,6 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.telephony.Annotation.NetworkType;
import android.telephony.euicc.EuiccManager;
import android.telephony.ims.ImsMmTelManager;
import android.util.DisplayMetrics;
@@ -2101,13 +2100,13 @@
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public static boolean isValidSlotIndex(int slotIndex) {
- return slotIndex >= 0 && slotIndex < TelephonyManager.getDefault().getMaxPhoneCount();
+ return slotIndex >= 0 && slotIndex < TelephonyManager.getDefault().getSupportedModemCount();
}
/** @hide */
@UnsupportedAppUsage
public static boolean isValidPhoneId(int phoneId) {
- return phoneId >= 0 && phoneId < TelephonyManager.getDefault().getMaxPhoneCount();
+ return phoneId >= 0 && phoneId < TelephonyManager.getDefault().getSupportedModemCount();
}
/** @hide */
@@ -2420,8 +2419,12 @@
* @param plans the list of plans. The first plan is always the primary and
* most important plan. Any additional plans are secondary and
* may not be displayed or used by decision making logic.
+ * The list of all plans must meet the requirements defined in
+ * {@link SubscriptionPlan.Builder#setNetworkTypes(int[])}.
* @throws SecurityException if the caller doesn't meet the requirements
* outlined above.
+ * @throws IllegalArgumentException if plans don't meet the requirements
+ * mentioned above.
*/
public void setSubscriptionPlans(int subId, @NonNull List<SubscriptionPlan> plans) {
try {
@@ -2466,51 +2469,10 @@
*/
public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered,
@DurationMillisLong long timeoutMillis) {
- setSubscriptionOverrideUnmetered(subId, null, overrideUnmetered, timeoutMillis);
- }
-
- /**
- * Temporarily override the billing relationship between a carrier and
- * a specific subscriber to be considered unmetered for the given network
- * types. This will be reflected to apps via
- * {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED}.
- * This method is only accessible to the following narrow set of apps:
- * <ul>
- * <li>The carrier app for this subscriberId, as determined by
- * {@link TelephonyManager#hasCarrierPrivileges()}.
- * <li>The carrier app explicitly delegated access through
- * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}.
- * </ul>
- *
- * @param subId the subscriber this override applies to.
- * @param networkTypes all network types to set an override for. A null
- * network type means to apply the override to all network types.
- * Any unspecified network types will default to metered.
- * @param overrideUnmetered set if the billing relationship should be
- * considered unmetered.
- * @param timeoutMillis the timeout after which the requested override will
- * be automatically cleared, or {@code 0} to leave in the
- * requested state until explicitly cleared, or the next reboot,
- * whichever happens first.
- * @throws SecurityException if the caller doesn't meet the requirements
- * outlined above.
- * {@hide}
- */
- public void setSubscriptionOverrideUnmetered(int subId,
- @Nullable @NetworkType int[] networkTypes, boolean overrideUnmetered,
- @DurationMillisLong long timeoutMillis) {
try {
- long networkTypeMask = 0;
- if (networkTypes != null) {
- for (int networkType : networkTypes) {
- networkTypeMask |= TelephonyManager.getBitMaskForNetworkType(networkType);
- }
- } else {
- networkTypeMask = ~0;
- }
final int overrideValue = overrideUnmetered ? OVERRIDE_UNMETERED : 0;
getNetworkPolicy().setSubscriptionOverride(subId, OVERRIDE_UNMETERED, overrideValue,
- networkTypeMask, timeoutMillis, mContext.getOpPackageName());
+ timeoutMillis, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2542,52 +2504,10 @@
*/
public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested,
@DurationMillisLong long timeoutMillis) {
- setSubscriptionOverrideCongested(subId, null, overrideCongested, timeoutMillis);
- }
-
- /**
- * Temporarily override the billing relationship plan between a carrier and
- * a specific subscriber to be considered congested. This will cause the
- * device to delay certain network requests when possible, such as developer
- * jobs that are willing to run in a flexible time window.
- * <p>
- * This method is only accessible to the following narrow set of apps:
- * <ul>
- * <li>The carrier app for this subscriberId, as determined by
- * {@link TelephonyManager#hasCarrierPrivileges()}.
- * <li>The carrier app explicitly delegated access through
- * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}.
- * </ul>
- *
- * @param subId the subscriber this override applies to.
- * @param networkTypes all network types to set an override for. A null
- * network type means to apply the override to all network types.
- * Any unspecified network types will default to not congested.
- * @param overrideCongested set if the subscription should be considered
- * congested.
- * @param timeoutMillis the timeout after which the requested override will
- * be automatically cleared, or {@code 0} to leave in the
- * requested state until explicitly cleared, or the next reboot,
- * whichever happens first.
- * @throws SecurityException if the caller doesn't meet the requirements
- * outlined above.
- * @hide
- */
- public void setSubscriptionOverrideCongested(int subId,
- @Nullable @NetworkType int[] networkTypes, boolean overrideCongested,
- @DurationMillisLong long timeoutMillis) {
try {
- long networkTypeMask = 0;
- if (networkTypes != null) {
- for (int networkType : networkTypes) {
- networkTypeMask |= TelephonyManager.getBitMaskForNetworkType(networkType);
- }
- } else {
- networkTypeMask = ~0;
- }
final int overrideValue = overrideCongested ? OVERRIDE_CONGESTED : 0;
getNetworkPolicy().setSubscriptionOverride(subId, OVERRIDE_CONGESTED, overrideValue,
- networkTypeMask, timeoutMillis, mContext.getOpPackageName());
+ timeoutMillis, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/telephony/java/android/telephony/SubscriptionPlan.java b/telephony/java/android/telephony/SubscriptionPlan.java
index ec2050f..e24eb26 100644
--- a/telephony/java/android/telephony/SubscriptionPlan.java
+++ b/telephony/java/android/telephony/SubscriptionPlan.java
@@ -24,6 +24,7 @@
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import android.telephony.Annotation.NetworkType;
import android.util.Range;
import android.util.RecurrenceRule;
@@ -33,6 +34,7 @@
import java.lang.annotation.RetentionPolicy;
import java.time.Period;
import java.time.ZonedDateTime;
+import java.util.Arrays;
import java.util.Iterator;
import java.util.Objects;
@@ -80,6 +82,8 @@
private int dataLimitBehavior = LIMIT_BEHAVIOR_UNKNOWN;
private long dataUsageBytes = BYTES_UNKNOWN;
private long dataUsageTime = TIME_UNKNOWN;
+ private @NetworkType int[] networkTypes;
+ private long networkTypesBitMask;
private SubscriptionPlan(RecurrenceRule cycleRule) {
this.cycleRule = Preconditions.checkNotNull(cycleRule);
@@ -93,6 +97,7 @@
dataLimitBehavior = source.readInt();
dataUsageBytes = source.readLong();
dataUsageTime = source.readLong();
+ networkTypes = source.createIntArray();
}
@Override
@@ -109,6 +114,7 @@
dest.writeInt(dataLimitBehavior);
dest.writeLong(dataUsageBytes);
dest.writeLong(dataUsageTime);
+ dest.writeIntArray(networkTypes);
}
@Override
@@ -121,13 +127,14 @@
.append(" dataLimitBehavior=").append(dataLimitBehavior)
.append(" dataUsageBytes=").append(dataUsageBytes)
.append(" dataUsageTime=").append(dataUsageTime)
+ .append(" networkTypes=").append(Arrays.toString(networkTypes))
.append("}").toString();
}
@Override
public int hashCode() {
return Objects.hash(cycleRule, title, summary, dataLimitBytes, dataLimitBehavior,
- dataUsageBytes, dataUsageTime);
+ dataUsageBytes, dataUsageTime, Arrays.hashCode(networkTypes));
}
@Override
@@ -140,7 +147,8 @@
&& dataLimitBytes == other.dataLimitBytes
&& dataLimitBehavior == other.dataLimitBehavior
&& dataUsageBytes == other.dataUsageBytes
- && dataUsageTime == other.dataUsageTime;
+ && dataUsageTime == other.dataUsageTime
+ && Arrays.equals(networkTypes, other.networkTypes);
}
return false;
}
@@ -204,6 +212,32 @@
}
/**
+ * Return an array containing all {@link NetworkType}s this SubscriptionPlan applies to.
+ * A null array means this SubscriptionPlan applies to all network types.
+ */
+ public @Nullable @NetworkType int[] getNetworkTypes() {
+ return networkTypes;
+ }
+
+ /**
+ * Return the networkTypes array converted to a {@link TelephonyManager.NetworkTypeBitMask}
+ * @hide
+ */
+ public long getNetworkTypesBitMask() {
+ // calculate bitmask the first time and save for future calls
+ if (networkTypesBitMask == 0) {
+ if (networkTypes == null) {
+ networkTypesBitMask = ~0;
+ } else {
+ for (int networkType : networkTypes) {
+ networkTypesBitMask |= TelephonyManager.getBitMaskForNetworkType(networkType);
+ }
+ }
+ }
+ return networkTypesBitMask;
+ }
+
+ /**
* Return an iterator that will return all valid data usage cycles based on
* any recurrence rules. The iterator starts from the currently active cycle
* and walks backwards through time.
@@ -335,5 +369,24 @@
plan.dataUsageTime = dataUsageTime;
return this;
}
+
+ /**
+ * Set the network types this SubscriptionPlan applies to.
+ * The developer must supply at least one plan that applies to all network types (default),
+ * and all additional plans may not include a particular network type more than once.
+ * Plan selection will prefer plans that have specific network types defined
+ * over plans that apply to all network types.
+ *
+ * @param networkTypes a set of all {@link NetworkType}s that apply to this plan.
+ * A null value or empty array means the plan applies to all network types.
+ */
+ public @NonNull Builder setNetworkTypes(@Nullable @NetworkType int[] networkTypes) {
+ if (networkTypes == null || networkTypes.length == 0) {
+ plan.networkTypes = null;
+ } else {
+ plan.networkTypes = networkTypes;
+ }
+ return this;
+ }
}
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 66571e3..03e57e7 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -65,7 +65,6 @@
import android.telecom.TelecomManager;
import android.telephony.Annotation.ApnType;
import android.telephony.Annotation.CallState;
-import android.telephony.Annotation.DataState;
import android.telephony.Annotation.NetworkType;
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.SimActivationState;
@@ -283,6 +282,21 @@
};
/** @hide */
+ @IntDef(prefix = {"MODEM_COUNT_"},
+ value = {
+ MODEM_COUNT_NO_MODEM,
+ MODEM_COUNT_SINGLE_MODEM,
+ MODEM_COUNT_DUAL_MODEM,
+ MODEM_COUNT_TRI_MODEM
+ })
+ public @interface ModemCount {}
+
+ public static final int MODEM_COUNT_NO_MODEM = 0;
+ public static final int MODEM_COUNT_SINGLE_MODEM = 1;
+ public static final int MODEM_COUNT_DUAL_MODEM = 2;
+ public static final int MODEM_COUNT_TRI_MODEM = 3;
+
+ /** @hide */
@UnsupportedAppUsage
public TelephonyManager(Context context) {
this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID);
@@ -359,12 +373,26 @@
/**
* Returns the number of phones available.
* Returns 0 if none of voice, sms, data is not supported
- * Returns 1 for Single standby mode (Single SIM functionality)
- * Returns 2 for Dual standby mode.(Dual SIM functionality)
- * Returns 3 for Tri standby mode.(Tri SIM functionality)
+ * Returns 1 for Single standby mode (Single SIM functionality).
+ * Returns 2 for Dual standby mode (Dual SIM functionality).
+ * Returns 3 for Tri standby mode (Tri SIM functionality).
+ * @deprecated Use {@link #getActiveModemCount} instead.
*/
+ @Deprecated
public int getPhoneCount() {
- int phoneCount = 1;
+ return getActiveModemCount();
+ }
+
+ /**
+ * Returns the number of logical modems currently configured to be activated.
+ *
+ * Returns 0 if none of voice, sms, data is not supported
+ * Returns 1 for Single standby mode (Single SIM functionality).
+ * Returns 2 for Dual standby mode (Dual SIM functionality).
+ * Returns 3 for Tri standby mode (Tri SIM functionality).
+ */
+ public @ModemCount int getActiveModemCount() {
+ int modemCount = 1;
switch (getMultiSimConfiguration()) {
case UNKNOWN:
ConnectivityManager cm = mContext == null ? null : (ConnectivityManager) mContext
@@ -372,33 +400,30 @@
// check for voice and data support, 0 if not supported
if (!isVoiceCapable() && !isSmsCapable() && cm != null
&& !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) {
- phoneCount = 0;
+ modemCount = MODEM_COUNT_NO_MODEM;
} else {
- phoneCount = 1;
+ modemCount = MODEM_COUNT_SINGLE_MODEM;
}
break;
case DSDS:
case DSDA:
- phoneCount = PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM;
+ modemCount = MODEM_COUNT_DUAL_MODEM;
break;
case TSTS:
- phoneCount = PhoneConstants.MAX_PHONE_COUNT_TRI_SIM;
+ modemCount = MODEM_COUNT_TRI_MODEM;
break;
}
- return phoneCount;
+ return modemCount;
}
/**
- *
- * Return how many phone / logical modem can be active simultaneously, in terms of device
+ * Return how many logical modem can be potentially active simultaneously, in terms of hardware
* capability.
- * For example, for a dual-SIM capable device, it always returns 2, even if only one logical
- * modem / SIM is active (aka in single SIM mode).
- *
- * TODO: b/139642279 publicize and rename.
- * @hide
+ * It might return different value from {@link #getActiveModemCount}. For example, for a
+ * dual-SIM capable device operating in single SIM mode (only one logical modem is turned on),
+ * {@link #getActiveModemCount} returns 1 while this API returns 2.
*/
- public int getMaxPhoneCount() {
+ public @ModemCount int getSupportedModemCount() {
// TODO: b/139642279 when turning on this feature, remove dependency of
// PROPERTY_REBOOT_REQUIRED_ON_MODEM_CHANGE and always return result based on
// PROPERTY_MAX_ACTIVE_MODEMS.
@@ -5545,18 +5570,20 @@
telephony.requestCellInfoUpdate(
getSubId(),
new ICellInfoCallback.Stub() {
+ @Override
public void onCellInfo(List<CellInfo> cellInfo) {
Binder.withCleanCallingIdentity(() ->
executor.execute(() -> callback.onCellInfo(cellInfo)));
}
- public void onError(int errorCode, android.os.ParcelableException detail) {
+ @Override
+ public void onError(int errorCode, String exceptionName, String message) {
Binder.withCleanCallingIdentity(() ->
executor.execute(() -> callback.onError(
- errorCode, detail.getCause())));
+ errorCode,
+ createThrowableByClassName(exceptionName, message))));
}
}, getOpPackageName());
-
} catch (RemoteException ex) {
}
}
@@ -5585,21 +5612,36 @@
telephony.requestCellInfoUpdateWithWorkSource(
getSubId(),
new ICellInfoCallback.Stub() {
+ @Override
public void onCellInfo(List<CellInfo> cellInfo) {
Binder.withCleanCallingIdentity(() ->
executor.execute(() -> callback.onCellInfo(cellInfo)));
}
- public void onError(int errorCode, android.os.ParcelableException detail) {
+ @Override
+ public void onError(int errorCode, String exceptionName, String message) {
Binder.withCleanCallingIdentity(() ->
executor.execute(() -> callback.onError(
- errorCode, detail.getCause())));
+ errorCode,
+ createThrowableByClassName(exceptionName, message))));
}
}, getOpPackageName(), workSource);
} catch (RemoteException ex) {
}
}
+ private static Throwable createThrowableByClassName(String className, String message) {
+ if (className == null) {
+ return null;
+ }
+ try {
+ Class<?> c = Class.forName(className);
+ return (Throwable) c.getConstructor(String.class).newInstance(message);
+ } catch (ReflectiveOperationException | ClassCastException e) {
+ }
+ return new RuntimeException(className + ": " + message);
+ }
+
/**
* Sets the minimum time in milli-seconds between {@link PhoneStateListener#onCellInfoChanged
* PhoneStateListener.onCellInfoChanged} will be invoked.
@@ -6852,6 +6894,40 @@
}
/**
+ * Replace the contents of the forbidden PLMN SIM file with the provided values.
+ * Passing an empty list will clear the contents of the EFfplmn file.
+ * If the provided list is shorter than the size of EFfplmn, then the list will be padded
+ * up to the file size with 'FFFFFF'. (required by 3GPP TS 31.102 spec 4.2.16)
+ * If the list is longer than the size of EFfplmn, then the file will be written from the
+ * beginning of the list up to the file size.
+ *
+ * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
+ * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}).
+ *
+ * @param fplmns a list of PLMNs to be forbidden.
+ *
+ * @return number of PLMNs that were successfully written to the SIM FPLMN list.
+ * This may be less than the number of PLMNs passed in where the SIM file does not have enough
+ * room for all of the values passed in. Return -1 in the event of an unexpected failure
+ */
+ @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public int setForbiddenPlmns(@NonNull List<String> fplmns) {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony == null) return 0;
+ return telephony.setForbiddenPlmns(
+ getSubId(), APPTYPE_USIM, fplmns, getOpPackageName());
+ } catch (RemoteException ex) {
+ Rlog.e(TAG, "setForbiddenPlmns RemoteException: " + ex.getMessage());
+ } catch (NullPointerException ex) {
+ // This could happen before phone starts
+ Rlog.e(TAG, "setForbiddenPlmns NullPointerException: " + ex.getMessage());
+ }
+ return 0;
+ }
+
+ /**
* Get P-CSCF address from PCO after data connection is established or modified.
* @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN
* @return array of P-CSCF address
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
index 28747da..9ff8515 100644
--- a/telephony/java/android/telephony/TelephonyScanManager.java
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -104,7 +104,7 @@
private final Looper mLooper;
private final Messenger mMessenger;
- private SparseArray<NetworkScanInfo> mScanInfo = new SparseArray<NetworkScanInfo>();
+ private final SparseArray<NetworkScanInfo> mScanInfo = new SparseArray<NetworkScanInfo>();
public TelephonyScanManager() {
HandlerThread thread = new HandlerThread(TAG);
@@ -204,14 +204,16 @@
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
- int scanId = telephony.requestNetworkScan(
- subId, request, mMessenger, new Binder(), callingPackage);
- if (scanId == INVALID_SCAN_ID) {
- Rlog.e(TAG, "Failed to initiate network scan");
- return null;
+ synchronized (mScanInfo) {
+ int scanId = telephony.requestNetworkScan(
+ subId, request, mMessenger, new Binder(), callingPackage);
+ if (scanId == INVALID_SCAN_ID) {
+ Rlog.e(TAG, "Failed to initiate network scan");
+ return null;
+ }
+ saveScanInfo(scanId, request, executor, callback);
+ return new NetworkScan(scanId, subId);
}
- saveScanInfo(scanId, request, executor, callback);
- return new NetworkScan(scanId, subId);
}
} catch (RemoteException ex) {
Rlog.e(TAG, "requestNetworkScan RemoteException", ex);
@@ -223,9 +225,7 @@
private void saveScanInfo(
int id, NetworkScanRequest request, Executor executor, NetworkScanCallback callback) {
- synchronized (mScanInfo) {
- mScanInfo.put(id, new NetworkScanInfo(request, executor, callback));
- }
+ mScanInfo.put(id, new NetworkScanInfo(request, executor, callback));
}
private ITelephony getITelephony() {
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index cabd4df..5a90cb1 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -32,6 +32,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.telephony.TelephonyManager;
+import android.telephony.euicc.EuiccCardManager.ResetOption;
import com.android.internal.telephony.euicc.IEuiccController;
@@ -821,17 +822,22 @@
}
/**
- * Erase all subscriptions and reset the eUICC.
+ * Erase all operational subscriptions and reset the eUICC.
*
* <p>Requires that the calling app has the
* {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
*
* @param callbackIntent a PendingIntent to launch when the operation completes.
+ *
+ * @deprecated From R, callers should specify a flag for specific set of subscriptions to erase
+ * and use @link{eraseSubscriptionsWithOptions} instead
+ *
* @hide
*/
@SystemApi
@RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
- public void eraseSubscriptions(PendingIntent callbackIntent) {
+ @Deprecated
+ public void eraseSubscriptions(@NonNull PendingIntent callbackIntent) {
if (!isEnabled()) {
sendUnavailableError(callbackIntent);
return;
@@ -844,6 +850,32 @@
}
/**
+ * Erase all specific subscriptions and reset the eUICC.
+ *
+ * <p>Requires that the calling app has the
+ * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission.
+ *
+ * @param options flag indicating specific set of subscriptions to erase
+ * @param callbackIntent a PendingIntent to launch when the operation completes.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+ public void eraseSubscriptionsWithOptions(
+ @ResetOption int options, @NonNull PendingIntent callbackIntent) {
+ if (!isEnabled()) {
+ sendUnavailableError(callbackIntent);
+ return;
+ }
+ try {
+ getIEuiccController().eraseSubscriptionsWithOptions(mCardId, options, callbackIntent);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Ensure that subscriptions will be retained on the next factory reset.
*
* <p>By default, all subscriptions on the eUICC are erased the first time a device boots (ever
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index a1a7fcc..2fad847 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -183,19 +183,17 @@
/**
* Notifies the framework when the IMS Provider is registered to the IMS network.
*
- * @param imsTransportType the radio access technology. Valid values are defined in
- * {@link android.telephony.AccessNetworkConstants.TransportType}.
+ * @param imsTransportType the radio access technology.
*/
- public void onRegistered(int imsTransportType) {
+ public void onRegistered(@AccessNetworkConstants.TransportType int imsTransportType) {
}
/**
* Notifies the framework when the IMS Provider is trying to register the IMS network.
*
- * @param imsTransportType the radio access technology. Valid values are defined in
- * {@link android.telephony.AccessNetworkConstants.TransportType}.
+ * @param imsTransportType the radio access technology.
*/
- public void onRegistering(int imsTransportType) {
+ public void onRegistering(@AccessNetworkConstants.TransportType int imsTransportType) {
}
/**
@@ -207,15 +205,14 @@
}
/**
- * A failure has occurred when trying to handover registration to another technology type,
- * defined in {@link android.telephony.AccessNetworkConstants.TransportType}
+ * A failure has occurred when trying to handover registration to another technology type.
*
- * @param imsTransportType The
- * {@link android.telephony.AccessNetworkConstants.TransportType}
- * transport type that has failed to handover registration to.
+ * @param imsTransportType The transport type that has failed to handover registration to.
* @param info A {@link ImsReasonInfo} that identifies the reason for failure.
*/
- public void onTechnologyChangeFailed(int imsTransportType, @Nullable ImsReasonInfo info) {
+ public void onTechnologyChangeFailed(
+ @AccessNetworkConstants.TransportType int imsTransportType,
+ @Nullable ImsReasonInfo info) {
}
/**
diff --git a/telephony/java/android/telephony/ims/ImsReasonInfo.java b/telephony/java/android/telephony/ims/ImsReasonInfo.java
index 1e0d9a78..10251d7 100644
--- a/telephony/java/android/telephony/ims/ImsReasonInfo.java
+++ b/telephony/java/android/telephony/ims/ImsReasonInfo.java
@@ -885,6 +885,12 @@
*/
public static final int CODE_WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION = 1623;
+ /**
+ * The dialed RTT call should be retried without RTT
+ * @hide
+ */
+ public static final int CODE_RETRY_ON_IMS_WITHOUT_RTT = 3001;
+
/*
* OEM specific error codes. To be used by OEMs when they don't want to reveal error code which
* would be replaced by ERROR_UNSPECIFIED.
@@ -1065,6 +1071,7 @@
CODE_REJECT_VT_AVPF_NOT_ALLOWED,
CODE_REJECT_ONGOING_ENCRYPTED_CALL,
CODE_REJECT_ONGOING_CS_CALL,
+ CODE_RETRY_ON_IMS_WITHOUT_RTT,
CODE_OEM_CAUSE_1,
CODE_OEM_CAUSE_2,
CODE_OEM_CAUSE_3,
diff --git a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
index 175769b..36ece95 100644
--- a/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsSmsImplBase.java
@@ -17,6 +17,7 @@
package android.telephony.ims.stub;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.SystemApi;
import android.os.RemoteException;
import android.telephony.SmsManager;
@@ -148,14 +149,16 @@
*
* @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
- * {@link SmsMessage#FORMAT_3GPP2}.
+ * @param messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
+ * @param format the format of the message.
* @param smsc the Short Message Service Center address.
* @param isRetry whether it is a retry of an already attempted message or not.
* @param pdu PDU representing the contents of the message.
*/
- public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
+ public void sendSms(int token, @IntRange(from = 0, to = 65535) int messageRef,
+ @SmsMessage.Format String format, String smsc, boolean isRetry,
byte[] pdu) {
// Base implementation returns error. Should be overridden.
try {
@@ -172,14 +175,13 @@
* provider.
*
* @param token token provided in {@link #onSmsReceived(int, String, byte[])}
- * @param messageRef the message reference
- * @param result result of delivering the message. Valid values are:
- * {@link #DELIVER_STATUS_OK},
- * {@link #DELIVER_STATUS_ERROR_GENERIC},
- * {@link #DELIVER_STATUS_ERROR_NO_MEMORY},
- * {@link #DELIVER_STATUS_ERROR_REQUEST_NOT_SUPPORTED}
+ * @param messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
+ * @param result result of delivering the message.
*/
- public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) {
+ public void acknowledgeSms(int token, @IntRange(from = 0, to = 65535) int messageRef,
+ @DeliverStatusResult int result) {
Log.e(LOG_TAG, "acknowledgeSms() not implemented.");
}
@@ -191,12 +193,13 @@
*
* @param token token provided in {@link #onSmsStatusReportReceived(int, int, String, byte[])}
* or {@link #onSmsStatusReportReceived(int, String, byte[])}
- * @param messageRef the message reference
- * @param result result of delivering the message. Valid values are:
- * {@link #STATUS_REPORT_STATUS_OK},
- * {@link #STATUS_REPORT_STATUS_ERROR}
+ * @param messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
+ * @param result result of delivering the message.
*/
- public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) {
+ public void acknowledgeSmsReport(int token, @IntRange(from = 0, to = 65535) int messageRef,
+ @StatusReportResult int result) {
Log.e(LOG_TAG, "acknowledgeSmsReport() not implemented.");
}
@@ -210,12 +213,12 @@
* {@link #DELIVER_STATUS_ERROR_GENERIC} result code.
* @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 format the format of the message.
* @param pdu PDU representing the contents of the message.
* @throws RuntimeException if called before {@link #onReady()} is triggered.
*/
- public final void onSmsReceived(int token, String format, byte[] pdu) throws RuntimeException {
+ public final void onSmsReceived(int token, @SmsMessage.Format String format, byte[] pdu)
+ throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
throw new RuntimeException("Feature not ready.");
@@ -241,13 +244,16 @@
* sent successfully.
*
* @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 messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
*
* @throws RuntimeException if called before {@link #onReady()} is triggered or if the
* connection to the framework is not available. If this happens attempting to send the SMS
* should be aborted.
*/
- public final void onSendSmsResultSuccess(int token, int messageRef) throws RuntimeException {
+ public final void onSendSmsResultSuccess(int token,
+ @IntRange(from = 0, to = 65535) int messageRef) throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
throw new RuntimeException("Feature not ready.");
@@ -266,34 +272,11 @@
* to the platform.
*
* @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 messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
* @param status result of sending the SMS.
- * @param reason reason in case status is failure. Valid values are:
- * {@link SmsManager#RESULT_ERROR_NONE},
- * {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE},
- * {@link SmsManager#RESULT_ERROR_RADIO_OFF},
- * {@link SmsManager#RESULT_ERROR_NULL_PDU},
- * {@link SmsManager#RESULT_ERROR_NO_SERVICE},
- * {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED},
- * {@link SmsManager#RESULT_ERROR_FDN_CHECK_FAILURE},
- * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED},
- * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED},
- * {@link SmsManager#RESULT_RADIO_NOT_AVAILABLE},
- * {@link SmsManager#RESULT_NETWORK_REJECT},
- * {@link SmsManager#RESULT_INVALID_ARGUMENTS},
- * {@link SmsManager#RESULT_INVALID_STATE},
- * {@link SmsManager#RESULT_NO_MEMORY},
- * {@link SmsManager#RESULT_INVALID_SMS_FORMAT},
- * {@link SmsManager#RESULT_SYSTEM_ERROR},
- * {@link SmsManager#RESULT_MODEM_ERROR},
- * {@link SmsManager#RESULT_NETWORK_ERROR},
- * {@link SmsManager#RESULT_ENCODING_ERROR},
- * {@link SmsManager#RESULT_INVALID_SMSC_ADDRESS},
- * {@link SmsManager#RESULT_OPERATION_NOT_ALLOWED},
- * {@link SmsManager#RESULT_INTERNAL_ERROR},
- * {@link SmsManager#RESULT_NO_RESOURCES},
- * {@link SmsManager#RESULT_CANCELLED},
- * {@link SmsManager#RESULT_REQUEST_NOT_SUPPORTED}
+ * @param reason reason in case status is failure.
*
* @throws RuntimeException if called before {@link #onReady()} is triggered or if the
* connection to the framework is not available. If this happens attempting to send the SMS
@@ -303,8 +286,8 @@
* send result.
*/
@Deprecated
- public final void onSendSmsResult(int token, int messageRef, @SendStatusResult int status,
- int reason) throws RuntimeException {
+ public final void onSendSmsResult(int token, @IntRange(from = 0, to = 65535) int messageRef,
+ @SendStatusResult int status, @SmsManager.Result int reason) throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
throw new RuntimeException("Feature not ready.");
@@ -324,34 +307,10 @@
* network.
*
* @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 messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format (see TS.123.040) or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
* @param status result of sending the SMS.
- * @param reason Valid values are:
- * {@link SmsManager#RESULT_ERROR_NONE},
- * {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE},
- * {@link SmsManager#RESULT_ERROR_RADIO_OFF},
- * {@link SmsManager#RESULT_ERROR_NULL_PDU},
- * {@link SmsManager#RESULT_ERROR_NO_SERVICE},
- * {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED},
- * {@link SmsManager#RESULT_ERROR_FDN_CHECK_FAILURE},
- * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED},
- * {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED},
- * {@link SmsManager#RESULT_RADIO_NOT_AVAILABLE},
- * {@link SmsManager#RESULT_NETWORK_REJECT},
- * {@link SmsManager#RESULT_INVALID_ARGUMENTS},
- * {@link SmsManager#RESULT_INVALID_STATE},
- * {@link SmsManager#RESULT_NO_MEMORY},
- * {@link SmsManager#RESULT_INVALID_SMS_FORMAT},
- * {@link SmsManager#RESULT_SYSTEM_ERROR},
- * {@link SmsManager#RESULT_MODEM_ERROR},
- * {@link SmsManager#RESULT_NETWORK_ERROR},
- * {@link SmsManager#RESULT_ENCODING_ERROR},
- * {@link SmsManager#RESULT_INVALID_SMSC_ADDRESS},
- * {@link SmsManager#RESULT_OPERATION_NOT_ALLOWED},
- * {@link SmsManager#RESULT_INTERNAL_ERROR},
- * {@link SmsManager#RESULT_NO_RESOURCES},
- * {@link SmsManager#RESULT_CANCELLED},
- * {@link SmsManager#RESULT_REQUEST_NOT_SUPPORTED}
* @param networkErrorCode the error code reported by the carrier network if sending this SMS
* has resulted in an error or {@link #RESULT_NO_NETWORK_ERROR} if no network error was
* generated. See 3GPP TS 24.011 Section 7.3.4 for valid error codes and more information.
@@ -360,9 +319,9 @@
* connection to the framework is not available. If this happens attempting to send the SMS
* should be aborted.
*/
- public final void onSendSmsResultError(int token, int messageRef, @SendStatusResult int status,
- int reason, int networkErrorCode)
- throws RuntimeException {
+ public final void onSendSmsResultError(int token,
+ @IntRange(from = 0, to = 65535) int messageRef, @SendStatusResult int status,
+ @SmsManager.Result int reason, int networkErrorCode) throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
throw new RuntimeException("Feature not ready.");
@@ -384,9 +343,10 @@
* the platform is not available, {@link #acknowledgeSmsReport(int, int, int)} will be called
* with the {@link #STATUS_REPORT_STATUS_ERROR} result code.
* @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 messageRef the message reference, which may be 1 byte if it is in
+ * {@link SmsMessage#FORMAT_3GPP} format or 2 bytes if it is in
+ * {@link SmsMessage#FORMAT_3GPP2} format (see 3GPP2 C.S0015-B).
+ * @param format the format of the message.
* @param pdu PDU representing the content of the status report.
* @throws RuntimeException if called before {@link #onReady()} is triggered
*
@@ -394,7 +354,8 @@
* message reference.
*/
@Deprecated
- public final void onSmsStatusReportReceived(int token, int messageRef, String format,
+ public final void onSmsStatusReportReceived(int token,
+ @IntRange(from = 0, to = 65535) int messageRef, @SmsMessage.Format String format,
byte[] pdu) throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
@@ -419,13 +380,12 @@
* with the {@link #STATUS_REPORT_STATUS_ERROR} result code.
* @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 format the format of the message.
* @param pdu PDU representing the content of the status report.
* @throws RuntimeException if called before {@link #onReady()} is triggered
*/
- public final void onSmsStatusReportReceived(int token, String format, byte[] pdu)
- throws RuntimeException {
+ public final void onSmsStatusReportReceived(int token, @SmsMessage.Format String format,
+ byte[] pdu) throws RuntimeException {
synchronized (mLock) {
if (mListener == null) {
throw new RuntimeException("Feature not ready.");
@@ -450,13 +410,11 @@
}
/**
- * Returns the SMS format. Default is {@link SmsMessage#FORMAT_3GPP} unless overridden by IMS
- * Provider.
+ * Returns the SMS format that the ImsService expects.
*
- * @return the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
- * {@link SmsMessage#FORMAT_3GPP2}.
+ * @return The expected format of the SMS messages.
*/
- public String getSmsFormat() {
+ public @SmsMessage.Format String getSmsFormat() {
return SmsMessage.FORMAT_3GPP;
}
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 668a6af..9e786ce 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -111,7 +111,7 @@
public static final int EVENT_DATA_SERVICE_BINDING_CHANGED = BASE + 49;
public static final int EVENT_DEVICE_PROVISIONED_CHANGE = BASE + 50;
public static final int EVENT_DATA_ENABLED_OVERRIDE_RULES_CHANGED = BASE + 51;
- public static final int EVENT_5G_NETWORK_CHANGED = BASE + 52;
+ public static final int EVENT_SERVICE_STATE_CHANGED = BASE + 52;
public static final int EVENT_5G_TIMER_HYSTERESIS = BASE + 53;
public static final int EVENT_5G_TIMER_WATCHDOG = BASE + 54;
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 4d90579..39e00cc 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1612,6 +1612,18 @@
String[] getForbiddenPlmns(int subId, int appType, String callingPackage);
/**
+ * Set the forbidden PLMN list from the givven app type (ex APPTYPE_USIM) on a particular
+ * subscription.
+ *
+ * @param subId subId the id of the subscription
+ * @param appType appType the uicc app type, must be USIM or SIM.
+ * @param fplmns plmns the Forbiden plmns list that needed to be written to the SIM.
+ * @param content callingPackage the op Package name.
+ * @return number of fplmns that is successfully written to the SIM
+ */
+ int setForbiddenPlmns(int subId, int appType, in List<String> fplmns, String callingPackage);
+
+ /**
* Check if phone is in emergency callback mode
* @return true if phone is in emergency callback mode
* @param subId the subscription ID that this action applies to.
@@ -2051,4 +2063,9 @@
* data might be disabled on non-default data subscription but explicitly turned on by settings.
*/
boolean isDataAllowedInVoiceCall(int subId);
+
+ /**
+ * Command line command to enable or disable handling of CEP data for test purposes.
+ */
+ oneway void setCepEnabled(boolean isCepEnabled);
}
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index c9ec0f8..c19ae7b 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -170,7 +170,7 @@
public static final int RIL_CARD_MAX_APPS = 8;
- public static final int DEFAULT_CARD_INDEX = 0;
+ public static final int DEFAULT_SLOT_INDEX = 0;
public static final int MAX_PHONE_COUNT_SINGLE_SIM = 1;
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index 4654437..b357fa4 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -900,6 +900,20 @@
}
/**
+ * @return the bearer data byte array
+ */
+ public byte[] getEnvelopeBearerData() {
+ return mEnvelope.bearerData;
+ }
+
+ /**
+ * @return the 16-bit CDMA SCPT service category
+ */
+ public @CdmaSmsCbProgramData.Category int getEnvelopeServiceCategory() {
+ return mEnvelope.serviceCategory;
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
index 2787b24..c924ab3 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
@@ -57,17 +57,17 @@
// CMAS alert service category assignments, see 3GPP2 C.R1001 table 9.3.3-1
public static final int SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT =
- CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT;
+ CdmaSmsCbProgramData.CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT; // = 4096
public static final int SERVICE_CATEGORY_CMAS_EXTREME_THREAT =
- CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT;
+ CdmaSmsCbProgramData.CATEGORY_CMAS_EXTREME_THREAT; // = 4097
public static final int SERVICE_CATEGORY_CMAS_SEVERE_THREAT =
- CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT;
+ CdmaSmsCbProgramData.CATEGORY_CMAS_SEVERE_THREAT; // = 4098
public static final int SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY =
- CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY;
+ CdmaSmsCbProgramData.CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY; // = 4099
public static final int SERVICE_CATEGORY_CMAS_TEST_MESSAGE =
- CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE;
+ CdmaSmsCbProgramData.CATEGORY_CMAS_TEST_MESSAGE; // = 4100
public static final int SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE =
- CdmaSmsCbProgramData.CATEGORY_CMAS_LAST_RESERVED_VALUE;
+ CdmaSmsCbProgramData.CATEGORY_CMAS_LAST_RESERVED_VALUE; // = 4351
/**
* Provides the type of a SMS message like point to point, broadcast or acknowledge
diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
index 2016915..7422863 100644
--- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
+++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl
@@ -44,5 +44,7 @@
oneway void updateSubscriptionNickname(int cardId, int subscriptionId, String nickname,
String callingPackage, in PendingIntent callbackIntent);
oneway void eraseSubscriptions(int cardId, in PendingIntent callbackIntent);
+ oneway void eraseSubscriptionsWithOptions(
+ int cardId, int options, in PendingIntent callbackIntent);
oneway void retainSubscriptionsForFactoryReset(int cardId, in PendingIntent callbackIntent);
}
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index 9c69e2d..f2d4624 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -22,11 +22,13 @@
import android.graphics.Color;
import android.telephony.Rlog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.GsmAlphabet;
import dalvik.annotation.compat.UnsupportedAppUsage;
import java.io.UnsupportedEncodingException;
+import java.util.List;
/**
* Various methods, useful for dealing with SIM data.
@@ -34,6 +36,11 @@
public class IccUtils {
static final String LOG_TAG="IccUtils";
+ // 3GPP specification constants
+ // Spec reference TS 31.102 section 4.2.16
+ @VisibleForTesting
+ static final int FPLMN_BYTE_SIZE = 3;
+
// A table mapping from a number to a hex character for fast encoding hex strings.
private static final char[] HEX_CHARS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
@@ -896,4 +903,27 @@
}
return 0;
}
+
+ /**
+ * Encode the Fplmns into byte array to write to EF.
+ *
+ * @param fplmns Array of fplmns to be serialized.
+ * @param dataLength the size of the EF file.
+ * @return the encoded byte array in the correct format for FPLMN file.
+ */
+ public static byte[] encodeFplmns(List<String> fplmns, int dataLength) {
+ byte[] serializedFplmns = new byte[dataLength];
+ int offset = 0;
+ for (String fplmn : fplmns) {
+ if (offset >= dataLength) break;
+ stringToBcdPlmn(fplmn, serializedFplmns, offset);
+ offset += FPLMN_BYTE_SIZE;
+ }
+ //pads to the length of the EF file.
+ while (offset < dataLength) {
+ // required by 3GPP TS 31.102 spec 4.2.16
+ serializedFplmns[offset++] = (byte) 0xff;
+ }
+ return serializedFplmns;
+ }
}
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index a95b6f1..5053cee 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -758,6 +758,12 @@
/** {@hide} */
@Override
+ public Context createContextAsUser(UserHandle user, @CreatePackageOptions int flags) {
+ throw new UnsupportedOperationException();
+ }
+
+ /** {@hide} */
+ @Override
public int getUserId() {
throw new UnsupportedOperationException();
}
diff --git a/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp b/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp
index b0c7251..02dfd73 100644
--- a/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp
+++ b/tests/ApkVerityTest/block_device_writer/block_device_writer.cpp
@@ -42,6 +42,42 @@
// https://www.kernel.org/doc/Documentation/filesystems/fiemap.txt
// https://git.kernel.org/pub/scm/fs/xfs/xfsprogs-dev.git/tree/io/fiemap.c
+#ifndef F2FS_IOC_SET_PIN_FILE
+#ifndef F2FS_IOCTL_MAGIC
+#define F2FS_IOCTL_MAGIC 0xf5
+#endif
+#define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32)
+#define F2FS_IOC_GET_PIN_FILE _IOR(F2FS_IOCTL_MAGIC, 14, __u32)
+#endif
+
+struct Args {
+ const char* block_device;
+ const char* file_name;
+ uint64_t byte_offset;
+ bool use_f2fs_pinning;
+};
+
+class ScopedF2fsFilePinning {
+ public:
+ explicit ScopedF2fsFilePinning(const char* file_path) {
+ fd_.reset(TEMP_FAILURE_RETRY(open(file_path, O_WRONLY | O_CLOEXEC, 0)));
+ if (fd_.get() == -1) {
+ perror("Failed to open");
+ return;
+ }
+ __u32 set = 1;
+ ioctl(fd_.get(), F2FS_IOC_SET_PIN_FILE, &set);
+ }
+
+ ~ScopedF2fsFilePinning() {
+ __u32 set = 0;
+ ioctl(fd_.get(), F2FS_IOC_SET_PIN_FILE, &set);
+ }
+
+ private:
+ android::base::unique_fd fd_;
+};
+
ssize_t get_logical_block_size(const char* block_device) {
android::base::unique_fd fd(open(block_device, O_RDONLY));
if (fd.get() < 0) {
@@ -138,28 +174,51 @@
return 0;
}
-int main(int argc, const char** argv) {
- if (argc != 4) {
+std::unique_ptr<Args> parse_args(int argc, const char** argv) {
+ if (argc != 4 && argc != 5) {
fprintf(stderr,
- "Usage: %s block_dev filename byte_offset\n"
+ "Usage: %s [--use-f2fs-pinning] block_dev filename byte_offset\n"
"\n"
"This program bypasses filesystem and damages the specified byte\n"
"at the physical position on <block_dev> corresponding to the\n"
"logical byte location in <filename>.\n",
argv[0]);
+ return nullptr;
+ }
+
+ auto args = std::make_unique<Args>();
+ const char** arg = &argv[1];
+ args->use_f2fs_pinning = strcmp(*arg, "--use-f2fs-pinning") == 0;
+ if (args->use_f2fs_pinning) {
+ ++arg;
+ }
+ args->block_device = *(arg++);
+ args->file_name = *(arg++);
+ args->byte_offset = strtoull(*arg, nullptr, 10);
+ if (args->byte_offset == ULLONG_MAX) {
+ perror("Invalid byte offset");
+ return nullptr;
+ }
+ return args;
+}
+
+int main(int argc, const char** argv) {
+ std::unique_ptr<Args> args = parse_args(argc, argv);
+ if (args == nullptr) {
return -1;
}
- const char* block_device = argv[1];
- const char* file_name = argv[2];
- uint64_t byte_offset = strtoull(argv[3], nullptr, 10);
-
- ssize_t block_size = get_logical_block_size(block_device);
+ ssize_t block_size = get_logical_block_size(args->block_device);
if (block_size < 0) {
return -1;
}
- int64_t physical_offset_signed = get_physical_offset(file_name, byte_offset);
+ std::unique_ptr<ScopedF2fsFilePinning> pinned_file;
+ if (args->use_f2fs_pinning) {
+ pinned_file = std::make_unique<ScopedF2fsFilePinning>(args->file_name);
+ }
+
+ int64_t physical_offset_signed = get_physical_offset(args->file_name, args->byte_offset);
if (physical_offset_signed < 0) {
return -1;
}
@@ -172,7 +231,7 @@
std::unique_ptr<char> buf(static_cast<char*>(
aligned_alloc(block_size /* alignment */, block_size /* size */)));
- if (read_block_from_device(block_device, physical_block_offset, block_size,
+ if (read_block_from_device(args->block_device, physical_block_offset, block_size,
buf.get()) < 0) {
return -1;
}
@@ -180,7 +239,7 @@
printf("before: %hhx\n", *p);
*p ^= 0xff;
printf("after: %hhx\n", *p);
- if (write_block_to_device(block_device, physical_block_offset, block_size,
+ if (write_block_to_device(args->block_device, physical_block_offset, block_size,
buf.get()) < 0) {
return -1;
}
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index 761c5ce..2445a6a 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -38,6 +38,7 @@
import org.junit.runner.RunWith;
import java.io.FileNotFoundException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
@@ -440,8 +441,15 @@
throws DeviceNotAvailableException {
assertTrue(path.startsWith("/data/"));
ITestDevice.MountPointInfo mountPoint = mDevice.getMountPointInfo("/data");
- expectRemoteCommandToSucceed(String.join(" ", DAMAGING_EXECUTABLE,
- mountPoint.filesystem, path, Long.toString(offsetOfTargetingByte)));
+ ArrayList<String> args = new ArrayList<>();
+ args.add(DAMAGING_EXECUTABLE);
+ if ("f2fs".equals(mountPoint.type)) {
+ args.add("--use-f2fs-pinning");
+ }
+ args.add(mountPoint.filesystem);
+ args.add(path);
+ args.add(Long.toString(offsetOfTargetingByte));
+ expectRemoteCommandToSucceed(String.join(" ", args));
}
private String getApkPath(String packageName) throws DeviceNotAvailableException {
diff --git a/tests/Codegen/runTest.sh b/tests/Codegen/runTest.sh
index 0e90dea..929f122 100755
--- a/tests/Codegen/runTest.sh
+++ b/tests/Codegen/runTest.sh
@@ -17,11 +17,13 @@
header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java && \
header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java && \
header_and_eval codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java && \
- cd $ANDROID_BUILD_TOP &&
- header_and_eval mmma -j16 frameworks/base/tests/Codegen && \
- header_and_eval adb install -r -t "$(find $ANDROID_TARGET_OUT_TESTCASES -name 'CodegenTests.apk')" && \
- # header_and_eval adb shell am set-debug-app -w com.android.codegentest && \
- header_and_eval adb shell am instrument -w -e package com.android.codegentest com.android.codegentest/androidx.test.runner.AndroidJUnitRunner
+ (
+ cd $ANDROID_BUILD_TOP &&
+ header_and_eval mmma -j16 frameworks/base/tests/Codegen && \
+ header_and_eval adb install -r -t "$(find $ANDROID_TARGET_OUT_TESTCASES -name 'CodegenTests.apk')" && \
+ # header_and_eval adb shell am set-debug-app -w com.android.codegentest && \
+ header_and_eval adb shell am instrument -w -e package com.android.codegentest com.android.codegentest/androidx.test.runner.AndroidJUnitRunner
+ )
exitCode=$?
diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
index 10eba6a..325c1c0 100644
--- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
+++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
@@ -32,13 +32,17 @@
- // Code below generated by codegen v1.0.7.
+ // Code below generated by codegen v1.0.9.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
@DataClass.Generated.Member
@@ -54,7 +58,7 @@
@Override
@DataClass.Generated.Member
- public void writeToParcel(android.os.Parcel dest, int flags) {
+ public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
@@ -68,7 +72,7 @@
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- protected HierrarchicalDataClassBase(android.os.Parcel in) {
+ protected HierrarchicalDataClassBase(@android.annotation.NonNull android.os.Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
@@ -88,14 +92,14 @@
}
@Override
- public HierrarchicalDataClassBase createFromParcel(android.os.Parcel in) {
+ public HierrarchicalDataClassBase createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
return new HierrarchicalDataClassBase(in);
}
};
@DataClass.Generated(
- time = 1570576455287L,
- codegenVersion = "1.0.7",
+ time = 1571258914826L,
+ codegenVersion = "1.0.9",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java",
inputSignatures = "private int mBaseData\nclass HierrarchicalDataClassBase extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genSetters=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
index 1085a6a..6c92009 100644
--- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
+++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
@@ -46,13 +46,17 @@
- // Code below generated by codegen v1.0.7.
+ // Code below generated by codegen v1.0.9.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
@DataClass.Generated.Member
@@ -70,7 +74,7 @@
@Override
@DataClass.Generated.Member
- public void writeToParcel(android.os.Parcel dest, int flags) {
+ public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
@@ -86,7 +90,7 @@
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- protected HierrarchicalDataClassChild(android.os.Parcel in) {
+ protected HierrarchicalDataClassChild(@NonNull android.os.Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
@@ -110,14 +114,14 @@
}
@Override
- public HierrarchicalDataClassChild createFromParcel(android.os.Parcel in) {
+ public HierrarchicalDataClassChild createFromParcel(@NonNull android.os.Parcel in) {
return new HierrarchicalDataClassChild(in);
}
};
@DataClass.Generated(
- time = 1570576456245L,
- codegenVersion = "1.0.7",
+ time = 1571258915848L,
+ codegenVersion = "1.0.9",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java",
inputSignatures = "private @android.annotation.NonNull java.lang.String mChildData\nclass HierrarchicalDataClassChild extends com.android.codegentest.HierrarchicalDataClassBase implements []\n@com.android.internal.util.DataClass(genParcelable=true, genConstructor=false, genSetters=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
index 75ef963..36def8a 100644
--- a/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
@@ -16,6 +16,7 @@
package com.android.codegentest;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.SparseArray;
@@ -46,15 +47,22 @@
@NonNull SparseArray<SampleWithCustomBuilder> mSparseArray = null;
@NonNull SparseIntArray mSparseIntArray = null;
+ @SuppressWarnings({"WeakerAccess"})
+ @Nullable Boolean mNullableBoolean = null;
- // Code below generated by codegen v1.0.7.
+
+ // Code below generated by codegen v1.0.9.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
@DataClass.Generated.Member
@@ -65,7 +73,8 @@
@NonNull Map<String,SampleWithCustomBuilder> map,
@NonNull Map<String,String> stringMap,
@NonNull SparseArray<SampleWithCustomBuilder> sparseArray,
- @NonNull SparseIntArray sparseIntArray) {
+ @NonNull SparseIntArray sparseIntArray,
+ @SuppressWarnings({ "WeakerAccess" }) @Nullable Boolean nullableBoolean) {
this.mStringArray = stringArray;
AnnotationValidations.validate(
NonNull.class, null, mStringArray);
@@ -87,6 +96,7 @@
this.mSparseIntArray = sparseIntArray;
AnnotationValidations.validate(
NonNull.class, null, mSparseIntArray);
+ this.mNullableBoolean = nullableBoolean;
// onConstructed(); // You can define this method to get a callback
}
@@ -126,6 +136,11 @@
return mSparseIntArray;
}
+ @DataClass.Generated.Member
+ public @SuppressWarnings({ "WeakerAccess" }) @Nullable Boolean getNullableBoolean() {
+ return mNullableBoolean;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -139,16 +154,20 @@
"map = " + mMap + ", " +
"stringMap = " + mStringMap + ", " +
"sparseArray = " + mSparseArray + ", " +
- "sparseIntArray = " + mSparseIntArray +
+ "sparseIntArray = " + mSparseIntArray + ", " +
+ "nullableBoolean = " + mNullableBoolean +
" }";
}
@Override
@DataClass.Generated.Member
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
+ int flg = 0;
+ if (mNullableBoolean != null) flg |= 0x80;
+ dest.writeInt(flg);
dest.writeStringArray(mStringArray);
dest.writeIntArray(mIntArray);
dest.writeStringList(mStringList);
@@ -156,6 +175,7 @@
dest.writeMap(mStringMap);
dest.writeSparseArray(mSparseArray);
dest.writeSparseIntArray(mSparseIntArray);
+ if (mNullableBoolean != null) dest.writeBoolean(mNullableBoolean);
}
@Override
@@ -165,10 +185,11 @@
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- protected ParcelAllTheThingsDataClass(Parcel in) {
+ protected ParcelAllTheThingsDataClass(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
+ int flg = in.readInt();
String[] stringArray = in.createStringArray();
int[] intArray = in.createIntArray();
List<String> stringList = new java.util.ArrayList<>();
@@ -179,6 +200,7 @@
in.readMap(stringMap, String.class.getClassLoader());
SparseArray<SampleWithCustomBuilder> sparseArray = (SparseArray) in.readSparseArray(SampleWithCustomBuilder.class.getClassLoader());
SparseIntArray sparseIntArray = (SparseIntArray) in.readSparseIntArray();
+ Boolean nullableBoolean = (flg & 0x80) == 0 ? null : (Boolean) in.readBoolean();
this.mStringArray = stringArray;
AnnotationValidations.validate(
@@ -201,6 +223,7 @@
this.mSparseIntArray = sparseIntArray;
AnnotationValidations.validate(
NonNull.class, null, mSparseIntArray);
+ this.mNullableBoolean = nullableBoolean;
// onConstructed(); // You can define this method to get a callback
}
@@ -214,7 +237,7 @@
}
@Override
- public ParcelAllTheThingsDataClass createFromParcel(Parcel in) {
+ public ParcelAllTheThingsDataClass createFromParcel(@NonNull Parcel in) {
return new ParcelAllTheThingsDataClass(in);
}
};
@@ -233,6 +256,7 @@
private @NonNull Map<String,String> mStringMap;
private @NonNull SparseArray<SampleWithCustomBuilder> mSparseArray;
private @NonNull SparseIntArray mSparseIntArray;
+ private @SuppressWarnings({ "WeakerAccess" }) @Nullable Boolean mNullableBoolean;
private long mBuilderFieldsSet = 0L;
@@ -328,10 +352,18 @@
return this;
}
+ @DataClass.Generated.Member
+ public @NonNull Builder setNullableBoolean(@SuppressWarnings({ "WeakerAccess" }) @Nullable Boolean value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x80;
+ mNullableBoolean = value;
+ return this;
+ }
+
/** Builds the instance. This builder should not be touched after calling this! */
public ParcelAllTheThingsDataClass build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x80; // Mark builder used
+ mBuilderFieldsSet |= 0x100; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mStringArray = null;
@@ -354,6 +386,9 @@
if ((mBuilderFieldsSet & 0x40) == 0) {
mSparseIntArray = null;
}
+ if ((mBuilderFieldsSet & 0x80) == 0) {
+ mNullableBoolean = null;
+ }
ParcelAllTheThingsDataClass o = new ParcelAllTheThingsDataClass(
mStringArray,
mIntArray,
@@ -361,12 +396,13 @@
mMap,
mStringMap,
mSparseArray,
- mSparseIntArray);
+ mSparseIntArray,
+ mNullableBoolean);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x80) != 0) {
+ if ((mBuilderFieldsSet & 0x100) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -374,10 +410,10 @@
}
@DataClass.Generated(
- time = 1570576454326L,
- codegenVersion = "1.0.7",
+ time = 1571258913802L,
+ codegenVersion = "1.0.9",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/ParcelAllTheThingsDataClass.java",
- inputSignatures = " @android.annotation.NonNull java.lang.String[] mStringArray\n @android.annotation.NonNull int[] mIntArray\n @android.annotation.NonNull java.util.List<java.lang.String> mStringList\n @android.annotation.NonNull java.util.Map<java.lang.String,com.android.codegentest.SampleWithCustomBuilder> mMap\n @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.String> mStringMap\n @android.annotation.NonNull android.util.SparseArray<com.android.codegentest.SampleWithCustomBuilder> mSparseArray\n @android.annotation.NonNull android.util.SparseIntArray mSparseIntArray\nclass ParcelAllTheThingsDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)")
+ inputSignatures = " @android.annotation.NonNull java.lang.String[] mStringArray\n @android.annotation.NonNull int[] mIntArray\n @android.annotation.NonNull java.util.List<java.lang.String> mStringList\n @android.annotation.NonNull java.util.Map<java.lang.String,com.android.codegentest.SampleWithCustomBuilder> mMap\n @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.String> mStringMap\n @android.annotation.NonNull android.util.SparseArray<com.android.codegentest.SampleWithCustomBuilder> mSparseArray\n @android.annotation.NonNull android.util.SparseIntArray mSparseIntArray\n @java.lang.SuppressWarnings({\"WeakerAccess\"}) @android.annotation.Nullable java.lang.Boolean mNullableBoolean\nclass ParcelAllTheThingsDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
index 14010a9..c444d61 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
@@ -342,13 +342,17 @@
- // Code below generated by codegen v1.0.7.
+ // Code below generated by codegen v1.0.9.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
@IntDef(prefix = "STATE_", value = {
@@ -1115,7 +1119,7 @@
@Override
@DataClass.Generated.Member
- public boolean equals(Object o) {
+ public boolean equals(@Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(SampleDataClass other) { ... }
// boolean fieldNameEquals(FieldType otherValue) { ... }
@@ -1180,8 +1184,8 @@
@DataClass.Generated.Member
void forEachField(
- DataClass.PerIntFieldAction<SampleDataClass> actionInt,
- DataClass.PerObjectFieldAction<SampleDataClass> actionObject) {
+ @NonNull DataClass.PerIntFieldAction<SampleDataClass> actionInt,
+ @NonNull DataClass.PerObjectFieldAction<SampleDataClass> actionObject) {
actionInt.acceptInt(this, "num", mNum);
actionInt.acceptInt(this, "num2", mNum2);
actionInt.acceptInt(this, "num4", mNum4);
@@ -1207,7 +1211,7 @@
/** @deprecated May cause boxing allocations - use with caution! */
@Deprecated
@DataClass.Generated.Member
- void forEachField(DataClass.PerObjectFieldAction<SampleDataClass> action) {
+ void forEachField(@NonNull DataClass.PerObjectFieldAction<SampleDataClass> action) {
action.acceptObject(this, "num", mNum);
action.acceptObject(this, "num2", mNum2);
action.acceptObject(this, "num4", mNum4);
@@ -1254,7 +1258,7 @@
@Override
@DataClass.Generated.Member
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
@@ -1293,7 +1297,7 @@
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- /* package-private */ SampleDataClass(Parcel in) {
+ /* package-private */ SampleDataClass(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
@@ -1416,7 +1420,7 @@
}
@Override
- public SampleDataClass createFromParcel(Parcel in) {
+ public SampleDataClass createFromParcel(@NonNull Parcel in) {
return new SampleDataClass(in);
}
};
@@ -1868,8 +1872,8 @@
}
@DataClass.Generated(
- time = 1570576452225L,
- codegenVersion = "1.0.7",
+ time = 1571258911688L,
+ codegenVersion = "1.0.9",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleDataClass.java",
inputSignatures = "public static final java.lang.String STATE_NAME_UNDEFINED\npublic static final java.lang.String STATE_NAME_ON\npublic static final java.lang.String STATE_NAME_OFF\npublic static final int STATE_UNDEFINED\npublic static final int STATE_ON\npublic static final int STATE_OFF\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @com.android.codegentest.SampleDataClass.RequestFlags int FLAG_AUGMENTED_REQUEST\nprivate int mNum\nprivate int mNum2\nprivate int mNum4\nprivate @android.annotation.Nullable java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mName2\nprivate @android.annotation.NonNull java.lang.String mName4\nprivate @android.annotation.Nullable android.view.accessibility.AccessibilityNodeInfo mOtherParcelable\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.codegentest.MyDateParcelling.class) @android.annotation.NonNull java.util.Date mDate\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForPattern.class) @android.annotation.NonNull java.util.regex.Pattern mPattern\nprivate @android.annotation.NonNull java.util.List<android.net.LinkAddress> mLinkAddresses2\nprivate @com.android.internal.util.DataClass.PluralOf(\"linkAddress\") @android.annotation.NonNull java.util.ArrayList<android.net.LinkAddress> mLinkAddresses\nprivate @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses4\nprivate @com.android.codegentest.SampleDataClass.StateName @android.annotation.NonNull java.lang.String mStateName\nprivate @com.android.codegentest.SampleDataClass.RequestFlags int mFlags\nprivate @com.android.codegentest.SampleDataClass.State int mState\npublic @android.annotation.NonNull java.lang.CharSequence charSeq\nprivate final @android.annotation.Nullable android.net.LinkAddress[] mLinkAddresses5\nprivate transient android.net.LinkAddress[] mLinkAddresses6\ntransient int[] mTmpStorage\nprivate @android.annotation.StringRes int mStringRes\nprivate @android.annotation.IntRange(from=0L, to=6L) int mDayOfWeek\nprivate @android.annotation.Size(2L) @android.annotation.NonNull @com.android.internal.util.DataClass.Each @android.annotation.FloatRange(from=0.0) float[] mCoords\nprivate static java.lang.String defaultName4()\nprivate int[] lazyInitTmpStorage()\npublic android.net.LinkAddress[] getLinkAddresses4()\nprivate boolean patternEquals(java.util.regex.Pattern)\nprivate int patternHashCode()\nprivate void onConstructed()\npublic void dump(java.io.PrintWriter)\nclass SampleDataClass extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=true, genEqualsHashCode=true, genToString=true, genForEachField=true, genSetters=true)")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
index b5f6c73..55feae7 100644
--- a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
+++ b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
@@ -85,13 +85,17 @@
- // Code below generated by codegen v1.0.7.
+ // Code below generated by codegen v1.0.9.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
@DataClass.Generated.Member
@@ -138,7 +142,7 @@
@Override
@DataClass.Generated.Member
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
@@ -154,7 +158,7 @@
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
- protected SampleWithCustomBuilder(Parcel in) {
+ protected SampleWithCustomBuilder(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
@@ -180,7 +184,7 @@
}
@Override
- public SampleWithCustomBuilder createFromParcel(Parcel in) {
+ public SampleWithCustomBuilder createFromParcel(@NonNull Parcel in) {
return new SampleWithCustomBuilder(in);
}
};
@@ -249,8 +253,8 @@
}
@DataClass.Generated(
- time = 1570576453295L,
- codegenVersion = "1.0.7",
+ time = 1571258912752L,
+ codegenVersion = "1.0.9",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java",
inputSignatures = " long delayAmount\n @android.annotation.NonNull java.util.concurrent.TimeUnit delayUnit\n long creationTimestamp\nprivate static java.util.concurrent.TimeUnit unparcelDelayUnit(android.os.Parcel)\nprivate void parcelDelayUnit(android.os.Parcel,int)\nclass SampleWithCustomBuilder extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genAidl=false, genToString=true)\nabstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayAmount(long)\npublic abstract com.android.codegentest.SampleWithCustomBuilder.Builder setDelayUnit(java.util.concurrent.TimeUnit)\npublic com.android.codegentest.SampleWithCustomBuilder.Builder setDelay(long,java.util.concurrent.TimeUnit)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
diff --git a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
index 0ce8aba..b967f19 100644
--- a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
+++ b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
@@ -51,18 +51,22 @@
- // Code below generated by codegen v1.0.7.
+ // Code below generated by codegen v1.0.9.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
@DataClass.Generated(
- time = 1570576457249L,
- codegenVersion = "1.0.7",
+ time = 1571258916868L,
+ codegenVersion = "1.0.9",
sourceFile = "frameworks/base/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java",
inputSignatures = "public @android.annotation.NonNull java.lang.String someMethod(int)\nclass StaleDataclassDetectorFalsePositivesTest extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false)")
@Deprecated
diff --git a/tests/FlickerTests/TEST_MAPPING b/tests/FlickerTests/TEST_MAPPING
index 55a6147..f34c432 100644
--- a/tests/FlickerTests/TEST_MAPPING
+++ b/tests/FlickerTests/TEST_MAPPING
@@ -1,7 +1,8 @@
{
"postsubmit": [
{
- "name": "FlickerTests"
+ "name": "FlickerTests",
+ "keywords": ["primary-device"]
}
]
}
\ No newline at end of file
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 231d045b..085c53c 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -19,7 +19,6 @@
static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"],
test_suites: ["general-tests"],
test_config: "RollbackTest.xml",
- // TODO: sdk_version: "test_current" when Intent#resolveSystemservice is TestApi
}
java_test_host {
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 9e6ac8e..8b97f61 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -92,7 +92,7 @@
* Enable rollback phase.
*/
@Test
- public void testBadApkOnlyEnableRollback() throws Exception {
+ public void testBadApkOnly_Phase1() throws Exception {
Uninstall.packages(TestApp.A);
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
@@ -101,9 +101,6 @@
InstallUtils.processUserData(TestApp.A);
Install.single(TestApp.ACrashing2).setEnableRollback().setStaged().commit();
-
- // At this point, the host test driver will reboot the device and run
- // testBadApkOnlyConfirmEnableRollback().
}
/**
@@ -111,7 +108,7 @@
* Confirm that rollback was successfully enabled.
*/
@Test
- public void testBadApkOnlyConfirmEnableRollback() throws Exception {
+ public void testBadApkOnly_Phase2() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
InstallUtils.processUserData(TestApp.A);
@@ -122,9 +119,6 @@
assertThat(rollback).packagesContainsExactly(
Rollback.from(TestApp.A2).to(TestApp.A1));
assertThat(rollback.isStaged()).isTrue();
-
- // At this point, the host test driver will run
- // testBadApkOnlyTriggerRollback().
}
/**
@@ -133,15 +127,14 @@
* rebooting the test out from under it.
*/
@Test
- public void testBadApkOnlyTriggerRollback() throws Exception {
+ public void testBadApkOnly_Phase3() throws Exception {
// Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
RollbackUtils.sendCrashBroadcast(TestApp.A, 5);
- // We expect the device to be rebooted automatically. Wait for that to
- // happen. At that point, the host test driver will wait for the
- // device to come back up and run testApkOnlyConfirmRollback().
+ // We expect the device to be rebooted automatically. Wait for that to happen.
Thread.sleep(30 * 1000);
+ // Raise an error anyway if reboot didn't happen.
fail("watchdog did not trigger reboot");
}
@@ -150,7 +143,7 @@
* Confirm rollback phase.
*/
@Test
- public void testBadApkOnlyConfirmRollback() throws Exception {
+ public void testBadApkOnly_Phase4() throws Exception {
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
InstallUtils.processUserData(TestApp.A);
@@ -177,8 +170,11 @@
networkStack)).isNull();
}
+ /**
+ * Stage install ModuleMetadata package to simulate a Mainline module update.
+ */
@Test
- public void installModuleMetadataPackage() throws Exception {
+ public void testNativeWatchdogTriggersRollback_Phase1() throws Exception {
resetModuleMetadataPackage();
Context context = InstrumentationRegistry.getContext();
PackageInfo metadataPackageInfo = context.getPackageManager().getPackageInfo(
@@ -192,6 +188,26 @@
+ metadataApkPath);
}
+ /**
+ * Verify the rollback is available.
+ */
+ @Test
+ public void testNativeWatchdogTriggersRollback_Phase2() throws Exception {
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ MODULE_META_DATA_PACKAGE)).isNotNull();
+ }
+
+ /**
+ * Verify the rollback is committed after crashing.
+ */
+ @Test
+ public void testNativeWatchdogTriggersRollback_Phase3() throws Exception {
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+ MODULE_META_DATA_PACKAGE)).isNotNull();
+ }
+
@Test
public void assertNetworkStackRollbackAvailable() throws Exception {
RollbackManager rm = RollbackUtils.getRollbackManager();
@@ -213,20 +229,6 @@
getNetworkStackPackageName())).isNull();
}
- @Test
- public void assertModuleMetadataRollbackAvailable() throws Exception {
- RollbackManager rm = RollbackUtils.getRollbackManager();
- assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
- MODULE_META_DATA_PACKAGE)).isNotNull();
- }
-
- @Test
- public void assertModuleMetadataRollbackCommitted() throws Exception {
- RollbackManager rm = RollbackUtils.getRollbackManager();
- assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
- MODULE_META_DATA_PACKAGE)).isNotNull();
- }
-
private String getNetworkStackPackageName() {
Intent intent = new Intent(NETWORK_STACK_CONNECTOR_CLASS);
ComponentName comp = intent.resolveSystemService(
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index bc98f06..2043027 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -68,35 +68,35 @@
*/
@Test
public void testBadApkOnly() throws Exception {
- runPhase("testBadApkOnlyEnableRollback");
+ runPhase("testBadApkOnly_Phase1");
getDevice().reboot();
- runPhase("testBadApkOnlyConfirmEnableRollback");
+ runPhase("testBadApkOnly_Phase2");
try {
// This is expected to fail due to the device being rebooted out
// from underneath the test. If this fails for reasons other than
// the device reboot, those failures should result in failure of
// the testApkOnlyConfirmRollback phase.
CLog.logAndDisplay(LogLevel.INFO, "testBadApkOnlyTriggerRollback is expected to fail");
- runPhase("testBadApkOnlyTriggerRollback");
+ runPhase("testBadApkOnly_Phase3");
} catch (AssertionError e) {
// AssertionError is expected.
}
getDevice().waitForDeviceAvailable();
- runPhase("testBadApkOnlyConfirmRollback");
+ runPhase("testBadApkOnly_Phase4");
}
@Test
public void testNativeWatchdogTriggersRollback() throws Exception {
//Stage install ModuleMetadata package - this simulates a Mainline module update
- runPhase("installModuleMetadataPackage");
+ runPhase("testNativeWatchdogTriggersRollback_Phase1");
// Reboot device to activate staged package
getDevice().reboot();
getDevice().waitForDeviceAvailable();
- runPhase("assertModuleMetadataRollbackAvailable");
+ runPhase("testNativeWatchdogTriggersRollback_Phase2");
// crash system_server enough times to trigger a rollback
crashProcess("system_server", NATIVE_CRASHES_THRESHOLD);
@@ -113,7 +113,7 @@
getDevice().waitForDeviceAvailable();
// verify rollback committed
- runPhase("assertModuleMetadataRollbackCommitted");
+ runPhase("testNativeWatchdogTriggersRollback_Phase3");
}
/**
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index bffbbfd..cf3fba8 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -5709,7 +5709,6 @@
}
@Test
- @FlakyTest(bugId = 140305678)
public void testTcpBufferReset() throws Exception {
final String testTcpBufferSizes = "1,2,3,4,5,6";
final NetworkRequest networkRequest = new NetworkRequest.Builder()
diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
index 7029218..3fdba6e 100644
--- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -54,7 +54,6 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
-import android.content.pm.PackageList;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
@@ -70,6 +69,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.LocalServices;
+import com.android.server.pm.PackageList;
import org.junit.Before;
import org.junit.Test;
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index 9e42c04..912c1ad 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -1976,7 +1976,9 @@
"""Catches missing nullability annotations"""
for f in clazz.fields:
- if f.value is not None and 'static' in f.split and 'final' in f.split:
+ if "enum_constant" in f.split:
+ continue # Enum constants are never null
+ if f.value is not None and 'final' in f.split:
continue # Nullability of constants can be inferred.
if f.typ not in PRIMITIVES and not has_nullability(f.annotations):
error(clazz, f, "M12", "Field must be marked either @NonNull or @Nullable")
@@ -1985,8 +1987,12 @@
verify_nullability_args(clazz, c)
for m in clazz.methods:
- if m.name == "writeToParcel" or m.name == "onReceive":
- continue # Parcelable.writeToParcel() and BroadcastReceiver.onReceive() are not yet annotated
+ if m.name == "writeToParcel" or m.name == "onReceive" or m.name == "onBind":
+ continue # Parcelable.writeToParcel(), BroadcastReceiver.onReceive(), and Service.onBind() are not yet annotated
+
+ if (m.name == "equals" and m.args == ["java.lang.Object"] or
+ m.name == "toString" and m.args == []):
+ continue # Nullability of equals and toString is implicit.
if m.typ not in PRIMITIVES and not has_nullability(m.annotations):
error(clazz, m, "M12", "Return value must be marked either @NonNull or @Nullable")
diff --git a/tools/codegen/src/com/android/codegen/FieldInfo.kt b/tools/codegen/src/com/android/codegen/FieldInfo.kt
index ba00264..1a7fd6e 100644
--- a/tools/codegen/src/com/android/codegen/FieldInfo.kt
+++ b/tools/codegen/src/com/android/codegen/FieldInfo.kt
@@ -191,7 +191,7 @@
* Parcel.write* and Parcel.read* method name wildcard values
*/
val ParcelMethodsSuffix = when {
- FieldClass in PRIMITIVE_TYPES - "char" - "boolean" +
+ FieldClass in PRIMITIVE_TYPES - "char" - "boolean" + BOXED_PRIMITIVE_TYPES +
listOf("String", "CharSequence", "Exception", "Size", "SizeF", "Bundle",
"FileDescriptor", "SparseBooleanArray", "SparseIntArray", "SparseArray") ->
FieldClass
diff --git a/tools/codegen/src/com/android/codegen/Generators.kt b/tools/codegen/src/com/android/codegen/Generators.kt
index 5a95676..431f378 100644
--- a/tools/codegen/src/com/android/codegen/Generators.kt
+++ b/tools/codegen/src/com/android/codegen/Generators.kt
@@ -417,7 +417,7 @@
if (!isMethodGenerationSuppressed("writeToParcel", Parcel, "int")) {
+"@Override"
+GENERATED_MEMBER_HEADER
- "public void writeToParcel($Parcel dest, int flags)" {
+ "public void writeToParcel(@$NonNull $Parcel dest, int flags)" {
+"// You can override field parcelling by defining methods like:"
+"// void parcelFieldName(Parcel dest, int flags) { ... }"
+""
@@ -473,7 +473,7 @@
+"/** @hide */"
+"@SuppressWarnings({\"unchecked\", \"RedundantCast\"})"
+GENERATED_MEMBER_HEADER
- "$visibility $ClassName($Parcel in) {" {
+ "$visibility $ClassName(@$NonNull $Parcel in) {" {
+"// You can override field unparcelling by defining methods like:"
+"// static FieldType unparcelFieldName(Parcel in) { ... }"
+""
@@ -598,7 +598,7 @@
}
+"@Override"
- "public $ClassName createFromParcel($Parcel in)" {
+ "public $ClassName createFromParcel(@$NonNull $Parcel in)" {
+"return new $ClassName(in);"
}
rmEmptyLine()
@@ -611,7 +611,7 @@
if (!isMethodGenerationSuppressed("equals", "Object")) {
+"@Override"
+GENERATED_MEMBER_HEADER
- "public boolean equals(Object o)" {
+ "public boolean equals(@$Nullable Object o)" {
+"// You can override field equality logic by defining either of the methods like:"
+"// boolean fieldNameEquals($ClassName other) { ... }"
+"// boolean fieldNameEquals(FieldType otherValue) { ... }"
@@ -854,6 +854,7 @@
it.nameAsString == intOrStringDef?.AnnotationName
|| it.nameAsString in knownNonValidationAnnotations
|| it in perElementValidations
+ || it.args.any { (_, value) -> value is ArrayInitializerExpr }
}.forEach { annotation ->
appendValidateCall(annotation,
valueToValidate = if (annotation.nameAsString == Size) sizeExpr else name)
@@ -874,14 +875,7 @@
val validate = memberRef("com.android.internal.util.AnnotationValidations.validate")
"$validate(" {
!"${annotation.nameAsString}.class, null, $valueToValidate"
- val params = when (annotation) {
- is MarkerAnnotationExpr -> emptyMap()
- is SingleMemberAnnotationExpr -> mapOf("value" to annotation.memberValue)
- is NormalAnnotationExpr ->
- annotation.pairs.map { it.name.asString() to it.value }.toMap()
- else -> throw IllegalStateException()
- }
- params.forEach { name, value ->
+ annotation.args.forEach { name, value ->
!",\n\"$name\", $value"
}
}
@@ -910,7 +904,7 @@
usedSpecializationsSet.toList().forEachLastAware { specType, isLast ->
val SpecType = specType.capitalize()
val ActionClass = classRef("com.android.internal.util.DataClass.Per${SpecType}FieldAction")
- +"$ActionClass<$ClassType> action$SpecType${if_(!isLast, ",")}"
+ +"@$NonNull $ActionClass<$ClassType> action$SpecType${if_(!isLast, ",")}"
}
}; " {" {
usedSpecializations.forEachIndexed { i, specType ->
@@ -925,7 +919,7 @@
+"/** @deprecated May cause boxing allocations - use with caution! */"
+"@Deprecated"
+GENERATED_MEMBER_HEADER
- "void forEachField($PerObjectFieldAction<$ClassType> action)" {
+ "void forEachField(@$NonNull $PerObjectFieldAction<$ClassType> action)" {
fields.forEachApply {
+"action.acceptObject(this, \"$nameLowerCamel\", $name);"
}
diff --git a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
index 24cf469..d6953c0 100644
--- a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
+++ b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt
@@ -87,6 +87,14 @@
is IntegerLiteralExpr -> sb.append(ex.asInt()).append("L")
is LongLiteralExpr -> sb.append(ex.asLong()).append("L")
is DoubleLiteralExpr -> sb.append(ex.asDouble())
+ is ArrayInitializerExpr -> {
+ sb.append("{")
+ ex.values.forEachLastAware { arrayElem, isLast ->
+ appendExpr(sb, arrayElem)
+ if (!isLast) sb.append(", ")
+ }
+ sb.append("}")
+ }
else -> sb.append(ex)
}
}
diff --git a/tools/codegen/src/com/android/codegen/Main.kt b/tools/codegen/src/com/android/codegen/Main.kt
index 039f7b2..ce83d3d 100755
--- a/tools/codegen/src/com/android/codegen/Main.kt
+++ b/tools/codegen/src/com/android/codegen/Main.kt
@@ -9,6 +9,7 @@
const val INDENT_SINGLE = " "
val PRIMITIVE_TYPES = listOf("byte", "short", "int", "long", "char", "float", "double", "boolean")
+val BOXED_PRIMITIVE_TYPES = PRIMITIVE_TYPES.map { it.capitalize() } - "Int" + "Integer" - "Char" + "Character"
val BUILTIN_SPECIAL_PARCELLINGS = listOf("Pattern")
@@ -142,6 +143,10 @@
//
// To regenerate run:
// $ $cliExecutable ${cliArgs.dropLast(1).joinToString("") { "$it " }}$fileEscaped
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
"""
diff --git a/tools/codegen/src/com/android/codegen/SharedConstants.kt b/tools/codegen/src/com/android/codegen/SharedConstants.kt
index 47f774f..3eb9e7b 100644
--- a/tools/codegen/src/com/android/codegen/SharedConstants.kt
+++ b/tools/codegen/src/com/android/codegen/SharedConstants.kt
@@ -1,7 +1,7 @@
package com.android.codegen
const val CODEGEN_NAME = "codegen"
-const val CODEGEN_VERSION = "1.0.7"
+const val CODEGEN_VERSION = "1.0.9"
const val CANONICAL_BUILDER_CLASS = "Builder"
const val BASE_BUILDER_CLASS = "BaseBuilder"
diff --git a/tools/codegen/src/com/android/codegen/Utils.kt b/tools/codegen/src/com/android/codegen/Utils.kt
index a1f068a..e703397 100644
--- a/tools/codegen/src/com/android/codegen/Utils.kt
+++ b/tools/codegen/src/com/android/codegen/Utils.kt
@@ -1,9 +1,6 @@
package com.android.codegen
-import com.github.javaparser.ast.Modifier
-import com.github.javaparser.ast.expr.AnnotationExpr
-import com.github.javaparser.ast.expr.Expression
-import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr
+import com.github.javaparser.ast.expr.*
import com.github.javaparser.ast.nodeTypes.NodeWithModifiers
import java.time.Instant
import java.time.ZoneId
@@ -88,3 +85,10 @@
}
fun bitAtExpr(bitIndex: Int) = "0x${java.lang.Long.toHexString(1L shl bitIndex)}"
+
+val AnnotationExpr.args: Map<String, Expression> get() = when (this) {
+ is MarkerAnnotationExpr -> emptyMap()
+ is SingleMemberAnnotationExpr -> mapOf("value" to memberValue)
+ is NormalAnnotationExpr -> pairs.map { it.name.asString() to it.value }.toMap()
+ else -> throw IllegalArgumentException("Unknown annotation expression: $this")
+}
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index 14eead8..1390f63 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -29,7 +29,7 @@
"-Werror",
],
- shared_libs: ["libprotoc"],
+ static_libs: ["libprotoc"],
}
cc_binary_host {
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 5782f5b..5496e83 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -1813,12 +1813,13 @@
}
/**
- * Remove the Passpoint configuration identified by its FQDN (Fully Qualified Domain Name).
+ * Remove the Passpoint configuration identified by its FQDN (Fully Qualified Domain Name) added
+ * by the caller.
*
- * @param fqdn The FQDN of the Passpoint configuration to be removed
+ * @param fqdn The FQDN of the Passpoint configuration added by the caller to be removed
* @throws IllegalArgumentException if no configuration is associated with the given FQDN or
* Passpoint is not enabled on the device.
- * @deprecated This is no longer supported.
+ * @deprecated This will be non-functional in a future release.
*/
@Deprecated
@RequiresPermission(anyOf = {
@@ -1842,12 +1843,12 @@
}
/**
- * Return the list of installed Passpoint configurations.
+ * Return the list of installed Passpoint configurations added by the caller.
*
* An empty list will be returned when no configurations are installed.
*
- * @return A list of {@link PasspointConfiguration}
- * @deprecated This is no longer supported.
+ * @return A list of {@link PasspointConfiguration} added by the caller
+ * @deprecated This will be non-functional in a future release.
*/
@Deprecated
@RequiresPermission(anyOf = {