diff options
713 files changed, 22046 insertions, 9895 deletions
diff --git a/Android.bp b/Android.bp index 1eba3e2711f1..05474815f62c 100644 --- a/Android.bp +++ b/Android.bp @@ -413,6 +413,8 @@ java_library { "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 @@ java_library { 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", @@ -1458,6 +1462,11 @@ droidstubs { 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, } @@ -1484,6 +1493,11 @@ droidstubs { 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, } @@ -1582,4 +1596,4 @@ filegroup { "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/AndroidManifest.xml b/apct-tests/perftests/autofill/AndroidManifest.xml index 9c8abc32eb72..1e3532b3c1ef 100644 --- a/apct-tests/perftests/autofill/AndroidManifest.xml +++ b/apct-tests/perftests/autofill/AndroidManifest.xml @@ -18,7 +18,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/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java b/apct-tests/perftests/autofill/src/android/view/autofill/AbstractAutofillPerfTestCase.java index 6979f0f0875d..48ce8ab2fce5 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 static org.junit.Assert.assertTrue; 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 @@ public abstract class AbstractAutofillPerfTestCase { 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 @@ public abstract class AbstractAutofillPerfTestCase { 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 @@ public abstract class AbstractAutofillPerfTestCase { } /** - * 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 80908266c5c0..fb5ea80e6ed1 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 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 @@ public class LoginTest extends AbstractAutofillPerfTestCase { } @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 525975d36772..290f178fb669 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 b3f83596e8a1..a320514dd97a 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.Context; 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 @@ public class PendingIntentPerfTest { @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 3a8002003299..b9c7af46c67c 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 @@ package android.graphics.perftests; 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 @@ public class PaintHasGlyphPerfTest { } @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 3b2b8a94f610..d14e93e553f6 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.graphics.drawable.VectorDrawable; 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 @@ public class VectorDrawablePerfTest { 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/os/PackageManagerPerfTest.java b/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java index 3aa6749502bc..236f548cf6dd 100644 --- a/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java +++ b/apct-tests/perftests/core/src/android/os/PackageManagerPerfTest.java @@ -38,7 +38,8 @@ public class PackageManagerPerfTest { private static final String PERMISSION_NAME_DOESNT_EXIST = "com.android.perftests.core.TestBadPermission"; private static final ComponentName TEST_ACTIVITY = - new ComponentName("com.android.perftests.core", "android.perftests.utils.StubActivity"); + new ComponentName("com.android.perftests.core", + "android.perftests.utils.PerfTestActivity"); @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 5be99d9d779e..6b295e55c9d4 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; 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 @@ public class DynamicLayoutPerfTest { } @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 b34001dcf6b5..b0edb117a00b 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.Color; 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 @@ import java.util.List; 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 b3ea62aa7da0..270b4e5b49cc 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 @@ package android.widget; 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 @@ public class EditTextBackspacePerfTest { } @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 aa47d5bdd998..8028f11be63b 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 @@ package android.widget; 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 @@ public class EditTextCursorMovementPerfTest { } @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 e50016c45008..f4ad5ddd3ed2 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 @@ package android.widget; 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 @@ public class EditTextLongTextPerfTest { } @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 644095b36206..223a3165b1d5 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.app.Activity; 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 @@ public class LayoutPerfTest { } @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 bed173bd269a..694e1f477f89 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.app.Activity; 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 @@ public class TextViewAutoSizeLayoutPerfTest { } @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 00bd8db7f5a3..a5466678167e 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 @@ package android.widget; 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 @@ public class TextViewSetTextLocalePerfTest { } @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 000000000000..c096cd22bdba --- /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 9cfc3d272145..73b4a1914ad1 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.os.SystemClock; 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 class RecentsAnimationPerfTest extends WindowManagerPerfTestBase { 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 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase { 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 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase { @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 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase { 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 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase { mMeasuredTimeNs += elapsedTimeNs; state.addExtraResult("startFromRecents", elapsedTimeNs); - mLifecycleListener.waitForIdleSync(Stage.RESUMED); + mActivityRule.waitForIdleSync(Stage.RESUMED); } makeInterval(); @@ -223,55 +194,18 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase { } }; + 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 f0c474bfc5bb..f43bdf8348ea 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.graphics.Rect; 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 class RelayoutPerfTest extends WindowManagerPerfTestBase { 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 4864da4b0195..4d278c3c2d9a 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 @@ package android.wm; 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 @@ public class WindowManagerPerfTestBase { 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 ffe39e8679e1..a83254b463f4 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 @@ import java.util.concurrent.TimeUnit; 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; + + @Retention(RetentionPolicy.RUNTIME) + @IntDef(value = { + FLAG_MEDIAN, + FLAG_MEAN, + FLAG_MIN, + FLAG_MAX, + FLAG_STDDEV, + FLAG_COEFFICIENT_VAR, + FLAG_ITERATION, + }) + @interface Flag {} - 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; + /** 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 @@ public final class ManualBenchmarkState { // 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 @@ public final class ManualBenchmarkState { 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 @@ public final class ManualBenchmarkState { } /** - * 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 @@ public final class ManualBenchmarkState { } 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 (shouldReport(STATS_REPORT_PERCENTILE95)) { - status.putLong(key + "_percentile95", stats.getPercentile95()); + if (mStatsReportPercentiles != null) { + for (int percentile : mStatsReportPercentiles) { + status.putLong(key + "_percentile" + percentile, stats.getPercentile(percentile)); + } } - 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 final class ManualBenchmarkState { 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/StubActivity.java b/apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java index 8f03f7eea584..e934feb01a84 100644 --- a/apct-tests/perftests/utils/src/android/perftests/utils/StubActivity.java +++ b/apct-tests/perftests/utils/src/android/perftests/utils/PerfTestActivity.java @@ -19,12 +19,28 @@ 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 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); + 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 f650e810c6de..fb516a818f75 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 class Stats { } public int getSize() { - return mSize; + return mValues.size(); } public double getMean() { @@ -82,12 +82,8 @@ public class Stats { 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/TraceMarkParser.java b/apct-tests/perftests/utils/src/android/perftests/utils/TraceMarkParser.java new file mode 100644 index 000000000000..1afed3a0be5b --- /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 9039f921b3ba..e27670c34fb2 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 @@ public class DeviceIdleManager { 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 9d5becbf77cd..20fb000b36d3 100644 --- a/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl +++ b/apex/jobscheduler/framework/java/android/os/IDeviceIdleController.aidl @@ -21,6 +21,7 @@ import android.os.UserHandle; /** @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 127324936e09..6475f5706a6d 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 @@ public interface DeviceIdleInternal { 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 3cfb08082ff7..c036c772d7d0 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 @@ public interface AppStandbyInternal { void onBootPhase(int phase); - boolean isParoledOrCharging(); - void postCheckIdleStates(int userId); /** @@ -59,13 +57,15 @@ public interface AppStandbyInternal { 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 3dc9a28ac965..4ee46f453bca 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -105,6 +105,8 @@ import java.io.IOException; 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; /** @@ -274,6 +276,7 @@ public class DeviceIdleController extends SystemService private IBatteryStats mBatteryStats; private ActivityManagerInternal mLocalActivityManager; private ActivityTaskManagerInternal mLocalActivityTaskManager; + private DeviceIdleInternal mLocalService; private PowerManagerInternal mLocalPowerManager; private PowerManager mPowerManager; private INetworkPolicyManager mNetworkPolicyManager; @@ -288,6 +291,7 @@ public class DeviceIdleController extends SystemService private boolean mLightEnabled; private boolean mDeepEnabled; private boolean mQuickDozeActivated; + private boolean mQuickDozeActivatedWhileIdling; private boolean mForceIdle; private boolean mNetworkConnected; private boolean mScreenOn; @@ -299,6 +303,10 @@ public class DeviceIdleController extends SystemService 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; @@ -548,6 +556,9 @@ public class DeviceIdleController extends SystemService */ 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; @@ -606,6 +617,22 @@ public class DeviceIdleController extends SystemService } }; + + 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 @@ -655,12 +682,70 @@ public class DeviceIdleController extends SystemService } }; + /** 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; } @@ -668,7 +753,6 @@ public class DeviceIdleController extends SystemService @Override public void onTrigger(TriggerEvent event) { synchronized (DeviceIdleController.this) { - active = false; motionLocked(); } } @@ -676,8 +760,6 @@ public class DeviceIdleController extends SystemService @Override public void onSensorChanged(SensorEvent event) { synchronized (DeviceIdleController.this) { - mSensorManager.unregisterListener(this, mMotionSensor); - active = false; motionLocked(); } } @@ -695,6 +777,7 @@ public class DeviceIdleController extends SystemService } if (success) { active = true; + activatedTimeElapsed = mInjector.getElapsedRealtime(); } else { Slog.e(TAG, "Unable to register for " + mMotionSensor); } @@ -1302,6 +1385,8 @@ public class DeviceIdleController extends SystemService 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; @@ -1427,6 +1512,32 @@ public class DeviceIdleController extends SystemService 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; } } } @@ -1440,11 +1551,20 @@ public class DeviceIdleController extends SystemService 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); } @@ -1679,6 +1799,16 @@ public class DeviceIdleController extends SystemService 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 { @@ -1862,7 +1992,8 @@ public class DeviceIdleController extends SystemService mBinderService = new BinderService(); publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService); - publishLocalService(DeviceIdleInternal.class, new LocalService()); + mLocalService = new LocalService(); + publishLocalService(DeviceIdleInternal.class, mLocalService); } @Override @@ -2068,21 +2199,35 @@ public class DeviceIdleController extends SystemService } } - 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) { @@ -2593,6 +2738,8 @@ public class DeviceIdleController extends SystemService 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(); @@ -2777,10 +2924,11 @@ public class DeviceIdleController extends SystemService mNextIdlePendingDelay = 0; mNextIdleDelay = 0; mIdleStartTime = 0; + mQuickDozeActivatedWhileIdling = false; cancelAlarmLocked(); cancelSensingTimeoutAlarmLocked(); cancelLocatingLocked(); - stopMonitoringMotionLocked(); + maybeStopMonitoringMotionLocked(); mAnyMotionDetector.stop(); updateActiveConstraintsLocked(); } @@ -3267,11 +3415,23 @@ public class DeviceIdleController extends SystemService 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. @@ -3323,10 +3483,15 @@ public class DeviceIdleController extends SystemService } } - 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(); } } @@ -3353,6 +3518,10 @@ public class DeviceIdleController extends SystemService } } + private void cancelMotionTimeoutAlarmLocked() { + mAlarmManager.cancel(mMotionTimeoutAlarmListener); + } + void cancelSensingTimeoutAlarmLocked() { if (mNextSensingTimeoutAlarmTime != 0) { mNextSensingTimeoutAlarmTime = 0; @@ -3393,6 +3562,14 @@ public class DeviceIdleController extends SystemService 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; @@ -3918,7 +4095,8 @@ public class DeviceIdleController extends SystemService 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); @@ -4310,9 +4488,14 @@ public class DeviceIdleController extends SystemService } 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 14d5a683ce83..c3ffad66d829 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -266,11 +266,6 @@ public class JobSchedulerService extends com.android.server.SystemService 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(); @@ -2361,14 +2356,6 @@ public class JobSchedulerService extends com.android.server.SystemService } @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); @@ -3031,10 +3018,6 @@ public class JobSchedulerService extends com.android.server.SystemService } 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); @@ -3222,7 +3205,6 @@ public class JobSchedulerService extends com.android.server.SystemService } 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/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 400d90203453..14dce84e686a 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 @@ public final class QuotaController extends StateController { 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 final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { } }); } - - @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 final class QuotaController extends StateController { 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 @@ public final class QuotaController extends StateController { 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 b97da59f8d17..aa7696df6dbd 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 @@ public class ThermalStatusRestriction extends JobRestriction { 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 7c472a9813aa..ecc045995521 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_NEVER; 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.database.ContentObserver; 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 @@ public class AppStandbyController implements AppStandbyInternal { 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 @@ public class AppStandbyController implements AppStandbyInternal { * 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 @@ public class AppStandbyController implements AppStandbyInternal { private AppWidgetManager mAppWidgetManager; private ConnectivityManager mConnectivityManager; - private PowerManager mPowerManager; private PackageManager mPackageManager; Injector mInjector; @@ -329,12 +310,6 @@ public class AppStandbyController implements AppStandbyInternal { 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 @@ public class AppStandbyController implements AppStandbyInternal { @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 @@ public class AppStandbyController implements AppStandbyInternal { 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 @@ public class AppStandbyController implements AppStandbyInternal { if (mPendingOneTimeCheckIdleStates) { postOneTimeCheckIdleStates(); } - } else if (phase == PHASE_BOOT_COMPLETED) { - setChargingState(mInjector.isCharging()); } } @@ -504,93 +468,6 @@ public class AppStandbyController implements AppStandbyInternal { } } - @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 @@ public class AppStandbyController implements AppStandbyInternal { 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 @@ public class AppStandbyController implements AppStandbyInternal { } } - 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 @@ public class AppStandbyController implements AppStandbyInternal { } @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 @@ public class AppStandbyController implements AppStandbyInternal { } } - 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 @@ public class AppStandbyController implements AppStandbyInternal { 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 @@ public class AppStandbyController implements AppStandbyInternal { 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 @@ public class AppStandbyController implements AppStandbyInternal { 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 @@ public class AppStandbyController implements AppStandbyInternal { 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 @@ public class AppStandbyController implements AppStandbyInternal { 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 @@ public class AppStandbyController implements AppStandbyInternal { reportExemptedSyncStart((String) msg.obj, msg.arg1); break; - case MSG_UPDATE_STABLE_CHARGING: - updateChargingStableState(); - break; - default: super.handleMessage(msg); break; @@ -1800,23 +1554,6 @@ public class AppStandbyController implements AppStandbyInternal { } }; - 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 @@ public class AppStandbyController implements AppStandbyInternal { @Override public void onAvailable(Network network) { mConnectivityManager.unregisterNetworkCallback(this); - checkParoleTimeout(); } }; @@ -1851,9 +1587,6 @@ public class AppStandbyController implements AppStandbyInternal { * 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 @@ public class AppStandbyController implements AppStandbyInternal { "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 class AppStandbyController implements AppStandbyInternal { 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 @@ public class AppStandbyController implements AppStandbyInternal { 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 @@ public class AppStandbyController implements AppStandbyInternal { 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 66ee7eccda66..cae2068cd479 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2946,6 +2946,7 @@ package android.accessibilityservice { field public static final int FEEDBACK_SPOKEN = 1; // 0x1 field public static final int FEEDBACK_VISUAL = 8; // 0x8 field public static final int FLAG_ENABLE_ACCESSIBILITY_VOLUME = 128; // 0x80 + field public static final int FLAG_HANDLE_SHORTCUT = 2048; // 0x800 field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2 field public static final int FLAG_REPORT_VIEW_IDS = 16; // 0x10 field public static final int FLAG_REQUEST_ACCESSIBILITY_BUTTON = 256; // 0x100 @@ -4481,7 +4482,7 @@ package android.app { 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 +5873,7 @@ package android.app { 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 +9807,7 @@ package android.content { 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 +12404,8 @@ package android.content.res { 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 +12432,7 @@ package android.content.res { 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 +12460,8 @@ package android.content.res { 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 +12529,33 @@ package android.content.res { } +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 { @@ -13739,7 +13773,9 @@ package android.graphics { public enum Bitmap.CompressFormat { enum_constant public static final android.graphics.Bitmap.CompressFormat JPEG; enum_constant public static final android.graphics.Bitmap.CompressFormat PNG; - enum_constant public static final android.graphics.Bitmap.CompressFormat WEBP; + enum_constant @Deprecated public static final android.graphics.Bitmap.CompressFormat WEBP; + enum_constant public static final android.graphics.Bitmap.CompressFormat WEBP_LOSSLESS; + enum_constant public static final android.graphics.Bitmap.CompressFormat WEBP_LOSSY; } public enum Bitmap.Config { @@ -24619,6 +24655,7 @@ package android.media { 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 @@ -25385,22 +25422,22 @@ package android.media { public class MediaMetadataRetriever implements java.lang.AutoCloseable { ctor public MediaMetadataRetriever(); method public void close(); - method public String extractMetadata(int); - method public byte[] getEmbeddedPicture(); - 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 @Nullable public String extractMetadata(int); + method @Nullable public byte[] getEmbeddedPicture(); + method @Nullable public android.graphics.Bitmap getFrameAtIndex(int, @NonNull android.media.MediaMetadataRetriever.BitmapParams); + method @Nullable public android.graphics.Bitmap getFrameAtIndex(int); + method @Nullable public android.graphics.Bitmap getFrameAtTime(long, int); + method @Nullable public android.graphics.Bitmap getFrameAtTime(long, int, @NonNull android.media.MediaMetadataRetriever.BitmapParams); + method @Nullable public android.graphics.Bitmap getFrameAtTime(long); + method @Nullable public android.graphics.Bitmap getFrameAtTime(); method @NonNull public java.util.List<android.graphics.Bitmap> getFramesAtIndex(int, int, @NonNull android.media.MediaMetadataRetriever.BitmapParams); method @NonNull public java.util.List<android.graphics.Bitmap> getFramesAtIndex(int, int); - method public android.graphics.Bitmap getImageAtIndex(int, @NonNull android.media.MediaMetadataRetriever.BitmapParams); - method public android.graphics.Bitmap getImageAtIndex(int); - 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 @Nullable public android.graphics.Bitmap getImageAtIndex(int, @NonNull android.media.MediaMetadataRetriever.BitmapParams); + method @Nullable public android.graphics.Bitmap getImageAtIndex(int); + method @Nullable public android.graphics.Bitmap getPrimaryImage(@NonNull android.media.MediaMetadataRetriever.BitmapParams); + method @Nullable public android.graphics.Bitmap getPrimaryImage(); + method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int); + method @Nullable 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; @@ -25415,6 +25452,9 @@ package android.media { 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 @@ -38246,6 +38286,7 @@ package android.provider { 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 @@ -38464,16 +38505,17 @@ package android.provider { 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"; @@ -38492,7 +38534,7 @@ package android.provider { 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"; } @@ -38515,19 +38557,23 @@ package android.provider { 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"; } @@ -38753,6 +38799,9 @@ package android.provider { 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"; @@ -40811,7 +40860,7 @@ package android.security { 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 { @@ -41872,6 +41921,7 @@ package android.service.quicksettings { 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"; } } @@ -44210,9 +44260,11 @@ package android.telephony { 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"; @@ -44414,6 +44466,8 @@ package android.telephony { public abstract class CellInfo implements android.os.Parcelable { method public int describeContents(); method public int getCellConnectionStatus(); + method @NonNull public abstract android.telephony.CellIdentity getCellIdentity(); + method @NonNull public abstract android.telephony.CellSignalStrength getCellSignalStrength(); method public long getTimeStamp(); method public boolean isRegistered(); field public static final int CONNECTION_NONE = 0; // 0x0 @@ -44558,6 +44612,7 @@ package android.telephony { 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; @@ -45063,6 +45118,7 @@ package android.telephony { 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); @@ -45082,6 +45138,7 @@ package android.telephony { 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); } @@ -45091,6 +45148,7 @@ package android.telephony { 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(); @@ -45123,7 +45181,7 @@ package android.telephony { 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(); @@ -45139,6 +45197,7 @@ package android.telephony { 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(); @@ -45243,6 +45302,10 @@ package android.telephony { 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 @@ -50172,14 +50235,14 @@ package android.view { } public static interface SurfaceHolder.Callback { - method public void surfaceChanged(android.view.SurfaceHolder, int, int, int); - method public void surfaceCreated(android.view.SurfaceHolder); - method public void surfaceDestroyed(android.view.SurfaceHolder); + method public void surfaceChanged(@NonNull android.view.SurfaceHolder, int, @IntRange(from=0) int, @IntRange(from=0) int); + method public void surfaceCreated(@NonNull android.view.SurfaceHolder); + method public void surfaceDestroyed(@NonNull android.view.SurfaceHolder); } public static interface SurfaceHolder.Callback2 extends android.view.SurfaceHolder.Callback { - method public void surfaceRedrawNeeded(android.view.SurfaceHolder); - method public default void surfaceRedrawNeededAsync(android.view.SurfaceHolder, Runnable); + method public void surfaceRedrawNeeded(@NonNull android.view.SurfaceHolder); + method public default void surfaceRedrawNeededAsync(@NonNull android.view.SurfaceHolder, @NonNull Runnable); } public class SurfaceView extends android.view.View { diff --git a/api/lint-baseline.txt b/api/lint-baseline.txt new file mode 100644 index 000000000000..2ca8cf4ae0a4 --- /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 447ba30382a8..117be3bb4c7a 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -172,6 +172,7 @@ package android { 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"; @@ -1359,7 +1360,7 @@ package android.content { 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); + method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int); method public abstract android.content.Context createCredentialProtectedStorageContext(); 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(); @@ -1754,7 +1755,9 @@ package android.content.pm { 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; } @@ -3420,7 +3423,9 @@ package android.location { 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 { @@ -5686,18 +5691,13 @@ package android.os.storage { } -package android.os.telephony { - - public class TelephonyRegistryManager { - method public void notifyCarrierNetworkChange(boolean); - } - -} - 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 @@ -5711,17 +5711,19 @@ package android.permission { 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"; } @@ -7312,7 +7314,7 @@ package android.telephony { 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"; } @@ -8369,6 +8371,14 @@ package android.telephony { field public static final int SRVCC_STATE_HANDOVER_STARTED = 0; // 0x0 } + public class TelephonyRegistryManager { + method public void addOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener, @NonNull java.util.concurrent.Executor); + method public void addOnSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener, @NonNull java.util.concurrent.Executor); + method public void notifyCarrierNetworkChange(boolean); + method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener); + method public void removeOnSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener); + } + public final class UiccAccessRule implements android.os.Parcelable { ctor public UiccAccessRule(byte[], @Nullable String, long); method public int describeContents(); @@ -9576,17 +9586,17 @@ package android.telephony.ims.stub { 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 @@ -9758,9 +9768,10 @@ package android.view { 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 000000000000..57a853a42676 --- /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 478540599fbd..b9ab3755a74a 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -131,7 +131,7 @@ package android.app { 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,7 @@ package android.content { } public abstract class Context { - method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle); + 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(); @@ -731,6 +731,7 @@ package android.content.pm { 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); @@ -769,8 +770,10 @@ package android.content.pm { 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; } @@ -788,7 +791,9 @@ package android.content.res { 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 { @@ -1089,11 +1094,14 @@ package android.location { 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); @@ -1741,6 +1749,7 @@ package android.os { } 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(); } @@ -2244,8 +2253,11 @@ package android.os.strictmode { 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 @@ -2786,6 +2798,10 @@ package android.telecom { 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/statsd/Android.bp b/cmds/statsd/Android.bp index cb2732561b56..8af925aa7a63 100644 --- a/cmds/statsd/Android.bp +++ b/cmds/statsd/Android.bp @@ -230,6 +230,7 @@ cc_test { "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 ff7416c4b9e0..6c3dff24e5fe 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 @@ void StatsLogProcessor::OnLogEvent(LogEvent* event) { 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 3d002d2efdd0..8292a3a9194a 100644 --- a/cmds/statsd/src/StatsLogProcessor.h +++ b/cmds/statsd/src/StatsLogProcessor.h @@ -262,6 +262,10 @@ private: 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 e09d5751d323..4c30c4cb223c 100644 --- a/cmds/statsd/src/anomaly/subscriber_util.cpp +++ b/cmds/statsd/src/anomaly/subscriber_util.cpp @@ -40,7 +40,7 @@ void triggerSubscribers(int64_t ruleId, int64_t metricId, const MetricDimensionK 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 43e33f59f612..5a76d1f9c80d 100644 --- a/cmds/statsd/src/external/StatsPullerManager.cpp +++ b/cmds/statsd/src/external/StatsPullerManager.cpp @@ -122,9 +122,10 @@ std::map<int, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = { {.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 b5c8e35accad..4a06387e357f 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; @@ -65,16 +67,16 @@ const int FIELD_ID_BUCKET_NUM = 4; 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, - const unordered_map<int, shared_ptr<Activation>>& - eventActivationMap, - const unordered_map<int, vector<shared_ptr<Activation>>>& - eventDeactivationMap) +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) { + eventDeactivationMap, slicedStateAtoms, stateGroupMap) { if (metric.has_bucket()) { mBucketSizeNs = TimeUnitToBucketSizeInMillisGuardrailed(key.GetUid(), metric.bucket()) * 1000000; @@ -100,6 +102,8 @@ CountMetricProducer::CountMetricProducer(const ConfigKey& key, const CountMetric mConditionSliced = true; } + // TODO(tsaichristine): b/142124705 handle metric state links + flushIfNeededLocked(startTimeNs); // Adjust start for partial bucket mCurrentBucketStartTimeNs = startTimeNs; @@ -112,6 +116,12 @@ CountMetricProducer::~CountMetricProducer() { 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 61913c71cdcc..61e0892d50a9 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,16 +41,20 @@ struct CountBucket { 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, - const std::unordered_map<int, std::shared_ptr<Activation>>& - eventActivationMap = {}, - const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>& - eventDeactivationMap = {}); + 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, @@ -106,6 +111,7 @@ private: 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 31b90f3b13b1..ab2a1c3bd65c 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp +++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp @@ -69,9 +69,11 @@ DurationMetricProducer::DurationMetricProducer( 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 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), + 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 0592b1808f52..7457d7fb2dd9 100644 --- a/cmds/statsd/src/metrics/DurationMetricProducer.h +++ b/cmds/statsd/src/metrics/DurationMetricProducer.h @@ -38,16 +38,16 @@ namespace statsd { 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, - const unordered_map<int, shared_ptr<Activation>>& - eventActivationMap = {}, - const unordered_map<int, vector<shared_ptr<Activation>>>& - eventDeactivationMap = {}); + 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 a60a9161bdbb..32eb077e1cf4 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.cpp +++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp @@ -52,16 +52,15 @@ const int FIELD_ID_DATA = 1; 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, - const unordered_map<int, shared_ptr<Activation>>& - eventActivationMap, - const unordered_map<int, vector<shared_ptr<Activation>>>& - eventDeactivationMap) +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) { + 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 aab53c8b6816..dca37e8790a4 100644 --- a/cmds/statsd/src/metrics/EventMetricProducer.h +++ b/cmds/statsd/src/metrics/EventMetricProducer.h @@ -33,13 +33,14 @@ namespace statsd { class EventMetricProducer : public MetricProducer { public: - 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 = {}); + 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 e409b6fbb9e9..d0f88a867da7 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp @@ -74,9 +74,11 @@ GaugeMetricProducer::GaugeMetricProducer( const int atomId, const int64_t timeBaseNs, 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 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), + eventDeactivationMap, slicedStateAtoms, stateGroupMap), mWhatMatcherIndex(whatMatcherIndex), mEventMatcherWizard(matcherWizard), mPullerManager(pullerManager), diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h index dfe1d56c61c7..640a02a9a8ab 100644 --- a/cmds/statsd/src/metrics/GaugeMetricProducer.h +++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h @@ -56,16 +56,17 @@ typedef std::unordered_map<MetricDimensionKey, std::vector<GaugeAtom>> // 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, - const std::unordered_map<int, std::shared_ptr<Activation>>& - eventActivationMap = {}, - const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>& - eventDeactivationMap = {}); + 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(); diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp index 3426a195a748..2a700efe3d37 100644 --- a/cmds/statsd/src/metrics/MetricProducer.cpp +++ b/cmds/statsd/src/metrics/MetricProducer.cpp @@ -45,23 +45,27 @@ MetricProducer::MetricProducer( 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) - : 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()) { - } + 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) { diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h index 1e1eb69aa8f0..a72de22bd433 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 { @@ -86,13 +86,15 @@ struct Activation { // 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, const std::unordered_map<int, std::shared_ptr<Activation>>& eventActivationMap, const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>& - eventDeactivationMap); + eventDeactivationMap, + const vector<int>& slicedStateAtoms, + const unordered_map<int, unordered_map<int, int64_t>>& stateGroupMap); virtual ~MetricProducer(){}; @@ -151,6 +153,9 @@ public: 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, @@ -230,6 +235,11 @@ public: 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) { @@ -381,6 +391,16 @@ protected: 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 963205ee56f4..7bae4b929b48 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(const ConfigKey& key, const StatsdConfig& config, } 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 3dad61465416..d184121f5ba0 100644 --- a/cmds/statsd/src/metrics/MetricsManager.h +++ b/cmds/statsd/src/metrics/MetricsManager.h @@ -282,6 +282,10 @@ private: 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 7fe5a834f412..6fd03273a434 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp +++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp @@ -83,9 +83,11 @@ ValueMetricProducer::ValueMetricProducer( const sp<EventMatcherWizard>& matcherWizard, const int pullTagId, const int64_t timeBaseNs, 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 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), + eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap), mWhatMatcherIndex(whatMatcherIndex), mEventMatcherWizard(matcherWizard), mPullerManager(pullerManager), diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index d7cd397da2c0..206e602dd1c7 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -52,15 +52,17 @@ struct ValueBucket { // - 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, - const std::unordered_map<int, std::shared_ptr<Activation>>& - eventActivationMap = {}, - const std::unordered_map<int, std::vector<std::shared_ptr<Activation>>>& - eventDeactivationMap = {}); + 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(); diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp index 0fee71e1fe2c..33e162ec2d24 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.cpp +++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp @@ -20,24 +20,24 @@ #include "metrics_manager_util.h" #include "MetricProducer.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 <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" -#include <inttypes.h> - using std::set; using std::string; using std::unordered_map; @@ -138,6 +138,41 @@ bool handleMetricWithConditions( 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. @@ -342,12 +377,32 @@ bool initConditions(const ConfigKey& key, const StatsdConfig& config, 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, vector<int>>& conditionToMetricMap, @@ -398,10 +453,9 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t 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 { @@ -411,6 +465,18 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t } } + 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, @@ -421,7 +487,7 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t sp<MetricProducer> countProducer = new CountMetricProducer( key, metric, conditionIndex, wizard, timeBaseTimeNs, currentTimeNs, - eventActivationMap, eventDeactivationMap); + eventActivationMap, eventDeactivationMap, slicedStateAtoms, stateGroupMap); allMetricProducers.push_back(countProducer); } @@ -721,6 +787,13 @@ bool initMetrics(const ConfigKey& key, const StatsdConfig& config, const int64_t } 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; } @@ -846,6 +919,8 @@ bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& 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"); @@ -858,9 +933,13 @@ bool initStatsdConfig(const ConfigKey& key, const StatsdConfig& config, UidMap& 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, activationAtomTrackerToMetricMap, deactivationAtomTrackerToMetricMap, metricsWithActivation)) { diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h index 3802948f64da..95b2ab81fa53 100644 --- a/cmds/statsd/src/metrics/metrics_manager_util.h +++ b/cmds/statsd/src/metrics/metrics_manager_util.h @@ -68,6 +68,17 @@ bool initConditions(const ConfigKey& key, const StatsdConfig& config, 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 @@ bool initConditions(const ConfigKey& key, const StatsdConfig& config, // [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,6 +101,8 @@ bool initMetrics( 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, diff --git a/cmds/statsd/src/socket/StatsSocketListener.cpp b/cmds/statsd/src/socket/StatsSocketListener.cpp index 92200f99c3cc..b59d88dc1cea 100755 --- a/cmds/statsd/src/socket/StatsSocketListener.cpp +++ b/cmds/statsd/src/socket/StatsSocketListener.cpp @@ -56,8 +56,7 @@ bool StatsSocketListener::onDataAvailable(SocketClient* cli) { } // + 1 to ensure null terminator if MAX_PAYLOAD buffer is received - char buffer[sizeof_log_id_t + sizeof(uint16_t) + sizeof(log_time) + LOGGER_ENTRY_MAX_PAYLOAD + - 1]; + char buffer[sizeof(android_log_header_t) + LOGGER_ENTRY_MAX_PAYLOAD + 1]; struct iovec iov = {buffer, sizeof(buffer) - 1}; alignas(4) char control[CMSG_SPACE(sizeof(struct ucred))]; diff --git a/cmds/statsd/src/state/StateManager.cpp b/cmds/statsd/src/state/StateManager.cpp index a3059c5b52ac..95b2c7691803 100644 --- a/cmds/statsd/src/state/StateManager.cpp +++ b/cmds/statsd/src/state/StateManager.cpp @@ -35,26 +35,25 @@ void StateManager::onLogEvent(const LogEvent& event) { } } -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 @@ void StateManager::unregisterListener(int stateAtomId, wp<StateListener> listene 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 @@ void StateManager::unregisterListener(int stateAtomId, wp<StateListener> listene } } 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 ce60f1482be7..89ee6c04b922 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 @@ public: // 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 5a91950b9f8b..323fc0e94ab2 100644 --- a/cmds/statsd/src/state/StateTracker.cpp +++ b/cmds/statsd/src/state/StateTracker.cpp @@ -30,13 +30,13 @@ StateTracker::StateTracker(const int atomId, : 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 @@ void StateTracker::unregisterListener(wp<StateListener> listener) { 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 f22706c8418d..cfa9fd8f6272 100644 --- a/cmds/statsd/src/state/StateTracker.h +++ b/cmds/statsd/src/state/StateTracker.h @@ -48,7 +48,7 @@ public: // 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 67625eb82454..c22e3cc07a3b 100644 --- a/cmds/statsd/src/stats_log_util.cpp +++ b/cmds/statsd/src/stats_log_util.cpp @@ -445,6 +445,8 @@ int64_t TimeUnitToBucketSizeInMillis(TimeUnit unit) { 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 c107397b0273..c98b2cf4b472 100644 --- a/cmds/statsd/src/statsd_config.proto +++ b/cmds/statsd/src/statsd_config.proto @@ -44,6 +44,7 @@ enum TimeUnit { 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 000000000000..6591d69cdc1a --- /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 839daa4b7f8a..8915c7364bc7 100644 --- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp @@ -43,8 +43,8 @@ TEST(CountMetricProducerTest, TestFirstBucket) { 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 @@ TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition) { 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 @@ TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced) { 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/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp index c89ffea85709..8d380006a8ab 100644 --- a/cmds/statsd/tests/state/StateTracker_test.cpp +++ b/cmds/statsd/tests/state/StateTracker_test.cpp @@ -44,7 +44,7 @@ public: 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 @@ std::shared_ptr<LogEvent> buildOverlayEvent(int uid, const std::string& packageN 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 @@ std::shared_ptr<LogEvent> buildIncorrectOverlayEvent(int uid, const std::string& 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 @@ TEST(StateTrackerTest, TestRegisterListener) { // 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 @@ TEST(StateTrackerTest, TestStateChangeNoPrimaryFields) { // 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 @@ TEST(StateTrackerTest, TestStateChangeOnePrimaryField) { 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 @@ TEST(StateTrackerTest, TestStateChangeOnePrimaryField) { // 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 @@ TEST(StateTrackerTest, TestStateChangeEventError) { 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 @@ TEST(StateTrackerTest, TestStateQuery) { 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 @@ TEST(StateTrackerTest, TestStateQuery) { // 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 2c4f3c7692c9..38c22ab5d253 100644 --- a/cmds/statsd/tests/statsd_test_util.cpp +++ b/cmds/statsd/tests/statsd_test_util.cpp @@ -251,6 +251,101 @@ Predicate CreateIsInBackgroundPredicate() { 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 635c5835333a..c02610540cf0 100644 --- a/cmds/statsd/tests/statsd_test_util.h +++ b/cmds/statsd/tests/statsd_test_util.h @@ -104,6 +104,37 @@ Predicate CreateIsSyncingPredicate(); // 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 @@ void backfillStartEndTimestampForSkippedBuckets(const int64_t timeBaseNs, T* met } } // namespace statsd } // namespace os -} // namespace android
\ No newline at end of file +} // namespace android diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 4603f08c765f..47fdcdeafce9 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -850,6 +850,7 @@ public abstract class AccessibilityService extends Service { GestureResultCallbackInfo callbackInfo; synchronized (mLock) { callbackInfo = mGestureStatusCallbackInfos.get(sequence); + mGestureStatusCallbackInfos.remove(sequence); } final GestureResultCallbackInfo finalCallbackInfo = callbackInfo; if ((callbackInfo != null) && (callbackInfo.gestureDescription != null) diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java index cf24b8e1ffa6..e738b19f420a 100644 --- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java +++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java @@ -18,6 +18,7 @@ package android.accessibilityservice; import static android.content.pm.PackageManager.FEATURE_FINGERPRINT; +import android.accessibilityservice.AccessibilityButtonController.AccessibilityButtonCallback; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.UnsupportedAppUsage; @@ -322,6 +323,14 @@ public class AccessibilityServiceInfo implements Parcelable { */ public static final int FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK = 0x00000400; + /** + * This flag indicates that the accessibility service will handle the shortcut action itself. + * A callback {@link AccessibilityButtonCallback#onClicked(AccessibilityButtonController)} is + * called when the user presses the accessibility shortcut. Otherwise, the service is enabled + * or disabled by the system instead. + */ + public static final int FLAG_HANDLE_SHORTCUT = 0x00000800; + /** {@hide} */ public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 0x00010000; @@ -423,12 +432,13 @@ public class AccessibilityServiceInfo implements Parcelable { * @see #FLAG_ENABLE_ACCESSIBILITY_VOLUME * @see #FLAG_REQUEST_ACCESSIBILITY_BUTTON * @see #FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK + * @see #FLAG_HANDLE_SHORTCUT */ public int flags; /** * Whether or not the service has crashed and is awaiting restart. Only valid from {@link - * android.view.accessibility.AccessibilityManager#getEnabledAccessibilityServiceList(int)}, + * android.view.accessibility.AccessibilityManager#getInstalledAccessibilityServiceList()}, * because that is populated from the internal list of running services. * * @hide @@ -1103,6 +1113,8 @@ public class AccessibilityServiceInfo implements Parcelable { return "FLAG_REQUEST_FINGERPRINT_GESTURES"; case FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK: return "FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK"; + case FLAG_HANDLE_SHORTCUT: + return "FLAG_HANDLE_SHORTCUT"; default: return null; } diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java index b56c00e44d3f..fbf1f59141a8 100644 --- a/core/java/android/app/ActivityView.java +++ b/core/java/android/app/ActivityView.java @@ -22,6 +22,7 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLI 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 @@ public class ActivityView extends ViewGroup { * 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 86bf20a57eec..03ef286c48c1 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -3160,6 +3160,15 @@ public class ApplicationPackageManager extends PackageManager { } @Override + public String[] getTelephonyPackageNames() { + try { + return mPM.getTelephonyPackageNames(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + @Override public String getSystemCaptionsServicePackageName() { try { return mPM.getSystemCaptionsServicePackageName(); diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java index df6533a340f2..241895c9ff64 100644 --- a/core/java/android/app/AsyncNotedAppOp.java +++ b/core/java/android/app/AsyncNotedAppOp.java @@ -66,14 +66,18 @@ public final class AsyncNotedAppOp implements Parcelable { - // 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 @@ public final class AsyncNotedAppOp implements Parcelable { * @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 @@ public final class AsyncNotedAppOp implements Parcelable { } /** - * 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 @@ public final class AsyncNotedAppOp implements Parcelable { @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 @@ public final class AsyncNotedAppOp implements Parcelable { @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 @@ public final class AsyncNotedAppOp implements Parcelable { @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 @@ public final class AsyncNotedAppOp implements Parcelable { } @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 39fab634f03c..d5bc9b0b213a 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2212,9 +2212,9 @@ class ContextImpl extends Context { } @Override - public Context createContextAsUser(UserHandle user) { + public Context createContextAsUser(UserHandle user, @CreatePackageOptions int flags) { try { - return createPackageContextAsUser(getPackageName(), mFlags, user); + return createPackageContextAsUser(getPackageName(), flags, user); } catch (NameNotFoundException e) { throw new IllegalStateException("Own package not found: package=" + getPackageName()); } diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index dca00a2bbd83..31a29d4a5c86 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -351,10 +351,6 @@ interface IActivityManager { // Request a heap dump for the system server. void requestSystemServerHeapDump(); - // Deprecated - This method is only used by a few internal components and it will soon start - // using bug report API (which will be restricted to a few, pre-defined apps). - // No new code should be calling it. - @UnsupportedAppUsage void requestBugReport(int bugreportType); void requestBugReportWithDescription(in @nullable String shareTitle, in @nullable String shareDescription, int bugreportType); @@ -364,7 +360,7 @@ interface IActivityManager { * that are passed to this API as parameters * * @param shareTitle should be a valid legible string less than 50 chars long - * @param shareDescription should be less than 91 bytes when encoded into UTF-8 format + * @param shareDescription should be less than 150 chars long * * @throws IllegalArgumentException if shareTitle or shareDescription is too big or if the * paremeters cannot be encoding to an UTF-8 charset. @@ -372,13 +368,12 @@ interface IActivityManager { void requestTelephonyBugReport(in String shareTitle, in String shareDescription); /** - * Deprecated - This method is only used by Wifi, and it will soon start using - * bug report API. + * This method is only used by Wifi. * * Takes a minimal bugreport of Wifi-related state. * * @param shareTitle should be a valid legible string less than 50 chars long - * @param shareDescription should be less than 91 bytes when encoded into UTF-8 format + * @param shareDescription should be less than 150 chars long * * @throws IllegalArgumentException if shareTitle or shareDescription is too big or if the * parameters cannot be encoding to an UTF-8 charset. diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index dda3bb53ebe3..f5914412663b 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -215,7 +215,6 @@ interface IActivityTaskManager { 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/NotificationManager.java b/core/java/android/app/NotificationManager.java index 316cab8d600b..6dca5d921210 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -997,8 +997,13 @@ public class NotificationManager { } /** - * @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 { @@ -1024,7 +1029,7 @@ public class NotificationManager { * Returns AutomaticZenRules owned by the caller. * * <p> - * Throws a SecurityException if policy access is granted to this package. + * Throws a SecurityException if policy access is not granted to this package. * See {@link #isNotificationPolicyAccessGranted}. */ public Map<String, AutomaticZenRule> getAutomaticZenRules() { @@ -1048,7 +1053,7 @@ public class NotificationManager { * Returns the AutomaticZenRule with the given id, if it exists and the caller has access. * * <p> - * Throws a SecurityException if policy access is granted to this package. + * Throws a SecurityException if policy access is not granted to this package. * See {@link #isNotificationPolicyAccessGranted}. * * <p> @@ -1068,7 +1073,7 @@ public class NotificationManager { * Creates the given zen rule. * * <p> - * Throws a SecurityException if policy access is granted to this package. + * Throws a SecurityException if policy access is not granted to this package. * See {@link #isNotificationPolicyAccessGranted}. * * @param automaticZenRule the rule to create. @@ -1087,7 +1092,7 @@ public class NotificationManager { * Updates the given zen rule. * * <p> - * Throws a SecurityException if policy access is granted to this package. + * Throws a SecurityException if policy access is not granted to this package. * See {@link #isNotificationPolicyAccessGranted}. * * <p> @@ -1129,7 +1134,7 @@ public class NotificationManager { * Deletes the automatic zen rule with the given id. * * <p> - * Throws a SecurityException if policy access is granted to this package. + * Throws a SecurityException if policy access is not granted to this package. * See {@link #isNotificationPolicyAccessGranted}. * * <p> diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index cb9ebac728ec..68ab89cfbd01 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -32,6 +32,7 @@ import android.content.res.Configuration; 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.util.Slog; 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.IOException; 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 @@ public class ResourcesManager { 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 class ResourcesManager { 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 @@ public class ResourcesManager { } } - 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 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { */ 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 @@ public class ResourcesManager { 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, - @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl, - @NonNull CompatibilityInfo compatInfo) { - final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked( - 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; + } - final int refCount = activityResources.activityResources.size(); - for (int i = 0; i < refCount; i++) { - WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i); - Resources resources = weakResourceRef.get(); + IBinder activityToken = resourcesWithLoaders.activityToken(); + ResourcesKey key = resourcesWithLoaders.resourcesKey(); - if (resources != null - && Objects.equals(resources.getClassLoader(), classLoader) - && resources.getImpl() == impl) { - if (DEBUG) { - Slog.d(TAG, "- using existing ref=" + resources); - } + 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); + Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader) : new Resources(classLoader); resources.setImpl(impl); @@ -661,28 +772,8 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { * {@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 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { * {@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 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { 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()); - 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); + updateActivityResources(resources, newKey, false); + } - 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); + // 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; } - // 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); - } + ResourcesKey newKey = rebaseActivityOverrideConfig(resources, oldConfig, + overrideConfig, displayId); - ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey); - if (resourcesImpl == null) { - resourcesImpl = createResourcesImpl(newKey); - if (resourcesImpl != null) { - mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl)); - } - } + updateActivityResources(resources, newKey, true); - 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 @@ public class ResourcesManager { } } + /** + * 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 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { } } + 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 @@ public class ResourcesManager { } } + 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 @@ public class ResourcesManager { } } } + + // 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/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index e81dc1c59040..8350fa13b097 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -152,7 +152,7 @@ import android.os.health.SystemHealthManager; import android.os.image.DynamicSystemManager; import android.os.image.IDynamicSystemService; import android.os.storage.StorageManager; -import android.os.telephony.TelephonyRegistryManager; +import android.telephony.TelephonyRegistryManager; import android.permission.PermissionControllerManager; import android.permission.PermissionManager; import android.print.IPrintManager; @@ -611,7 +611,7 @@ public final class SystemServiceRegistry { new CachedServiceFetcher<TelephonyRegistryManager>() { @Override public TelephonyRegistryManager createService(ContextImpl ctx) { - return new TelephonyRegistryManager(); + return new TelephonyRegistryManager(ctx); }}); registerService(Context.TELEPHONY_SUBSCRIPTION_SERVICE, SubscriptionManager.class, diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index c3c383ce5e55..8ea1ff539c9f 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2330,6 +2330,12 @@ public class DevicePolicyManager { "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 @@ public class DevicePolicyManager { } /** + * 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 @@ public class DevicePolicyManager { 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 0da5b7a1cf62..7d2c54ea1436 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -72,6 +72,8 @@ interface IDevicePolicyManager { 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 992985528fca..d9bfde5af61a 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 class PasswordMetrics implements Parcelable { 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 @@ public class PasswordMetrics implements Parcelable { @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 @@ public class PasswordMetrics implements Parcelable { 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 @@ public class PasswordMetrics implements Parcelable { }; /** - * 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 @@ public class PasswordMetrics implements Parcelable { 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 @@ public class PasswordMetrics implements Parcelable { 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 @@ public class PasswordMetrics implements Parcelable { } } - /** 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)); - - /** - * 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)); - - /** - * 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; + 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); + } + + /** + * 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; + } + } + + /** + * 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 000000000000..13f11ad74d12 --- /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 955093d3380e..90ecce2a2170 100644 --- a/core/java/android/app/slice/SliceManager.java +++ b/core/java/android/app/slice/SliceManager.java @@ -390,6 +390,8 @@ public class SliceManager { } 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 da21794cd649..b49426000d88 100644 --- a/core/java/android/app/timedetector/TimeSignal.java +++ b/core/java/android/app/timedetector/TimeSignal.java @@ -56,8 +56,7 @@ public final class TimeSignal implements Parcelable { 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 @@ public final class TimeSignal implements Parcelable { @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 b3260c4c5cce..024afe25f98e 100644 --- a/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -154,12 +154,6 @@ public abstract class UsageStatsManagerInternal { 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 @@ public abstract class UsageStatsManagerInternal { 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/ContentProvider.java b/core/java/android/content/ContentProvider.java index 9dbfbc7795a3..02b6b3ef64ef 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -1469,9 +1469,8 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * This method can be called from multiple threads, as described in * <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes * and Threads</a>. - * @param uri The content:// URI of the insertion request. This must not be {@code null}. + * @param uri The content:// URI of the insertion request. * @param values A set of column_name/value pairs to add to the database. - * This must not be {@code null}. * @return The URI for the newly inserted item. */ @Override @@ -1538,7 +1537,6 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * @param uri The URI to query. This can potentially have a record ID if this * is an update request for a specific record. * @param values A set of column_name/value pairs to update in the database. - * This must not be {@code null}. * @param selection An optional filter to match rows to update. * @return the number of rows affected. */ diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 2dde3ae5909c..3eb066e9d99e 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -63,6 +63,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.storage.StorageManager; import android.provider.MediaStore; +import android.telephony.TelephonyRegistryManager; import android.util.AttributeSet; import android.view.Display; import android.view.DisplayAdjustments; @@ -1559,7 +1560,14 @@ public abstract class Context { * @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(); /** @@ -4709,7 +4717,7 @@ public abstract class Context { /** * Use with {@link #getSystemService(String)} to retrieve an - * {@link android.os.telephony.TelephonyRegistryManager}. + * {@link TelephonyRegistryManager}. * @hide */ @SystemApi @@ -5237,7 +5245,7 @@ public abstract class Context { @SystemApi @TestApi @NonNull - public Context createContextAsUser(@NonNull UserHandle user) { + public Context createContextAsUser(@NonNull UserHandle user, @CreatePackageOptions int flags) { if (Build.IS_ENG) { throw new IllegalStateException("createContextAsUser not overridden!"); } diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index f7cd51e7ffbc..7993ea192424 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -885,8 +885,8 @@ public class ContextWrapper extends Context { /** @hide */ @Override - public Context createContextAsUser(UserHandle user) { - return mBase.createContextAsUser(user); + public Context createContextAsUser(UserHandle user, @CreatePackageOptions int flags) { + return mBase.createContextAsUser(user, flags); } /** @hide */ diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java index 853e8189ea8a..a2f8886eb7d2 100644 --- a/core/java/android/content/om/OverlayManager.java +++ b/core/java/android/content/om/OverlayManager.java @@ -166,9 +166,8 @@ public class OverlayManager { } /** - * 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/AndroidTestBaseUpdater.java b/core/java/android/content/pm/AndroidTestBaseUpdater.java index 8fcfe711a882..18d3ba33552e 100644 --- a/core/java/android/content/pm/AndroidTestBaseUpdater.java +++ b/core/java/android/content/pm/AndroidTestBaseUpdater.java @@ -55,15 +55,20 @@ public class AndroidTestBaseUpdater extends PackageSharedLibraryUpdater { private static final long REMOVE_ANDROID_TEST_BASE = 133396946L; private static boolean isChangeEnabled(Package pkg) { - IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( - ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); - try { - return platformCompat.isChangeEnabled(REMOVE_ANDROID_TEST_BASE, pkg.applicationInfo); - } catch (RemoteException | NullPointerException e) { - Log.e(TAG, "Failed to get a response from PLATFORM_COMPAT_SERVICE", e); + // Do not ask platform compat for system apps to prevent a boot time regression in tests. + // b/142558883. + if (!pkg.applicationInfo.isSystemApp()) { + IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + try { + return platformCompat.isChangeEnabled(REMOVE_ANDROID_TEST_BASE, + pkg.applicationInfo); + } catch (RemoteException | NullPointerException e) { + Log.e(TAG, "Failed to get a response from PLATFORM_COMPAT_SERVICE", e); + } } // Fall back to previous behaviour. - return pkg.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.Q; + return pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.Q; } @Override diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 19d8edfa3884..1d78e2c36cd3 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -682,6 +682,8 @@ interface IPackageManager { String getWellbeingPackageName(); + String[] getTelephonyPackageNames(); + String getAppPredictionServicePackageName(); String getSystemCaptionsServicePackageName(); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 69ce3bd55071..edc66c58b197 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -349,14 +349,7 @@ public class PackageInstaller { */ 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 9513ce802813..7509065a34b1 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -2858,6 +2858,14 @@ public abstract class PackageManager { /** * 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 @@ public abstract class PackageManager { } /** + * @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 diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 0b157fa3bb1e..cf21e9665c71 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -577,8 +577,6 @@ public class PackageParser { */ public interface Callback { boolean hasFeature(String feature); - String[] getOverlayPaths(String targetPackageName, String targetPath); - String[] getOverlayApks(String targetPackageName); } /** @@ -595,14 +593,6 @@ public class PackageParser { @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 @@ public class PackageParser { } 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 @@ public class PackageParser { 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 @@ public class PackageParser { * 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 @@ public class PackageParser { * @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 @@ public class PackageParser { 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 dd5c6a53cc20..aa6f58e82bcf 100644 --- a/core/java/android/content/pm/PermissionInfo.java +++ b/core/java/android/content/pm/PermissionInfo.java @@ -237,6 +237,28 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { @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 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { 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 @@ public class PermissionInfo extends PackageItemInfo implements Parcelable { 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/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java index a35ad567ed81..de1d514d0a5b 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 @@ import java.io.IOException; */ 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 @@ public final class ApkAssets { * @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 final class ApkAssets { */ 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 final class ApkAssets { */ 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 final class ApkAssets { 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 final class ApkAssets { */ 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*/); + } + + /** + * 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*/); } - private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay) + /** + * 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 @@ public final class ApkAssets { } 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 @@ public final class ApkAssets { 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 7d6dc97e82cb..23e772075ad6 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -27,9 +27,13 @@ import android.annotation.TestApi; 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 com.android.internal.util.Preconditions; 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 @@ public final class AssetManager implements AutoCloseable { @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 @@ public final class AssetManager implements AutoCloseable { 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 @@ public final class AssetManager implements AutoCloseable { 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 @@ public final class AssetManager implements AutoCloseable { 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 @@ public final class AssetManager implements AutoCloseable { 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 @@ public final class AssetManager implements AutoCloseable { * * @hide */ + @TestApi public void setResourceResolutionLoggingEnabled(boolean enabled) { synchronized (this) { ensureValidLocked(); @@ -768,6 +784,7 @@ public final class AssetManager implements AutoCloseable { * * @hide */ + @TestApi public @Nullable String getLastResourceResolution() { synchronized (this) { ensureValidLocked(); @@ -814,6 +831,13 @@ public final class AssetManager implements AutoCloseable { 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 @@ public final class AssetManager implements AutoCloseable { 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 @@ public final class AssetManager implements AutoCloseable { 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 @@ public final class AssetManager implements AutoCloseable { 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 @@ public final class AssetManager implements AutoCloseable { 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 @@ public final class AssetManager implements AutoCloseable { } } + 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 @@ public final class AssetManager implements AutoCloseable { * * <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 @@ public final class AssetManager implements AutoCloseable { 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 d7e4e1452cfe..2698c2de4c61 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -30,6 +30,7 @@ import android.annotation.DimenRes; 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.StyleRes; 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.AttributeSet; 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.IOException; 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 @@ public class Resources { @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 @@ public class Resources { 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 @@ public class Resources { 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 @@ public class Resources { 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 @@ public class Resources { @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 @@ public class Resources { 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 @@ public class Resources { @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 @@ public class Resources { } 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 b72544c02d6a..84489cfb768c 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -57,6 +57,8 @@ import android.view.DisplayAdjustments; import com.android.internal.util.GrowingArrayUtils; +import libcore.io.IoUtils; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -376,7 +378,7 @@ public class ResourcesImpl { 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 @@ public class ResourcesImpl { } } + /** + * 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 @@ public class ResourcesImpl { } /** + * 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 @@ public class ResourcesImpl { } 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 2ae1932c3437..d43bd36b4c74 100644 --- a/core/java/android/content/res/StringBlock.java +++ b/core/java/android/content/res/StringBlock.java @@ -47,6 +47,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; +import java.io.Closeable; import java.util.Arrays; /** @@ -54,7 +55,7 @@ import java.util.Arrays; * * {@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 @@ final class StringBlock { } } + @Override public void close() { synchronized (this) { if (mOpen) { @@ -517,7 +519,7 @@ final class StringBlock { * 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 06cafdb2bb91..968ab401ccba 100644 --- a/core/java/android/content/res/ThemedResourceCache.java +++ b/core/java/android/content/res/ThemedResourceCache.java @@ -22,8 +22,8 @@ import android.annotation.UnsupportedAppUsage; 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 @@ abstract class ThemedResourceCache<T> { 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 000000000000..7d90e72ab07e --- /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 000000000000..af32aa2c6875 --- /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 000000000000..ddbfa81390e4 --- /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 000000000000..050aeb7c5fda --- /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/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl index 106b7be5c8d3..fe9141cb6a20 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 @@ oneway interface INetworkPolicyListener { 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 90327663e34b..385cb1d68b57 100644 --- a/core/java/android/net/INetworkPolicyManager.aidl +++ b/core/java/android/net/INetworkPolicyManager.aidl @@ -76,7 +76,7 @@ interface INetworkPolicyManager { 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 628dcd2691cf..9150aae14049 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -31,6 +31,7 @@ import android.net.wifi.WifiInfo; 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 @@ public class NetworkPolicyManager { @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 2c9333bc1a7c..ef3afabfe878 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -1085,4 +1085,13 @@ public class Binder implements IBinder { 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/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index bcb94ce2d2d5..fdb44e7050e1 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -35,12 +35,15 @@ import android.annotation.TestApi; 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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { /** * 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 @@ public class ParcelFileDescriptor implements Parcelable, Closeable { /** * 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 654b0f7d30aa..26c1ec170834 100644 --- a/core/java/android/permission/IPermissionController.aidl +++ b/core/java/android/permission/IPermissionController.aidl @@ -31,8 +31,8 @@ oneway interface IPermissionController { 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 923d9f85cc79..421e29ed0542 100644 --- a/core/java/android/permission/PermissionControllerManager.java +++ b/core/java/android/permission/PermissionControllerManager.java @@ -62,6 +62,7 @@ import libcore.util.EmptyArray; 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 @@ public final class PermissionControllerManager { } /** - * 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 @@ public final class PermissionControllerManager { } /** + * 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 @@ public final class PermissionControllerManager { } // 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 @@ public final class PermissionControllerManager { * * @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. - * @param user The user to be restore + * <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. * - * @hide + * @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 */ - @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 @@ public final class PermissionControllerManager { } /** - * 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 @@ public final class PermissionControllerManager { 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 @@ public final class PermissionControllerManager { 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 7363d7783903..8f765faa1a0b 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -54,6 +54,7 @@ import java.io.IOException; 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 class PermissionControllerService extends Service { 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 @@ public abstract class PermissionControllerService extends Service { request.put(packageName, permissions); } - enforceCallingPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, null); + enforceSomePermissionsGrantedToCaller( + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS); // Verify callerPackageName try { @@ -258,12 +283,33 @@ public abstract class PermissionControllerService extends Service { }); } + /** + * 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 @@ public abstract class PermissionControllerService extends Service { } @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 @@ public abstract class PermissionControllerService extends Service { } @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 @@ public abstract class PermissionControllerService extends Service { 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 @@ public abstract class PermissionControllerService extends Service { 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 @@ public abstract class PermissionControllerService extends Service { 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 @@ public abstract class PermissionControllerService extends Service { 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 @@ public abstract class PermissionControllerService extends Service { 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 abstract class PermissionControllerService extends Service { 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 000000000000..4fa882265e53 --- /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 000000000000..d763598f5ba0 --- /dev/null +++ b/core/java/android/print/pdf/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "presubmit": [ + { + "name": "CtsPdfTestCases" + } + ] +} diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index fd81178d2cfb..eb09930005b5 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -363,15 +363,22 @@ public final class DocumentsContract { * <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 @@ public final class DocumentsContract { * @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 079a42ddaa6c..a1333df13820 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -39,7 +39,6 @@ import android.content.Context; 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.Point; 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 android.util.Log; 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 @@ public final class MediaStore { /** * 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 @@ public final class MediaStore { /** * 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 @@ public final class MediaStore { /** * 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 final class MediaStore { 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. - */ - 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 ""; - } + * @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}. + */ + @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 @@ public final class MediaStore { /** * 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 final class MediaStore { 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 @@ public final class MediaStore { /** * 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 final class MediaStore { 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 457dcc0ea42c..800c15c18143 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -8241,20 +8241,6 @@ public final class Settings { 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. * @@ -10877,16 +10863,13 @@ public final class Settings { * 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) @@ -10897,17 +10880,12 @@ public final class Settings { * 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"; diff --git a/core/java/android/service/carrier/CarrierService.java b/core/java/android/service/carrier/CarrierService.java index 9184d6d51f44..eefc1b70bac9 100644 --- a/core/java/android/service/carrier/CarrierService.java +++ b/core/java/android/service/carrier/CarrierService.java @@ -22,7 +22,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.PersistableBundle; import android.os.ResultReceiver; -import android.os.telephony.TelephonyRegistryManager; +import android.telephony.TelephonyRegistryManager; import android.util.Log; /** diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java index f1c870d96065..dd2586cd58ad 100644 --- a/core/java/android/service/quicksettings/TileService.java +++ b/core/java/android/service/quicksettings/TileService.java @@ -126,11 +126,29 @@ public class TileService extends Service { = "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/telephony/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java index 1ba0a41024ed..a65c8fdf50c9 100644 --- a/telephony/java/android/telephony/PhoneStateListener.java +++ b/core/java/android/telephony/PhoneStateListener.java @@ -35,8 +35,8 @@ import android.telephony.Annotation.SrvccState; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.ImsReasonInfo; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.IPhoneStateListener; +import com.android.internal.annotations.VisibleForTesting; import dalvik.system.VMRuntime; diff --git a/core/java/android/os/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java index 133233124b70..64d612405c34 100644 --- a/core/java/android/os/telephony/TelephonyRegistryManager.java +++ b/core/java/android/telephony/TelephonyRegistryManager.java @@ -13,12 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.os.telephony; +package android.telephony; +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; import android.annotation.SystemApi; +import android.content.Context; import android.net.LinkProperties; import android.net.NetworkCapabilities; +import android.os.Binder; import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerExecutor; import android.os.RemoteException; import android.os.ServiceManager; import android.telephony.Annotation.ApnType; @@ -40,8 +46,15 @@ import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.telephony.data.ApnSetting; import android.telephony.ims.ImsReasonInfo; +import android.util.Log; + import com.android.internal.telephony.ITelephonyRegistry; +import com.android.internal.telephony.IOnSubscriptionsChangedListener; + +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; /** * A centralized place to notify telephony related status changes, e.g, {@link ServiceState} update @@ -58,9 +71,26 @@ public class TelephonyRegistryManager { private static final String TAG = "TelephonyRegistryManager"; private static ITelephonyRegistry sRegistry; + private final Context mContext; + + /** + * A mapping between {@link SubscriptionManager.OnSubscriptionsChangedListener} and + * its callback IOnSubscriptionsChangedListener. + */ + private final Map<SubscriptionManager.OnSubscriptionsChangedListener, + IOnSubscriptionsChangedListener> mSubscriptionChangedListenerMap = new HashMap<>(); + /** + * A mapping between {@link SubscriptionManager.OnOpportunisticSubscriptionsChangedListener} and + * its callback IOnSubscriptionsChangedListener. + */ + private final Map<SubscriptionManager.OnOpportunisticSubscriptionsChangedListener, + IOnSubscriptionsChangedListener> mOpportunisticSubscriptionChangedListenerMap + = new HashMap<>(); + /** @hide **/ - public TelephonyRegistryManager() { + public TelephonyRegistryManager(@NonNull Context context) { + mContext = context; if (sRegistry == null) { sRegistry = ITelephonyRegistry.Stub.asInterface( ServiceManager.getService("telephony.registry")); @@ -68,6 +98,113 @@ public class TelephonyRegistryManager { } /** + * Register for changes to the list of active {@link SubscriptionInfo} records or to the + * individual records themselves. When a change occurs the onSubscriptionsChanged method of + * the listener will be invoked immediately if there has been a notification. The + * onSubscriptionChanged method will also be triggered once initially when calling this + * function. + * + * @param listener an instance of {@link SubscriptionManager.OnSubscriptionsChangedListener} + * with onSubscriptionsChanged overridden. + * @param executor the executor that will execute callbacks. + */ + public void addOnSubscriptionsChangedListener( + @NonNull SubscriptionManager.OnSubscriptionsChangedListener listener, + @NonNull Executor executor) { + IOnSubscriptionsChangedListener callback = new IOnSubscriptionsChangedListener.Stub() { + @Override + public void onSubscriptionsChanged () { + Log.d(TAG, "onSubscriptionsChangedListener callback received."); + executor.execute(() -> listener.onSubscriptionsChanged()); + } + }; + mSubscriptionChangedListenerMap.put(listener, callback); + try { + sRegistry.addOnSubscriptionsChangedListener(mContext.getOpPackageName(), callback); + } catch (RemoteException ex) { + // system server crash + } + } + + /** + * Unregister the {@link SubscriptionManager.OnSubscriptionsChangedListener}. This is not + * strictly necessary as the listener will automatically be unregistered if an attempt to + * invoke the listener fails. + * + * @param listener that is to be unregistered. + */ + public void removeOnSubscriptionsChangedListener( + @NonNull SubscriptionManager.OnSubscriptionsChangedListener listener) { + if (mSubscriptionChangedListenerMap.get(listener) == null) { + return; + } + try { + sRegistry.removeOnSubscriptionsChangedListener(mContext.getOpPackageName(), + mSubscriptionChangedListenerMap.get(listener)); + mSubscriptionChangedListenerMap.remove(listener); + } catch (RemoteException ex) { + // system server crash + } + } + + /** + * Register for changes to the list of opportunistic subscription records or to the + * individual records themselves. When a change occurs the onOpportunisticSubscriptionsChanged + * method of the listener will be invoked immediately if there has been a notification. + * + * @param listener an instance of + * {@link SubscriptionManager.OnOpportunisticSubscriptionsChangedListener} with + * onOpportunisticSubscriptionsChanged overridden. + * @param executor an Executor that will execute callbacks. + */ + public void addOnOpportunisticSubscriptionsChangedListener( + @NonNull SubscriptionManager.OnOpportunisticSubscriptionsChangedListener listener, + @NonNull Executor executor) { + /** + * The callback methods need to be called on the executor thread where + * this object was created. If the binder did that for us it'd be nice. + */ + IOnSubscriptionsChangedListener callback = new IOnSubscriptionsChangedListener.Stub() { + @Override + public void onSubscriptionsChanged() { + final long identity = Binder.clearCallingIdentity(); + try { + Log.d(TAG, "onOpportunisticSubscriptionsChanged callback received."); + executor.execute(() -> listener.onOpportunisticSubscriptionsChanged()); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + }; + mOpportunisticSubscriptionChangedListenerMap.put(listener, callback); + try { + sRegistry.addOnOpportunisticSubscriptionsChangedListener(mContext.getOpPackageName(), + callback); + } catch (RemoteException ex) { + // system server crash + } + } + + /** + * Unregister the {@link SubscriptionManager.OnOpportunisticSubscriptionsChangedListener} + * that is currently listening opportunistic subscriptions change. This is not strictly + * necessary as the listener will automatically be unregistered if an attempt to invoke the + * listener fails. + * + * @param listener that is to be unregistered. + */ + public void removeOnOpportunisticSubscriptionsChangedListener( + @NonNull SubscriptionManager.OnOpportunisticSubscriptionsChangedListener listener) { + try { + sRegistry.removeOnSubscriptionsChangedListener(mContext.getOpPackageName(), + mOpportunisticSubscriptionChangedListenerMap.get(listener)); + mOpportunisticSubscriptionChangedListenerMap.remove(listener); + } catch (RemoteException ex) { + // system server crash + } + } + + /** * Informs the system of an intentional upcoming carrier network change by a carrier app. * This call only used to allow the system to provide alternative UI while telephony is * performing an action that may result in intentional, temporary network lack of connectivity. @@ -546,4 +683,15 @@ public class TelephonyRegistryManager { } } + /** + * @param activeDataSubId + * @hide + */ + public void notifyActiveDataSubIdChanged(int activeDataSubId) { + try { + sRegistry.notifyActiveDataSubIdChanged(activeDataSubId); + } catch (RemoteException ex) { + + } + } } diff --git a/core/java/android/util/DebugUtils.java b/core/java/android/util/DebugUtils.java index 20e0d14ac14d..bc5edf89b4b0 100644 --- a/core/java/android/util/DebugUtils.java +++ b/core/java/android/util/DebugUtils.java @@ -255,7 +255,7 @@ public class DebugUtils { 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 1289e4db0743..45056730b08b 100644 --- a/core/java/android/util/TimestampedValue.java +++ b/core/java/android/util/TimestampedValue.java @@ -19,6 +19,7 @@ package android.util; 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 @@ import java.util.Objects; * 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 @@ public final class TimestampedValue<T> { } /** - * 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 955be8d40c47..762366eb6295 100644 --- a/core/java/android/view/IRecentsAnimationController.aidl +++ b/core/java/android/view/IRecentsAnimationController.aidl @@ -120,4 +120,10 @@ interface IRecentsAnimationController { * @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 4b872d3ad758..8bf99ec4f251 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -81,6 +81,14 @@ oneway interface IWindow { */ 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 1a1d7e682f6e..ad1f201ba3c1 100644 --- a/core/java/android/view/InputMonitor.java +++ b/core/java/android/view/InputMonitor.java @@ -40,8 +40,6 @@ public final class InputMonitor implements Parcelable { 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 @@ public final class InputMonitor implements Parcelable { - // 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 @@ public final class InputMonitor implements Parcelable { } @DataClass.Generated.Member - public @NonNull String getName() { - return mName; - } - - @DataClass.Generated.Member public @NonNull InputChannel getInputChannel() { return mInputChannel; } @@ -130,7 +119,6 @@ public final class InputMonitor implements Parcelable { // String fieldNameToString() { ... } return "InputMonitor { " + - "name = " + mName + ", " + "inputChannel = " + mInputChannel + ", " + "host = " + mHost + " }"; @@ -142,7 +130,6 @@ public final class InputMonitor implements Parcelable { // 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 @@ public final class InputMonitor implements Parcelable { @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 @@ public final class InputMonitor implements Parcelable { } @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 10a9aaaa5b0c..08e4eeb45400 100644 --- a/core/java/android/view/InputWindowHandle.java +++ b/core/java/android/view/InputWindowHandle.java @@ -38,10 +38,8 @@ public final class InputWindowHandle { // 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 @@ public final class InputWindowHandle { 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 341c2147c64a..e4deffadc966 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -229,6 +229,10 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll 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 5a8636d85a08..5bb4f63d62c8 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -251,6 +251,10 @@ public class InsetsController implements WindowInsetsController { @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 @@ public class InsetsController implements WindowInsetsController { } typesReady |= InsetsState.toPublicType(consumer.getType()); } - applyAnimation(typesReady, false /* show */, false /* fromIme */); + applyAnimation(typesReady, false /* show */, fromIme /* fromIme */); } @Override @@ -331,42 +335,35 @@ public class InsetsController implements WindowInsetsController { 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/SurfaceHolder.java b/core/java/android/view/SurfaceHolder.java index 2fd2e966dc38..c5d45c318f67 100644 --- a/core/java/android/view/SurfaceHolder.java +++ b/core/java/android/view/SurfaceHolder.java @@ -16,7 +16,10 @@ package android.view; +import android.annotation.IntRange; +import android.annotation.NonNull; import android.graphics.Canvas; +import android.graphics.PixelFormat; import android.graphics.Rect; /** @@ -76,7 +79,7 @@ public interface SurfaceHolder { * * @param holder The SurfaceHolder whose surface is being created. */ - public void surfaceCreated(SurfaceHolder holder); + void surfaceCreated(@NonNull SurfaceHolder holder); /** * This is called immediately after any structural changes (format or @@ -85,12 +88,12 @@ public interface SurfaceHolder { * once, after {@link #surfaceCreated}. * * @param holder The SurfaceHolder whose surface has changed. - * @param format The new PixelFormat of the surface. + * @param format The new {@link PixelFormat} of the surface. * @param width The new width of the surface. * @param height The new height of the surface. */ - public void surfaceChanged(SurfaceHolder holder, int format, int width, - int height); + void surfaceChanged(@NonNull SurfaceHolder holder, @PixelFormat.Format int format, + @IntRange(from = 0) int width, @IntRange(from = 0) int height); /** * This is called immediately before a surface is being destroyed. After @@ -101,7 +104,7 @@ public interface SurfaceHolder { * * @param holder The SurfaceHolder whose surface is being destroyed. */ - public void surfaceDestroyed(SurfaceHolder holder); + void surfaceDestroyed(@NonNull SurfaceHolder holder); } /** @@ -122,7 +125,7 @@ public interface SurfaceHolder { * * @param holder The SurfaceHolder whose surface has changed. */ - void surfaceRedrawNeeded(SurfaceHolder holder); + void surfaceRedrawNeeded(@NonNull SurfaceHolder holder); /** * An alternative to surfaceRedrawNeeded where it is not required to block @@ -140,7 +143,8 @@ public interface SurfaceHolder { * from any thread. * */ - default void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable drawingFinished) { + default void surfaceRedrawNeededAsync(@NonNull SurfaceHolder holder, + @NonNull Runnable drawingFinished) { surfaceRedrawNeeded(holder); drawingFinished.run(); } diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 8a1fd62c0201..ad59ae5d2bee 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -32,6 +32,7 @@ import android.graphics.RenderNode; 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.DataOutputStream; 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.annotation.Target; 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.TimeoutException; 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 class ViewDebug { 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 @@ public class ViewDebug { 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 @@ public class ViewDebug { 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 @@ public class ViewDebug { 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 @@ public class ViewDebug { 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); - } - if (sAnnotations == null) { - sAnnotations = new HashMap<AccessibleObject, ExportedProperty>(512); - } - - final HashMap<Class<?>, Method[]> map = sMethodsForClasses; - - Method[] methods = map.get(klass); - if (methods != null) { - return methods; + private static PropertyInfo<ExportedProperty, ?>[] getExportedProperties(Class<?> klass) { + if (sExportProperties == null) { + sExportProperties = new HashMap<>(); } + final HashMap<Class<?>, PropertyInfo<ExportedProperty, ?>[]> map = sExportProperties; + PropertyInfo<ExportedProperty, ?>[] properties = sExportProperties.get(klass); - 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)); - } + if (properties == null) { + properties = convertToPropertyInfos(klass.getDeclaredMethodsUnchecked(false), + klass.getDeclaredFieldsUnchecked(false), ExportedProperty.class); + map.put(klass, properties); } - - 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 @@ public class ViewDebug { 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 = "()"; + value = info.invoke(view); + } catch (Exception e) { + // ignore + continue; + } - exportUnrolledArray(context, out, property, array, valuePrefix, suffix); + String categoryPrefix = + info.property.category().length() != 0 ? info.property.category() + ":" : ""; - 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]); - } + if (info.returnType == int.class || info.returnType == byte.class) { + if (info.property.resolveId() && context != null) { + final int id = (Integer) value; + value = resolveId(context, id); - } + } 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); } - - continue; - } else if (!returnType.isPrimitive()) { - if (property.deepExport()) { - dumpViewProperties(context, methodValue, out, prefix + property.prefix()); - continue; + } 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); } - } - - writeEntry(out, categoryPrefix + prefix, method.getName(), "()", methodValue); - } catch (IllegalAccessException e) { - } catch (InvocationTargetException e) { - } catch (TimeoutException e) { - } - } - } - - private static void exportFields(Context context, Object view, BufferedWriter out, - Class<?> klass, String prefix) throws IOException { - - final Field[] fields = getExportedPropertyFields(klass); - 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; + 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 (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); - } + if (!mapped) { + value = intValue; } } - } 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); + } + } 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 (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]); - } + 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]); } } - - continue; - } else if (!type.isPrimitive()) { - if (property.deepExport()) { - dumpViewProperties(context, field.get(view), out, prefix + - property.prefix()); - continue; - } } - 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 @@ public class ViewDebug { } } - private static Field[] capturedViewGetPropertyFields(Class<?> klass) { - if (mCapturedViewFieldsForClasses == null) { - mCapturedViewFieldsForClasses = new HashMap<Class<?>, Field[]>(); - } - final HashMap<Class<?>, Field[]> map = mCapturedViewFieldsForClasses; - - Field[] fields = map.get(klass); - if (fields != null) { - return fields; - } - - 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; - } - - 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; + private static PropertyInfo<CapturedViewProperty, ?>[] getCapturedViewProperties( + Class<?> klass) { + if (sCapturedViewProperties == null) { + sCapturedViewProperties = new HashMap<>(); } + final HashMap<Class<?>, PropertyInfo<CapturedViewProperty, ?>[]> map = + sCapturedViewProperties; - 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); - } + PropertyInfo<CapturedViewProperty, ?>[] infos = map.get(klass); + if (infos == null) { + infos = convertToPropertyInfos(klass.getMethods(), klass.getFields(), + CapturedViewProperty.class); + map.put(klass, infos); } - - methods = foundMethods.toArray(new Method[foundMethods.size()]); - map.put(klass, methods); - - return methods; + return infos; } - 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 @@ public class ViewDebug { } else { sb.append("null"); } - sb.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(pi.valueSuffix).append(" "); } - 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 class ViewDebug { 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 9ddd84f1943c..20dc23492ae5 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -4570,6 +4570,7 @@ public final class ViewRootImpl implements ViewParent, 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 { @@ -4636,6 +4637,8 @@ public final class ViewRootImpl implements ViewParent, 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); } @@ -4754,6 +4757,10 @@ public final class ViewRootImpl implements ViewParent, 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(); @@ -7559,6 +7566,10 @@ public final class ViewRootImpl implements ViewParent, 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) { @@ -8682,6 +8693,14 @@ public final class ViewRootImpl implements ViewParent, } @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 4a6ef987a642..db76bb6d3cde 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -197,12 +197,6 @@ public interface WindowManager extends ViewManager { 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 @@ public interface WindowManager extends ViewManager { 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 @@ public interface WindowManager extends ViewManager { * 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. @@ -1849,6 +1843,7 @@ public interface WindowManager extends ViewManager { @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 {} @@ -1870,8 +1865,8 @@ public interface WindowManager extends ViewManager { 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/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java index 0817452718b5..cc2884075c39 100644 --- a/core/java/android/view/accessibility/AccessibilityManager.java +++ b/core/java/android/view/accessibility/AccessibilityManager.java @@ -707,10 +707,10 @@ public final class AccessibilityManager { try { services = service.getEnabledAccessibilityServiceList(feedbackTypeFlags, userId); if (DEBUG) { - Log.i(LOG_TAG, "Installed AccessibilityServices " + services); + Log.i(LOG_TAG, "Enabled AccessibilityServices " + services); } } catch (RemoteException re) { - Log.e(LOG_TAG, "Error while obtaining the installed AccessibilityServices. ", re); + Log.e(LOG_TAG, "Error while obtaining the enabled AccessibilityServices. ", re); } if (mAccessibilityPolicy != null) { services = mAccessibilityPolicy.getEnabledAccessibilityServiceList( diff --git a/core/java/android/webkit/FindAddress.java b/core/java/android/webkit/FindAddress.java index 9183227b3962..b146e3f614a2 100644 --- a/core/java/android/webkit/FindAddress.java +++ b/core/java/android/webkit/FindAddress.java @@ -154,7 +154,7 @@ class FindAddress { // 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 @@ class FindAddress { 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 925a5894db06..0b15cd06a7ea 100644 --- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java +++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java @@ -246,7 +246,7 @@ public class AccessibilityShortcutController { 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 407a85f1bb05..068056f091d7 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -388,21 +388,24 @@ public class ResolverActivity extends Activity { 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 @@ public class ResolverActivity extends Activity { 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 @@ public class ResolverActivity extends Activity { // 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 @@ public class ResolverActivity extends Activity { 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 @@ public class ResolverActivity extends Activity { 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 0ed252400aa1..1d4239f7c6ed 100644 --- a/core/java/com/android/internal/app/procstats/AssociationState.java +++ b/core/java/com/android/internal/app/procstats/AssociationState.java @@ -950,6 +950,14 @@ public final class AssociationState { 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); diff --git a/services/core/java/com/android/server/wm/WindowHashMap.java b/core/java/com/android/internal/compat/CompatibilityChangeConfig.aidl index 49bba4145996..434c1b819582 100644 --- a/services/core/java/com/android/server/wm/WindowHashMap.java +++ b/core/java/com/android/internal/compat/CompatibilityChangeConfig.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 The Android Open Source Project + * 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. @@ -11,18 +11,9 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License + * limitations under the License. */ -package com.android.server.wm; +package com.android.internal.compat; -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> { -} +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 000000000000..fd2ada08edc1 --- /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 4d8378a34599..4099cfa51b33 100644 --- a/core/java/com/android/internal/compat/IPlatformCompat.aidl +++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl @@ -18,6 +18,8 @@ package com.android.internal.compat; import android.content.pm.ApplicationInfo; +parcelable CompatibilityChangeConfig; + /** * Platform private API for talking with the PlatformCompat service. * @@ -125,4 +127,21 @@ interface IPlatformCompat * @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/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java index cdb79abbb7ce..f5708a5c89af 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 @@ public abstract class FileSystemProvider extends DocumentsProvider { 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 @@ public abstract class FileSystemProvider extends DocumentsProvider { 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/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 158700b2a449..363e5498f87c 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -184,6 +184,12 @@ public class ZygoteInit { 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 a845b587c49f..659134adec78 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 @@ interface IStatusBarService 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/telephony/java/com/android/internal/telephony/IOnSubscriptionsChangedListener.aidl b/core/java/com/android/internal/telephony/IOnSubscriptionsChangedListener.aidl index 493b1ff6aba7..493b1ff6aba7 100644 --- a/telephony/java/com/android/internal/telephony/IOnSubscriptionsChangedListener.aidl +++ b/core/java/com/android/internal/telephony/IOnSubscriptionsChangedListener.aidl diff --git a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl index 90019eef62fd..084a3cc64a35 100644 --- a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl +++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl @@ -29,6 +29,9 @@ import android.telephony.SignalStrength; import android.telephony.emergency.EmergencyNumber; import android.telephony.ims.ImsReasonInfo; +/** + * {@hide} + */ oneway interface IPhoneStateListener { void onServiceStateChanged(in ServiceState serviceState); void onSignalStrengthChanged(int asu); diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl index d7a7af1d530f..d7a7af1d530f 100644 --- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl +++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java index 0e078dd3732d..7e1f13afc2cb 100644 --- a/core/java/com/android/internal/view/BaseIWindow.java +++ b/core/java/com/android/internal/view/BaseIWindow.java @@ -80,6 +80,10 @@ public class BaseIWindow extends IWindow.Stub { } @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 09bc28c1f5ec..85a45fd8e0c0 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 @@ public final class LockPatternChecker { } /** - * 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 @@ public final class LockPatternChecker { @Override protected void onPostExecute(byte[] result) { callback.onVerified(result, mThrottleTimeout); + credentialCopy.zeroize(); + } + + @Override + protected void onCancelled() { + credentialCopy.zeroize(); } }; task.execute(); @@ -97,32 +93,26 @@ public final class LockPatternChecker { } /** - * 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 @@ public final class LockPatternChecker { @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 @@ public final class LockPatternChecker { } /** - * 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 @@ public final class LockPatternChecker { @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 070121cd1feb..8fea703e7e42 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_SOMETHING; 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 android.util.SparseLongArray; 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 @@ import java.util.StringJoiner; * 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 class LockPatternUtils { */ 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 @@ public class LockPatternUtils { 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 @@ public class LockPatternUtils { 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 @@ public class LockPatternUtils { } } - 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 @@ public class LockPatternUtils { } /** - * Check to see if a pattern matches the saved pattern. - * If pattern matches, return an opaque attestation 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. - */ - 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. + * 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 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 { + * @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[] 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 @@ public class LockPatternUtils { } /** - * - * 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 @@ public class LockPatternUtils { * 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 @@ public class LockPatternUtils { } /** - * 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 @@ public class LockPatternUtils { || isDemoUser; } + /** 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. - * @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) { - return saveLockPattern(pattern, savedPattern, userId, false); + public boolean setLockCredential(@NonNull LockscreenCredential newCredential, + @NonNull LockscreenCredential savedCredential, int userId) { + return setLockCredential(newCredential, savedCredential, userId, false); } /** - * 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. + * 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. * - * @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. + * @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 saveLockPattern(List<LockPatternView.Cell> pattern, byte[] savedPattern, - int userId, boolean allowUntrustedChange) { + 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 int currentQuality = getKeyguardStoredPasswordQuality(userHandle); + setKeyguardStoredPasswordQuality(newCredential.getQuality(), userHandle); - final byte[] bytePattern = patternToByteArray(pattern); - final int currentQuality = getKeyguardStoredPasswordQuality(userId); - setKeyguardStoredPasswordQuality(PASSWORD_QUALITY_SOMETHING, userId); 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 @@ public class LockPatternUtils { } /** - * 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 @@ public class LockPatternUtils { 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 @@ public class LockPatternUtils { } /** - * 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 @@ public class LockPatternUtils { } /** - * 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 @@ public class LockPatternUtils { /** * 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 @@ public class LockPatternUtils { /** * 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 @@ public class LockPatternUtils { 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 @@ public class LockPatternUtils { * @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 @@ public class LockPatternUtils { /** * 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 @@ public class LockPatternUtils { } /** - * 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 @@ public class LockPatternUtils { } /** - * 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 @@ public class LockPatternUtils { } } - private void onAfterChangingPassword(int userHandle) { + private void reportEnabledTrustAgentsChanged(int userHandle) { getTrustManager().reportEnabledTrustAgentsChanged(userHandle); } @@ -1823,54 +1538,36 @@ public class LockPatternUtils { * <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)) { - 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); + final int currentQuality = getKeyguardStoredPasswordQuality(userHandle); + setKeyguardStoredPasswordQuality(credential.getQuality(), userHandle); - if (userId == UserHandle.USER_SYSTEM) { - // Set the encryption password to default. - updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null); - setCredentialRequiredToDecrypt(false); + try { + if (!localService.setLockCredentialWithToken(credential.getCredential(), + credential.getType(), + tokenHandle, token, credential.getType(), userHandle)) { + setKeyguardStoredPasswordQuality(currentQuality, userHandle); + return 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 @@ public class LockPatternUtils { } /** - * @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 @@ public class LockPatternUtils { } /** - * @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 @@ public class LockPatternUtils { } /** - * 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 @@ public class LockPatternUtils { 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 3f6c4d4f5634..74a0aa37dafb 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -1318,7 +1318,7 @@ public class LockPatternView extends View { 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 000000000000..19e6d97eaa06 --- /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 000000000000..41b234ef024e --- /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 697825dcefd7..ea0389f49a45 100644 --- a/core/java/com/android/server/SystemConfig.java +++ b/core/java/com/android/server/SystemConfig.java @@ -168,6 +168,10 @@ public class SystemConfig { // 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 @@ public class SystemConfig { return mBackupTransportWhitelist; } + public ArrayMap<String, Boolean> getComponentsEnabledStates(String packageName) { + return mPackageComponentEnabledState.get(packageName); + } + public ArraySet<String> getDisabledUntilUsedPreinstalledCarrierApps() { return mDisabledUntilUsedPreinstalledCarrierApps; } @@ -846,6 +854,14 @@ public class SystemConfig { } 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 @@ public class SystemConfig { } } + 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/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 89c12f88594d..0487e139d264 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -9,6 +9,7 @@ #include "SkColorSpace.h" #include "GraphicsJNI.h" #include "SkStream.h" +#include "SkWebpEncoder.h" #include "android_os_Parcel.h" #include "android_util_Binder.h" @@ -526,27 +527,14 @@ static void Bitmap_reconfigure(JNIEnv* env, jobject clazz, jlong bitmapHandle, enum JavaEncodeFormat { kJPEG_JavaEncodeFormat = 0, kPNG_JavaEncodeFormat = 1, - kWEBP_JavaEncodeFormat = 2 + kWEBP_JavaEncodeFormat = 2, + kWEBP_LOSSY_JavaEncodeFormat = 3, + kWEBP_LOSSLESS_JavaEncodeFormat = 4, }; static jboolean Bitmap_compress(JNIEnv* env, jobject clazz, jlong bitmapHandle, jint format, jint quality, jobject jstream, jbyteArray jstorage) { - SkEncodedImageFormat fm; - switch (format) { - case kJPEG_JavaEncodeFormat: - fm = SkEncodedImageFormat::kJPEG; - break; - case kPNG_JavaEncodeFormat: - fm = SkEncodedImageFormat::kPNG; - break; - case kWEBP_JavaEncodeFormat: - fm = SkEncodedImageFormat::kWEBP; - break; - default: - return JNI_FALSE; - } - LocalScopedBitmap bitmap(bitmapHandle); if (!bitmap.valid()) { return JNI_FALSE; @@ -577,6 +565,30 @@ static jboolean Bitmap_compress(JNIEnv* env, jobject clazz, jlong bitmapHandle, } skbitmap = p3; } + SkEncodedImageFormat fm; + switch (format) { + case kJPEG_JavaEncodeFormat: + fm = SkEncodedImageFormat::kJPEG; + break; + case kPNG_JavaEncodeFormat: + fm = SkEncodedImageFormat::kPNG; + break; + case kWEBP_JavaEncodeFormat: + fm = SkEncodedImageFormat::kWEBP; + break; + case kWEBP_LOSSY_JavaEncodeFormat: + case kWEBP_LOSSLESS_JavaEncodeFormat: { + SkWebpEncoder::Options options; + options.fQuality = quality; + options.fCompression = format == kWEBP_LOSSY_JavaEncodeFormat ? + SkWebpEncoder::Compression::kLossy : SkWebpEncoder::Compression::kLossless; + return SkWebpEncoder::Encode(strm.get(), skbitmap.pixmap(), options) ? + JNI_TRUE : JNI_FALSE; + } + default: + return JNI_FALSE; + } + return SkEncodeImage(strm.get(), skbitmap, fm, quality) ? JNI_TRUE : JNI_FALSE; } diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp index ec91cbf854a6..4d907f6d4942 100644 --- a/core/jni/android/graphics/ImageDecoder.cpp +++ b/core/jni/android/graphics/ImageDecoder.cpp @@ -374,16 +374,17 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong if (scale || jsubset) { int translateX = 0; int translateY = 0; + SkImageInfo scaledInfo; if (jsubset) { SkIRect subset; GraphicsJNI::jrect_to_irect(env, jsubset, &subset); - translateX = -subset.fLeft; - translateY = -subset.fTop; - desiredWidth = subset.width(); - desiredHeight = subset.height(); + translateX = -subset.fLeft; + translateY = -subset.fTop; + scaledInfo = bitmapInfo.makeWH(subset.width(), subset.height()); + } else { + scaledInfo = bitmapInfo.makeWH(desiredWidth, desiredHeight); } - SkImageInfo scaledInfo = bitmapInfo.makeWH(desiredWidth, desiredHeight); SkBitmap scaledBm; if (!scaledBm.setInfo(scaledInfo)) { doThrowIOE(env, "Failed scaled setInfo"); diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp index bd4862dfb08d..637025329e37 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 @@ using ::android::base::unique_fd; 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 @@ static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboole } 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 NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboole } 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 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descri 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 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descri 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 @@ static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring fil // 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_os_Debug.cpp b/core/jni/android_os_Debug.cpp index 9445319e47ec..d62d2d967d85 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -139,8 +139,8 @@ static stat_field_names stat_field_names[_NUM_CORE_HEAP] = { "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_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index daf33f61105c..c7b36d0f8fc9 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -108,7 +108,7 @@ static struct arraymap_offsets_t { jmethodID put; } gArrayMapOffsets; -jclass g_stringClass = nullptr; +static jclass g_stringClass = nullptr; // ---------------------------------------------------------------------------- @@ -763,6 +763,41 @@ static jlong NativeOpenXmlAsset(JNIEnv* env, jobject /*clazz*/, jlong ptr, jint 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 @@ static const JNINativeMethod gAssetManagerMethods[] = { {"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 e406e2257c67..22323931e94c 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -992,6 +992,31 @@ static void android_os_Binder_blockUntilThreadAvailable(JNIEnv* env, jobject cla 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 @@ static const JNINativeMethod gBinderMethods[] = { { "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 0fada1bae8ed..49c5cade314f 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 @@ void android_os_Process_setThreadGroup(JNIEnv* env, jobject clazz, int tid, jint 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 @@ void android_os_Process_setThreadGroupAndCpuset(JNIEnv* env, jobject clazz, int 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 @@ void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jin 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 @@ void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jin } } - 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 af34e7b7a7ff..bf1cea8cff2e 100644 --- a/core/jni/android_view_InputChannel.cpp +++ b/core/jni/android_view_InputChannel.cpp @@ -112,7 +112,9 @@ void android_view_InputChannel_setDisposeCallback(JNIEnv* env, jobject inputChan } 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 @@ static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* 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/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto index b8c5270ef9d8..94be61f40eae 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -2412,4 +2412,18 @@ enum PageId { // 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 15b98af8e1f3..06040a599df1 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -43,7 +43,7 @@ message JobSchedulerServiceDumpProto { 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 @@ message StateControllerProto { 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 301fa13ce4d8..d010c8ff8ad9 100644 --- a/core/proto/android/service/package.proto +++ b/core/proto/android/service/package.proto @@ -114,6 +114,13 @@ message PackageProto { 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 @@ message PackageProto { 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 f49a04422c0e..ad7299d9a45c 100644 --- a/core/proto/android/service/procstats.proto +++ b/core/proto/android/service/procstats.proto @@ -241,12 +241,25 @@ message PackageAssociationSourceProcessStatsProto { 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 0821d147044d..15813a1b2a72 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 @@ enum EventId { 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 b030b33daf5e..f98ea2cbaa17 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 0bdb25a8d307..485709523e66 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 7d9ffd72870d..0a35edc42329 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 1dd420746e8a..6e45e7a4c509 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 740a7eb9374e..dbba0b7bcc25 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.xml b/core/res/res/values/attrs.xml index 14f5d97b6481..acaaeec7b2ee 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3701,6 +3701,8 @@ <flag name="flagRequestFingerprintGestures" value="0x00000200" /> <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK}. --> <flag name="flagRequestShortcutWarningDialogSpokenFeedback" value="0x00000400" /> + <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_HANDLE_SHORTCUT}. --> + <flag name="flagHandleShortcut" value="0x00000800" /> </attr> <!-- Component name of an activity that allows the user to modify the settings for this service. This setting cannot be changed at runtime. --> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index a301702e5f9b..ffcfe4310f06 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. --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 3fef7a2dffae..5ce0adcf903c 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -442,7 +442,7 @@ </string-array> <!-- Package name for the default CellBroadcastService module [DO NOT TRANSLATE] --> - <string name="cellbroadcast_default_package" translatable="false">com.android.cellbroadcastreceiver + <string name="cellbroadcast_default_package" translatable="false">com.android.cellbroadcastservice </string> <!-- If the mobile hotspot feature requires provisioning, a package name and class name @@ -758,6 +758,9 @@ <!-- Indicates that p2p MAC randomization is supported on this device --> <bool translatable="false" name="config_wifi_p2p_mac_randomization_supported">false</bool> + <!-- Indicates that AP mode MAC randomization is supported on this device --> + <bool translatable="false" name="config_wifi_ap_mac_randomization_supported">true</bool> + <!-- flag for activating paranoid MAC randomization on a limited set of SSIDs --> <bool translatable="false" name="config_wifi_aggressive_randomization_ssid_whitelist_enabled">false</bool> @@ -3687,6 +3690,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 +4322,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 609659b62948..a01bbe38f296 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/symbols.xml b/core/res/res/values/symbols.xml index 363bc9ddd75c..6371d805f37c 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1970,6 +1970,7 @@ <java-symbol type="bool" name="config_wifi_local_only_hotspot_5ghz" /> <java-symbol type="bool" name="config_wifi_connected_mac_randomization_supported" /> <java-symbol type="bool" name="config_wifi_p2p_mac_randomization_supported" /> + <java-symbol type="bool" name="config_wifi_ap_mac_randomization_supported" /> <java-symbol type="bool" name="config_wifi_aggressive_randomization_ssid_whitelist_enabled" /> <java-symbol type="bool" name="config_wifi_link_probing_supported" /> <java-symbol type="bool" name="config_wifi_fast_bss_transition_enabled" /> @@ -3467,6 +3468,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 +3822,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 000000000000..53db8322f7b8 --- /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 000000000000..00b4ccbd8030 --- /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 000000000000..702151d01110 --- /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 000000000000..5c0b2cc98d64 --- /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 000000000000..897897fbf254 --- /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 000000000000..b14bd8600f31 --- /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 000000000000..3c215ebc287c --- /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 000000000000..03f9a0fd146a --- /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 Binary files differnew file mode 100644 index 000000000000..a12e33a34aee --- /dev/null +++ b/core/tests/ResourceLoaderTests/lib/kotlin-reflect-sources.jar diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar b/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar Binary files differnew file mode 100644 index 000000000000..182cbabadfe6 --- /dev/null +++ b/core/tests/ResourceLoaderTests/lib/kotlin-reflect.jar diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar Binary files differnew file mode 100644 index 000000000000..e6b5f15b8a57 --- /dev/null +++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7-sources.jar diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar Binary files differnew file mode 100644 index 000000000000..e9c743c60289 --- /dev/null +++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk7.jar diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar Binary files differnew file mode 100644 index 000000000000..cd0536042662 --- /dev/null +++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8-sources.jar diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar Binary files differnew file mode 100644 index 000000000000..dc8aa90385fd --- /dev/null +++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-jdk8.jar diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar Binary files differnew file mode 100644 index 000000000000..8a672bac4685 --- /dev/null +++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib-sources.jar diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar Binary files differnew file mode 100644 index 000000000000..56f3d1e385e4 --- /dev/null +++ b/core/tests/ResourceLoaderTests/lib/kotlin-stdlib.jar diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar b/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar Binary files differnew file mode 100644 index 000000000000..663d3128dd54 --- /dev/null +++ b/core/tests/ResourceLoaderTests/lib/kotlin-test-sources.jar diff --git a/core/tests/ResourceLoaderTests/lib/kotlin-test.jar b/core/tests/ResourceLoaderTests/lib/kotlin-test.jar Binary files differnew file mode 100644 index 000000000000..5f6e4b8cc988 --- /dev/null +++ b/core/tests/ResourceLoaderTests/lib/kotlin-test.jar diff --git a/core/tests/ResourceLoaderTests/overlay/Android.bp b/core/tests/ResourceLoaderTests/overlay/Android.bp new file mode 100644 index 000000000000..63e7e61d797a --- /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 000000000000..942f7da9aa27 --- /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 000000000000..348bb353611a --- /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 Binary files differnew file mode 100644 index 000000000000..efd71ee039e2 --- /dev/null +++ b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png 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 000000000000..d1211c50a203 --- /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 000000000000..d59059b453d6 --- /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 000000000000..28b8f73d45a6 --- /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 000000000000..5dd8a966e2b7 --- /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 000000000000..5a92ae9e662b --- /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 000000000000..885f681f4261 --- /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 Binary files differnew file mode 100644 index 000000000000..f3e53d7596c1 --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/drawable-mdpi/ic_delete.png diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapBlue.png b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapBlue.png Binary files differnew file mode 100644 index 000000000000..5231d175569e --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapBlue.png diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapGreen.png b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapGreen.png Binary files differnew file mode 100644 index 000000000000..671d6d00be31 --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapGreen.png 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 000000000000..f1a93d2d2f21 --- /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 000000000000..7c455a57fb0b --- /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 000000000000..d59059b453d6 --- /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 000000000000..ede3838be8de --- /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 000000000000..d8bff90d56d8 --- /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 000000000000..a552431e23be --- /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 000000000000..69ecf2316284 --- /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 000000000000..4d55deffbd2a --- /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 000000000000..b5b4dfd22231 --- /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 000000000000..4962a07bc8c7 --- /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 000000000000..38b152beb76f --- /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 000000000000..bdd6f58e5824 --- /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 000000000000..4fc52723946e --- /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 000000000000..3604d7b21cf5 --- /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 000000000000..4582808934df --- /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 000000000000..aad8c27a1a3b --- /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 000000000000..a367063dd43e --- /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 000000000000..b1bdc967e68f --- /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 000000000000..a6a83789c082 --- /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 000000000000..e01e254b1f16 --- /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 000000000000..09fd27e02b59 --- /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 000000000000..1ec209486c18 --- /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 000000000000..5af453d526e4 --- /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 000000000000..017552a02152 --- /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 000000000000..df2d09adf503 --- /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 4ae9494aa362..fb0dd46a52f0 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_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 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 @@ public class PasswordMetricsTest { 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 @@ public class PasswordMetricsTest { 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 @@ public class PasswordMetricsTest { } @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 @@ public class PasswordMetricsTest { } @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 @@ public class PasswordMetricsTest { } @Test - public void testGetTargetQualityMetrics_noneComplexityReturnsDefaultMetrics() { - PasswordMetrics metrics = - getTargetQualityMetrics(PASSWORD_COMPLEXITY_NONE, PASSWORD_QUALITY_ALPHANUMERIC); - - assertTrue(metrics.isDefault()); - } - - @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); - } - - @Test - public void testGetTargetQualityMetrics_highComplexityNumericComplex() { - PasswordMetrics metrics = getTargetQualityMetrics( - PASSWORD_COMPLEXITY_HIGH, PASSWORD_QUALITY_NUMERIC_COMPLEX); - - assertEquals(PASSWORD_QUALITY_NUMERIC_COMPLEX, metrics.quality); - assertEquals(/* expected= */ 8, metrics.length); - } - - @Test - public void testGetTargetQualityMetrics_mediumComplexityAlphabetic() { - PasswordMetrics metrics = getTargetQualityMetrics( - PASSWORD_COMPLEXITY_MEDIUM, PASSWORD_QUALITY_ALPHABETIC); - - 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); - } + public void testMerge_single() { + PasswordMetrics metrics = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); + assertEquals(CREDENTIAL_TYPE_PASSWORD, + PasswordMetrics.merge(Collections.singletonList(metrics)).credType); + } + + @Test + 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 testValidatePasswordMetrics_credentialTypes() { + PasswordMetrics none = new PasswordMetrics(CREDENTIAL_TYPE_NONE); + PasswordMetrics pattern = new PasswordMetrics(CREDENTIAL_TYPE_PATTERN); + PasswordMetrics password = new PasswordMetrics(CREDENTIAL_TYPE_PASSWORD); + + // 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); + } + + /** + * @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); + } - @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 000000000000..e951054e6558 --- /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/util/TimestampedValueTest.java b/core/tests/coretests/src/android/util/TimestampedValueTest.java index 6e3ab796c5d3..6fc2400316c2 100644 --- a/core/tests/coretests/src/android/util/TimestampedValueTest.java +++ b/core/tests/coretests/src/android/util/TimestampedValueTest.java @@ -55,12 +55,12 @@ public class TimestampedValueTest { 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 @@ public class TimestampedValueTest { 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 @@ public class TimestampedValueTest { } @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 @@ public class TimestampedValueTest { 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 abee1da2ed7a..7b4054348642 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 @@ public class AccessibilityShortcutControllerTest { 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 000000000000..5eec91c8840b --- /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 d36a82684e9e..61281eea7134 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 987c3b439db8..7296cfdfbec7 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 f1ba3f6f7859..07a5617009d5 100644 --- a/data/etc/hiddenapi-package-whitelist.xml +++ b/data/etc/hiddenapi-package-whitelist.xml @@ -56,7 +56,7 @@ Do NOT include any apps that are updatable via Play Store! <!-- 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 51136b993e57..ac742e217ceb 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -50,6 +50,12 @@ applications that come with the platform <permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/> </privapp-permissions> + <privapp-permissions package="com.android.cellbroadcastservice"> + <permission name="android.permission.MODIFY_PHONE_STATE"/> + <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> + <permission name="android.permission.RECEIVE_EMERGENCY_BROADCAST"/> + </privapp-permissions> + <privapp-permissions package="com.android.externalstorage"> <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <permission name="android.permission.WRITE_MEDIA_STORAGE"/> @@ -121,6 +127,7 @@ applications that come with the platform <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 +361,12 @@ applications that come with the platform <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 454dceb9c82c..4226e0882538 100644 --- a/data/fonts/Android.mk +++ b/data/fonts/Android.mk @@ -83,8 +83,8 @@ font_src_files := ################################ # 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/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 44710178da5e..d900a42b1e66 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -1359,9 +1359,44 @@ public final class Bitmap implements Parcelable { * Specifies the known formats a bitmap can be compressed into */ public enum CompressFormat { - JPEG (0), - PNG (1), - WEBP (2); + /** + * Compress to the JPEG format. {@code quality} of {@code 0} means + * compress for the smallest size. {@code 100} means compress for max + * visual quality. + */ + JPEG (0), + /** + * Compress to the PNG format. PNG is lossless, so {@code quality} is + * ignored. + */ + PNG (1), + /** + * Compress to the WEBP format. {@code quality} of {@code 0} means + * compress for the smallest size. {@code 100} means compress for max + * visual quality. As of {@link android.os.Build.VERSION_CODES#Q}, a + * value of {@code 100} results in a file in the lossless WEBP format. + * Otherwise the file will be in the lossy WEBP format. + * + * @deprecated in favor of the more explicit + * {@link CompressFormat#WEBP_LOSSY} and + * {@link CompressFormat#WEBP_LOSSLESS}. + */ + @Deprecated + WEBP (2), + /** + * Compress to the WEBP lossy format. {@code quality} of {@code 0} means + * compress for the smallest size. {@code 100} means compress for max + * visual quality. + */ + WEBP_LOSSY (3), + /** + * Compress to the WEBP lossless format. {@code quality} refers to how + * much effort to put into compression. A value of {@code 0} means to + * compress quickly, resulting in a relatively large file size. + * {@code 100} means to spend more time compressing, resulting in a + * smaller file. + */ + WEBP_LOSSLESS (4); CompressFormat(int nativeInt) { this.nativeInt = nativeInt; @@ -1385,10 +1420,8 @@ public final class Bitmap implements Parcelable { * pixels). * * @param format The format of the compressed image - * @param quality Hint to the compressor, 0-100. 0 meaning compress for - * small size, 100 meaning compress for max quality. Some - * formats, like PNG which is lossless, will ignore the - * quality setting + * @param quality Hint to the compressor, 0-100. The value is interpreted + * differently depending on the {@link CompressFormat}. * @param stream The outputstream to write the compressed data. * @return true if successfully compressed to the specified stream. */ diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java index 9c4b5e8b0165..06d4fbdd85b1 100644 --- a/graphics/java/android/graphics/ColorSpace.java +++ b/graphics/java/android/graphics/ColorSpace.java @@ -1380,9 +1380,9 @@ public abstract class ColorSpace { */ @NonNull static ColorSpace get(@IntRange(from = MIN_ID, to = MAX_ID) int index) { - if (index < 0 || index >= Named.values().length) { + if (index < 0 || index >= sNamedColorSpaces.length) { throw new IllegalArgumentException("Invalid ID, must be in the range [0.." + - Named.values().length + ")"); + sNamedColorSpaces.length + ")"); } return sNamedColorSpaces[index]; } diff --git a/graphics/java/android/graphics/pdf/TEST_MAPPING b/graphics/java/android/graphics/pdf/TEST_MAPPING new file mode 100644 index 000000000000..d763598f5ba0 --- /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 bf2363481f73..254456cea536 100644 --- a/keystore/java/android/security/KeyChain.java +++ b/keystore/java/android/security/KeyChain.java @@ -350,7 +350,7 @@ public final class KeyChain { * 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 eeaefc5b157c..a34a6c0b3724 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -166,7 +166,10 @@ cc_test { 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 cf2ef3070385..b309621435b5 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -42,12 +42,16 @@ static const std::string kResourcesArsc("resources.arsc"); 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::LoadOverlay(const std::string& idmap 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<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) { 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 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl( 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 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl( 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 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl( 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 @@ std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMod 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::ForEachFile(const std::string& root_path, } 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 92125c9da8bb..c132f343713f 100644 --- a/libs/androidfw/Asset.cpp +++ b/libs/androidfw/Asset.cpp @@ -133,14 +133,24 @@ Asset::Asset(void) */ /*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 eec49df79630..e914f37bcac4 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -493,8 +493,12 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri 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 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri 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 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri 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 @@ ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_overri 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 @@ std::string AssetManager2::GetLastResourceResolution() const { 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 72873abc6a42..882dc0d71759 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -401,7 +401,9 @@ const LoadedPackage* LoadedArsc::GetPackageById(uint8_t package_id) const { 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 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, 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 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, } 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 @@ bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, 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 @@ bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap, } 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 @@ std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const StringPiece& data, 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/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h index 49fc82bff11e..625b68207d83 100644 --- a/libs/androidfw/include/androidfw/ApkAssets.h +++ b/libs/androidfw/include/androidfw/ApkAssets.h @@ -40,7 +40,8 @@ class ApkAssets { // 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 @@ class ApkAssets { // 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 @@ class ApkAssets { 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 9d12a35395c9..053dbb7864c6 100644 --- a/libs/androidfw/include/androidfw/Asset.h +++ b/libs/androidfw/include/androidfw/Asset.h @@ -121,6 +121,11 @@ public: */ 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 de46081a6aa3..c7348b180648 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -382,7 +382,13 @@ class AssetManager2 { 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 950f5413f550..1a56876b9686 100644 --- a/libs/androidfw/include/androidfw/LoadedArsc.h +++ b/libs/androidfw/include/androidfw/LoadedArsc.h @@ -137,7 +137,8 @@ class LoadedPackage { 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 @@ class LoadedPackage { 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 @@ class LoadedPackage { 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 @@ class LoadedArsc { 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 @@ class LoadedArsc { 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/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp index d58e8d20c8aa..fd57a92c216b 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 @@ TEST(LoadedArscTest, GetOverlayableMap) { 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 Binary files differnew file mode 100644 index 000000000000..2c881f2cdfe5 --- /dev/null +++ b/libs/androidfw/tests/data/loader/resources.arsc diff --git a/libs/androidfw/tests/data/system/R.h b/libs/androidfw/tests/data/system/R.h index becb38830fb3..374107484784 100644 --- a/libs/androidfw/tests/data/system/R.h +++ b/libs/androidfw/tests/data/system/R.h @@ -40,6 +40,12 @@ struct R { 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 ae90f117d448..61b72cf9d193 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -26,8 +26,10 @@ cc_defaults { // 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 7d0b6877a71a..030a20f31c42 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/location/java/android/location/AbstractListenerManager.java b/location/java/android/location/AbstractListenerManager.java index c41023e31065..944ebf937dc8 100644 --- a/location/java/android/location/AbstractListenerManager.java +++ b/location/java/android/location/AbstractListenerManager.java @@ -42,8 +42,8 @@ abstract class AbstractListenerManager<T> { @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 @@ abstract class AbstractListenerManager<T> { 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 d06ba12f5e2a..daa2e08a6780 100644 --- a/location/java/android/location/ILocationManager.aidl +++ b/location/java/android/location/ILocationManager.aidl @@ -42,11 +42,11 @@ import com.android.internal.location.ProviderProperties; 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 @@ interface ILocationManager 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 @@ interface ILocationManager 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 9c36d76cf370..27274d12b1de 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 @@ public class Location implements Parcelable { * * @hide */ + @TestApi + @SystemApi public static final String EXTRA_NO_GPS_LOCATION = "noGPSLocation"; /** @@ -1208,14 +1211,16 @@ public class Location implements Parcelable { } /** - * 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 90e29df45e51..987947b93060 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -51,6 +51,7 @@ import com.android.internal.annotations.GuardedBy; 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; @@ -542,6 +543,23 @@ public class LocationManager { } /** + * 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 @@ public class LocationManager { 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 @@ public class LocationManager { try { mService.requestLocationUpdates(locationRequest, null, pendingIntent, - mContext.getPackageName()); + mContext.getPackageName(), getListenerIdentifier(pendingIntent)); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1235,6 +1253,24 @@ public class LocationManager { } /** + * 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. * @@ -1471,7 +1507,8 @@ public class LocationManager { 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(); } @@ -1548,7 +1585,8 @@ public class LocationManager { 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(); } @@ -1762,6 +1800,7 @@ public class LocationManager { * @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) @@ -1781,10 +1820,12 @@ public class LocationManager { /** * 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) @@ -1853,6 +1894,8 @@ public class LocationManager { * @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) @@ -1874,6 +1917,9 @@ public class LocationManager { * @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) @@ -1925,7 +1971,7 @@ public class LocationManager { public void removeGpsMeasurementListener(GpsMeasurementsEvent.Listener listener) {} /** - * Registers a GPS Measurement callback. + * Registers a GPS Measurement callback which will run on a binder thread. * * @param callback a {@link GnssMeasurementsEvent.Callback} object to register. * @return {@code true} if the callback was added successfully, {@code false} otherwise. @@ -1937,7 +1983,7 @@ public class LocationManager { @RequiresPermission(ACCESS_FINE_LOCATION) public boolean registerGnssMeasurementsCallback( @NonNull GnssMeasurementsEvent.Callback callback) { - return registerGnssMeasurementsCallback(callback, null); + return registerGnssMeasurementsCallback(Runnable::run, callback); } /** @@ -1946,6 +1992,9 @@ public class LocationManager { * @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( @@ -1966,6 +2015,10 @@ public class LocationManager { * @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( @@ -1983,6 +2036,9 @@ public class LocationManager { * * @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 @@ -2057,6 +2113,9 @@ public class LocationManager { * @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( @@ -2078,6 +2137,10 @@ public class LocationManager { * @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( @@ -2538,7 +2601,7 @@ public class LocationManager { mListenerTransport = new GnssMeasurementsListener(); return mService.addGnssMeasurementsListener(mListenerTransport, - mContext.getPackageName()); + mContext.getPackageName(), "gnss measurement callback"); } @Override @@ -2574,7 +2637,7 @@ public class LocationManager { mListenerTransport = new GnssNavigationMessageListener(); return mService.addGnssNavigationMessageListener(mListenerTransport, - mContext.getPackageName()); + mContext.getPackageName(), "gnss navigation callback"); } @Override @@ -2609,7 +2672,8 @@ public class LocationManager { 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/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java index 5341d072df23..33ddfa7849e1 100644 --- a/media/java/android/media/ExifInterface.java +++ b/media/java/android/media/ExifInterface.java @@ -2776,7 +2776,7 @@ public class ExifInterface { 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 @@ public class ExifInterface { // 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 @@ public class ExifInterface { // 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 @@ public class ExifInterface { + "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 @@ public class ExifInterface { 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 @@ public class ExifInterface { } // 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 f132cefbfdc7..66764c73ee5c 100644 --- a/media/java/android/media/IMediaRoute2Provider.aidl +++ b/media/java/android/media/IMediaRoute2Provider.aidl @@ -27,4 +27,6 @@ oneway interface IMediaRoute2Provider { 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 81213b943c81..7b7a34e5151f 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -46,6 +46,8 @@ interface IMediaRouterService { 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 @@ interface IMediaRouterService { */ 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/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java index 26e79364bb02..91d644b5db74 100644 --- a/media/java/android/media/MediaCodecInfo.java +++ b/media/java/android/media/MediaCodecInfo.java @@ -21,6 +21,7 @@ import static android.media.Utils.sortDistinctRanges; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; import android.os.Build; @@ -3750,8 +3751,11 @@ public final class MediaCodecInfo { 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/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java index f421029909bd..cc5ddeb49813 100644 --- a/media/java/android/media/MediaMetadataRetriever.java +++ b/media/java/android/media/MediaMetadataRetriever.java @@ -224,7 +224,7 @@ public class MediaMetadataRetriever implements AutoCloseable { * @return The meta data value associate with the given keyCode on success; * null on failure. */ - public native String extractMetadata(int keyCode); + public native @Nullable String extractMetadata(int keyCode); /** * This method is similar to {@link #getFrameAtTime(long, int, BitmapParams)} @@ -255,7 +255,7 @@ public class MediaMetadataRetriever implements AutoCloseable { * * @see {@link #getFrameAtTime(long, int, BitmapParams)} */ - public Bitmap getFrameAtTime(long timeUs, @Option int option) { + public @Nullable Bitmap getFrameAtTime(long timeUs, @Option int option) { if (option < OPTION_PREVIOUS_SYNC || option > OPTION_CLOSEST) { throw new IllegalArgumentException("Unsupported option: " + option); @@ -301,7 +301,7 @@ public class MediaMetadataRetriever implements AutoCloseable { * * @see {@link #getFrameAtTime(long, int)} */ - public Bitmap getFrameAtTime( + public @Nullable Bitmap getFrameAtTime( long timeUs, @Option int option, @NonNull BitmapParams params) { if (option < OPTION_PREVIOUS_SYNC || option > OPTION_CLOSEST) { @@ -343,7 +343,7 @@ public class MediaMetadataRetriever implements AutoCloseable { * is less than or equal to 0. * @see {@link #getScaledFrameAtTime(long, int, int, int, BitmapParams)} */ - public Bitmap getScaledFrameAtTime( + public @Nullable Bitmap getScaledFrameAtTime( long timeUs, @Option int option, int dstWidth, int dstHeight) { validate(option, dstWidth, dstHeight); return _getFrameAtTime(timeUs, option, dstWidth, dstHeight, null); @@ -388,7 +388,7 @@ public class MediaMetadataRetriever implements AutoCloseable { * is less than or equal to 0. * @see {@link #getScaledFrameAtTime(long, int, int, int)} */ - public Bitmap getScaledFrameAtTime(long timeUs, @Option int option, + public @Nullable 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); @@ -430,7 +430,7 @@ public class MediaMetadataRetriever implements AutoCloseable { * * @see #getFrameAtTime(long, int) */ - public Bitmap getFrameAtTime(long timeUs) { + public @Nullable Bitmap getFrameAtTime(long timeUs) { return getFrameAtTime(timeUs, OPTION_CLOSEST_SYNC); } @@ -452,7 +452,7 @@ public class MediaMetadataRetriever implements AutoCloseable { * @see #getFrameAtTime(long) * @see #getFrameAtTime(long, int) */ - public Bitmap getFrameAtTime() { + public @Nullable Bitmap getFrameAtTime() { return _getFrameAtTime( -1, OPTION_CLOSEST_SYNC, -1 /*dst_width*/, -1 /*dst_height*/, null); } @@ -528,7 +528,7 @@ public class MediaMetadataRetriever implements AutoCloseable { * @see #getFramesAtIndex(int, int, BitmapParams) * @see #getFramesAtIndex(int, int) */ - public Bitmap getFrameAtIndex(int frameIndex, @NonNull BitmapParams params) { + public @Nullable Bitmap getFrameAtIndex(int frameIndex, @NonNull BitmapParams params) { List<Bitmap> bitmaps = getFramesAtIndex(frameIndex, 1, params); return bitmaps.get(0); } @@ -550,7 +550,7 @@ public class MediaMetadataRetriever implements AutoCloseable { * @see #getFramesAtIndex(int, int, BitmapParams) * @see #getFramesAtIndex(int, int) */ - public Bitmap getFrameAtIndex(int frameIndex) { + public @Nullable Bitmap getFrameAtIndex(int frameIndex) { List<Bitmap> bitmaps = getFramesAtIndex(frameIndex, 1); return bitmaps.get(0); } @@ -653,7 +653,7 @@ public class MediaMetadataRetriever implements AutoCloseable { * @see #getPrimaryImage(BitmapParams) * @see #getPrimaryImage() */ - public Bitmap getImageAtIndex(int imageIndex, @NonNull BitmapParams params) { + public @Nullable Bitmap getImageAtIndex(int imageIndex, @NonNull BitmapParams params) { return getImageAtIndexInternal(imageIndex, params); } @@ -691,7 +691,7 @@ public class MediaMetadataRetriever implements AutoCloseable { * @see #getPrimaryImage(BitmapParams) * @see #getPrimaryImage() */ - public Bitmap getImageAtIndex(int imageIndex) { + public @Nullable Bitmap getImageAtIndex(int imageIndex) { return getImageAtIndexInternal(imageIndex, null); } @@ -713,7 +713,7 @@ public class MediaMetadataRetriever implements AutoCloseable { * @see #getImageAtIndex(int) * @see #getPrimaryImage() */ - public Bitmap getPrimaryImage(@NonNull BitmapParams params) { + public @Nullable Bitmap getPrimaryImage(@NonNull BitmapParams params) { return getImageAtIndexInternal(-1, params); } @@ -729,7 +729,7 @@ public class MediaMetadataRetriever implements AutoCloseable { * @see #getImageAtIndex(int) * @see #getPrimaryImage(BitmapParams) */ - public Bitmap getPrimaryImage() { + public @Nullable Bitmap getPrimaryImage() { return getImageAtIndexInternal(-1, null); } @@ -755,7 +755,7 @@ public class MediaMetadataRetriever implements AutoCloseable { * * @return null if no such graphic is found. */ - public byte[] getEmbeddedPicture() { + public @Nullable byte[] getEmbeddedPicture() { return getEmbeddedPicture(EMBEDDED_PICTURE_TYPE_ANY); } @@ -1028,8 +1028,6 @@ public class MediaMetadataRetriever implements AutoCloseable { * @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; @@ -1040,8 +1038,6 @@ public class MediaMetadataRetriever implements AutoCloseable { * @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; @@ -1050,8 +1046,6 @@ public class MediaMetadataRetriever implements AutoCloseable { * * @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 abd774d2bb00..59bd96ffb1bd 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -79,6 +79,8 @@ public final class MediaRoute2Info implements Parcelable { @Nullable final Bundle mExtras; + private final String mUniqueId; + MediaRoute2Info(@NonNull Builder builder) { mId = builder.mId; mProviderId = builder.mProviderId; @@ -90,6 +92,7 @@ public final class MediaRoute2Info implements Parcelable { mVolumeMax = builder.mVolumeMax; mVolumeHandling = builder.mVolumeHandling; mExtras = builder.mExtras; + mUniqueId = createUniqueId(); } MediaRoute2Info(@NonNull Parcel in) { @@ -103,6 +106,15 @@ public final class MediaRoute2Info implements Parcelable { mVolumeMax = in.readInt(); mVolumeHandling = in.readInt(); mExtras = in.readBundle(); + mUniqueId = createUniqueId(); + } + + private String createUniqueId() { + String uniqueId = null; + if (mProviderId != null) { + uniqueId = mProviderId + ":" + mId; + } + return uniqueId; } /** @@ -147,13 +159,33 @@ public final class MediaRoute2Info implements Parcelable { return Objects.hash(mId, mName, mDescription, mSupportedCategories); } + /** + * Gets the id of the route. + * Use {@link #getUniqueId()} if you need a unique identifier. + * + * @see #getUniqueId() + */ @NonNull public String getId() { return mId; } /** - * Gets the provider id of the route. + * Gets the unique id of the route. A route obtained from + * {@link com.android.server.media.MediaRouterService} always has a unique id. + * + * @return unique id of the route or null if it has no unique id. + */ + @Nullable + public String getUniqueId() { + return mUniqueId; + } + + /** + * Gets the provider id of the route. It is assigned automatically by + * {@link com.android.server.media.MediaRouterService}. + * + * @return provider id of the route or null if it's not set. * @hide */ @Nullable @@ -337,7 +369,7 @@ public final class MediaRoute2Info implements Parcelable { @NonNull public Builder setProviderId(@NonNull String providerId) { if (TextUtils.isEmpty(providerId)) { - throw new IllegalArgumentException("id must not be null or empty"); + throw new IllegalArgumentException("providerId must not be null or empty"); } mProviderId = providerId; return this; diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index e8e0f826e6b6..5f5d200c6f5e 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -30,7 +30,7 @@ import android.util.Log; * @hide */ public abstract class MediaRoute2ProviderService extends Service { - private static final String TAG = "MediaRouteProviderSrv"; + private static final String TAG = "MR2ProviderService"; public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService"; @@ -81,6 +81,20 @@ public abstract class MediaRoute2ProviderService extends Service { 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 @@ public abstract class MediaRoute2ProviderService extends Service { 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 ed35ef6a7ac7..b52e2d647e5a 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -44,7 +44,7 @@ import java.util.concurrent.Executor; * @hide */ public class MediaRouter2 { - private static final String TAG = "MediaRouter"; + private static final String TAG = "MR2"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final Object sLock = new Object(); @@ -54,7 +54,8 @@ public class MediaRouter2 { private Context mContext; private final IMediaRouterService mMediaRouterService; - private CopyOnWriteArrayList<CallbackRecord> mCallbackRecords = new CopyOnWriteArrayList<>(); + private final CopyOnWriteArrayList<CallbackRecord> mCallbackRecords = + new CopyOnWriteArrayList<>(); @GuardedBy("sLock") private List<String> mControlCategories = Collections.emptyList(); @GuardedBy("sLock") @@ -265,6 +266,54 @@ public class MediaRouter2 { } } + /** + * 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 +359,7 @@ public class MediaRouter2 { List<MediaRoute2Info> outRoutes) { if (provider == null || !provider.isValid()) { Log.w(TAG, "Ignoring invalid provider : " + provider); + return; } final Collection<MediaRoute2Info> routes = provider.getRoutes(); @@ -321,10 +371,21 @@ public class MediaRouter2 { 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 +393,18 @@ public class MediaRouter2 { } } + 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 +438,7 @@ public class MediaRouter2 { 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 0b645691ea3b..0d7b6ff0ea91 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -46,7 +46,7 @@ import java.util.concurrent.Executor; * @hide */ public class MediaRouter2Manager { - private static final String TAG = "MediaRouter2Manager"; + private static final String TAG = "MR2Manager"; private static final Object sLock = new Object(); @GuardedBy("sLock") @@ -234,6 +234,54 @@ public class MediaRouter2Manager { } } + /** + * 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 771628cf7b1e..ca96c9ab5670 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 @@ import java.util.concurrent.atomic.AtomicBoolean; */ @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"); - } - - 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); + throw new UnsupportedOperationException(); } - @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 + @Deprecated + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "All scanning requests should be performed through {@link android.media.MediaScannerConnection}") 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 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; - } - - 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(); + throw new UnsupportedOperationException(); } - 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; + throw new UnsupportedOperationException(); } - - 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; - } - } - - @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(); - } - } - - 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(); - } - } + 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 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(); - } - - 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(); - } + throw new UnsupportedOperationException(); } - // 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(); - } - } - - 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(); - } - } + throw new UnsupportedOperationException(); } - @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); - } - - 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(); - } + throw new UnsupportedOperationException(); } - @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; - } - - 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(); + @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 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 435d8d766149..ff4044220428 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -846,7 +846,7 @@ public class RingtoneManager { * 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 fb581b532dd2..a315c1eefb52 100644 --- a/media/java/android/media/ThumbnailUtils.java +++ b/media/java/android/media/ThumbnailUtils.java @@ -139,6 +139,12 @@ public class ThumbnailUtils { /** * 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 @@ public class ThumbnailUtils { /** * 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 @@ public class ThumbnailUtils { /** * 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 45ee210c80c9..bd9ea13af046 100644 --- a/media/jni/Android.bp +++ b/media/jni/Android.bp @@ -17,7 +17,6 @@ cc_library_shared { "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_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index d24edc7552ae..94299bc8431a 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -1446,7 +1446,6 @@ extern int register_android_media_MediaHTTPConnection(JNIEnv *env); 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 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) 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 58044c0307eb..000000000000 --- 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 8d39a930a8f2..f4f8d0b73658 100644 --- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java +++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java @@ -26,7 +26,7 @@ import java.util.HashMap; import java.util.Map; public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService { - private static final String TAG = "SampleMediaRoute2Serv"; + private static final String TAG = "SampleMR2ProviderSvc"; public static final String ROUTE_ID1 = "route_id1"; public static final String ROUTE_NAME1 = "Sample Route 1"; @@ -130,6 +130,33 @@ public class SampleMediaRoute2ProviderService extends MediaRoute2ProviderService } } + @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 da832ac5241d..ca43d04573f3 100644 --- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java +++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java @@ -47,8 +47,10 @@ import java.util.ArrayList; 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 @@ -190,17 +192,8 @@ public class MediaRouterManagerTest { 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)); @@ -214,12 +207,10 @@ public class MediaRouterManagerTest { @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)); @@ -228,51 +219,40 @@ public class MediaRouterManagerTest { @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); - - verify(managerCallback, timeout(TIMEOUT_MS)) - .onRoutesChanged(argThat(routes -> routes.size() > 0)); + mRouter2.registerCallback(mExecutor, routerCallback); - 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))); @@ -291,14 +271,67 @@ public class MediaRouterManagerTest { } @Test - public void testVolumeHandling() { + public void testControlVolumeWithRouter() throws Exception { MediaRouter2.Callback mockCallback = mock(MediaRouter2.Callback.class); - mRouter2.setControlCategories(CONTROL_CATEGORIES_ALL); + 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()) - .onRoutesChanged(argThat(routes -> routes.size() > 0)); - Map<String, MediaRoute2Info> routes = createRouteMap(mRouter2.getRoutes()); + .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); @@ -310,6 +343,48 @@ public class MediaRouterManagerTest { 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/packages/BackupEncryption/Android.bp b/packages/BackupEncryption/Android.bp index 342d796de402..68e937c24a89 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 000000000000..2035b6605559 --- /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 000000000000..1d841b4a2c8f --- /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/transport/IntermediateEncryptingTransport.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java index 1d0224d49be7..c3cb335db89e 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 @@ package com.android.server.backup.encryption.transport; 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 @@ public class IntermediateEncryptingTransport extends DelegatingTransport { 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 @@ public class IntermediateEncryptingTransport extends DelegatingTransport { 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 6e6d571aa3c7..7c4082c2a54d 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 android.util.Log; 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 class IntermediateEncryptingTransportManager { public IntermediateEncryptingTransportManager(Context context) { this(new TransportClientManager(UserHandle.myUserId(), context, new TransportStats())); + mContext = context; } /** @@ -55,31 +56,42 @@ public class IntermediateEncryptingTransportManager { * 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/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 cc4b0ab1bb36..a85b2e4498a6 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 @@ package com.android.server.backup.encryption.transport; 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 { + 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; - private IntermediateEncryptingTransport mIntermediateEncryptingTransport; + @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 @@ public class IntermediateEncryptingTransportTest { 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 72ec8d86368d..94f5b9616d9a 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 897976f439b5..18ae582c86a1 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 72ec8d86368d..d36d1d68b276 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 000000000000..8247211dcb32 --- /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 08d48bfb1230..37cd1d49cb85 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 fb422aff51ef..e2da91b6d746 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 c7654e81e0b1..be2d5425cac1 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.keyguard.ViewMediatorCallback; 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 @@ public class CarSystemUIFactory extends SystemUIFactory { 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 5ec1baee5b14..b1067f8c3df5 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.notification.collection.NotificationData; 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 @@ abstract class CarSystemUIModule { @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/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java index 681d8f543575..1eeaa7c0c939 100644 --- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java +++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java @@ -87,6 +87,7 @@ import com.android.systemui.plugins.qs.QS; import com.android.systemui.qs.car.CarQSFragment; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.TaskStackChangeListener; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.FlingAnimationUtils; import com.android.systemui.statusbar.NavigationBarController; import com.android.systemui.statusbar.NotificationListener; @@ -102,7 +103,7 @@ import com.android.systemui.statusbar.car.hvac.HvacController; import com.android.systemui.statusbar.car.hvac.TemperatureView; import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.NotifPipelineInitializer; +import com.android.systemui.statusbar.notification.NewNotifPipeline; import com.android.systemui.statusbar.notification.NotificationAlertingManager; import com.android.systemui.statusbar.notification.NotificationEntryManager; import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider; @@ -113,6 +114,7 @@ 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; @@ -139,6 +141,8 @@ import java.util.Map; import javax.inject.Inject; import javax.inject.Named; +import dagger.Lazy; + /** * A status bar (and navigation bar) tailored for the automotive use case. */ @@ -162,9 +166,11 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt 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; @@ -176,7 +182,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt 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; @@ -197,6 +203,9 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt 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; @@ -245,6 +254,8 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt @Inject public CarStatusBar( + Context context, + FeatureFlags featureFlags, LightBarController lightBarController, AutoHideController autoHideController, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -259,7 +270,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress, - NotifPipelineInitializer notifPipelineInitializer, + Lazy<NewNotifPipeline> newNotifPipeline, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, @@ -298,8 +309,11 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt ConfigurationController configurationController, StatusBarWindowController statusBarWindowController, StatusBarWindowViewController.Builder statusBarWindowViewControllerBuild, - NotifLog notifLog) { + NotifLog notifLog, + DozeParameters dozeParameters) { super( + context, + featureFlags, lightBarController, autoHideController, keyguardUpdateMonitor, @@ -314,7 +328,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt dynamicPrivacyController, bypassHeadsUpNotifier, allowNotificationLongPress, - notifPipelineInitializer, + newNotifPipeline, falsingManager, broadcastDispatcher, remoteInputQuickSettingsDisabler, @@ -353,7 +367,8 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt configurationController, statusBarWindowController, statusBarWindowViewControllerBuild, - notifLog); + notifLog, + dozeParameters); mNavigationBarController = navigationBarController; } @@ -362,7 +377,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt // 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( @@ -374,6 +389,10 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt 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(); @@ -392,25 +411,18 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt 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); @@ -422,6 +434,14 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt 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. @@ -430,8 +450,8 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt // 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; @@ -525,7 +545,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt @Override protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) { super.makeStatusBarView(result); - mHvacController = new HvacController(mContext); CarSystemUIFactory factory = SystemUIFactory.getInstance(); mCarFacetButtonController = factory.getCarDependencyComponent() @@ -550,7 +569,8 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt * 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 @@ -583,6 +603,18 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt 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); @@ -593,21 +625,6 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt 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 @@ -914,23 +931,30 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt } 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); @@ -963,15 +987,25 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt } 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( @@ -1009,47 +1043,32 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt } } - /** - * 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); - } - /** - * 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; - } + mNavigationBarWindow.setVisibility(isKeyboardVisible ? View.GONE : View.VISIBLE); + mBottomNavBarVisible = !isKeyboardVisible; + } - if (!mBottomNavBarVisible) { - return; - } - mBottomNavBarVisible = false; - mWindowManager.removeView(mNavigationBarWindow); + 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"); + } + mTopNavigationBarView.setStatusBar(this); + addTemperatureViewToController(mTopNavigationBarView); + mTopNavigationBarView.setStatusBarWindowTouchListener(mTopNavBarNotificationTouchListener); } private void buildBottomBar(int layout) { @@ -1060,7 +1079,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt 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); @@ -1071,7 +1090,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt 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); @@ -1084,7 +1103,7 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt 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 886162f249a1..f7e5d012c76f 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.Bitmap; 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 com.android.systemui.statusbar.phone.SystemUIDialog; 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 @@ public class UserGridRecyclerView extends RecyclerView { 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 @@ public class UserGridRecyclerView extends RecyclerView { 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 @@ public class UserGridRecyclerView extends RecyclerView { * @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 @@ public class UserGridRecyclerView extends RecyclerView { private void onUsersUpdate() { mAdapter.clearUsers(); - mAdapter.updateUsers(createUserRecords(mCarUserManagerHelper.getAllUsers())); + mAdapter.updateUsers(createUserRecords(getAllUsers())); mAdapter.notifyDataSetChanged(); } 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 ead1de2bd352..88d641e8bb36 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 @@ import java.util.stream.Collectors; 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 @@ public class OngoingPrivacyChip extends LinearLayout implements View.OnClickList 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 @@ public class OngoingPrivacyChip extends LinearLayout implements View.OnClickList @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 @@ public class OngoingPrivacyChip extends LinearLayout implements View.OnClickList } 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 5ec7a77cb305..820bea99823e 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.content.pm.PackageManager; 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 class PrivacyApplication { 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 fca137392d74..d3e123ed8c25 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 class PrivacyItem { 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 71cc19b63ac1..4d6af95b3f9c 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 @@ package com.android.systemui.volume; 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 81c5bcd47981..642dc82c4d28 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.Uri; 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 @@ public class CaptivePortalLoginActivity extends Activity { } 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 02c61d778086..46b1d5fe27be 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.Context; 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.telephony.TelephonyIntents; import com.android.internal.util.ArrayUtils; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; /** @@ -44,7 +42,7 @@ public class CustomConfigLoader { 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 @@ public class CustomConfigLoader { // 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 @@ public class CustomConfigLoader { .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 @@ public class CustomConfigLoader { 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 @@ public class CustomConfigLoader { 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 000000000000..dd30ad177d69 --- /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 000000000000..dc3617fa6d4e --- /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 000000000000..e4ce3c524e35 --- /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 000000000000..3dd453e83ab8 --- /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 000000000000..952f90d8b11f --- /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 1b27b52f1fa1..48d34ae7ba47 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 @@ public class ExternalStorageProvider extends FileSystemProvider { 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 4408ef5a2c75..50f858eb04c1 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 @@ public class LocalTransport extends BackupTransport { // 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 @@ public class LocalTransport extends BackupTransport { makeDataDirs(); } - LocalTransportParameters getParameters() { + public LocalTransportParameters getParameters() { return mParameters; } @@ -142,11 +143,18 @@ public class LocalTransport extends BackupTransport { 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 @@ public class LocalTransport extends BackupTransport { 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 @@ public class LocalTransport extends BackupTransport { } 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 @@ public class LocalTransport extends BackupTransport { 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 784be224f367..8b4db92910f5 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.os.Handler; 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 @@ class LocalTransportParameters extends KeyValueSettingObserver { 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 000000000000..5ae4957b494d --- /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 000000000000..4fa882265e53 --- /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/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java index a18600abf788..2b841967d4a8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/accessibility/AccessibilityUtils.java @@ -48,11 +48,21 @@ public class AccessibilityUtils { return getEnabledServicesFromSettings(context, UserHandle.myUserId()); } + /** + * Check if the accessibility service is crashed + * + * @param packageName The package name to check + * @param serviceName The service name to check + * @param installedServiceInfos The list of installed accessibility service + * @return {@code true} if the accessibility service is crashed for the user. + * {@code false} otherwise. + */ public static boolean hasServiceCrashed(String packageName, String serviceName, - List<AccessibilityServiceInfo> enabledServiceInfos) { - for (int i = 0; i < enabledServiceInfos.size(); i++) { - AccessibilityServiceInfo accessibilityServiceInfo = enabledServiceInfos.get(i); - final ServiceInfo serviceInfo = enabledServiceInfos.get(i).getResolveInfo().serviceInfo; + List<AccessibilityServiceInfo> installedServiceInfos) { + for (int i = 0; i < installedServiceInfos.size(); i++) { + final AccessibilityServiceInfo accessibilityServiceInfo = installedServiceInfos.get(i); + final ServiceInfo serviceInfo = + installedServiceInfos.get(i).getResolveInfo().serviceInfo; if (TextUtils.equals(serviceInfo.packageName, packageName) && TextUtils.equals(serviceInfo.name, serviceName)) { return accessibilityServiceInfo.crashed; diff --git a/packages/SettingsProvider/src/android/provider/settings/OWNERS b/packages/SettingsProvider/src/android/provider/settings/OWNERS new file mode 100644 index 000000000000..541dd8787545 --- /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 000000000000..e425790110aa --- /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/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index 976f3365ab98..ef67bbdcec4a 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 @@ import java.util.Map; 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 5e2b7c86ee93..17c621e6fbef 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.telephony.RILConstants; 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 @@ class DatabaseHelper extends SQLiteOpenHelper { 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/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index f545fa65fb90..7e60452411cb 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.Build; 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 @@ public class SettingsBackupAgent extends BackupAgentHelper { 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 @@ public class SettingsBackupAgent extends BackupAgentHelper { 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/test/src/android/provider/OWNERS b/packages/SettingsProvider/test/src/android/provider/OWNERS new file mode 100644 index 000000000000..f3241ea9d1f9 --- /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 8437eae20637..62827bc3a98e 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; @@ -750,7 +752,7 @@ public class SettingsBackupTest { 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 a0972498ab2a..665bde341515 100644 --- a/packages/Shell/src/com/android/shell/BugreportProgressService.java +++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java @@ -476,10 +476,10 @@ public class BugreportProgressService extends Service { } 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 83acfa06976f..656827a80117 100644 --- a/packages/SystemUI/OWNERS +++ b/packages/SystemUI/OWNERS @@ -46,3 +46,6 @@ winsonc@google.com #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 cac673fdbf0b..c1d4b03b6620 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 @@ public interface HomeControlsPlugin extends Plugin { * 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 db026cad6ef5..6518924ca0c2 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 @@ public interface QSTile { 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 22b0ab7dde4e..3e74970ee725 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 000000000000..87684a32e016 --- /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 000000000000..bb971c206e5c --- /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 0e59a41a8e2c..4869be11a906 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 105b27eb7e1b..efcc2c44ba94 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 8335c116c95f..3cc683ae614b 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 2ef042210e67..748f356c25b9 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 @@ public class RecentsAnimationControllerCompat { 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 ad182fe57f81..22d1675cf17e 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 class WindowManagerWrapper { 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 ef9538dbef38..46b4c6b7d44c 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextController.java @@ -170,7 +170,7 @@ public class CarrierTextController { 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 d45603fd23ff..ebac3d9da5e1 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java @@ -33,10 +33,9 @@ import android.widget.LinearLayout; 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 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout 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 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout 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 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout } onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */, true /* isValidPassword */); - Arrays.fill(entry, (byte) 0); + password.zeroize(); } @Override @@ -181,7 +181,7 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout onPasswordChecked(userId, false /* matched */, timeoutMs, true /* isValidPassword */); } - Arrays.fill(entry, (byte) 0); + password.zeroize(); } @Override @@ -192,7 +192,7 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout LatencyTracker.getInstance(mContext).onActionEnd( ACTION_CHECK_CREDENTIAL_UNLOCKED); } - Arrays.fill(entry, (byte) 0); + password.zeroize(); } }); } @@ -223,7 +223,7 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout } 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 e3ac0f684e44..12c9fc9ce536 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -243,8 +243,8 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView } @Override - protected byte[] getPasswordText() { - return charSequenceToByteArray(mPasswordEntry.getText()); + protected CharSequence getPasswordText() { + return mPasswordEntry.getText(); } @Override @@ -379,18 +379,4 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView 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 297052f842f3..9eb168a7b567 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.util.LatencyTracker; 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 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit 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 274f739d8c29..8e9df5563123 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -167,8 +167,8 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView } @Override - protected byte[] getPasswordText() { - return charSequenceToByteArray(mPasswordEntry.getText()); + protected CharSequence getPasswordText() { + return mPasswordEntry.getText(); } @Override @@ -266,18 +266,4 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView 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 af4e61b3f6bc..5d35169cf926 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.app.PendingIntent; 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 @@ public class KeyguardSliceView extends LinearLayout implements View.OnClickListe 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 9bcccbc903dc..ac5e255289cf 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.PhoneConstants; 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 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { } private void handleScreenTurnedOff() { + final String tag = "KeyguardUpdateMonitor#handleScreenTurnedOff"; + DejankUtils.startDetectingBlockingIpcs(tag); checkIsHandlerThread(); mHardwareFingerprintUnavailableRetryCount = 0; mHardwareFaceUnavailableRetryCount = 0; @@ -1446,6 +1449,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener { 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 07bfa7194413..ff8a932a6a92 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -17,6 +17,7 @@ package com.android.systemui; 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.ChannelEditorDialogContro 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 @@ public class Dependency { @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 @@ public class Dependency { 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 0d24321d8db7..9192eed369f0 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_HANDLER_NAME; 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.ActivityManagerWrapper; 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.DeviceProvisionedControllerImpl; 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 @@ import dagger.Provides; */ @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 @@ public class DependencyProvider { @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 class DependencyProvider { 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 1c0e0b37a3f2..29a7167394ae 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -38,6 +38,8 @@ import com.android.systemui.statusbar.phone.DozeParameters; 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 @@ public class ImageWallpaper extends WallpaperService { 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 @@ public class ImageWallpaper extends WallpaperService { @Override public Engine onCreateEngine() { - return new GLEngine(this); + return new GLEngine(this, mDozeParameters); } @Override @@ -89,9 +98,9 @@ public class ImageWallpaper extends WallpaperService { // 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 @@ public class ImageWallpaper extends WallpaperService { 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 50f1b44b05b1..ddbabee283c2 100644 --- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java +++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java @@ -41,6 +41,10 @@ public class LatencyTester extends SystemUI { 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 3e068b0a0964..ad209861d273 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -132,12 +132,15 @@ public class ScreenDecorations extends SystemUI implements Tunable { 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 @@ public class ScreenDecorations extends SystemUI implements Tunable { | 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 e761a2be0b0f..c11236ebae53 100644 --- a/packages/SystemUI/src/com/android/systemui/ServiceBinder.java +++ b/packages/SystemUI/src/com/android/systemui/ServiceBinder.java @@ -19,6 +19,7 @@ package com.android.systemui; 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 @@ import dagger.multibindings.IntoMap; */ @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 c54f6306ddb1..10009f5f5582 100644 --- a/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java +++ b/packages/SystemUI/src/com/android/systemui/SizeCompatModeActivityController.java @@ -59,12 +59,13 @@ public class SizeCompatModeActivityController extends SystemUI implements Comman /** 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 @@ public class SizeCompatModeActivityController extends SystemUI implements Comman 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 b3fc69e8a49d..92fbd259b471 100644 --- a/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java +++ b/packages/SystemUI/src/com/android/systemui/SliceBroadcastRelayHandler.java @@ -39,6 +39,10 @@ public class SliceBroadcastRelayHandler extends SystemUI { 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 8bcf0571b2d0..0f7f1bebae57 100644 --- a/packages/SystemUI/src/com/android/systemui/SysUIToast.java +++ b/packages/SystemUI/src/com/android/systemui/SysUIToast.java @@ -31,7 +31,7 @@ public class SysUIToast { 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 30fbef6cbefb..75700379caca 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.io.PrintWriter; 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 56b5d080acc0..91776a335f0d 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.StatusBar; 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 @@ public class SystemUIApplication extends Application implements SysUiServiceProv 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 @@ public class SystemUIApplication extends Application implements SysUiServiceProv 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 785038f2b7f7..a5a55983fe51 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 @@ import dagger.multibindings.IntoMap; */ @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 @@ public abstract class SystemUIBinder { @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 530dcdc98fbd..ef7526b35cc8 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.ScrimState; 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 @@ public class SystemUIFactory { 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 @@ public class SystemUIFactory { 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 0be6b12fa365..13d847bf93c4 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 739eade35ca6..6f5a17dca432 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.Handler; 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 @@ public abstract class AssistModule { } @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 9958124a14e4..4cb1708468ea 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 @@ public class DefaultUiController implements AssistManager.UiController { } 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 a9359d4ff0db..f1abdb31b5f8 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 @@ public class AuthContainerView extends LinearLayout 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 4c2afb0a14ca..cdc2623d34fd 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 class AuthController extends SystemUI implements CommandQueue.Callbacks, } } - 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 8df072e9b99e..bebaa4b688db 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.EditText; 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 @@ public class AuthCredentialPasswordView extends AuthCredentialView } 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 6c36f8263237..14414a4225de 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 android.util.AttributeSet; 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 @@ public class AuthCredentialPatternView extends AuthCredentialView { 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 cc548d0c7f8e..9568a18e5ca3 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 @@ package com.android.systemui.bubbles; 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; @@ -965,16 +964,6 @@ public class BubbleController implements ConfigurationController.ConfigurationLi + 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 521ebde7d2f0..6f953d584589 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 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList 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 85a4d234b5df..b726c3e2783f 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 @@ import java.util.Locale; 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 @@ class PointerCountClassifier extends FalsingClassifier { @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 ca4ec6d78a07..3f0505f33d7c 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 @@ package com.android.systemui.doze; 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.hardware.display.AmbientDisplayConfiguration; 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.sensors.ProximitySensor; 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 @@ public class DozeFactory { 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 07dd2cd77043..1a6bd60816b3 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 @@ public interface DozeHost { 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 38ee2fed136d..95c42fcd175c 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 @@ public class DozeScreenState implements DozeMachine.Part { 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 @@ public class DozeScreenState implements DozeMachine.Part { 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 @@ public class DozeScreenState implements DozeMachine.Part { 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 @@ public class DozeScreenState implements DozeMachine.Part { } 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 17559c9b79ee..08734d27ba55 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.os.SystemClock; 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 @@ public class DozeService extends DreamService 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 @@ public class DozeService extends DreamService 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 2c0ccd214188..f1557838fd73 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 @@ public class DozeUi implements DozeMachine.Part { 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 35c8b741381c..9457dc9e580b 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 @@ public class DozeWallpaperState implements DozeMachine.Part { 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 e8ef454bd466..c11127d1dc0b 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 @@ public class GlobalActionsComponent extends SystemUI implements Callbacks, Globa 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 7d52a9a08c2a..42f455af7df4 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 @@ public class KeyboardUI extends SystemUI implements InputManager.OnTabletModeCha 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 b3481c52d7f2..4a33590f64d2 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 @@ package com.android.systemui.keyguard; 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 @@ public class KeyguardLifecyclesDispatcher { 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 81247cd2f727..9f4056fdf65b 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.IKeyguardDrawnCallback; 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 a8027c025cf2..e0270decff08 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 @@ public class KeyguardViewMediator extends SystemUI { Context context, FalsingManager falsingManager, LockPatternUtils lockPatternUtils) { - super(); + super(context); - mContext = context; mFalsingManager = falsingManager; mLockPatternUtils = lockPatternUtils; @@ -795,7 +794,6 @@ public class KeyguardViewMediator extends SystemUI { 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 a9fe54bae19d..4d061e18ebba 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 @@ public class WorkLockActivity extends Activity { 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 @@ public class WorkLockActivity extends Activity { 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 aebadf936e0c..4c96de232810 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 @@ public class RingtonePlayer extends SystemUI { 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 682c76c6136a..f1e801b3a474 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 @@ package com.android.systemui.pip; 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 com.android.systemui.statusbar.CommandQueue; 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 @@ public class PipUI extends SystemUI implements CommandQueue.Callbacks { 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 8224365e7b96..3f15966c7fbb 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 @@ public class PipDismissViewController { | 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 a258f356bf53..98f0b2a35f9f 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 @@ public class PowerUI extends SystemUI { 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 ae83567ed159..2e24403d460f 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.metrics.LogMaker; 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.customize.QSCustomizer; 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 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne 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 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne 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 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne 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 @@ public class QSPanel extends LinearLayout implements Tunable, Callback, Brightne 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 14addb99c0c5..37743ec55517 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 @@ package com.android.systemui.qs; 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 @@ public class TileLayout extends ViewGroup implements QSTileLayout { 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 class TileLayout extends ViewGroup implements QSTileLayout { 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 @@ public class TileLayout extends ViewGroup implements QSTileLayout { 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 466c8082f0b9..411980b399bd 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.text.format.DateUtils; 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 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener 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 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener @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 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener } 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 effea6a877b8..f59e0c2d9bc2 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 @@ public class TileLifecycleManager extends BroadcastReceiver implements } /** + * 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 2a7e55fe6f8f..0b4e6485551c 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 @@ public class TileServiceManager { 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 daaee4cd5336..1c8e451e52f7 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 @@ package com.android.systemui.qs.tileimpl; 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.BluetoothTile; 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 @@ public class QSFactoryImpl implements QSFactory { 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 @@ public class QSFactoryImpl implements QSFactory { @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 @@ public class QSFactoryImpl implements QSFactory { Provider<UiModeNightTile> uiModeNightTileProvider) { mWifiTileProvider = wifiTileProvider; mBluetoothTileProvider = bluetoothTileProvider; + mControlsTileProvider = controlsTileProvider; mCellularTileProvider = cellularTileProvider; mDndTileProvider = dndTileProvider; mColorInversionTileProvider = colorInversionTileProvider; @@ -138,6 +143,11 @@ public class QSFactoryImpl implements QSFactory { 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 681de378ff57..e0f26cd1a267 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 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy mQSSettingsPanelOption = QSSettingsControllerKt.getQSSettingsPanelOption(); } + protected final void resetStates() { + mState = newTileState(); + mTmpState = newTileState(); + } + @NonNull @Override public Lifecycle getLifecycle() { @@ -629,6 +634,11 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy } } + /** + * 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 000000000000..0a59618b59a1 --- /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 e0ae8ed66f41..3fc139882693 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 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis 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 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } 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 class OverviewProxyService implements CallbackController<OverviewProxyLis 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 a1b4a93c454a..0a8264bcef87 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 @@ public class Recents extends SystemUI implements CommandQueue.Callbacks { 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 c1ce16337f8d..aa6444973a6f 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 @@ public class RecentsOnboarding { 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 0f277ca8b2c6..2d1c08719119 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 @@ public class ScreenPinningRequest implements View.OnClickListener, | 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 07675e248906..df9791d1bd37 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 @@ package com.android.systemui.shortcut; 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 @@ public class ShortcutKeyDispatcher extends SystemUI 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 cd2074fd64b8..c8b2b6aee0b3 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 @@ package com.android.systemui.stackdivider; 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 @@ public class Divider extends SystemUI implements DividerView.DividerCallbacks { 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 36e04fe42ced..d6a8f906197d 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 @@ public class CommandQueue extends IStatusBar.Stub implements CallbackController< // 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/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java new file mode 100644 index 000000000000..f91341f8f4ea --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.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 com.android.systemui.statusbar; + +import android.annotation.NonNull; +import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.Looper; +import android.provider.DeviceConfig; +import android.util.ArrayMap; + +import java.util.Map; + +import javax.inject.Inject; + +/** + * Class to manage simple DeviceConfig-based feature flags. + * + * To enable or disable a flag, run: + * + * {@code + * $ adb shell device_config put systemui <key> <true|false> +* } + * + * You will probably need to @{$ adb reboot} afterwards in order for the code to pick up the change. + */ +public class FeatureFlags { + private final Map<String, Boolean> mCachedDeviceConfigFlags = new ArrayMap<>(); + + @Inject + public FeatureFlags() { + DeviceConfig.addOnPropertiesChangedListener( + "systemui", + new HandlerExecutor(new Handler(Looper.getMainLooper())), + this::onPropertiesChanged); + } + + public boolean isNewNotifPipelineEnabled() { + return getDeviceConfigFlag("notification.newpipeline.enabled", false); + } + + private void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { + synchronized (mCachedDeviceConfigFlags) { + for (String key : properties.getKeyset()) { + mCachedDeviceConfigFlags.remove(key); + } + } + } + + private boolean getDeviceConfigFlag(String key, boolean defaultValue) { + synchronized (mCachedDeviceConfigFlags) { + Boolean flag = mCachedDeviceConfigFlags.get(key); + if (flag == null) { + flag = DeviceConfig.getBoolean("systemui", key, defaultValue); + mCachedDeviceConfigFlags.put(key, flag); + } + return flag; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java index 16cdfaa18a53..5144a95a0a51 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 @@ public class NavigationBarController implements Callbacks { // 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 @@ public class NavigationBarController implements Callbacks { } /** @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/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java index a5b7fa7d1db6..f284f737b26d 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.NotificationManager; 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 com.android.systemui.util.NotificationChannels; 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 @@ public class InstantAppNotifier extends SystemUI 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/NewNotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NewNotifPipeline.java new file mode 100644 index 000000000000..31921a436747 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NewNotifPipeline.java @@ -0,0 +1,49 @@ +/* + * 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.notification; + +import android.util.Log; + +import com.android.systemui.statusbar.NotificationListener; +import com.android.systemui.statusbar.notification.collection.NotifCollection; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Initialization code for the new notification pipeline. + */ +@Singleton +public class NewNotifPipeline { + private final NotifCollection mNotifCollection; + + @Inject + public NewNotifPipeline( + NotifCollection notifCollection) { + mNotifCollection = notifCollection; + } + + /** Hooks the new pipeline up to NotificationManager */ + public void initialize( + NotificationListener notificationService) { + mNotifCollection.attach(notificationService); + + Log.d(TAG, "Notif pipeline initialized"); + } + + private static final String TAG = "NewNotifPipeline"; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineInitializer.java deleted file mode 100644 index df70828a46be..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineInitializer.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.notification; - -import android.service.notification.NotificationListenerService; -import android.service.notification.StatusBarNotification; -import android.util.Log; - -import com.android.systemui.statusbar.NotificationListener; - -import javax.inject.Inject; - -/** - * Initialization code for the new notification pipeline. - */ -public class NotifPipelineInitializer { - - @Inject - public NotifPipelineInitializer() { - } - - public void initialize( - NotificationListener notificationService) { - - // TODO Put real code here - notificationService.setDownstreamListener(new NotificationListener.NotifServiceListener() { - @Override - public void onNotificationPosted(StatusBarNotification sbn, - NotificationListenerService.RankingMap rankingMap) { - Log.d(TAG, "onNotificationPosted " + sbn.getKey()); - } - - @Override - public void onNotificationRemoved(StatusBarNotification sbn, - NotificationListenerService.RankingMap rankingMap) { - Log.d(TAG, "onNotificationRemoved " + sbn.getKey()); - } - - @Override - public void onNotificationRemoved(StatusBarNotification sbn, - NotificationListenerService.RankingMap rankingMap, int reason) { - Log.d(TAG, "onNotificationRemoved " + sbn.getKey()); - } - - @Override - public void onNotificationRankingUpdate( - NotificationListenerService.RankingMap rankingMap) { - Log.d(TAG, "onNotificationRankingUpdate"); - } - }); - } - - private static final String TAG = "NotifInitializer"; -} 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 2f67f90a115e..8a23e3796e9b 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 @@ import javax.inject.Singleton @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 @@ class NotificationWakeUpCoordinator @Inject constructor( 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 @@ class NotificationWakeUpCoordinator @Inject constructor( 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 @@ class NotificationWakeUpCoordinator @Inject constructor( } 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/DismissedByUserStats.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java new file mode 100644 index 000000000000..ecce6ea1b211 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java @@ -0,0 +1,38 @@ +/* + * 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.notification.collection; + +import android.service.notification.NotificationStats.DismissalSentiment; +import android.service.notification.NotificationStats.DismissalSurface; + +import com.android.internal.statusbar.NotificationVisibility; + +/** Information that must be supplied when dismissing a notification on the behalf of the user. */ +public class DismissedByUserStats { + public final @DismissalSurface int dismissalSurface; + public final @DismissalSentiment int dismissalSentiment; + public final NotificationVisibility notificationVisibility; + + public DismissedByUserStats( + @DismissalSurface int dismissalSurface, + @DismissalSentiment int dismissalSentiment, + NotificationVisibility notificationVisibility) { + this.dismissalSurface = dismissalSurface; + this.dismissalSentiment = dismissalSentiment; + this.notificationVisibility = notificationVisibility; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java new file mode 100644 index 000000000000..3203c30ce422 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -0,0 +1,426 @@ +/* + * 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.notification.collection; + +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_ALL; +import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED; +import static android.service.notification.NotificationListenerService.REASON_CLICK; +import static android.service.notification.NotificationListenerService.REASON_ERROR; +import static android.service.notification.NotificationListenerService.REASON_GROUP_OPTIMIZATION; +import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; +import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL; +import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL_ALL; +import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED; +import static android.service.notification.NotificationListenerService.REASON_PACKAGE_CHANGED; +import static android.service.notification.NotificationListenerService.REASON_PACKAGE_SUSPENDED; +import static android.service.notification.NotificationListenerService.REASON_PROFILE_TURNED_OFF; +import static android.service.notification.NotificationListenerService.REASON_SNOOZED; +import static android.service.notification.NotificationListenerService.REASON_TIMEOUT; +import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED; +import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED; + +import static com.android.internal.util.Preconditions.checkNotNull; + +import android.annotation.IntDef; +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.RemoteException; +import android.service.notification.NotificationListenerService.Ranking; +import android.service.notification.NotificationListenerService.RankingMap; +import android.service.notification.StatusBarNotification; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.internal.statusbar.IStatusBarService; +import com.android.systemui.statusbar.NotificationListener; +import com.android.systemui.statusbar.NotificationListener.NotifServiceListener; +import com.android.systemui.util.Assert; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Keeps a record of all of the "active" notifications, i.e. the notifications that are currently + * posted to the phone. This collection is unsorted, ungrouped, and unfiltered. Just because a + * notification appears in this collection doesn't mean that it's currently present in the shade + * (notifications can be hidden for a variety of reasons). Code that cares about what notifications + * are *visible* right now should register listeners later in the pipeline. + * + * Each notification is represented by a {@link NotificationEntry}, which is itself made up of two + * parts: a {@link StatusBarNotification} and a {@link Ranking}. When notifications are updated, + * their underlying SBNs and Rankings are swapped out, but the enclosing NotificationEntry (and its + * associated key) remain the same. In general, an SBN can only be updated when the notification is + * reposted by the source app; Rankings are updated much more often, usually every time there is an + * update from any kind from NotificationManager. + * + * In general, this collection closely mirrors the list maintained by NotificationManager, but it + * can occasionally diverge due to lifetime extenders (see + * {@link #addNotificationLifetimeExtender(NotifLifetimeExtender)}). + * + * Interested parties can register listeners + * ({@link #addCollectionListener(NotifCollectionListener)}) to be informed when notifications are + * added, updated, or removed. + */ +@MainThread +@Singleton +public class NotifCollection { + private final IStatusBarService mStatusBarService; + + private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>(); + private final Collection<NotificationEntry> mReadOnlyNotificationSet = + Collections.unmodifiableCollection(mNotificationSet.values()); + + @Nullable private NotifListBuilder mListBuilder; + private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>(); + private final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>(); + + private boolean mAttached = false; + private boolean mAmDispatchingToOtherCode; + + @Inject + public NotifCollection(IStatusBarService statusBarService) { + Assert.isMainThread(); + mStatusBarService = statusBarService; + } + + /** Initializes the NotifCollection and registers it to receive notification events. */ + public void attach(NotificationListener listenerService) { + Assert.isMainThread(); + if (mAttached) { + throw new RuntimeException("attach() called twice"); + } + mAttached = true; + + listenerService.setDownstreamListener(mNotifServiceListener); + } + + /** + * Sets the class responsible for converting the collection into the list of currently-visible + * notifications. + */ + public void setListBuilder(NotifListBuilder listBuilder) { + Assert.isMainThread(); + mListBuilder = listBuilder; + } + + /** + * Returns the list of "active" notifications, i.e. the notifications that are currently posted + * to the phone. In general, this tracks closely to the list maintained by NotificationManager, + * but it can diverge slightly due to lifetime extenders. + * + * The returned list is read-only, unsorted, unfiltered, and ungrouped. + */ + public Collection<NotificationEntry> getNotifs() { + Assert.isMainThread(); + return mReadOnlyNotificationSet; + } + + /** + * Registers a listener to be informed when notifications are added, removed or updated. + */ + public void addCollectionListener(NotifCollectionListener listener) { + Assert.isMainThread(); + mNotifCollectionListeners.add(listener); + } + + /** + * Registers a lifetime extender. Lifetime extenders can cause notifications that have been + * dismissed or retracted to be temporarily retained in the collection. + */ + public void addNotificationLifetimeExtender(NotifLifetimeExtender extender) { + Assert.isMainThread(); + checkForReentrantCall(); + if (mLifetimeExtenders.contains(extender)) { + throw new IllegalArgumentException("Extender " + extender + " already added."); + } + mLifetimeExtenders.add(extender); + extender.setCallback(this::onEndLifetimeExtension); + } + + /** + * Dismiss a notification on behalf of the user. + */ + public void dismissNotification( + NotificationEntry entry, + @CancellationReason int reason, + @NonNull DismissedByUserStats stats) { + Assert.isMainThread(); + checkNotNull(stats); + checkForReentrantCall(); + + removeNotification(entry.key(), null, reason, stats); + } + + private void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { + Assert.isMainThread(); + + NotificationEntry entry = mNotificationSet.get(sbn.getKey()); + + if (entry == null) { + // A new notification! + Log.d(TAG, "POSTED " + sbn.getKey()); + + entry = new NotificationEntry(sbn, requireRanking(rankingMap, sbn.getKey())); + mNotificationSet.put(sbn.getKey(), entry); + applyRanking(rankingMap); + + dispatchOnEntryAdded(entry); + + } else { + // Update to an existing entry + Log.d(TAG, "UPDATED " + sbn.getKey()); + + // Notification is updated so it is essentially re-added and thus alive again. Don't + // need to keep its lifetime extended. + cancelLifetimeExtension(entry); + + entry.setNotification(sbn); + applyRanking(rankingMap); + + dispatchOnEntryUpdated(entry); + } + + rebuildList(); + } + + private void onNotificationRemoved( + StatusBarNotification sbn, + @Nullable RankingMap rankingMap, + int reason) { + Assert.isMainThread(); + Log.d(TAG, "REMOVED " + sbn.getKey() + " reason=" + reason); + removeNotification(sbn.getKey(), rankingMap, reason, null); + } + + private void onNotificationRankingUpdate(RankingMap rankingMap) { + Assert.isMainThread(); + applyRanking(rankingMap); + rebuildList(); + } + + private void removeNotification( + String key, + @Nullable RankingMap rankingMap, + @CancellationReason int reason, + DismissedByUserStats dismissedByUserStats) { + + NotificationEntry entry = mNotificationSet.get(key); + if (entry == null) { + throw new IllegalStateException("No notification to remove with key " + key); + } + + entry.mLifetimeExtenders.clear(); + mAmDispatchingToOtherCode = true; + for (NotifLifetimeExtender extender : mLifetimeExtenders) { + if (extender.shouldExtendLifetime(entry, reason)) { + entry.mLifetimeExtenders.add(extender); + } + } + mAmDispatchingToOtherCode = false; + + if (!isLifetimeExtended(entry)) { + mNotificationSet.remove(entry.key()); + + if (dismissedByUserStats != null) { + try { + mStatusBarService.onNotificationClear( + entry.sbn().getPackageName(), + entry.sbn().getTag(), + entry.sbn().getId(), + entry.sbn().getUser().getIdentifier(), + entry.sbn().getKey(), + dismissedByUserStats.dismissalSurface, + dismissedByUserStats.dismissalSentiment, + dismissedByUserStats.notificationVisibility); + } catch (RemoteException e) { + // system process is dead if we're here. + } + } + + if (rankingMap != null) { + applyRanking(rankingMap); + } + + dispatchOnEntryRemoved(entry, reason, dismissedByUserStats != null /* removedByUser */); + } + + rebuildList(); + } + + private void applyRanking(RankingMap rankingMap) { + for (NotificationEntry entry : mNotificationSet.values()) { + if (!isLifetimeExtended(entry)) { + Ranking ranking = requireRanking(rankingMap, entry.key()); + entry.setRanking(ranking); + } + } + } + + private void rebuildList() { + if (mListBuilder != null) { + mListBuilder.onBuildList(mReadOnlyNotificationSet); + } + } + + private void onEndLifetimeExtension(NotifLifetimeExtender extender, NotificationEntry entry) { + Assert.isMainThread(); + if (!mAttached) { + return; + } + checkForReentrantCall(); + + if (!entry.mLifetimeExtenders.remove(extender)) { + throw new IllegalStateException( + String.format( + "Cannot end lifetime extension for extender \"%s\" (%s)", + extender.getName(), + extender)); + } + + if (!isLifetimeExtended(entry)) { + // TODO: This doesn't need to be undefined -- we can set either EXTENDER_EXPIRED or + // save the original reason + removeNotification(entry.key(), null, REASON_UNKNOWN, null); + } + } + + private void cancelLifetimeExtension(NotificationEntry entry) { + mAmDispatchingToOtherCode = true; + for (NotifLifetimeExtender extender : entry.mLifetimeExtenders) { + extender.cancelLifetimeExtension(entry); + } + mAmDispatchingToOtherCode = false; + entry.mLifetimeExtenders.clear(); + } + + private boolean isLifetimeExtended(NotificationEntry entry) { + return entry.mLifetimeExtenders.size() > 0; + } + + private void checkForReentrantCall() { + if (mAmDispatchingToOtherCode) { + throw new IllegalStateException("Reentrant call detected"); + } + } + + private static Ranking requireRanking(RankingMap rankingMap, String key) { + // TODO: Modify RankingMap so that we don't have to make a copy here + Ranking ranking = new Ranking(); + if (!rankingMap.getRanking(key, ranking)) { + throw new IllegalArgumentException("Ranking map doesn't contain key: " + key); + } + return ranking; + } + + private void dispatchOnEntryAdded(NotificationEntry entry) { + mAmDispatchingToOtherCode = true; + if (mListBuilder != null) { + mListBuilder.onBeginDispatchToListeners(); + } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryAdded(entry); + } + mAmDispatchingToOtherCode = false; + } + + private void dispatchOnEntryUpdated(NotificationEntry entry) { + mAmDispatchingToOtherCode = true; + if (mListBuilder != null) { + mListBuilder.onBeginDispatchToListeners(); + } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryUpdated(entry); + } + mAmDispatchingToOtherCode = false; + } + + private void dispatchOnEntryRemoved( + NotificationEntry entry, + @CancellationReason int reason, + boolean removedByUser) { + mAmDispatchingToOtherCode = true; + if (mListBuilder != null) { + mListBuilder.onBeginDispatchToListeners(); + } + for (NotifCollectionListener listener : mNotifCollectionListeners) { + listener.onEntryRemoved(entry, reason, removedByUser); + } + mAmDispatchingToOtherCode = false; + } + + private final NotifServiceListener mNotifServiceListener = new NotifServiceListener() { + @Override + public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { + NotifCollection.this.onNotificationPosted(sbn, rankingMap); + } + + @Override + public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) { + NotifCollection.this.onNotificationRemoved(sbn, rankingMap, REASON_UNKNOWN); + } + + @Override + public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, + int reason) { + NotifCollection.this.onNotificationRemoved(sbn, rankingMap, reason); + } + + @Override + public void onNotificationRankingUpdate(RankingMap rankingMap) { + NotifCollection.this.onNotificationRankingUpdate(rankingMap); + } + }; + + private static final String TAG = "NotifCollection"; + + @IntDef(prefix = { "REASON_" }, value = { + REASON_UNKNOWN, + REASON_CLICK, + REASON_CANCEL_ALL, + REASON_ERROR, + REASON_PACKAGE_CHANGED, + REASON_USER_STOPPED, + REASON_PACKAGE_BANNED, + REASON_APP_CANCEL, + REASON_APP_CANCEL_ALL, + REASON_LISTENER_CANCEL, + REASON_LISTENER_CANCEL_ALL, + REASON_GROUP_SUMMARY_CANCELED, + REASON_GROUP_OPTIMIZATION, + REASON_PACKAGE_SUSPENDED, + REASON_PROFILE_TURNED_OFF, + REASON_UNAUTOBUNDLED, + REASON_CHANNEL_BANNED, + REASON_SNOOZED, + REASON_TIMEOUT, + }) + @Retention(RetentionPolicy.SOURCE) + @interface CancellationReason {} + + public static final int REASON_UNKNOWN = 0; +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java new file mode 100644 index 000000000000..032620e14336 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java @@ -0,0 +1,46 @@ +/* + * 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.notification.collection; + +import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; + +/** + * Listener interface for {@link NotifCollection}. + */ +public interface NotifCollectionListener { + /** + * Called whenever a notification with a new key is posted. + */ + default void onEntryAdded(NotificationEntry entry) { + } + + /** + * Called whenever a notification with the same key as an existing notification is posted. By + * the time this listener is called, the entry's SBN and Ranking will already have been updated. + */ + default void onEntryUpdated(NotificationEntry entry) { + } + + /** + * Called immediately after a notification has been removed from the collection. + */ + default void onEntryRemoved( + NotificationEntry entry, + @CancellationReason int reason, + boolean removedByUser) { + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java new file mode 100644 index 000000000000..2c7b13866c10 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java @@ -0,0 +1,58 @@ +/* + * 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.notification.collection; + +import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; + +/** + * A way for other code to temporarily extend the lifetime of a notification after it has been + * retracted. See {@link NotifCollection#addNotificationLifetimeExtender(NotifLifetimeExtender)}. + */ +public interface NotifLifetimeExtender { + /** Name to associate with this extender (for the purposes of debugging) */ + String getName(); + + /** + * Called on the extender immediately after it has been registered. The extender should hang on + * to this callback and execute it whenever it no longer needs to extend the lifetime of a + * notification. + */ + void setCallback(OnEndLifetimeExtensionCallback callback); + + /** + * Called by the NotifCollection whenever a notification has been retracted (by the app) or + * dismissed (by the user). If the extender returns true, it is considered to be extending the + * lifetime of that notification. Lifetime-extended notifications are kept around until all + * active extenders expire their extension by calling onEndLifetimeExtension(). This method is + * called on all lifetime extenders even if earlier ones return true (in other words, multiple + * lifetime extenders can be extending a notification at the same time). + */ + boolean shouldExtendLifetime(NotificationEntry entry, @CancellationReason int reason); + + /** + * Called by the NotifCollection to inform a lifetime extender that its extension of a notif + * is no longer valid (usually because the notif has been reposted and so no longer needs + * lifetime extension). The extender should clean up any references it has to the notif in + * question. + */ + void cancelLifetimeExtension(NotificationEntry entry); + + /** Callback for notifying the NotifCollection that a lifetime extension has expired. */ + interface OnEndLifetimeExtensionCallback { + void onEndLifetimeExtension(NotifLifetimeExtender extender, NotificationEntry entry); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilder.java new file mode 100644 index 000000000000..17fef6850f97 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilder.java @@ -0,0 +1,43 @@ +/* + * 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.notification.collection; + +import java.util.Collection; + +/** + * Interface for the class responsible for converting a NotifCollection into the final sorted, + * filtered, and grouped list of currently visible notifications. + */ +public interface NotifListBuilder { + /** + * Called after the NotifCollection has received an update from NotificationManager but before + * it dispatches any change events to its listeners. This is to inform the list builder that + * the first stage of the pipeline has been triggered. After events have been dispatched, + * onBuildList() will be called. + * + * While onBuildList() is always called after this method is called, the converse is not always + * true: sometimes the NotifCollection applies an update that does not need to dispatch events, + * in which case this method will be skipped and onBuildList will be called directly. + */ + void onBeginDispatchToListeners(); + + /** + * Called by the NotifCollection to indicate that something in the collection has changed and + * that the list builder should regenerate the list. + */ + void onBuildList(Collection<NotificationEntry> entries); +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java index c3211e307845..b12461a36a6a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java @@ -94,6 +94,20 @@ public final class NotificationEntry { public StatusBarNotification notification; private Ranking mRanking; + + /* + * Bookkeeping members + */ + + /** List of lifetime extenders that are extending the lifetime of this notification. */ + final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>(); + + + /* + * Old members + * TODO: Remove every member beneath this line if possible + */ + public boolean noisy; public StatusBarIconView icon; public StatusBarIconView expandedIcon; 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 7bbe8188b402..98178252124a 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd HeadsUpManagerPhone headsUpManager, KeyguardBypassController keyguardBypassController, FalsingManager falsingManager, + NotificationLockscreenUserManager notificationLockscreenUserManager, + NotificationGutsManager notificationGutsManager, NotificationSectionsFeatureManager sectionsFeatureManager) { super(context, attrs, 0, 0); Resources res = getResources(); @@ -526,6 +526,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd mRoundnessManager = notificationRoundnessManager; + mLockscreenUserManager = notificationLockscreenUserManager; + mNotificationGutsManager = notificationGutsManager; mHeadsUpManager = headsUpManager; mHeadsUpManager.addListener(mRoundnessManager); mHeadsUpManager.setAnimationStateHandler(this::setHeadsUpGoingAwayAnimationsAllowed); 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 5912cd7b6433..175d072e4c8b 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.MotionEvent; 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 @@ public class AutoHideController implements CommandQueue.Callbacks { }; @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 7cbdfb0b12f4..548afd503061 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 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback { 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 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback { 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 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback { } // 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 0d62703cbced..78ea5c03a7b5 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 @@ public class DoubleTapHelper { event.getY() - mActivationY); } if (withinDoubleTapSlop) { + makeInactive(); if (!mDoubleTapListener.onDoubleTap()) { return false; } @@ -134,6 +135,7 @@ public class DoubleTapHelper { 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 bb6a38e1dcf5..28dac87c92cf 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.provider.Settings; 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 com.android.systemui.tuner.TunerService; 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 class DozeParameters implements TunerService.Tunable, 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 class DozeParameters implements TunerService.Tunable, } 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 class DozeParameters implements TunerService.Tunable, } public float getScreenBrightnessDoze() { - return mContext.getResources().getInteger( + return mResources.getInteger( com.android.internal.R.integer.config_screenBrightnessDoze) / 255f; } @@ -173,7 +175,7 @@ public class DozeParameters implements TunerService.Tunable, * @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 @@ public class DozeParameters implements TunerService.Tunable, } 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 ca7c227f42b7..442c08991581 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java @@ -276,7 +276,7 @@ public class EdgeBackGestureHandler implements DisplayListener { | 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; 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 deb314bae5c9..a7849847387d 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 @@ public class FloatingRotationButton implements RotationButton { 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 da62d9b3adba..ce96005aa306 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 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, 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 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, StatusBarStateController stateController, KeyguardBypassController bypassController, NotificationWakeUpCoordinator wakeUpCoordinator, + KeyguardStateController keyguardStateController, HeadsUpStatusBarView headsUpStatusBarView, NotificationStackScrollLayout stackScroller, NotificationPanelView panelView, @@ -160,7 +162,7 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, 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 a7e7f085ffd7..c6d051d74239 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.DisplayCutout; import android.view.Gravity; import android.view.View; import android.view.ViewTreeObserver; +import android.view.WindowInsets; import androidx.collection.ArraySet; @@ -390,7 +391,12 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable, } 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 cac33044ea13..d95d2b7db4c9 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 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private ActivityStarter mActivityStarter; private KeyguardStateController mKeyguardStateController; - private LockPatternUtils mLockPatternUtils; private FlashlightController mFlashlightController; private PreviewInflater mPreviewInflater; private AccessibilityController mAccessibilityController; @@ -231,7 +230,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL @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/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 9804f9ff4698..ae18833d99b5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -1212,8 +1212,11 @@ public class NavigationBarView extends FrameLayout implements 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 1a3560ece1d7..1a37520d546e 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 @@ public class NotificationIconAreaController implements DarkReceiver, 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 @@ public class NotificationIconAreaController implements DarkReceiver, 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 class NotificationIconAreaController implements DarkReceiver, } 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 00736b73f12e..89051cda15ab 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.hardware.biometrics.BiometricSourceType; 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.NotificationShelf; 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 @@ public class NotificationPanelView extends PanelView implements * Fling until QS is completely hidden. */ public static final int FLING_HIDE = 2; + private final DozeParameters mDozeParameters; private double mQqsSplitFraction; @@ -452,17 +455,17 @@ public class NotificationPanelView extends PanelView implements @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 @@ public class NotificationPanelView extends PanelView implements mCommandQueue = getComponent(context, CommandQueue.class); mDisplayId = context.getDisplayId(); mPulseExpansionHandler = pulseExpansionHandler; + mDozeParameters = dozeParameters; pulseExpansionHandler.setPulseExpandAbortListener(() -> { if (mQs != null) { mQs.animateHeaderSlidingOut(); @@ -537,7 +541,10 @@ public class NotificationPanelView extends PanelView implements 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 @@ public class NotificationPanelView extends PanelView implements mPluginFrame.setLayoutParams(lp); } - mNPVPluginManager.replaceFrameLayout(mPluginFrame); + if (mNPVPluginManager != null) mNPVPluginManager.replaceFrameLayout(mPluginFrame); } private void initBottomArea() { @@ -804,8 +811,10 @@ public class NotificationPanelView extends PanelView implements 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 @@ public class NotificationPanelView extends PanelView implements 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 @@ public class NotificationPanelView extends PanelView implements 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 @@ public class NotificationPanelView extends PanelView implements 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 @@ public class NotificationPanelView extends PanelView implements 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 class NotificationPanelView extends PanelView implements 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 432d63648cfe..e8e5e1f60c51 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.Interpolators; 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 @@ public abstract class PanelView extends FrameLayout { 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 abstract class PanelView extends FrameLayout { } 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 5ba19cca6532..e7d896c2b88d 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo } /** - * 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo @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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo } 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo } } - 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 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, OnCo } 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 7463c7c232bd..e059715986dc 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 @@ public enum ScrimState { /** * 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 @@ public enum ScrimState { /** * 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 @@ public enum ScrimState { /** * 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 @@ public enum ScrimState { /** * Changing screen brightness from quick settings. */ - BRIGHTNESS_MIRROR(3) { + BRIGHTNESS_MIRROR { @Override public void prepare(ScrimState previousState) { mBehindAlpha = 0; @@ -101,7 +119,7 @@ public enum ScrimState { /** * Always on display or screen off. */ - AOD(4) { + AOD { @Override public void prepare(ScrimState previousState) { final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn(); @@ -136,7 +154,7 @@ public enum ScrimState { /** * When phone wakes up because you received a notification. */ - PULSING(5) { + PULSING { @Override public void prepare(ScrimState previousState) { mFrontAlpha = mAodFrontScrimAlpha; @@ -164,7 +182,7 @@ public enum ScrimState { /** * 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 @@ public enum ScrimState { /** * Unlocked with a bubble expanded. */ - BUBBLE_EXPANDED(7) { + BUBBLE_EXPANDED { @Override public void prepare(ScrimState previousState) { mFrontTint = Color.TRANSPARENT; @@ -237,17 +255,12 @@ public enum ScrimState { 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 enum ScrimState { 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 2b80d2282661..8e70d082dbb6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -179,6 +179,7 @@ import com.android.systemui.statusbar.BackDropView; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.EmptyShadeView; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.GestureRecorder; import com.android.systemui.statusbar.KeyboardShortcuts; import com.android.systemui.statusbar.KeyguardIndicationController; @@ -198,7 +199,7 @@ import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.NotifPipelineInitializer; +import com.android.systemui.statusbar.notification.NewNotifPipeline; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.NotificationAlertingManager; import com.android.systemui.statusbar.notification.NotificationClicker; @@ -246,6 +247,7 @@ import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; +import dagger.Lazy; import dagger.Subcomponent; @Singleton @@ -370,6 +372,7 @@ public class StatusBar extends SystemUI implements DemoMode, private final Object mQueueLock = new Object(); + private final FeatureFlags mFeatureFlags; private final StatusBarIconController mIconController; private final DozeLog mDozeLog; private final InjectionInflationController mInjectionInflater; @@ -381,12 +384,13 @@ public class StatusBar extends SystemUI implements DemoMode, private final DynamicPrivacyController mDynamicPrivacyController; private final BypassHeadsUpNotifier mBypassHeadsUpNotifier; private final boolean mAllowNotificationLongPress; - private final NotifPipelineInitializer mNotifPipelineInitializer; + private final Lazy<NewNotifPipeline> mNewNotifPipeline; private final FalsingManager mFalsingManager; 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 @@ -497,8 +501,7 @@ public class StatusBar extends SystemUI implements DemoMode, 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) @@ -620,6 +623,8 @@ public class StatusBar extends SystemUI implements DemoMode, @Inject public StatusBar( + Context context, + FeatureFlags featureFlags, LightBarController lightBarController, AutoHideController autoHideController, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -634,7 +639,7 @@ public class StatusBar extends SystemUI implements DemoMode, DynamicPrivacyController dynamicPrivacyController, BypassHeadsUpNotifier bypassHeadsUpNotifier, @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress, - NotifPipelineInitializer notifPipelineInitializer, + Lazy<NewNotifPipeline> newNotifPipeline, FalsingManager falsingManager, BroadcastDispatcher broadcastDispatcher, RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler, @@ -673,7 +678,10 @@ public class StatusBar extends SystemUI implements DemoMode, ConfigurationController configurationController, StatusBarWindowController statusBarWindowController, StatusBarWindowViewController.Builder statusBarWindowViewControllerBuilder, - NotifLog notifLog) { + NotifLog notifLog, + DozeParameters dozeParameters) { + super(context); + mFeatureFlags = featureFlags; mLightBarController = lightBarController; mAutoHideController = autoHideController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; @@ -688,7 +696,7 @@ public class StatusBar extends SystemUI implements DemoMode, mDynamicPrivacyController = dynamicPrivacyController; mBypassHeadsUpNotifier = bypassHeadsUpNotifier; mAllowNotificationLongPress = allowNotificationLongPress; - mNotifPipelineInitializer = notifPipelineInitializer; + mNewNotifPipeline = newNotifPipeline; mFalsingManager = falsingManager; mBroadcastDispatcher = broadcastDispatcher; mRemoteInputQuickSettingsDisabler = remoteInputQuickSettingsDisabler; @@ -728,6 +736,7 @@ public class StatusBar extends SystemUI implements DemoMode, mStatusBarWindowController = statusBarWindowController; mStatusBarWindowViewControllerBuilder = statusBarWindowViewControllerBuilder; mNotifLog = notifLog; + mDozeParameters = dozeParameters; mBubbleExpandListener = (isExpanding, key) -> { @@ -748,7 +757,7 @@ public class StatusBar extends SystemUI implements DemoMode, 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"); } @@ -959,7 +968,7 @@ public class StatusBar extends SystemUI implements DemoMode, mHeadsUpAppearanceController = new HeadsUpAppearanceController( mNotificationIconAreaController, mHeadsUpManager, mStatusBarWindow, mStatusBarStateController, mKeyguardBypassController, - mWakeUpCoordinator); + mKeyguardStateController, mWakeUpCoordinator); mHeadsUpAppearanceController.readFrom(oldController); mStatusBarWindowViewController.setStatusBarView(mStatusBarView); updateAreThereNotifications(); @@ -1030,13 +1039,12 @@ public class StatusBar extends SystemUI implements DemoMode, 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), @@ -1171,7 +1179,7 @@ public class StatusBar extends SystemUI implements DemoMode, mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanel, mHeadsUpManager, mStatusBarWindow, mStackScroller, mDozeScrimController, mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController, - mNotificationAlertingManager, rowBinder); + mNotificationAlertingManager, rowBinder, mKeyguardStateController); mNotificationListController = new NotificationListController( @@ -1208,7 +1216,9 @@ public class StatusBar extends SystemUI implements DemoMode, mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager); mNotificationListController.bind(); - mNotifPipelineInitializer.initialize(mNotificationListener); + if (mFeatureFlags.isNewNotifPipelineEnabled()) { + mNewNotifPipeline.get().initialize(mNotificationListener); + } } /** @@ -1341,7 +1351,7 @@ public class StatusBar extends SystemUI implements DemoMode, 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, @@ -3569,8 +3579,7 @@ public class StatusBar extends SystemUI implements DemoMode, mDozing = isDozing; // Collapse the notification panel if open - boolean dozingAnimated = mDozingRequested - && DozeParameters.getInstance(mContext).shouldControlScreenOff(); + boolean dozingAnimated = mDozingRequested && mDozeParameters.shouldControlScreenOff(); mNotificationPanel.resetViews(dozingAnimated); updateQsExpansionEnabled(); @@ -3600,7 +3609,6 @@ public class StatusBar extends SystemUI implements DemoMode, private void updateKeyguardState() { mKeyguardStateController.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(), - mKeyguardStateController.isMethodSecure(), mStatusBarKeyguardViewManager.isOccluded()); } @@ -3827,7 +3835,7 @@ public class StatusBar extends SystemUI implements DemoMode, */ private void updateNotificationPanelTouchState() { boolean goingToSleepWithoutAnimation = isGoingToSleep() - && !DozeParameters.getInstance(mContext).shouldControlScreenOff(); + && !mDozeParameters.shouldControlScreenOff(); boolean disabled = (!mDeviceInteractive && !mPulsing) || goingToSleepWithoutAnimation; mNotificationPanel.setTouchAndAnimationDisabled(disabled); mNotificationIconAreaController.setAnimationsEnabled(!disabled); @@ -4017,6 +4025,13 @@ public class StatusBar extends SystemUI implements DemoMode, } 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) { @@ -4043,6 +4058,7 @@ public class StatusBar extends SystemUI implements DemoMode, private boolean mAnimateWakeup; private boolean mAnimateScreenOff; private boolean mIgnoreTouchWhilePulsing; + private Runnable mPendingScreenOffCallback; @VisibleForTesting boolean mWakeLockScreenPerformsAuth = SystemProperties.getBoolean( "persist.sysui.wake_performs_auth", true); @@ -4265,8 +4281,33 @@ public class StatusBar extends SystemUI implements DemoMode, } @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 75b0cdcf13bd..8683586326d3 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.ViewGroup; 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 com.android.systemui.statusbar.policy.KeyguardStateController; 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 class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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 class StatusBarKeyguardViewManager implements RemoteInputController.Callb 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 3e0c268a5e70..f4a26badf719 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 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, 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 @@ public class StatusBarNotificationPresenter implements NotificationPresenter, 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 9a281cea314b..1def89b3af54 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.View; 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 @@ import javax.inject.Singleton; 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 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks, /** */ @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 724b46297e75..ca7a936d58f7 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 android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M 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.ViewGroup; 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 @@ public class StatusBarWindowController implements Callback, Dumpable, Configurat 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 @@ public class StatusBarWindowController implements Callback, Dumpable, Configurat 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 f21085ef0b1d..fd3f9c803c12 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.ExpandHelper; 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 @@ public class StatusBarWindowViewController { ShadeController shadeController, NotificationLockscreenUserManager notificationLockscreenUserManager, NotificationEntryManager notificationEntryManager, - DozeLog dozeLog) { + KeyguardStateController keyguardStateController, + SysuiStatusBarStateController statusBarStateController, + DozeLog dozeLog, + DozeParameters dozeParameters) { mView = view; mFalsingManager = falsingManager; @@ -106,7 +112,10 @@ public class StatusBarWindowViewController { 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 @@ public class StatusBarWindowViewController { 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 @@ public class StatusBarWindowViewController { 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 @@ public class StatusBarWindowViewController { mTunerService = tunerService; mNotificationLockScreenUserManager = notificationLockscreenUserManager; mNotificationEntryManager = notificationEntryManager; + mKeyguardStateController = keyguardStateController; + mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController; mDozeLog = dozeLog; + mDozeParameters = dozeParameters; } /** @@ -537,7 +555,10 @@ public class StatusBarWindowViewController { 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 ce929b7c621b..44be6bc282a7 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 class SystemUIDialog extends AlertDialog { 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 aefe201de45a..692c34c62dd8 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 @@ public interface KeyguardStateController extends CallbackController<Callback> { /** **/ 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 392094d0288e..1cb2bd430199 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.hardware.biometrics.BiometricSourceType; 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 @@ import javax.inject.Singleton; /** */ @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 @@ public class KeyguardStateControllerImpl extends KeyguardUpdateMonitorCallback /** */ @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 @@ public class KeyguardStateControllerImpl extends KeyguardUpdateMonitorCallback 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 @@ public class KeyguardStateControllerImpl extends KeyguardUpdateMonitorCallback } @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 @@ public class KeyguardStateControllerImpl extends KeyguardUpdateMonitorCallback 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 @@ public class KeyguardStateControllerImpl extends KeyguardUpdateMonitorCallback 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 @@ public class KeyguardStateControllerImpl extends KeyguardUpdateMonitorCallback 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 @@ public class KeyguardStateControllerImpl extends KeyguardUpdateMonitorCallback @Override public void onTrustChanged(int userId) { update(false /* updateAlways */); + notifyKeyguardChanged(); } @Override @@ -327,11 +314,6 @@ public class KeyguardStateControllerImpl extends KeyguardUpdateMonitorCallback } @Override - public void onScreenTurnedOff() { - update(false /* updateAlways */); - } - - @Override public void onKeyguardVisibilityChanged(boolean showing) { update(false /* updateAlways */); } @@ -340,5 +322,5 @@ public class KeyguardStateControllerImpl extends KeyguardUpdateMonitorCallback 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 43795dc08c91..cca9479d20d6 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.ActivityManager; 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.LinearLayout; 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.row.wrapper.NotificationViewW 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 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene private RemoteInputController mController; private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; + private IStatusBarService mStatusBarManagerService; + private NotificationEntry mEntry; private boolean mRemoved; @@ -103,6 +115,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene 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 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene if (isSoftImeEvent || isKeyboardEnterKey) { if (mEditText.length() > 0) { - sendRemoteInput(); + sendRemoteInput(prepareRemoteInputFromText()); } // Consume action to prevent IME from closing. return true; @@ -141,7 +155,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene 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 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene 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 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene 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 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene 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 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene @Override public void onClick(View v) { if (v == mSendButton) { - sendRemoteInput(); + sendRemoteInput(prepareRemoteInputFromText()); } } @@ -518,6 +553,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene private RemoteInputView mRemoteInputView; boolean mShowImeOnInputConnection; private LightBarController mLightBarController; + UserHandle mUser; public RemoteEditText(Context context, AttributeSet attrs) { super(context, attrs); @@ -617,11 +653,47 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene @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 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene } } - 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 95ae23cda812..bcfbdacf9ea8 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 @@ public class UserSwitcherController implements Dumpable { // 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 000000000000..a601e9b86ef4 --- /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 b80b6d54427c..c2ed7df7cb2e 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 @@ import com.android.systemui.statusbar.CommandQueue; */ 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 89aa7979e7d8..9a58a355c586 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 @@ public class ThemeOverlayController extends SystemUI { 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 ff5bd03740bd..11885c55b51d 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 @@ public class StorageNotification extends SystemUI { 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 0a3e34ee951d..fd99ef389bec 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.ComponentName; 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 @@ public class UsbConfirmActivity extends AlertActivity 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 @@ public class UsbConfirmActivity extends AlertActivity 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 f35af90edc3c..8c60747dffc7 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 class NotificationChannels extends SystemUI { 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 2d5ebc4875cb..63db755ef09d 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 @@ public class GarbageMonitor implements Dumpable { } /** */ + @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 dcd0c58a5310..2224c9cce40a 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 @@ public class AsyncSensorManager extends SensorManager @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 d2f185a88bfd..25a5139bf661 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 android.view.WindowManager.LayoutParams; 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 com.android.systemui.tuner.TunerService; 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 class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna 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 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna 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 @@ public class VolumeDialogComponent implements VolumeComponent, TunerService.Tuna @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 f8cf79322b40..b74313975223 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 @@ public class VolumeUI extends SystemUI { 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 @@ public class VolumeUI extends SystemUI { 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 38537fdb00b1..1dd48634615b 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 @@ public class CarrierTextControllerTest extends SysuiTestCase { 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 a07f25a144b1..364ee666e17d 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.annotation.UiThreadTest; 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 @@ public class ExpandHelperTest extends SysuiTestCase { @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 64ab060e14a2..c338d7031fea 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 @@ public class ScreenDecorationsTest extends SysuiTestCase { when(mFragmentService.getFragmentHostManager(any())).thenReturn(mFragmentHostManager); - mScreenDecorations = new ScreenDecorations() { + mScreenDecorations = new ScreenDecorations(mContext) { @Override public void start() { super.start(); @@ -126,7 +126,6 @@ public class ScreenDecorationsTest extends SysuiTestCase { 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 3ea7150afca0..06999bc45e17 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 @@ public class SizeCompatModeActivityControllerTest extends SysuiTestCase { 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 19e1a5cef183..a766885297fa 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 androidx.test.filters.SmallTest; 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 @@ import org.mockito.ArgumentCaptor; 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 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { .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 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { 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 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { .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 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { 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 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { .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 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { .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 @@ public class SliceBroadcastRelayHandlerTest extends SysuiTestCase { 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 dcdb5c3f8e9e..e1eb3b0c81b2 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 static org.mockito.Mockito.when; 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 @@ public class AuthControllerTest extends SysuiTestCase { 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 @@ public class AuthControllerTest extends SysuiTestCase { 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 5a2b5e38222a..34725733251a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -56,8 +56,10 @@ import android.widget.FrameLayout; 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; @@ -141,6 +143,10 @@ public class BubbleControllerTest extends SysuiTestCase { private BubbleController.BubbleExpandListener mBubbleExpandListener; @Mock private PendingIntent mDeleteIntent; + @Mock + private SysuiColorExtractor mColorExtractor; + @Mock + ColorExtractor.GradientColors mGradientColors; private BubbleData mBubbleData; @@ -150,15 +156,16 @@ public class BubbleControllerTest extends SysuiTestCase { 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(); 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 392a7cbc4d6c..96ee079f1fee 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 @@ public class BubbleDataTest extends SysuiTestCase { @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 d011e486d2e0..3ba5d1ac79ea 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 @@ public class ClassifierTest extends SysuiTestCase { 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 341b74b33784..96b2028da326 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 @@ public class PointerCountClassifierTest extends ClassifierTest { 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 1ce01729f2e9..98ec45947f79 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.any; 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.AndroidTestingRunner; 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 com.android.systemui.doze.DozeMachine.State; 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 abfa755671db..000000000000 --- 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 aa62e9aca4fe..316b891080ce 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 com.android.systemui.doze.DozeMachine.State.UNINITIALIZED; 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 com.android.systemui.util.sensors.FakeSensorManager; 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 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { 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 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { @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 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { 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 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { 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 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { 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 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { 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 @@ public class DozeScreenBrightnessTest extends SysuiTestCase { 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 bfd448a2926d..b92f173e8002 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 @@ 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_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.assertEquals; 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 com.android.systemui.utils.os.FakeHandler; 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 @@ import org.mockito.MockitoAnnotations; @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 @@ public class DozeScreenStateTest extends SysuiTestCase { 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 @@ public class DozeScreenStateTest extends SysuiTestCase { 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 e5ae6d50c3e5..777fec75d159 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.any; 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 com.android.systemui.util.wakelock.WakeLockFake; 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 @@ public class DozeTriggersTest extends SysuiTestCase { @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 @@ public class DozeTriggersTest extends SysuiTestCase { 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 @@ public class DozeTriggersTest extends SysuiTestCase { @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 4958c649d532..8f4de3f114a2 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 @@ public class PowerUITest extends SysuiTestCase { } 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 000000000000..4becd522ebd6 --- /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 f35295cf6f99..11b0c69e8a41 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 @@ public class TileLifecycleManagerTest extends SysuiTestCase { 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 @@ public class TileLifecycleManagerTest extends SysuiTestCase { 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/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java index a0a410dfa361..e67aa69d48ce 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 @@ import org.mockito.MockitoAnnotations; */ @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 0569c55981fb..85a0fbd74550 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -52,6 +52,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationData; 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; @@ -83,6 +84,7 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager); mDependency.injectTestDependency(DeviceProvisionedController.class, mDeviceProvisionedController); + mDependency.injectMockDependency(KeyguardStateController.class); mContext.addMockSystemService(UserManager.class, mUserManager); mCurrentUserId = ActivityManager.getCurrentUser(); 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 de77af8f4d14..cb4096c454da 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 android.widget.RemoteViews; 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.notification.row.NotificationContentInflat 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 @@ public class NotificationTestHelper { 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 9e7250412499..6efa57d21cce 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 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase { 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 db2c8780e783..4103edee6255 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 @@ public class AboveShelfObserverTest extends SysuiTestCase { @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/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java index edd0a10672fb..45e1721782a1 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.ForegroundServiceController; 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 @@ public class NotificationFilterTest extends SysuiTestCase { 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 8207a041ef50..170c661eb325 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 @@ -42,6 +42,7 @@ import com.android.systemui.ForegroundServiceController; 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; @@ -85,6 +86,7 @@ public class NotificationListControllerTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); + mDependency.injectMockDependency(NotificationLockscreenUserManager.class); when(mEntryManager.getNotificationData()).thenReturn(mNotificationData); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java new file mode 100644 index 000000000000..e4a67dbbfa20 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -0,0 +1,625 @@ +/* + * 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.notification.collection; + +import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL; +import static android.service.notification.NotificationListenerService.REASON_CLICK; + +import static com.android.internal.util.Preconditions.checkNotNull; +import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_UNKNOWN; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.annotation.Nullable; +import android.os.RemoteException; +import android.service.notification.NotificationListenerService.Ranking; +import android.service.notification.NotificationListenerService.RankingMap; +import android.service.notification.NotificationStats; +import android.service.notification.StatusBarNotification; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.ArrayMap; + +import androidx.test.filters.SmallTest; + +import com.android.internal.statusbar.IStatusBarService; +import com.android.internal.statusbar.NotificationVisibility; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.NotificationEntryBuilder; +import com.android.systemui.statusbar.NotificationListener; +import com.android.systemui.statusbar.NotificationListener.NotifServiceListener; +import com.android.systemui.statusbar.RankingBuilder; +import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason; +import com.android.systemui.util.Assert; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; + +import java.util.Arrays; +import java.util.Map; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class NotifCollectionTest extends SysuiTestCase { + + @Mock private IStatusBarService mStatusBarService; + @Mock private NotificationListener mListenerService; + @Spy private RecordingCollectionListener mCollectionListener; + + @Spy private RecordingLifetimeExtender mExtender1 = new RecordingLifetimeExtender("Extender1"); + @Spy private RecordingLifetimeExtender mExtender2 = new RecordingLifetimeExtender("Extender2"); + @Spy private RecordingLifetimeExtender mExtender3 = new RecordingLifetimeExtender("Extender3"); + + @Captor private ArgumentCaptor<NotifServiceListener> mListenerCaptor; + @Captor private ArgumentCaptor<NotificationEntry> mEntryCaptor; + + private NotifCollection mCollection; + private NotifServiceListener mServiceListener; + + private NoManSimulator mNoMan; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + Assert.sMainLooper = TestableLooper.get(this).getLooper(); + + mCollection = new NotifCollection(mStatusBarService); + mCollection.attach(mListenerService); + mCollection.addCollectionListener(mCollectionListener); + + // Capture the listener object that the collection registers with the listener service so + // we can simulate listener service events in tests below + verify(mListenerService).setDownstreamListener(mListenerCaptor.capture()); + mServiceListener = checkNotNull(mListenerCaptor.getValue()); + + mNoMan = new NoManSimulator(mServiceListener); + } + + @Test + public void testEventDispatchedWhenNotifPosted() { + // WHEN a notification is posted + PostedNotif notif1 = mNoMan.postNotif( + buildNotif(TEST_PACKAGE, 3) + .setRank(4747)); + + // THEN the listener is notified + verify(mCollectionListener).onEntryAdded(mEntryCaptor.capture()); + + NotificationEntry entry = mEntryCaptor.getValue(); + assertEquals(notif1.key, entry.key()); + assertEquals(notif1.sbn, entry.sbn()); + assertEquals(notif1.ranking, entry.ranking()); + } + + @Test + public void testEventDispatchedWhenNotifUpdated() { + // GIVEN a collection with one notif + mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3) + .setRank(4747)); + + // WHEN the notif is reposted + PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3) + .setRank(89)); + + // THEN the listener is notified + verify(mCollectionListener).onEntryUpdated(mEntryCaptor.capture()); + + NotificationEntry entry = mEntryCaptor.getValue(); + assertEquals(notif2.key, entry.key()); + assertEquals(notif2.sbn, entry.sbn()); + assertEquals(notif2.ranking, entry.ranking()); + } + + @Test + public void testEventDispatchedWhenNotifRemoved() { + // GIVEN a collection with one notif + mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)); + clearInvocations(mCollectionListener); + + PostedNotif notif = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); + NotificationEntry entry = mCollectionListener.getEntry(notif.key); + clearInvocations(mCollectionListener); + + // WHEN a notif is retracted + mNoMan.retractNotif(notif.sbn, REASON_APP_CANCEL); + + // THEN the listener is notified + verify(mCollectionListener).onEntryRemoved(entry, REASON_APP_CANCEL, false); + assertEquals(notif.sbn, entry.sbn()); + assertEquals(notif.ranking, entry.ranking()); + } + + @Test + public void testRankingsAreUpdatedForOtherNotifs() { + // GIVEN a collection with one notif + PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3) + .setRank(47)); + NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); + + // WHEN a new notif is posted, triggering a rerank + mNoMan.setRanking(notif1.sbn.getKey(), new RankingBuilder(notif1.ranking) + .setRank(56) + .build()); + mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 77)); + + // THEN the ranking is updated on the first entry + assertEquals(56, entry1.ranking().getRank()); + } + + @Test + public void testRankingUpdateIsProperlyIssuedToEveryone() { + // GIVEN a collection with a couple notifs + PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3) + .setRank(3)); + PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 8) + .setRank(2)); + PostedNotif notif3 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 77) + .setRank(1)); + + NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); + NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); + NotificationEntry entry3 = mCollectionListener.getEntry(notif3.key); + + // WHEN a ranking update is delivered + Ranking newRanking1 = new RankingBuilder(notif1.ranking) + .setRank(4) + .setExplanation("Foo bar") + .build(); + Ranking newRanking2 = new RankingBuilder(notif2.ranking) + .setRank(5) + .setExplanation("baz buzz") + .build(); + Ranking newRanking3 = new RankingBuilder(notif3.ranking) + .setRank(6) + .setExplanation("Penguin pizza") + .build(); + + mNoMan.setRanking(notif1.sbn.getKey(), newRanking1); + mNoMan.setRanking(notif2.sbn.getKey(), newRanking2); + mNoMan.setRanking(notif3.sbn.getKey(), newRanking3); + mNoMan.issueRankingUpdate(); + + // THEN all of the NotifEntries have their rankings properly updated + assertEquals(newRanking1, entry1.ranking()); + assertEquals(newRanking2, entry2.ranking()); + assertEquals(newRanking3, entry3.ranking()); + } + + @Test + public void testNotifEntriesAreNotPersistedAcrossRemovalAndReposting() { + // GIVEN a notification that has been posted + PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)); + NotificationEntry entry1 = mCollectionListener.getEntry(notif1.key); + + // WHEN the notification is retracted and then reposted + mNoMan.retractNotif(notif1.sbn, REASON_APP_CANCEL); + mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3)); + + // THEN the new NotificationEntry is a new object + NotificationEntry entry2 = mCollectionListener.getEntry(notif1.key); + assertNotEquals(entry2, entry1); + } + + @Test + public void testDismissNotification() throws RemoteException { + // GIVEN a collection with a couple notifications and a lifetime extender + mCollection.addNotificationLifetimeExtender(mExtender1); + + PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag")); + PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88, "barTag")); + NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); + + // WHEN a notification is manually dismissed + DismissedByUserStats stats = new DismissedByUserStats( + NotificationStats.DISMISSAL_SHADE, + NotificationStats.DISMISS_SENTIMENT_NEUTRAL, + NotificationVisibility.obtain(entry2.key(), 7, 2, true)); + + mCollection.dismissNotification(entry2, REASON_CLICK, stats); + + // THEN we check for lifetime extension + verify(mExtender1).shouldExtendLifetime(entry2, REASON_CLICK); + + // THEN we send the dismissal to system server + verify(mStatusBarService).onNotificationClear( + notif2.sbn.getPackageName(), + notif2.sbn.getTag(), + 88, + notif2.sbn.getUser().getIdentifier(), + notif2.sbn.getKey(), + stats.dismissalSurface, + stats.dismissalSentiment, + stats.notificationVisibility); + + // THEN we fire a remove event + verify(mCollectionListener).onEntryRemoved(entry2, REASON_CLICK, true); + } + + @Test(expected = IllegalStateException.class) + public void testDismissingNonExistentNotificationThrows() { + // GIVEN a collection that originally had three notifs, but where one was dismissed + PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); + PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); + PostedNotif notif3 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 99)); + NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); + mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); + + // WHEN we try to dismiss a notification that isn't present + mCollection.dismissNotification( + entry2, + REASON_CLICK, + new DismissedByUserStats(0, 0, NotificationVisibility.obtain("foo", 47, 3, true))); + + // THEN an exception is thrown + } + + @Test + public void testLifetimeExtendersAreQueriedWhenNotifRemoved() { + // GIVEN a couple notifications and a few lifetime extenders + mExtender1.shouldExtendLifetime = true; + mExtender2.shouldExtendLifetime = true; + + mCollection.addNotificationLifetimeExtender(mExtender1); + mCollection.addNotificationLifetimeExtender(mExtender2); + mCollection.addNotificationLifetimeExtender(mExtender3); + + PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); + PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); + NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); + + // WHEN a notification is removed + mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); + + // THEN each extender is asked whether to extend, even if earlier ones return true + verify(mExtender1).shouldExtendLifetime(entry2, REASON_UNKNOWN); + verify(mExtender2).shouldExtendLifetime(entry2, REASON_UNKNOWN); + verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN); + + // THEN the entry is not removed + assertTrue(mCollection.getNotifs().contains(entry2)); + + // THEN the entry properly records all extenders that returned true + assertEquals(Arrays.asList(mExtender1, mExtender2), entry2.mLifetimeExtenders); + } + + @Test + public void testWhenLastLifetimeExtenderExpiresAllAreReQueried() { + // GIVEN a couple notifications and a few lifetime extenders + mExtender2.shouldExtendLifetime = true; + + mCollection.addNotificationLifetimeExtender(mExtender1); + mCollection.addNotificationLifetimeExtender(mExtender2); + mCollection.addNotificationLifetimeExtender(mExtender3); + + PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); + PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); + NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); + + // GIVEN a notification gets lifetime-extended by one of them + mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL); + assertTrue(mCollection.getNotifs().contains(entry2)); + clearInvocations(mExtender1, mExtender2, mExtender3); + + // WHEN the last active extender expires (but new ones become active) + mExtender1.shouldExtendLifetime = true; + mExtender2.shouldExtendLifetime = false; + mExtender3.shouldExtendLifetime = true; + mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2); + + // THEN each extender is re-queried + verify(mExtender1).shouldExtendLifetime(entry2, REASON_UNKNOWN); + verify(mExtender2).shouldExtendLifetime(entry2, REASON_UNKNOWN); + verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN); + + // THEN the entry is not removed + assertTrue(mCollection.getNotifs().contains(entry2)); + + // THEN the entry properly records all extenders that returned true + assertEquals(Arrays.asList(mExtender1, mExtender3), entry2.mLifetimeExtenders); + } + + @Test + public void testExtendersAreNotReQueriedUntilFinalActiveExtenderExpires() { + // GIVEN a couple notifications and a few lifetime extenders + mExtender1.shouldExtendLifetime = true; + mExtender2.shouldExtendLifetime = true; + + mCollection.addNotificationLifetimeExtender(mExtender1); + mCollection.addNotificationLifetimeExtender(mExtender2); + mCollection.addNotificationLifetimeExtender(mExtender3); + + PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); + PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); + NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); + + // GIVEN a notification gets lifetime-extended by a couple of them + mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL); + assertTrue(mCollection.getNotifs().contains(entry2)); + clearInvocations(mExtender1, mExtender2, mExtender3); + + // WHEN one (but not all) of the extenders expires + mExtender2.shouldExtendLifetime = false; + mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2); + + // THEN the entry is not removed + assertTrue(mCollection.getNotifs().contains(entry2)); + + // THEN we don't re-query the extenders + verify(mExtender1, never()).shouldExtendLifetime(eq(entry2), anyInt()); + verify(mExtender2, never()).shouldExtendLifetime(eq(entry2), anyInt()); + verify(mExtender3, never()).shouldExtendLifetime(eq(entry2), anyInt()); + + // THEN the entry properly records all extenders that returned true + assertEquals(Arrays.asList(mExtender1), entry2.mLifetimeExtenders); + } + + @Test + public void testNotificationIsRemovedWhenAllLifetimeExtendersExpire() { + // GIVEN a couple notifications and a few lifetime extenders + mExtender1.shouldExtendLifetime = true; + mExtender2.shouldExtendLifetime = true; + + mCollection.addNotificationLifetimeExtender(mExtender1); + mCollection.addNotificationLifetimeExtender(mExtender2); + mCollection.addNotificationLifetimeExtender(mExtender3); + + PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); + PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); + NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); + + // GIVEN a notification gets lifetime-extended by a couple of them + mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); + assertTrue(mCollection.getNotifs().contains(entry2)); + clearInvocations(mExtender1, mExtender2, mExtender3); + + // WHEN all of the active extenders expire + mExtender2.shouldExtendLifetime = false; + mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2); + mExtender1.shouldExtendLifetime = false; + mExtender1.callback.onEndLifetimeExtension(mExtender1, entry2); + + // THEN the entry removed + assertFalse(mCollection.getNotifs().contains(entry2)); + verify(mCollectionListener).onEntryRemoved(entry2, REASON_UNKNOWN, false); + } + + @Test + public void testLifetimeExtensionIsCanceledWhenNotifIsUpdated() { + // GIVEN a few lifetime extenders and a couple notifications + mCollection.addNotificationLifetimeExtender(mExtender1); + mCollection.addNotificationLifetimeExtender(mExtender2); + mCollection.addNotificationLifetimeExtender(mExtender3); + + mExtender1.shouldExtendLifetime = true; + mExtender2.shouldExtendLifetime = true; + + PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); + PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); + NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); + + // GIVEN a notification gets lifetime-extended by a couple of them + mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); + assertTrue(mCollection.getNotifs().contains(entry2)); + clearInvocations(mExtender1, mExtender2, mExtender3); + + // WHEN the notification is reposted + mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); + + // THEN all of the active lifetime extenders are canceled + verify(mExtender1).cancelLifetimeExtension(entry2); + verify(mExtender2).cancelLifetimeExtension(entry2); + + // THEN the notification is still present + assertTrue(mCollection.getNotifs().contains(entry2)); + } + + @Test(expected = IllegalStateException.class) + public void testReentrantCallsToLifetimeExtendersThrow() { + // GIVEN a few lifetime extenders and a couple notifications + mCollection.addNotificationLifetimeExtender(mExtender1); + mCollection.addNotificationLifetimeExtender(mExtender2); + mCollection.addNotificationLifetimeExtender(mExtender3); + + mExtender1.shouldExtendLifetime = true; + mExtender2.shouldExtendLifetime = true; + + PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); + PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); + NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); + + // GIVEN a notification gets lifetime-extended by a couple of them + mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); + assertTrue(mCollection.getNotifs().contains(entry2)); + clearInvocations(mExtender1, mExtender2, mExtender3); + + // WHEN a lifetime extender makes a reentrant call during cancelLifetimeExtension() + mExtender2.onCancelLifetimeExtension = () -> { + mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2); + }; + // This triggers the call to cancelLifetimeExtension() + mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); + + // THEN an exception is thrown + } + + @Test + public void testRankingIsUpdatedWhenALifetimeExtendedNotifIsReposted() { + // GIVEN a few lifetime extenders and a couple notifications + mCollection.addNotificationLifetimeExtender(mExtender1); + mCollection.addNotificationLifetimeExtender(mExtender2); + mCollection.addNotificationLifetimeExtender(mExtender3); + + mExtender1.shouldExtendLifetime = true; + mExtender2.shouldExtendLifetime = true; + + PostedNotif notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47)); + PostedNotif notif2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88)); + NotificationEntry entry2 = mCollectionListener.getEntry(notif2.key); + + // GIVEN a notification gets lifetime-extended by a couple of them + mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN); + assertTrue(mCollection.getNotifs().contains(entry2)); + clearInvocations(mExtender1, mExtender2, mExtender3); + + // WHEN the notification is reposted + PostedNotif notif2a = mNoMan.postNotif(buildNotif(TEST_PACKAGE2, 88) + .setRank(4747) + .setExplanation("Some new explanation")); + + // THEN the notification's ranking is properly updated + assertEquals(notif2a.ranking, entry2.ranking()); + } + + private static NotificationEntryBuilder buildNotif(String pkg, int id, String tag) { + return new NotificationEntryBuilder() + .setPkg(pkg) + .setId(id) + .setTag(tag); + } + + private static NotificationEntryBuilder buildNotif(String pkg, int id) { + return new NotificationEntryBuilder() + .setPkg(pkg) + .setId(id); + } + + private static class NoManSimulator { + private final NotifServiceListener mListener; + private final Map<String, Ranking> mRankings = new ArrayMap<>(); + + private NoManSimulator( + NotifServiceListener listener) { + mListener = listener; + } + + PostedNotif postNotif(NotificationEntryBuilder builder) { + NotificationEntry entry = builder.build(); + mRankings.put(entry.key(), entry.ranking()); + mListener.onNotificationPosted(entry.sbn(), buildRankingMap()); + return new PostedNotif(entry.sbn(), entry.ranking()); + } + + void retractNotif(StatusBarNotification sbn, int reason) { + assertNotNull(mRankings.remove(sbn.getKey())); + mListener.onNotificationRemoved(sbn, buildRankingMap(), reason); + } + + void issueRankingUpdate() { + mListener.onNotificationRankingUpdate(buildRankingMap()); + } + + void setRanking(String key, Ranking ranking) { + mRankings.put(key, ranking); + } + + private RankingMap buildRankingMap() { + return new RankingMap(mRankings.values().toArray(new Ranking[0])); + } + } + + private static class PostedNotif { + public final String key; + public final StatusBarNotification sbn; + public final Ranking ranking; + + private PostedNotif(StatusBarNotification sbn, + Ranking ranking) { + this.key = sbn.getKey(); + this.sbn = sbn; + this.ranking = ranking; + } + } + + private static class RecordingCollectionListener implements NotifCollectionListener { + private final Map<String, NotificationEntry> mLastSeenEntries = new ArrayMap<>(); + + @Override + public void onEntryAdded(NotificationEntry entry) { + mLastSeenEntries.put(entry.key(), entry); + } + + @Override + public void onEntryUpdated(NotificationEntry entry) { + } + + @Override + public void onEntryRemoved(NotificationEntry entry, int reason, boolean removedByUser) { + } + + public NotificationEntry getEntry(String key) { + if (!mLastSeenEntries.containsKey(key)) { + throw new RuntimeException("Key not found: " + key); + } + return mLastSeenEntries.get(key); + } + } + + private static class RecordingLifetimeExtender implements NotifLifetimeExtender { + private final String mName; + + public @Nullable OnEndLifetimeExtensionCallback callback; + public boolean shouldExtendLifetime = false; + public @Nullable Runnable onCancelLifetimeExtension; + + private RecordingLifetimeExtender(String name) { + mName = name; + } + + @Override + public String getName() { + return mName; + } + + @Override + public void setCallback(OnEndLifetimeExtensionCallback callback) { + this.callback = callback; + } + + @Override + public boolean shouldExtendLifetime( + NotificationEntry entry, + @CancellationReason int reason) { + return shouldExtendLifetime; + } + + @Override + public void cancelLifetimeExtension(NotificationEntry entry) { + if (onCancelLifetimeExtension != null) { + onCancelLifetimeExtension.run(); + } + } + } + + private static final String TEST_PACKAGE = "com.android.test.collection"; + private static final String TEST_PACKAGE2 = "com.android.test.collection2"; +} 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 59c76a8cbb77..1be6f6178d46 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,6 +74,7 @@ import com.android.systemui.InitController; 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; @@ -137,13 +138,14 @@ public class NotificationDataTest extends SysuiTestCase { 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(); + mRow = new NotificationTestHelper(getContext(), mDependency).createRow(); Dependency.get(InitController.class).executePostInitTasks(); } @@ -159,10 +161,11 @@ public class NotificationDataTest extends SysuiTestCase { @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()); @@ -189,7 +192,8 @@ public class NotificationDataTest extends SysuiTestCase { @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<>(); @@ -221,7 +225,8 @@ public class NotificationDataTest extends SysuiTestCase { 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( 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 c56a168a29d9..5e6c96313d36 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 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { @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 cc89504cb54d..444a6e5b4b13 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 @@ -88,7 +88,7 @@ public class NotificationBlockingHelperManagerTest extends SysuiTestCase { 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. 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 ccadcc35f37a..71c2e11f2fc4 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 @@ public class NotificationContentInflaterTest extends SysuiTestCase { .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 db6b6130ebf1..41fe1735b5b2 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 androidx.test.filters.SmallTest; 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 @@ public class NotificationGutsManagerTest extends SysuiTestCase { 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 49a64101eb84..d280f185edd3 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 @@ public class NotificationCustomViewWrapperTest extends SysuiTestCase { @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 4f844f067566..4f45f680f475 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 @@ public class NotificationMediaTemplateViewWrapperTest extends SysuiTestCase { 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 546315900275..14e2fded6cdc 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 class NotificationViewWrapperTest extends SysuiTestCase { 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 22d2585130fd..ddd2884ec311 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 @@ public class NotificationChildrenContainerTest extends SysuiTestCase { @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 3f467eae1d57..34a309f1d80c 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 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { 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 @@ public class NotificationRoundnessManagerTest extends SysuiTestCase { 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 11ae0cc34787..4b82f59e2a35 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.SysuiTestCase; 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.collection.NotificationEntry; 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 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { 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 f614354a7691..16f02d9dd661 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 static org.mockito.Mockito.verify; 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 androidx.test.filters.SmallTest; 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 class AutoHideControllerTest extends SysuiTestCase { 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 ff9aae793f08..72bea564d2bf 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 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { private Handler mHandler; @Mock private KeyguardBypassController mKeyguardBypassController; + @Mock + private DozeParameters mDozeParameters; private BiometricUnlockController mBiometricUnlockController; @Before @@ -92,7 +94,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { 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 000000000000..df1233af6406 --- /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 60050b182e0f..debc840394b5 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 a38094da3e1c..0216d2effef3 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.NotificationTestHelper; 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 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { 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 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { 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 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { 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 ef9665a80848..6fcf550c56bc 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 android.view.View; 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.NotificationEntryBuilder; 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 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { 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 688a6fbc6d78..2b091f297184 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 @@ import org.mockito.MockitoAnnotations @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 cb87d7d842a4..67b8e07f2bec 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 @@ public class KeyguardBouncerTest extends SysuiTestCase { 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 93fdce173f12..b1580eedc43c 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 android.testing.TestableLooper; 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 class LightBarTransitionsControllerTest extends SysuiTestCase { 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 81e8abf5a058..098a69f5a897 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 androidx.test.filters.SmallTest; 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 @@ public class NavigationBarButtonTest extends SysuiTestCase { (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 be69f5f8a844..6433376cd2a9 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.filters.SmallTest; 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 @@ public class NavigationBarContextTest extends SysuiTestCase { @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 cec7feb23752..991e49588417 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 android.widget.FrameLayout; 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 @@ public class NavigationBarInflaterViewTest extends SysuiTestCase { @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 bb109bd931de..1e9378aea075 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 android.view.IWindowManager; 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 @@ public class NavigationBarTransitionsTest extends SysuiTestCase { @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 a0d264d81c7c..225423415421 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 android.testing.TestableLooper; 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 @@ public class NotificationGroupAlertTransferHelperTest extends SysuiTestCase { @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 dd274c7c09b9..493b74dd66cf 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 android.testing.TestableLooper; 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 @@ public class NotificationGroupManagerTest extends SysuiTestCase { @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 95e9e67cb830..cff663562ba8 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 @@ -60,6 +60,7 @@ 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; @@ -115,6 +116,7 @@ public class NotificationPanelViewTest extends SysuiTestCase { private FalsingManager mFalsingManager; @Mock private KeyguardBypassController mKeyguardBypassController; + @Mock private DozeParameters mDozeParameters; private NotificationPanelView mNotificationPanelView; @Before @@ -129,10 +131,11 @@ public class NotificationPanelViewTest extends SysuiTestCase { 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, @@ -141,7 +144,7 @@ public class NotificationPanelViewTest extends SysuiTestCase { mStatusBarStateController, new FalsingManagerFake()); mNotificationPanelView = new TestableNotificationPanelView(coordinator, expansionHandler, - mKeyguardBypassController); + mKeyguardBypassController, mStatusBarStateController); mNotificationPanelView.setHeadsUpManager(mHeadsUpManager); mNotificationPanelView.setBar(mPanelBar); @@ -218,7 +221,8 @@ public class NotificationPanelViewTest extends SysuiTestCase { private class TestableNotificationPanelView extends NotificationPanelView { TestableNotificationPanelView(NotificationWakeUpCoordinator coordinator, PulseExpansionHandler expansionHandler, - KeyguardBypassController bypassController) { + KeyguardBypassController bypassController, + SysuiStatusBarStateController statusBarStateController) { super( NotificationPanelViewTest.this.mContext, null, @@ -235,7 +239,10 @@ public class NotificationPanelViewTest extends SysuiTestCase { new NotificationEntryManager(new NotificationData(mock( NotificationSectionsFeatureManager.class), mock(NotifLog.class)), mock(NotifLog.class)), - mock(DozeLog.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 214e26a4a2b5..f3ff7ec68bb2 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 @@ public class ScrimControllerTest extends SysuiTestCase { } @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 aafcdd09fc10..3e07cff9b09b 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.keyguard.DismissCallbackRegistry; 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 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { 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 266abcfc788f..d8a68b0c230c 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 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase { 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 24ec1097ea8b..210c9d6470fa 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 androidx.test.filters.SmallTest; 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 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { @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 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { 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 4b6ca56fc8e3..a65f5a503375 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.Mockito.when; 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 @@ import org.mockito.MockitoAnnotations; @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 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase { 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 12e9be15354f..52b3720125c8 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 @@ -91,6 +91,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.FeatureFlags; import com.android.systemui.statusbar.KeyguardIndicationController; import com.android.systemui.statusbar.NavigationBarController; import com.android.systemui.statusbar.NotificationEntryBuilder; @@ -107,7 +108,7 @@ import com.android.systemui.statusbar.StatusBarStateControllerImpl; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.notification.BypassHeadsUpNotifier; import com.android.systemui.statusbar.notification.DynamicPrivacyController; -import com.android.systemui.statusbar.notification.NotifPipelineInitializer; +import com.android.systemui.statusbar.notification.NewNotifPipeline; import com.android.systemui.statusbar.notification.NotificationAlertingManager; import com.android.systemui.statusbar.notification.NotificationEntryListener; import com.android.systemui.statusbar.notification.NotificationEntryManager; @@ -156,6 +157,7 @@ public class StatusBarTest extends SysuiTestCase { private TestableNotificationInterruptionStateProvider mNotificationInterruptionStateProvider; private CommandQueue mCommandQueue; + @Mock private FeatureFlags mFeatureFlags; @Mock private LightBarController mLightBarController; @Mock private StatusBarIconController mStatusBarIconController; @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @@ -205,7 +207,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private KeyguardBypassController mKeyguardBypassController; @Mock private InjectionInflationController mInjectionInflationController; @Mock private DynamicPrivacyController mDynamicPrivacyController; - @Mock private NotifPipelineInitializer mNotifPipelineInitializer; + @Mock private NewNotifPipeline mNewNotifPipeline; @Mock private ZenModeController mZenModeController; @Mock private AutoHideController mAutoHideController; @Mock private NotificationViewHierarchyManager mNotificationViewHierarchyManager; @@ -220,6 +222,7 @@ public class StatusBarTest extends SysuiTestCase { @Mock private StatusBarWindowViewController.Builder mStatusBarWindowViewControllerBuilder; @Mock private StatusBarWindowViewController mStatusBarWindowViewController; @Mock private NotifLog mNotifLog; + @Mock private DozeParameters mDozeParameters; @Before public void setup() throws Exception { @@ -286,6 +289,8 @@ public class StatusBarTest extends SysuiTestCase { .thenReturn(mStatusBarWindowViewController); mStatusBar = new StatusBar( + mContext, + mFeatureFlags, mLightBarController, mAutoHideController, mKeyguardUpdateMonitor, @@ -300,7 +305,7 @@ public class StatusBarTest extends SysuiTestCase { mDynamicPrivacyController, mBypassHeadsUpNotifier, true, - mNotifPipelineInitializer, + () -> mNewNotifPipeline, new FalsingManagerFake(), mBroadcastDispatcher, new RemoteInputQuickSettingsDisabler( @@ -342,10 +347,10 @@ public class StatusBarTest extends SysuiTestCase { configurationController, mStatusBarWindowController, mStatusBarWindowViewControllerBuilder, - mNotifLog); + 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; 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 4ffaeaef77b4..a21a658348c4 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 android.view.WindowManager; 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 @@ import org.mockito.MockitoAnnotations; @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 class StatusBarWindowControllerTest extends SysuiTestCase { 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 @@ public class StatusBarWindowControllerTest extends SysuiTestCase { @Test public void testOnThemeChanged_doesntCrash() { - mStatusBarWindowController = new StatusBarWindowController(mContext, mWindowManager, - mActivityManager, mDozeParameters, mStatusBarStateController, - mConfigurationController, mKeyguardBypassController); mStatusBarWindowController.onThemeChanged(); } @@ -109,9 +104,6 @@ public class StatusBarWindowControllerTest extends SysuiTestCase { @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 9f4dfb4453fc..7c1dfa6c4f54 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.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.policy.KeyguardStateController; import com.android.systemui.tuner.TunerService; import com.android.systemui.util.InjectionInflationController; @@ -61,11 +63,14 @@ public class StatusBarWindowViewTest extends SysuiTestCase { @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 @@ public class StatusBarWindowViewTest extends SysuiTestCase { 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 d16dc168d74c..943674a7bf64 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.MetricsLogger; 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 @@ public class KeyButtonViewTest extends SysuiTestCase { 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 000000000000..e57bbc1623f0 --- /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 bc468bf2fb82..390e812b3613 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 @@ public class RemoteInputViewTest extends SysuiTestCase { @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 @@ public class RemoteInputViewTest extends SysuiTestCase { 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 @@ public class RemoteInputViewTest extends SysuiTestCase { @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 0d56cbe84eb0..b5e4cb965de8 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.SysuiTestCase; 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 @@ public class SmartReplyViewTest extends SysuiTestCase { }); 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 5b826b1c551b..b0e401bdda8a 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -2584,15 +2584,15 @@ message MetricsEvent { // 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/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 91269c7ab37e..68e11df32d79 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.PackageManager; 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.io.PrintWriter; 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 @@ import java.util.function.Consumer; */ public class AccessibilityManagerService extends IAccessibilityManager.Stub implements AbstractAccessibilityServiceConnection.SystemSupport, + AccessibilityUserState.ServiceInfoChangeListener, AccessibilityWindowManager.AccessibilityEventSender, AccessibilitySecurityPolicy.AccessibilityUserManager { @@ -178,12 +169,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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; @@ -226,7 +211,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private boolean mIsAccessibilityButtonShown; - private UserState getCurrentUserStateLocked() { + private AccessibilityUserState getCurrentUserStateLocked() { return getUserStateLocked(mCurrentUserId); } @@ -293,6 +278,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return mIsAccessibilityButtonShown; } + @Override + public void onServiceInfoChangedLocked(AccessibilityUserState userState) { + scheduleNotifyClientsOfServicesStateChangeLocked(userState); + } + @Nullable public FingerprintGestureDispatcher getFingerprintGestureDispatcher() { return mFingerprintGestureDispatcher; @@ -307,40 +297,40 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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,9 +366,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (userId != mCurrentUserId) { return; } - UserState userState = getUserStateLocked(userId); - boolean reboundAService = userState.mBindingServices.removeIf( + final AccessibilityUserState userState = getUserStateLocked(userId); + final boolean reboundAService = userState.getBindingServicesLocked().removeIf( component -> component != null + && component.getPackageName().equals(packageName)) + || userState.mCrashedServices.removeIf(component -> component != null && component.getPackageName().equals(packageName)); if (reboundAService) { onUserStateChangedLocked(userState); @@ -395,14 +387,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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); + userState.getCrashedServicesLocked().remove(comp); // Update the enabled services setting. persistComponentNamesToSettingLocked( Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, @@ -430,7 +423,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +434,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return true; } it.remove(); - userState.mBindingServices.remove(comp); + userState.getBindingServicesLocked().remove(comp); persistComponentNamesToSettingLocked( Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, userState.mEnabledServices, userId); @@ -478,7 +471,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } 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 +502,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // 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 +510,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +522,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub + " and userId:" + mCurrentUserId); } return IntPair.of( - (resolvedUserId == mCurrentUserId) ? userState.getClientState() : 0, + (resolvedUserId == mCurrentUserId) ? getClientStateLocked(userState) : 0, client.mLastSentRelevantEventTypes); } } @@ -644,7 +637,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub .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(); } @@ -754,15 +747,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } 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.getCrashedServicesLocked().clear(); userState.mTouchExplorationGrantedServices.clear(); userState.mTouchExplorationGrantedServices.add(service); @@ -943,7 +937,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } // 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 +954,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // 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 +973,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +991,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub synchronized (mLock) { int parentUserId = mSecurityPolicy.resolveProfileParentLocked(userId); if (parentUserId == mCurrentUserId) { - UserState userState = getUserStateLocked(mCurrentUserId); + AccessibilityUserState userState = getUserStateLocked(mCurrentUserId); onUserStateChangedLocked(userState); } } @@ -1015,7 +1009,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +1019,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub onUserStateChangedLocked(userState); } + private int getClientStateLocked(AccessibilityUserState userState) { + return userState.getClientStateLocked(mUiAutomationManager.isUiAutomationRunningLocked()); + } + private InteractionBridge getInteractionBridge() { synchronized (mLock) { if (mInteractionBridge == null) { @@ -1044,7 +1042,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // 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 +1054,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } 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 +1063,17 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +1085,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return; } if (potentialTargets == 1) { - if (state.mIsNavBarMagnificationEnabled) { + if (state.isNavBarMagnificationEnabledLocked()) { mMainHandler.sendMessage(obtainMessage( AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this, displayId)); @@ -1110,13 +1100,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } } 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 +1115,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +1144,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } 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 +1155,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - private boolean readInstalledAccessibilityServiceLocked(UserState userState) { + private boolean readInstalledAccessibilityServiceLocked(AccessibilityUserState userState) { mTempAccessibilityServiceInfoList.clear(); int flags = PackageManager.GET_SERVICES @@ -1174,7 +1164,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; - if (userState.getBindInstantServiceAllowed()) { + if (userState.getBindInstantServiceAllowedLocked()) { flags |= PackageManager.MATCH_INSTANT; } @@ -1192,6 +1182,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub AccessibilityServiceInfo accessibilityServiceInfo; try { accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext); + if (userState.mCrashedServices.contains(serviceInfo.getComponentName())) { + // Restore the crashed attribute. + accessibilityServiceInfo.crashed = true; + } mTempAccessibilityServiceInfoList.add(accessibilityServiceInfo); } catch (XmlPullParserException | IOException xppe) { Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe); @@ -1209,7 +1203,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return false; } - private boolean readInstalledAccessibilityShortcutLocked(UserState userState) { + private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState) { final List<AccessibilityShortcutInfo> shortcutInfos = AccessibilityManager .getInstance(mContext).getInstalledAccessibilityShortcutListAsUser( mContext, mCurrentUserId); @@ -1221,7 +1215,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +1230,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } private boolean readTouchExplorationGrantedAccessibilityServicesLocked( - UserState userState) { + AccessibilityUserState userState) { mTempComponentNameSet.clear(); readComponentNamesFromSettingLocked( Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES, @@ -1261,7 +1255,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +1270,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - private void updateRelevantEventsLocked(UserState userState) { + private void updateRelevantEventsLocked(AccessibilityUserState userState) { mMainHandler.post(() -> { broadcastToClients(userState, ignoreRemoteException(client -> { int relevantEventTypes; @@ -1296,7 +1290,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub }); } - private int computeRelevantEventTypesLocked(UserState userState, Client client) { + private int computeRelevantEventTypesLocked(AccessibilityUserState userState, Client client) { int relevantEventTypes = 0; int serviceCount = userState.mBoundServices.size(); @@ -1342,20 +1336,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } 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 +1407,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - private void updateServicesLocked(UserState userState) { + private void updateServicesLocked(AccessibilityUserState userState) { Map<ComponentName, AccessibilityServiceConnection> componentNameToServiceMap = userState.mComponentNameToServiceMap; boolean isUnlockingOrUnlocked = LocalServices.getService(UserManagerInternal.class) @@ -1441,8 +1426,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub continue; } - // Wait for the binding if it is in process. - if (userState.mBindingServices.contains(componentName)) { + // Skip the component since it may be in process or crashed. + if (userState.getBindingServicesLocked().contains(componentName) + || userState.getCrashedServicesLocked().contains(componentName)) { continue; } if (userState.mEnabledServices.contains(componentName) @@ -1478,15 +1464,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +1494,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub client -> client.setState(clientState))); } - private void scheduleNotifyClientsOfServicesStateChangeLocked(UserState userState) { + private void scheduleNotifyClientsOfServicesStateChangeLocked( + AccessibilityUserState userState) { updateRecommendedUiTimeoutLocked(userState); mMainHandler.sendMessage(obtainMessage( AccessibilityManagerService::sendServicesStateChanged, @@ -1527,44 +1514,45 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +1585,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +1596,40 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +1640,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * * @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 +1658,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +1690,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - 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 +1712,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - 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 +1733,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub && (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 +1755,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +1769,18 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - 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 +1789,40 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +1832,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +1845,61 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - 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 +1908,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +1925,22 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * * @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 +1956,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } 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 +1986,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return false; } - private void updateMagnificationLocked(UserState userState) { + private void updateMagnificationLocked(AccessibilityUserState userState) { if (userState.mUserId != mCurrentUserId) { return; } @@ -2012,8 +2001,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub // 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 +2026,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * 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 +2041,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub * 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 +2054,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return false; } - private void updateFingerprintGestureHandling(UserState userState) { + private void updateFingerprintGestureHandling(AccessibilityUserState userState) { final List<AccessibilityServiceConnection> services; synchronized (mLock) { services = userState.mBoundServices; @@ -2097,7 +2086,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - 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 +2096,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - 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 +2121,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub newInteractiveUiTimeout = serviceInteractiveUiTimeout; } } - userState.mNonInteractiveUiTimeout = newNonInteractiveUiTimeout; - userState.mInteractiveUiTimeout = newInteractiveUiTimeout; + userState.setNonInteractiveUiTimeoutLocked(newNonInteractiveUiTimeout); + userState.setInteractiveUiTimeoutLocked(newInteractiveUiTimeout); } @GuardedBy("mLock") @@ -2180,8 +2169,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +2207,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub "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 +2226,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +2243,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +2311,14 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @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 +2327,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +2377,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub @Override public void onClientChangeLocked(boolean serviceInfoChanged) { - AccessibilityManagerService.UserState userState = getUserStateLocked(mCurrentUserId); + AccessibilityUserState userState = getUserStateLocked(mCurrentUserId); onUserStateChangedLocked(userState); if (serviceInfoChanged) { scheduleNotifyClientsOfServicesStateChangeLocked(userState); @@ -2474,7 +2405,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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(); } @@ -2593,7 +2524,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +2549,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +2589,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 +2599,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - 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 +2679,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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)) { @@ -3074,6 +2696,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } else if (mEnabledAccessibilityServicesUri.equals(uri)) { if (readEnabledAccessibilityServicesLocked(userState)) { + userState.updateCrashedServicesIfNeededLocked(); onUserStateChangedLocked(userState); } } else if (mTouchExplorationGrantedAccessibilityServicesUri.equals(uri)) { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java index e7f3ccc9f883..a0a755a30cb3 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.provider.Settings; 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,21 +51,18 @@ import java.util.Set; 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; - 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, @@ -74,7 +70,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect SystemActionPerformer systemActionPerfomer, AccessibilityWindowManager awm) { super(context, componentName, accessibilityServiceInfo, id, mainHandler, lock, securityPolicy, systemSupport, windowManagerInternal, systemActionPerfomer, awm); - mUserStateWeakReference = new WeakReference<UserState>(userState); + mUserStateWeakReference = new WeakReference<AccessibilityUserState>(userState); mIntent = new Intent().setComponent(mComponentName); mMainHandler = mainHandler; mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL, @@ -89,13 +85,13 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } 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 +105,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect 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 +119,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @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 +152,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } } mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service); - UserState userState = mUserStateWeakReference.get(); + AccessibilityUserState userState = mUserStateWeakReference.get(); if (userState == null) return; userState.addServiceLocked(this); mSystemSupport.onClientChangeLocked(false); @@ -169,20 +165,21 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @Override public AccessibilityServiceInfo getServiceInfo() { - // Update crashed data - mAccessibilityServiceInfo.crashed = mWasConnectedAndDied; return mAccessibilityServiceInfo; } 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) { + final Set<ComponentName> bindingServices = userState.getBindingServicesLocked(); + final Set<ComponentName> crashedServices = userState.getCrashedServicesLocked(); + if (bindingServices.contains(mComponentName) + || crashedServices.contains(mComponentName)) { bindingServices.remove(mComponentName); - mWasConnectedAndDied = false; + crashedServices.remove(mComponentName); + mAccessibilityServiceInfo.crashed = false; serviceInterface = mServiceInterface; } // There's a chance that service is removed from enabled_accessibility_services setting @@ -240,7 +237,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect 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 +245,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect @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 +255,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect if (!hasRightsToCurrentUserLocked()) { return false; } - UserState userState = mUserStateWeakReference.get(); + AccessibilityUserState userState = mUserStateWeakReference.get(); return (userState != null) && isAccessibilityButtonAvailableLocked(userState); } } @@ -272,8 +269,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect if (!isConnectedLocked()) { return; } - mWasConnectedAndDied = true; - UserState userState = mUserStateWeakReference.get(); + mAccessibilityServiceInfo.crashed = true; + AccessibilityUserState userState = mUserStateWeakReference.get(); if (userState != null) { userState.serviceDisconnectedLocked(this); } @@ -283,7 +280,7 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } } - 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 +292,8 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect } // 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 +311,14 @@ class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnect 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 000000000000..a0b9866e24d2 --- /dev/null +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java @@ -0,0 +1,616 @@ +/* + * 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> mCrashedServices = 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(); + mCrashedServices.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 four states to a service here: off, bound, and binding, and crashed. + * This drops a service from a bound state, to the crashed state. + * The crashed state describes the situation where a service used to be bound, but no longer is + * despite still being enabled. + * + * @param serviceConnection The service. + */ + void serviceDisconnectedLocked(AccessibilityServiceConnection serviceConnection) { + removeServiceLocked(serviceConnection); + mCrashedServices.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; + } + + /** + * Returns binding service list. + */ + Set<ComponentName> getBindingServicesLocked() { + return mBindingServices; + } + + /** + * Returns crashed service list. + */ + Set<ComponentName> getCrashedServicesLocked() { + return mCrashedServices; + } + + /** + * Returns enabled service list. + */ + Set<ComponentName> getEnabledServicesLocked() { + return mEnabledServices; + } + + /** + * Remove service from crashed service list if users disable it. + */ + void updateCrashedServicesIfNeededLocked() { + for (int i = 0, count = mInstalledServices.size(); i < count; i++) { + final AccessibilityServiceInfo installedService = mInstalledServices.get(i); + final ComponentName componentName = ComponentName.unflattenFromString( + installedService.getId()); + + if (mCrashedServices.contains(componentName) + && !mEnabledServices.contains(componentName)) { + // Remove it from mCrashedServices since users toggle the switch bar to retry. + mCrashedServices.remove(componentName); + } + } + } + + 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("}"); + pw.append(" Crashed services:{"); + it = mCrashedServices.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/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java index b35300ceb399..7d129eaa5390 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 @@ final class SaveUi { 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 de48f4b13d7b..30ce4cf2fd3f 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 @@ public class TransportManager { 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 e5e11ea2253e..ac006df7f475 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 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { 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 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { // 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 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { 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 c8dbb363bc36..27824afb8d46 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 @@ package com.android.server.contentcapture; 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 @@ public final class ContentCaptureManagerService extends 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 @@ public final class ContentCaptureManagerService extends 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/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 1d666adff561..dc24cffa54a4 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -59,6 +59,8 @@ public abstract class PackageManagerInternal { 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, @@ -72,6 +74,8 @@ public abstract class PackageManagerInternal { PACKAGE_CONFIGURATOR, PACKAGE_INCIDENT_REPORT_APPROVER, PACKAGE_APP_PREDICTOR, + PACKAGE_TELEPHONY, + PACKAGE_WIFI, }) @Retention(RetentionPolicy.SOURCE) public @interface KnownPackage {} @@ -546,10 +550,11 @@ public abstract class PackageManagerInternal { */ 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 9a97ddb3e3a7..b41e95fee15c 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.PackageManager.NameNotFoundException; 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 @@ class AlarmManagerService extends SystemService { Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, UserHandle.ALL); mClockReceiver = mInjector.getClockReceiver(this); + new ChargingReceiver(); new InteractiveStateReceiver(); new UninstallReceiver(); @@ -4148,7 +4150,7 @@ class AlarmManagerService extends SystemService { 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 @@ class AlarmManagerService extends SystemService { } break; - case APP_STANDBY_PAROLE_CHANGED: + case CHARGING_STATUS_CHANGED: synchronized (mLock) { mAppStandbyParole = (Boolean) msg.obj; if (reorderAlarmsBasedOnStandbyBuckets(null)) { @@ -4247,6 +4249,37 @@ class AlarmManagerService extends SystemService { } } + @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 @@ class AlarmManagerService extends SystemService { @Override public void onUidCachedChanged(int uid, boolean cached) { } - }; + } /** * Tracking of app assignments to standby buckets @@ -4447,18 +4480,7 @@ class AlarmManagerService extends SystemService { 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 2c67c5046c6d..da760b6f7ffd 100644 --- a/services/core/java/com/android/server/AppStateTracker.java +++ b/services/core/java/com/android/server/AppStateTracker.java @@ -71,8 +71,7 @@ import java.util.List; * - 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 @@ public class AppStateTracker { 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 e0f60b43f8c8..0bb72cb9d554 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -1149,7 +1149,6 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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/GnssManagerService.java b/services/core/java/com/android/server/GnssManagerService.java index b6e619effb08..44a823475b7e 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 @@ public class GnssManagerService { * @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 @@ public class GnssManagerService { } 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 @@ public class GnssManagerService { 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 @@ public class GnssManagerService { } 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 @@ public class GnssManagerService { return addGnssDataListenerLocked( listener, packageName, - "GnssStatusListener", + "Gnss status", mGnssStatusProvider, mGnssStatusListeners, this::unregisterGnssStatusCallback); @@ -632,12 +636,13 @@ public class GnssManagerService { * @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 @@ public class GnssManagerService { * @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 09f62ff3fc56..aa22feb56534 100644 --- a/services/core/java/com/android/server/LocationManagerService.java +++ b/services/core/java/com/android/server/LocationManagerService.java @@ -1223,8 +1223,10 @@ public class LocationManagerService extends ILocationManager.Stub { 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 @@ public class LocationManagerService extends ILocationManager.Stub { } @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 @@ public class LocationManagerService extends ILocationManager.Stub { } } - 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 @@ public class LocationManagerService extends ILocationManager.Stub { @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 @@ public class LocationManagerService extends ILocationManager.Stub { @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 @@ public class LocationManagerService extends ILocationManager.Stub { @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 @@ public class LocationManagerService extends ILocationManager.Stub { 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 @@ public class LocationManagerService extends ILocationManager.Stub { 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 @@ public class LocationManagerService extends ILocationManager.Stub { } // 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 @@ public class LocationManagerService extends ILocationManager.Stub { @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 @@ public class LocationManagerService extends ILocationManager.Stub { 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 @@ public class LocationManagerService extends ILocationManager.Stub { } @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 @@ public class LocationManagerService extends ILocationManager.Stub { } @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 @@ public class LocationManagerService extends ILocationManager.Stub { 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 @@ public class LocationManagerService extends ILocationManager.Stub { 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); diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 0f8a3b54acc5..447ed59a775e 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -385,7 +385,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { 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/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index a502ff24e5b8..83ad4e7e7100 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -104,6 +104,7 @@ public class Watchdog extends Thread { "android.hardware.audio@2.0::IDevicesFactory", "android.hardware.audio@4.0::IDevicesFactory", "android.hardware.audio@5.0::IDevicesFactory", + "android.hardware.audio@6.0::IDevicesFactory", "android.hardware.biometrics.face@1.0::IBiometricsFace", "android.hardware.bluetooth@1.0::IBluetoothHci", "android.hardware.camera.provider@2.4::ICameraProvider", diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 8c672a118843..b1df26bf4d40 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -283,7 +283,6 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.DebugUtils; import android.util.EventLog; -import android.util.FeatureFlagUtils; import android.util.Log; import android.util.Pair; import android.util.PrintWriterPrinter; @@ -381,7 +380,6 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.StringWriter; -import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -555,6 +553,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Max character limit for a notification title. If the notification title is larger than this // the notification will not be legible to the user. private static final int MAX_BUGREPORT_TITLE_SIZE = 50; + private static final int MAX_BUGREPORT_DESCRIPTION_SIZE = 150; private static final int NATIVE_DUMP_TIMEOUT_MS = 2000; // 2 seconds; private static final int JAVA_DUMP_MINIMUM_SIZE = 100; // 100 bytes. @@ -900,7 +899,7 @@ public class ActivityManagerService extends IActivityManager.Stub // The other observer methods are unused @Override - public void onIntentStarted(Intent intent) { + public void onIntentStarted(Intent intent, long timestampNs) { } @Override @@ -912,7 +911,11 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void onActivityLaunchFinished(byte[] finalActivity) { + public void onActivityLaunchFinished(byte[] finalActivity, long timestampNs) { + } + + @Override + public void onReportFullyDrawn(byte[] finalActivity, long timestampNs) { } }; @@ -8247,24 +8250,18 @@ public class ActivityManagerService extends IActivityManager.Stub } /** - * @deprecated This method is only used by a few internal components and it will soon start - * using bug report API (which will be restricted to a few, pre-defined apps). - * No new code should be calling it. + * Takes a bugreport using bug report API ({@code BugreportManager}) with no pre-set + * title and description */ - // TODO(b/137825297): Remove deprecated annotation and rephrase comments for all - // requestBugreport functions below. - @Deprecated @Override - public void requestBugReport(int bugreportType) { + public void requestBugReport(@BugreportParams.BugreportMode int bugreportType) { requestBugReportWithDescription(null, null, bugreportType); } /** - * @deprecated This method is only used by a few internal components and it will soon start - * using bug report API (which will be restricted to a few, pre-defined apps). - * No new code should be calling it. + * Takes a bugreport using bug report API ({@code BugreportManager}) which gets + * triggered by sending a broadcast to Shell. */ - @Deprecated @Override public void requestBugReportWithDescription(@Nullable String shareTitle, @Nullable String shareDescription, int bugreportType) { @@ -8299,60 +8296,45 @@ public class ActivityManagerService extends IActivityManager.Stub if (!TextUtils.isEmpty(shareTitle)) { if (shareTitle.length() > MAX_BUGREPORT_TITLE_SIZE) { - String errorStr = "shareTitle should be less than " + - MAX_BUGREPORT_TITLE_SIZE + " characters"; + String errorStr = "shareTitle should be less than " + + MAX_BUGREPORT_TITLE_SIZE + " characters"; throw new IllegalArgumentException(errorStr); } if (!TextUtils.isEmpty(shareDescription)) { - int length = shareDescription.getBytes(StandardCharsets.UTF_8).length; - if (length > SystemProperties.PROP_VALUE_MAX) { - String errorStr = "shareTitle should be less than " + - SystemProperties.PROP_VALUE_MAX + " bytes"; + if (shareDescription.length() > MAX_BUGREPORT_DESCRIPTION_SIZE) { + String errorStr = "shareDescription should be less than " + + MAX_BUGREPORT_DESCRIPTION_SIZE + " characters"; throw new IllegalArgumentException(errorStr); - } else { - SystemProperties.set("dumpstate.options.description", shareDescription); } } - SystemProperties.set("dumpstate.options.title", shareTitle); Slog.d(TAG, "Bugreport notification title " + shareTitle + " description " + shareDescription); } - final boolean useApi = FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.USE_BUGREPORT_API); - - if (useApi) { - // Create intent to trigger Bugreport API via Shell - Intent triggerShellBugreport = new Intent(); - triggerShellBugreport.setAction(INTENT_BUGREPORT_REQUESTED); - triggerShellBugreport.setPackage(SHELL_APP_PACKAGE); - triggerShellBugreport.putExtra(EXTRA_BUGREPORT_TYPE, bugreportType); - triggerShellBugreport.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - triggerShellBugreport.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); - if (shareTitle != null) { - triggerShellBugreport.putExtra(EXTRA_TITLE, shareTitle); - } - if (shareDescription != null) { - triggerShellBugreport.putExtra(EXTRA_DESCRIPTION, shareDescription); - } - final long identity = Binder.clearCallingIdentity(); - try { - // Send broadcast to shell to trigger bugreport using Bugreport API - mContext.sendBroadcast(triggerShellBugreport); - } finally { - Binder.restoreCallingIdentity(identity); - } - } else { - SystemProperties.set("dumpstate.options", type); - SystemProperties.set("ctl.start", "bugreport"); + // Create intent to trigger Bugreport API via Shell + Intent triggerShellBugreport = new Intent(); + triggerShellBugreport.setAction(INTENT_BUGREPORT_REQUESTED); + triggerShellBugreport.setPackage(SHELL_APP_PACKAGE); + triggerShellBugreport.putExtra(EXTRA_BUGREPORT_TYPE, bugreportType); + triggerShellBugreport.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + triggerShellBugreport.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); + if (shareTitle != null) { + triggerShellBugreport.putExtra(EXTRA_TITLE, shareTitle); + } + if (shareDescription != null) { + triggerShellBugreport.putExtra(EXTRA_DESCRIPTION, shareDescription); + } + final long identity = Binder.clearCallingIdentity(); + try { + // Send broadcast to shell to trigger bugreport using Bugreport API + mContext.sendBroadcast(triggerShellBugreport); + } finally { + Binder.restoreCallingIdentity(identity); } } /** - * @deprecated This method is only used by a few internal components and it will soon start - * using bug report API (which will be restricted to a few, pre-defined apps). - * No new code should be calling it. + * Takes a telephony bugreport with title and description */ - @Deprecated @Override public void requestTelephonyBugReport(String shareTitle, String shareDescription) { requestBugReportWithDescription(shareTitle, shareDescription, @@ -8360,11 +8342,8 @@ public class ActivityManagerService extends IActivityManager.Stub } /** - * @deprecated This method is only used by a few internal components and it will soon start - * using bug report API (which will be restricted to a few, pre-defined apps). - * No new code should be calling it. + * Takes a minimal bugreport of Wifi-related state with pre-set title and description */ - @Deprecated @Override public void requestWifiBugReport(String shareTitle, String shareDescription) { requestBugReportWithDescription(shareTitle, shareDescription, @@ -8658,7 +8637,7 @@ public class ActivityManagerService extends IActivityManager.Stub 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); } diff --git a/services/core/java/com/android/server/am/AppErrorDialog.java b/services/core/java/com/android/server/am/AppErrorDialog.java index a80a5b5211ea..852c9b65e3c0 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 @@ final class AppErrorDialog extends BaseErrorDialog implements View.OnClickListen 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 cb76e2fafebd..65d7e01a3e0c 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 @@ final class AppNotRespondingDialog extends BaseErrorDialog implements View.OnCli 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/UserSwitchingDialog.java b/services/core/java/com/android/server/am/UserSwitchingDialog.java index 98f5557903d6..bbf57728a20a 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 @@ class UserSwitchingDialog extends AlertDialog 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 6c4cc2d43866..2ac6eb01f9da 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -68,6 +68,7 @@ import android.hardware.camera2.CameraDevice.CAMERA_AUDIO_RESTRICTION; 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; @@ -1398,6 +1399,12 @@ public class AppOpsService extends IAppOpsService.Stub { } 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)) { diff --git a/services/core/java/com/android/server/appop/TEST_MAPPING b/services/core/java/com/android/server/appop/TEST_MAPPING index 1a5dac503463..e9d2b312e01c 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/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java index 027e2fb5ccaa..0fabd9aef373 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 android.util.Slog; 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 @@ public final class CompatConfig { } 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/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java index a7378880a91d..8a7dcc12baae 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.Slog; 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,6 +101,17 @@ public class PlatformCompat extends IPlatformCompat.Stub { } @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); diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java index 96ba8ef6b20e..648e07a01c1c 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 @@ final class Constants { 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 b4c7dd310dd0..080e6cea8333 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 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction { 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 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction { 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 61d4d4bf9ac1..dde873b5ef97 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 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { 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 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { 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 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { } // 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 413e7a087434..09443242d499 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; 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 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { 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 7e6e6680905e..362955d589af 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.Display; 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 @@ public class InputManagerService extends IInputManager.Stub 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 @@ public class InputManagerService extends IInputManager.Stub 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 @@ public class InputManagerService extends IInputManager.Stub throw new IllegalArgumentException("displayId must >= 0."); } - final long ident = Binder.clearCallingIdentity(); try { InputChannel[] inputChannels = InputChannel.openInputChannelPair(inputChannelName); @@ -537,29 +531,25 @@ public class InputManagerService extends IInputManager.Stub 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 @@ public class InputManagerService extends IInputManager.Stub @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 @@ public class InputManagerService extends IInputManager.Stub // 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 @@ public class InputManagerService extends IInputManager.Stub * @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 b7fcd3f19334..471fa72b1957 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub 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 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // 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/RuleEvaluator.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java index 4ed581d6e915..641650557f4c 100644 --- a/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java +++ b/services/core/java/com/android/server/integrity/engine/RuleEvaluator.java @@ -37,6 +37,9 @@ final class 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. @@ -45,7 +48,7 @@ final class RuleEvaluator { */ static Rule evaluateRules(List<Rule> rules, AppInstallMetadata appInstallMetadata) { for (Rule rule : rules) { - if (isMatch(rule, appInstallMetadata)) { + if (isConjunctionOfFormulas(rule.getFormula()) && isMatch(rule, appInstallMetadata)) { return rule; } } @@ -88,11 +91,8 @@ final class RuleEvaluator { // NOT connector has only 1 formula attached. return !isMatch(openFormula.getFormulas().get(0), appInstallMetadata); case AND: - boolean result = true; - for (Formula subFormula : openFormula.getFormulas()) { - result &= isMatch(subFormula, appInstallMetadata); - } - return result; + return openFormula.getFormulas().stream().allMatch( + subFormula -> isMatch(subFormula, appInstallMetadata)); default: Slog.i(TAG, String.format("Returned no match for unknown connector %s", openFormula.getConnector())); @@ -102,4 +102,25 @@ final class RuleEvaluator { 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 dde8c2a31537..b9b46e3e1aae 100644 --- a/services/core/java/com/android/server/integrity/model/AtomicFormula.java +++ b/services/core/java/com/android/server/integrity/model/AtomicFormula.java @@ -22,6 +22,8 @@ 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. * @@ -130,11 +132,6 @@ public final class AtomicFormula extends Formula { return mBoolValue.toString(); } - @Override - public String toString() { - return String.format("%s %s %s", mKey, mOperator, getValue()); - } - /** * Check if the formula is true when substituting its {@link Key} with the string value. * @@ -188,6 +185,32 @@ public final class AtomicFormula extends Formula { 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) { 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 5ba9f46f5aba..21da629fba2e 100644 --- a/services/core/java/com/android/server/integrity/model/OpenFormula.java +++ b/services/core/java/com/android/server/integrity/model/OpenFormula.java @@ -21,6 +21,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import java.util.Collections; import java.util.List; +import java.util.Objects; /** * Represents a complex formula consisting of other simple and complex formulas. @@ -64,6 +65,24 @@ public final class OpenFormula extends Formula { return sb.toString(); } + @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: 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 41611d0f7154..63b9b911ff4f 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 @@ package com.android.server.integrity.model; 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. * @@ -66,4 +68,22 @@ public final class Rule { 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 da31d0b8e9a3..61e5d1f31f73 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 class CallerIdentity { 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 a1922067e7cf..895c4b388eeb 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 class GeofenceManager implements LocationListener, PendingIntent.OnFinish } 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 @@ public class GeofenceManager implements LocationListener, PendingIntent.OnFinish 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 3ebe20a7e8e6..fe0719df1ced 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 class GeofenceState { 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 @@ public class GeofenceState { 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 c6226fa98197..8bf01a366f65 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.ProviderProperties; 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 @@ public class GnssLocationProvider extends AbstractLocationProvider implements 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 @@ public class GnssLocationProvider extends AbstractLocationProvider implements 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 class GnssLocationProvider extends AbstractLocationProvider implements 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 @@ public class GnssLocationProvider extends AbstractLocationProvider implements 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 @@ public class GnssLocationProvider extends AbstractLocationProvider implements } 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 @@ public class GnssLocationProvider extends AbstractLocationProvider implements 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 aa8a25a36333..0929d93513b9 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 @@ public abstract class RemoteListenerHelper<TListener extends IInterface> { } 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 bad484fa3807..34fb64190e8e 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.ILockSettings; 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 @@ public class LockSettingsService extends ILockSettings.Stub { @UserIdInt int userHandle) { synchronized (this) { mUserPasswordMetrics.put(userHandle, - PasswordMetrics.computeForCredential(credentialType, password)); + PasswordMetrics.computeForCredential( + LockscreenCredential.createRaw(credentialType, password))); } } @@ -2069,7 +2071,7 @@ public class LockSettingsService extends ILockSettings.Stub { // 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 a5d59e3c8884..0a8e5bd7ad4b 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 @@ class LockSettingsShellCommand extends ShellCommand { 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 @@ class LockSettingsShellCommand extends ShellCommand { } 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 559461485042..f4cad634ac1a 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 @@ class LockSettingsStorage { @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 @@ class LockSettingsStorage { 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 e753a7be99f6..4816ceb5d76c 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java @@ -43,7 +43,7 @@ import java.util.Objects; * Maintains a connection to a particular media route provider service. */ final class MediaRoute2ProviderProxy implements ServiceConnection { - private static final String TAG = "MediaRoute2Provider"; + private static final String TAG = "MR2ProviderProxy"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final Context mContext; @@ -93,7 +93,7 @@ final class MediaRoute2ProviderProxy implements ServiceConnection { public void unselectRoute(String packageName, String routeId) { if (mConnectionReady) { - mActiveConnection.unselectRotue(packageName, routeId); + mActiveConnection.unselectRoute(packageName, routeId); updateBinding(); } } @@ -105,6 +105,20 @@ final class MediaRoute2ProviderProxy implements ServiceConnection { } } + 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; @@ -260,7 +274,9 @@ final class MediaRoute2ProviderProxy implements ServiceConnection { .setUniqueId(mUniqueId) .build(); } - mHandler.post(mStateChanged); + if (mCallback != null) { + mCallback.onProviderStateChanged(MediaRoute2ProviderProxy.this); + } } private void disconnect() { @@ -277,15 +293,6 @@ final class MediaRoute2ProviderProxy implements ServiceConnection { return "Service connection " + mComponentName.flattenToShortString(); } - private final Runnable mStateChanged = new Runnable() { - @Override - public void run() { - if (mCallback != null) { - mCallback.onProviderStateChanged(MediaRoute2ProviderProxy.this); - } - } - }; - public interface Callback { void onProviderStateChanged(MediaRoute2ProviderProxy provider); } @@ -324,7 +331,7 @@ final class MediaRoute2ProviderProxy implements ServiceConnection { } } - public void unselectRotue(String packageName, String routeId) { + public void unselectRoute(String packageName, String routeId) { try { mProvider.unselectRoute(packageName, routeId); } catch (RemoteException ex) { @@ -340,6 +347,22 @@ final class MediaRoute2ProviderProxy implements ServiceConnection { } } + 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/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java index 194015d306de..c95119da6d25 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java @@ -35,9 +35,10 @@ import java.util.ArrayList; import java.util.Collections; /** + * Watches changes of packages, or scan them for finding media route providers. */ final class MediaRoute2ProviderWatcher { - private static final String TAG = "MediaRouteProvider"; // max. 23 chars + private static final String TAG = "MR2ProviderWatcher"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final Context mContext; diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 668f2be158c6..361dc3673665 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -55,7 +55,7 @@ import java.util.Objects; * TODO: Merge this to MediaRouterService once it's finished. */ class MediaRouter2ServiceImpl { - private static final String TAG = "MediaRouter2ServiceImpl"; + private static final String TAG = "MR2ServiceImpl"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final Context mContext; @@ -198,6 +198,34 @@ class MediaRouter2ServiceImpl { } } + 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 @@ class MediaRouter2ServiceImpl { } } + 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"); @@ -280,11 +339,11 @@ class MediaRouter2ServiceImpl { int uid, int pid, String packageName, int userId, boolean trusted) { final IBinder binder = client.asBinder(); if (mAllClientRecords.get(binder) == null) { - boolean newUser = false; UserRecord userRecord = mUserRecords.get(userId); if (userRecord == null) { userRecord = new UserRecord(userId); - newUser = true; + mUserRecords.put(userId, userRecord); + initializeUserLocked(userRecord); } Client2Record clientRecord = new Client2Record(userRecord, client, uid, pid, packageName, trusted); @@ -294,11 +353,6 @@ class MediaRouter2ServiceImpl { throw new RuntimeException("Media router client died prematurely.", ex); } - if (newUser) { - mUserRecords.put(userId, userRecord); - initializeUserLocked(userRecord); - } - userRecord.mClientRecords.add(clientRecord); mAllClientRecords.put(binder, clientRecord); @@ -362,6 +416,30 @@ class MediaRouter2ServiceImpl { } } + 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 +502,31 @@ class MediaRouter2ServiceImpl { } } + 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 +782,20 @@ class MediaRouter2ServiceImpl { } } + 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; @@ -693,8 +810,6 @@ class MediaRouter2ServiceImpl { if (service == null) { return; } - final List<IMediaRouter2Manager> managers = new ArrayList<>(); - final List<IMediaRouter2Client> clients = new ArrayList<>(); final List<MediaRoute2ProviderInfo> providers = new ArrayList<>(); for (MediaRoute2ProviderProxy mediaProvider : mMediaProviders) { final MediaRoute2ProviderInfo providerInfo = @@ -707,6 +822,8 @@ class MediaRouter2ServiceImpl { } mProviderInfos = providers; + final List<IMediaRouter2Manager> managers = new ArrayList<>(); + final List<IMediaRouter2Client> clients = new ArrayList<>(); synchronized (service.mLock) { for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) { managers.add(managerRecord.mManager); @@ -756,9 +873,8 @@ class MediaRouter2ServiceImpl { } List<IMediaRouter2Manager> managers = new ArrayList<>(); synchronized (service.mLock) { - final int count = mUserRecord.mManagerRecords.size(); - for (int i = 0; i < count; i++) { - managers.add(mUserRecord.mManagerRecords.get(i).mManager); + for (ManagerRecord managerRecord : mUserRecord.mManagerRecords) { + managers.add(managerRecord.mManager); } } for (IMediaRouter2Manager manager : managers) { diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index 796a25d7e295..afd92f61b9d3 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 @@ public final class MediaRouterService extends IMediaRouterService.Stub 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 976a0c663101..09be474a5598 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.GuardedBy; 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { writePolicyAL(); } + enableFirewallChainUL(FIREWALL_CHAIN_STANDBY, true); setRestrictBackgroundUL(mLoadedRestrictBackground); updateRulesForGlobalChangeAL(false); updateNotificationsNL(); @@ -1509,6 +1510,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } /** - * 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { * * @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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } 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); + } catch (RemoteException ignored) { + } + } + } + + private void dispatchSubscriptionPlansChanged(INetworkPolicyListener listener, int subId, + SubscriptionPlan[] plans) { if (listener != null) { try { - listener.onSubscriptionOverride(subId, overrideMask, overrideValue, - networkTypeMask); + listener.onSubscriptionPlansChanged(subId, plans); } catch (RemoteException ignored) { } } @@ -4550,16 +4548,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { 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 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } /** - * 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 61be1f5e559b..6f0ad33244e4 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 @@ public interface NotificationDelegate { 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 4293ccadd1fb..cd3343bbec7b 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.CATEGORY_CALL; 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_ALLOW_WHITELIST_MANAGEMENT; 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_FULL 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 @@ public class NotificationManagerService extends SystemService { } } } + + @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 @@ public class NotificationManagerService extends SystemService { } @GuardedBy("mNotificationLock") - private void clearSoundLocked() { + void clearSoundLocked() { mSoundNotificationKey = null; long identity = Binder.clearCallingIdentity(); try { @@ -1200,7 +1250,7 @@ public class NotificationManagerService extends SystemService { } @GuardedBy("mNotificationLock") - private void clearVibrateLocked() { + void clearVibrateLocked() { mVibrateNotificationKey = null; long identity = Binder.clearCallingIdentity(); try { @@ -4497,13 +4547,13 @@ public class NotificationManagerService extends SystemService { 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"; } } @@ -5062,8 +5112,8 @@ public class NotificationManagerService extends SystemService { } 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; @@ -5251,18 +5301,6 @@ public class NotificationManagerService extends SystemService { + 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; } @@ -6074,7 +6112,6 @@ public class NotificationManagerService extends SystemService { 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); @@ -6090,7 +6127,6 @@ public class NotificationManagerService extends SystemService { } if (aboveThreshold && isNotificationForCurrentUser(record)) { - if (mSystemReady && mAudioManager != null) { Uri soundUri = record.getSound(); hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri); @@ -6105,7 +6141,6 @@ public class NotificationManagerService extends SystemService { vibration = mFallbackVibrationPattern; } hasValidVibrate = vibration != null; - boolean hasAudibleAlert = hasValidSound || hasValidVibrate; if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) { if (!sentAccessibilityEvent) { @@ -6262,11 +6297,29 @@ public class NotificationManagerService extends SystemService { 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) @@ -6318,7 +6371,6 @@ public class NotificationManagerService extends SystemService { 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) { @@ -7026,7 +7078,6 @@ public class NotificationManagerService extends SystemService { 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 9e7b46485d4c..5f3e50320752 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 @@ public final class OverlayManagerService extends SystemService { } 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 @@ public final class OverlayManagerService extends SystemService { 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/TEST_MAPPING b/services/core/java/com/android/server/os/TEST_MAPPING new file mode 100644 index 000000000000..502f1e852e08 --- /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 05b61689fdcf..c8179a767d23 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -131,7 +131,7 @@ public class AppsFilter { private static class FeatureConfigImpl implements FeatureConfig { private static final String FILTERING_ENABLED_NAME = "package_query_filtering_enabled"; - private volatile boolean mFeatureEnabled = true; + private volatile boolean mFeatureEnabled = false; private CompatConfig mCompatibility; private FeatureConfigImpl(PackageManagerService.Injector injector) { @@ -141,12 +141,12 @@ public class AppsFilter { @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); } }); } diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java index 976cdfbf8f60..b1eb7e79bc1b 100644 --- a/services/core/java/com/android/server/pm/ComponentResolver.java +++ b/services/core/java/com/android/server/pm/ComponentResolver.java @@ -50,6 +50,7 @@ import android.util.Pair; 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; @@ -386,8 +387,11 @@ public class ComponentResolver { 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 @@ -421,8 +425,11 @@ public class ComponentResolver { 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"); 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 000000000000..b0cf525c830a --- /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 a7d423784b4b..eca93bbd808e 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 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements 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 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements 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 b72029046067..d8bfa7df0218 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mOriginalInstallerUid = installerUid; mInstallerPackageName = installerPackageName; mInstallerUid = installerUid; + mInstallSource = Preconditions.checkNotNull(installSource); this.params = params; this.createdMillis = createdMillis; this.updatedMillis = createdMillis; @@ -1225,6 +1233,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mInstallerPackageName = packageName; mInstallerUid = newOwnerAppInfo.uid; + mInstallSource = InstallSource.create(packageName); } // Persist the fact that we've sealed ourselves to prevent @@ -1443,7 +1452,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mRelinquished = true; return new PackageManagerService.ActiveInstallSession(mPackageName, stageDir, - localObserver, params, mInstallerPackageName, mInstallerUid, user, + localObserver, params, mInstallerPackageName, mInstallerUid, mInstallSource, user, mSigningDetails); } @@ -2336,6 +2345,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { 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 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { 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/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index d675e36faae6..d057aa22eb45 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -974,124 +974,9 @@ public class PackageManagerService extends IPackageManager.Stub @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<>(); @@ -1603,6 +1488,8 @@ public class PackageManagerService extends IPackageManager.Stub 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; @@ -1675,7 +1562,8 @@ public class PackageManagerService extends IPackageManager.Stub } // 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; @@ -2163,7 +2051,7 @@ public class PackageManagerService extends IPackageManager.Stub // 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); } } @@ -2805,8 +2693,6 @@ public class PackageManagerService extends IPackageManager.Stub systemScanFlags | partition.scanFlag, 0); } - mParallelPackageParserCallback.findStaticOverlayPackages(); - scanDirTracedLI(frameworkDir, systemParseFlags, systemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED, 0); if (!mPackages.containsKey("android")) { @@ -3058,6 +2944,8 @@ public class PackageManagerService extends IPackageManager.Stub 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. @@ -8466,7 +8354,7 @@ public class PackageManagerService extends IPackageManager.Stub } try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser( mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir, - mParallelPackageParserCallback)) { + mPackageParserCallback)) { // Submit files for parsing in parallel int fileCount = 0; for (File file : files) { @@ -10682,6 +10570,50 @@ public class PackageManagerService extends IPackageManager.Stub 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. @@ -10849,6 +10781,10 @@ public class PackageManagerService extends IPackageManager.Stub pkg.applicationInfo.initForUser(UserHandle.USER_SYSTEM); } + if (pkg.isSystem()) { + configurePackageComponents(pkg); + } + final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting); if ((scanFlags & SCAN_NEW_INSTALL) == 0) { @@ -13979,6 +13915,7 @@ public class PackageManagerService extends IPackageManager.Stub final IPackageInstallObserver2 observer; int installFlags; final String installerPackageName; + final InstallSource installSource; final String volumeUuid; private boolean mVerificationCompleted; private boolean mEnableRollbackCompleted; @@ -13995,10 +13932,11 @@ public class PackageManagerService extends IPackageManager.Stub 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; @@ -14006,6 +13944,7 @@ public class PackageManagerService extends IPackageManager.Stub this.observer = observer; this.installFlags = installFlags; this.installerPackageName = installerPackageName; + this.installSource = installSource; this.volumeUuid = volumeUuid; this.verificationInfo = verificationInfo; this.packageAbiOverride = packageAbiOverride; @@ -14037,6 +13976,7 @@ public class PackageManagerService extends IPackageManager.Stub observer = activeInstallSession.getObserver(); installFlags = activeInstallSession.getSessionParams().installFlags; installerPackageName = activeInstallSession.getInstallerPackageName(); + installSource = activeInstallSession.getInstallSource(); volumeUuid = activeInstallSession.getSessionParams().volumeUuid; packageAbiOverride = activeInstallSession.getSessionParams().abiOverride; grantedRuntimePermissions = activeInstallSession.getSessionParams() @@ -14519,6 +14459,7 @@ public class PackageManagerService extends IPackageManager.Stub // Always refers to PackageManager flags only final int installFlags; final String installerPackageName; + final InstallSource installSource; final String volumeUuid; final UserHandle user; final String abiOverride; @@ -14537,7 +14478,8 @@ public class PackageManagerService extends IPackageManager.Stub /* 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, @@ -14549,6 +14491,7 @@ public class PackageManagerService extends IPackageManager.Stub this.installFlags = installFlags; this.observer = observer; this.installerPackageName = installerPackageName; + this.installSource = installSource; this.volumeUuid = volumeUuid; this.user = user; this.instructionSets = instructionSets; @@ -14562,6 +14505,16 @@ public class PackageManagerService extends IPackageManager.Stub 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); @@ -14642,17 +14595,12 @@ public class PackageManagerService extends IPackageManager.Stub /** 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; @@ -14831,12 +14779,7 @@ public class PackageManagerService extends IPackageManager.Stub /** 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() { @@ -15095,38 +15038,38 @@ public class PackageManagerService extends IPackageManager.Stub 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) { @@ -15162,6 +15105,9 @@ public class PackageManagerService extends IPackageManager.Stub 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<>(); @@ -15805,8 +15751,7 @@ public class PackageManagerService extends IPackageManager.Stub } 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) { @@ -15816,8 +15761,7 @@ public class PackageManagerService extends IPackageManager.Stub 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( @@ -16064,10 +16008,6 @@ public class PackageManagerService extends IPackageManager.Stub * 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; @@ -16076,24 +16016,16 @@ public class PackageManagerService extends IPackageManager.Stub 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; @@ -16101,7 +16033,6 @@ public class PackageManagerService extends IPackageManager.Stub this.packageToScan = packageToScan; this.clearCodeCache = clearCodeCache; this.system = system; - this.renamedPackage = renamedPackage; this.freezer = freezer; this.originalPs = originalPs; this.disabledPs = disabledPs; @@ -16141,8 +16072,6 @@ public class PackageManagerService extends IPackageManager.Stub 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); @@ -16567,14 +16496,12 @@ public class PackageManagerService extends IPackageManager.Stub 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, @@ -16826,9 +16753,8 @@ public class PackageManagerService extends IPackageManager.Stub // 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) { @@ -17509,7 +17435,7 @@ public class PackageManagerService extends IPackageManager.Stub 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 " @@ -19797,6 +19723,16 @@ public class PackageManagerService extends IPackageManager.Stub } @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; @@ -20039,7 +19975,7 @@ public class PackageManagerService extends IPackageManager.Stub 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); @@ -20071,7 +20007,8 @@ public class PackageManagerService extends IPackageManager.Stub } 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); @@ -20082,6 +20019,9 @@ public class PackageManagerService extends IPackageManager.Stub 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. @@ -20329,6 +20269,34 @@ public class PackageManagerService extends IPackageManager.Stub }, 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 @@ -21909,6 +21877,7 @@ public class PackageManagerService extends IPackageManager.Stub final String currentVolumeUuid; final File codeFile; final String installerPackageName; + final InstallSource installSource; final String packageAbiOverride; final int appId; final String seinfo; @@ -21966,6 +21935,7 @@ public class PackageManagerService extends IPackageManager.Stub 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; @@ -22111,7 +22081,7 @@ public class PackageManagerService extends IPackageManager.Stub 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); @@ -22869,34 +22839,38 @@ public class PackageManagerService extends IPackageManager.Stub } @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 @@ -23994,18 +23968,21 @@ public class PackageManagerService extends IPackageManager.Stub 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; } @@ -24034,6 +24011,10 @@ public class PackageManagerService extends IPackageManager.Stub 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 1c1c947c0a9e..52a7c6ef4b71 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 @@ class PackageManagerShellCommand extends ShellCommand { * @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 4ea8a30fa206..be0621bf298a 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 @@ public final class PackageSetting extends PackageSettingBase { 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 0da6b549f296..f0857dd4de84 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_DEFAULT; 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 @@ public abstract class PackageSettingBase extends SettingBase { 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 @@ public abstract class PackageSettingBase extends SettingBase { ? 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 @@ public abstract class PackageSettingBase extends SettingBase { 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 @@ public abstract class PackageSettingBase extends SettingBase { 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 @@ public abstract class PackageSettingBase extends SettingBase { 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 0db6e79ba2ed..5e209965c05a 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 @@ public final class Settings { 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 @@ public final class Settings { 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 @@ public final class Settings { 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 @@ public final class Settings { 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 @@ public final class Settings { 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 @@ public final class Settings { } 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 @@ public final class Settings { 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 036d1e807f97..323c957acdd0 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 @@ class UserSystemPackageInstaller { } 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 6d22faa7032e..037912a66b3a 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 final class BasePermission { 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 793cdd2f2f6d..f247037edf5c 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 @@ public final class DefaultPermissionGrantPolicy { // 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 @@ public final class DefaultPermissionGrantPolicy { 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 @@ public final class DefaultPermissionGrantPolicy { } } - 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 89908f04a6b3..5c657521e784 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { // 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { return; } - mPermissionControllerManager.restoreDelayedRuntimePermissionBackup(packageName, user, + mPermissionControllerManager.applyStagedRuntimePermissionBackup(packageName, user, mContext.getMainExecutor(), (hasMoreBackup) -> { if (hasMoreBackup) { return; @@ -3078,8 +3078,9 @@ public class PermissionManagerService extends IPermissionManager.Stub { } } } - 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { // 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 @@ public class PermissionManagerService extends IPermissionManager.Stub { 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/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index c851cc69a732..2593c38180e2 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_SUB_WINDOW; 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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 @@ public class PhoneWindowManager implements WindowManagerPolicy { 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; diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index eb648b33c4ca..befe4e968b12 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -548,12 +548,16 @@ public final class PowerManagerService extends SystemService private final class ForegroundProfileObserver extends SynchronousUserSwitchObserver { @Override - public void onUserSwitching(int newUserId) throws RemoteException {} + public void onUserSwitching(@UserIdInt int newUserId) throws RemoteException { + synchronized (mLock) { + mUserId = newUserId; + } + } @Override public void onForegroundProfileSwitch(@UserIdInt int newProfileId) throws RemoteException { final long now = SystemClock.uptimeMillis(); - synchronized(mLock) { + synchronized (mLock) { mForegroundProfile = newProfileId; maybeUpdateForegroundProfileLastActivityLocked(now); } @@ -562,6 +566,8 @@ public final class PowerManagerService extends SystemService // User id corresponding to activity the user is currently interacting with. private @UserIdInt int mForegroundProfile; + // User id of main profile for the current user (doesn't include managed profiles) + private @UserIdInt int mUserId; // Per-profile state to track when a profile should be locked. private final SparseArray<ProfilePowerState> mProfilePowerState = new SparseArray<>(); @@ -1792,9 +1798,9 @@ public final class PowerManagerService extends SystemService if (mBootCompleted) { if (mIsPowered && !BatteryManager.isPlugWired(oldPlugType) && BatteryManager.isPlugWired(mPlugType)) { - mNotifier.onWiredChargingStarted(mForegroundProfile); + mNotifier.onWiredChargingStarted(mUserId); } else if (dockedOnWirelessCharger) { - mNotifier.onWirelessChargingStarted(mBatteryLevel, mForegroundProfile); + mNotifier.onWirelessChargingStarted(mBatteryLevel, mUserId); } } } @@ -3493,6 +3499,7 @@ public final class PowerManagerService extends SystemService pw.println(" mDoubleTapWakeEnabled=" + mDoubleTapWakeEnabled); pw.println(" mIsVrModeEnabled=" + mIsVrModeEnabled); pw.println(" mForegroundProfile=" + mForegroundProfile); + pw.println(" mUserId=" + mUserId); final long sleepTimeout = getSleepTimeoutLocked(); final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 3439d3841973..65bb2342d504 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.graphics.Rect; 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 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @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 3cdb59beb23c..3663f4696a27 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 @@ public class WallpaperManagerService extends IWallpaperManager.Stub saveSettingsLocked(mWallpaper.userId); } FgThread.getHandler().removeCallbacks(mResetRunnable); + mContext.getMainThreadHandler().removeCallbacks(this::tryToRebind); } } } @@ -1270,6 +1271,34 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } + 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 @@ public class WallpaperManagerService extends IWallpaperManager.Stub 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/ActivityDisplay.java b/services/core/java/com/android/server/wm/ActivityDisplay.java index 9d9a37c13bdd..673366f34fc2 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 @@ class ActivityDisplay extends ConfigurationContainer<ActivityStack> { 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 eff0f75466d9..c6b17e24b1de 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 @@ import java.lang.annotation.RetentionPolicy; * 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 @@ import java.lang.annotation.RetentionPolicy; * 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 @@ public interface ActivityMetricsLaunchObserver { * 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 @@ public interface ActivityMetricsLaunchObserver { * 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 @@ public interface ActivityMetricsLaunchObserver { * 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 a0a296792680..e6c6b12e18c6 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.logging.MetricsLogger; 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 @@ class ActivityMetricsLogger { // 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 @@ class ActivityMetricsLogger { 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 @@ class ActivityMetricsLogger { ? 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 @@ class ActivityMetricsLogger { 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 @@ class ActivityMetricsLogger { * 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 @@ class ActivityMetricsLogger { * @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 @@ class ActivityMetricsLogger { return; } if (DEBUG_METRICS) Slog.i(TAG, "notifyTransitionStarting"); - mCurrentTransitionDelayMs = calculateDelay(timestamp); + mCurrentTransitionDelayMs = calculateDelay(timestampNs); mLoggedTransitionStarting = true; WindowingModeTransitionInfo foundInfo = null; @@ -501,7 +505,8 @@ class ActivityMetricsLogger { 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 @@ class ActivityMetricsLogger { } 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 @@ class ActivityMetricsLogger { 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 @@ class ActivityMetricsLogger { } 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 @@ class ActivityMetricsLogger { 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 @@ class ActivityMetricsLogger { 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 @@ class ActivityMetricsLogger { // 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 @@ class ActivityMetricsLogger { 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 @@ class ActivityMetricsLogger { 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 @@ class ActivityMetricsLogger { } /** 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 @@ class ActivityMetricsLogger { } /** + * 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 @@ class ActivityMetricsLogger { * 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 4f52d9df817a..fb4de01d8f61 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.ActivityTaskManagerDebugConfig.TAG_WITH_CLAS 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 @@ final class ActivityRecord extends AppWindowToken { 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 @@ final class ActivityRecord extends AppWindowToken { 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 @@ final class ActivityRecord extends AppWindowToken { 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); } } @@ -2004,10 +2005,6 @@ final class ActivityRecord extends AppWindowToken { if (stopped) { clearOptionsLocked(); } - - if (mAtmService != null) { - mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged(); - } } /** @@ -2788,12 +2785,12 @@ final class ActivityRecord extends AppWindowToken { 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. @@ -2844,11 +2841,14 @@ final class ActivityRecord extends AppWindowToken { } 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) { @@ -3102,7 +3102,7 @@ final class ActivityRecord extends AppWindowToken { 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()); @@ -3110,13 +3110,13 @@ final class ActivityRecord extends AppWindowToken { } /** 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, @@ -3542,7 +3542,7 @@ final class ActivityRecord extends AppWindowToken { 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); } @@ -4198,12 +4198,19 @@ final class ActivityRecord extends AppWindowToken { 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 595925442053..41c1e4e7fcc5 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.app.WindowConfiguration.windowingModeToString; 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { // 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 @@ class ActivityStack extends ConfigurationContainer { } 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 @@ class ActivityStack extends ConfigurationContainer { && 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { } 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 @@ class ActivityStack extends ConfigurationContainer { // 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { // 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 @@ class ActivityStack extends ConfigurationContainer { } 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { } } else { if (taskInsertionPoint < 0) { - taskInsertionPoint = task.mActivities.size(); + taskInsertionPoint = task.getChildCount(); } @@ -3452,7 +3443,7 @@ class ActivityStack extends ConfigurationContainer { "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 @@ class ActivityStack extends ConfigurationContainer { /** 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { } } 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { } 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { } } finally { mService.continueWindowLayout(); - Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } @@ -4425,9 +4415,9 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { 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 @@ class ActivityStack extends ConfigurationContainer { // 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 f1284d81bfbf..ca7419649182 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.graphics.Rect.copyOrNull; 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 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { 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 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { Binder.restoreCallingIdentity(token); } } finally { - Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } @@ -824,7 +824,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { 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 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { 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 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } finally { mAllowDockedStackResize = true; mService.continueWindowLayout(); - Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } @@ -1717,7 +1717,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { 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 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { !DEFER_RESUME); } finally { mService.continueWindowLayout(); - Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } @@ -1796,6 +1796,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { 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 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { 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 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { 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 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { } 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 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { 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 939789307333..c50545425cc2 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 @@ class ActivityStarter { 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 @@ class ActivityStarter { 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 @@ class ActivityStarter { } /** + * 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 @@ class ActivityStarter { } // 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 @@ class ActivityStarter { // accordingly. if (mTargetStack.isFocusable() && !mRootActivityContainer.isTopDisplayFocusedStack(mTargetStack)) { - mTargetStack.moveToFront("startActivityUnchecked"); + mTargetStack.moveToFront("startActivityInner"); } mRootActivityContainer.resumeFocusedStacksTopActivities( mTargetStack, mStartActivity, mOptions); @@ -1820,7 +1834,9 @@ class ActivityStarter { */ private void complyActivityFlags(TaskRecord targetTask, ActivityRecord reusedActivity) { ActivityRecord targetTaskTop = targetTask.getTopActivity(); - if (reusedActivity != null && (mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { + final boolean resetTask = + reusedActivity != null && (mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0; + if (resetTask) { targetTaskTop = mTargetStack.resetTaskIfNeededLocked(targetTaskTop, mStartActivity); } @@ -1873,7 +1889,7 @@ class ActivityStarter { 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 @@ -1912,7 +1928,7 @@ class ActivityStarter { } else if (reusedActivity == null) { mAddingToTask = true; } - } else if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) { + } else if (!resetTask) { // In this case an activity is being launched in to an existing task, without // resetting that task. This is typically the situation of launching an activity // from a notification or shortcut. We want to place the new activity on top of the @@ -2430,7 +2446,7 @@ class ActivityStarter { 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 ab35652eb525..0488a3b7065b 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 @@ public abstract class ActivityTaskManagerInternal { * * @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 750fc6832627..20113a68fdc6 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_LOW_LEVEL; 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.text.format.DateUtils.MINUTE_IN_MILLIS; 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } final long origId = Binder.clearCallingIdentity(); + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "finishActivity"); try { boolean res; final boolean finishWithRootActivity = @@ -1634,6 +1634,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } return res; } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); Binder.restoreCallingIdentity(origId); } } @@ -1668,6 +1669,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); Binder.restoreCallingIdentity(origId); } } @@ -1708,10 +1711,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } r.activityStoppedLocked(icicle, persistentState, description); } + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } if (restartingName != null) { @@ -1764,12 +1771,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { 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 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public void notifyAppTransitionStarting(SparseIntArray reasons, - long timestamp) { + long timestampNs) { synchronized (mGlobalLock) { mStackSupervisor.getActivityMetricsLogger().notifyTransitionStarting( - reasons, timestamp); + reasons, timestampNs); } } @@ -6797,7 +6783,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @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 93c461f3add9..394b47520492 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_NONE; 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 @@ public class AppTransition implements Dump { 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 20a871baada4..0c07e15b9da5 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_KEYGUARD_GOING_AWAY_ON_WALLPAPE 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 @@ public class AppTransitionController { 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 @@ public class AppTransitionController { 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 @@ public class AppTransitionController { } 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 a261341cfa53..e56fdd244d4f 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 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree 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 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree mReparenting = true; getParent().removeChild(this); - task.addChild(this, position); + task.addChild((ActivityRecord) this, position); mReparenting = false; @@ -2283,7 +2283,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } } - private final Runnable mAddStartingWindow = new Runnable() { + private class AddStartingWindow implements Runnable { @Override public void run() { @@ -2343,7 +2343,9 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree AppWindowToken.this); } } - }; + } + + private final AddStartingWindow mAddStartingWindow = new AddStartingWindow(); private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents, diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index 8afbbdf303ab..300ee1ddc77d 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 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { 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 ac910cde79b0..4c3611e971b4 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -5152,7 +5152,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // 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 7be4dbd99766..60e9819ecbcf 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 @@ public class DisplayPolicy { // 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 @@ public class DisplayPolicy { } /** - * 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 @@ public class DisplayPolicy { * {@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 @@ public class DisplayPolicy { 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 @@ public class DisplayPolicy { } } 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 @@ public class DisplayPolicy { } } } 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 @@ public class DisplayPolicy { // 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 @@ public class DisplayPolicy { || 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 @@ public class DisplayPolicy { 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 34820acfeccc..abd7222feea2 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 @@ class DragState { 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 @@ class DragState { 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/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 7e085f677be9..bc95481dcee9 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 @@ import android.view.WindowInsets; */ class ImeInsetsSourceProvider extends InsetsSourceProvider { - private WindowState mCurImeTarget; + private WindowState mImeTargetFromIme; private Runnable mShowImeRunner; private boolean mIsImeLayoutDrawn; @@ -40,8 +40,8 @@ class ImeInsetsSourceProvider extends InsetsSourceProvider { void onPostLayout() { super.onPostLayout(); - if (mCurImeTarget != null - && mCurImeTarget == mDisplayContent.mInputMethodTarget + if (mImeTargetFromIme != null + && isImeTargetFromDisplayContentAndImeSame() && mWin != null && mWin.isDrawnLw() && !mWin.mGivenInsetsPending) { @@ -64,18 +64,33 @@ class ImeInsetsSourceProvider extends InsetsSourceProvider { /** * 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 d774dc3fd2f1..82ac6b897a8f 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 @@ public class ImmersiveModeConfirmation { | 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 8dae0163b612..ebe9f08f83b7 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 @@ class InputConsumerImpl implements IBinder.DeathRecipient { } 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 ec36a820f5fe..9973e11b94bc 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.Display.DEFAULT_DISPLAY; 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 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal // 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 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal } 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 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal 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 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal // 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 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal 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 932b4fac4592..584c6e19bfa1 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 @@ final class InputMonitor { } } - private final Runnable mUpdateInputWindows = new Runnable() { + private class UpdateInputWindows implements Runnable { @Override public void run() { synchronized (mService.mGlobalLock) { @@ -148,7 +148,9 @@ final class InputMonitor { mUpdateInputForAllWindowsConsumer.updateInputWindows(inDrag); } } - }; + } + + private final UpdateInputWindows mUpdateInputWindows = new UpdateInputWindows(); public InputMonitor(WindowManagerService service, int displayId) { mService = service; @@ -411,7 +413,7 @@ final class InputMonitor { 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 b7184a5e83d5..22ba82ace865 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 @@ interface InsetsControlTarget { */ 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 cc55e0137e20..3731d3f38800 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 @@ class InsetsSourceProvider { @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 2b5eb3ac29fb..52cc422f8c51 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 @@ class KeyguardController { 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 @@ class KeyguardController { 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 93e2d8d6fba4..362ed3c380c5 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 @@ class LaunchObserverRegistryImpl implements } @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 @@ class LaunchObserverRegistryImpl implements @Override public void onActivityLaunchFinished( - @ActivityRecordProto byte[] activity) { + @ActivityRecordProto byte[] activity, + long timestampNs) { + mHandler.sendMessage(PooledLambda.obtainMessage( + LaunchObserverRegistryImpl::handleOnActivityLaunchFinished, + this, + activity, + timestampNs)); + } + + @Override + public void onReportFullyDrawn(@ActivityRecordProto byte[] activity, long timestampNs) { mHandler.sendMessage(PooledLambda.obtainMessage( - LaunchObserverRegistryImpl::handleOnActivityLaunchFinished, this, activity)); + LaunchObserverRegistryImpl::handleOnReportFullyDrawn, + this, + activity, + timestampNs)); } // Use PooledLambda.obtainMessage to invoke below methods. Every method reference must be @@ -116,11 +132,11 @@ class LaunchObserverRegistryImpl implements 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 @@ class LaunchObserverRegistryImpl implements } 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, 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.onActivityLaunchFinished(activity); + o.onReportFullyDrawn(activity, timestampNs); } } } diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java index 94d010e846f5..c59a73b217ad 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 static android.view.SurfaceControl.HIDDEN; 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 @@ public class Letterbox { 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 @@ public class Letterbox { 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 diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index 062cdc5b2b60..12579e6b4058 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.ACTIVITY_TYPE_RECENTS; 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 @@ class RecentsAnimation implements RecentsAnimationCallbacks, 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 @@ class RecentsAnimation implements RecentsAnimationCallbacks, throw e; } finally { mService.continueWindowLayout(); - Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } } @@ -297,7 +297,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, } mWindowManager.inSurfaceTransaction(() -> { - Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RecentsAnimation#onAnimationFinished_inSurfaceTransaction"); mService.deferWindowLayout(); try { @@ -394,7 +394,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, 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 bd27905e1a0f..d606e5d8c1a2 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 @@ public class RecentsAnimationController implements DeathRecipient { 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 @@ public class RecentsAnimationController implements DeathRecipient { } } } + + @Override + public void setWillFinishToHome(boolean willFinishToHome) { + synchronized (mService.getWindowManagerLock()) { + mWillFinishToHome = willFinishToHome; + } + } }; /** @@ -494,7 +503,8 @@ public class RecentsAnimationController implements DeathRecipient { } 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 d78d517fcfc1..60833c38c9c1 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 @@ class RootActivityContainer extends ConfigurationContainer 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/Task.java b/services/core/java/com/android/server/wm/Task.java index a181c1837af7..2657826be416 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 com.android.internal.annotations.VisibleForTesting; 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 @@ class Task extends WindowContainer<AppWindowToken> implements ConfigurationConta // 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 @@ class Task extends WindowContainer<AppWindowToken> implements ConfigurationConta } @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 @@ class Task extends WindowContainer<AppWindowToken> implements ConfigurationConta } @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 @@ class Task extends WindowContainer<AppWindowToken> implements ConfigurationConta 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 b680fa45f00f..478b1b533f4c 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 @@ class TaskPositioner implements IBinder.DeathRecipient { 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 @@ class TaskPositioner implements IBinder.DeathRecipient { 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 75333c728e0b..299b32cce039 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.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VER 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 @@ class TaskRecord extends ConfigurationContainer { } void cleanUpResourcesForDestroy() { - if (!mActivities.isEmpty()) { + if (hasChild()) { return; } @@ -553,7 +553,7 @@ class TaskRecord extends ConfigurationContainer { // 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 @@ class TaskRecord extends ConfigurationContainer { saveLaunchingStateIfNeeded(); - Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); return kept; } finally { mAtmService.continueWindowLayout(); @@ -1095,7 +1095,7 @@ class TaskRecord extends ConfigurationContainer { // There are no non-finishing activities in the task. return null; } - return mActivities.get(rootActivityIndex); + return getChildAt(rootActivityIndex); } ActivityRecord getTopActivity() { @@ -1103,8 +1103,8 @@ class TaskRecord extends ConfigurationContainer { } 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 @@ class TaskRecord extends ConfigurationContainer { 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 @@ class TaskRecord extends ConfigurationContainer { } 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 @@ class TaskRecord extends ConfigurationContainer { * 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 @@ class TaskRecord extends ConfigurationContainer { 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 @@ class TaskRecord extends ConfigurationContainer { 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 @@ class TaskRecord extends ConfigurationContainer { */ 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 @@ class TaskRecord extends ConfigurationContainer { } 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 @@ class TaskRecord extends ConfigurationContainer { 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 @@ class TaskRecord extends ConfigurationContainer { 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 @@ class TaskRecord extends ConfigurationContainer { mAtmService.getTaskChangeNotificationController().notifyTaskStackChanged(); } - if (mActivities.isEmpty()) { + if (!hasChild()) { return !mReuseTask; } updateEffectiveIntent(); @@ -1355,8 +1355,8 @@ class TaskRecord extends ConfigurationContainer { */ 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 @@ class TaskRecord extends ConfigurationContainer { // 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 @@ class TaskRecord extends ConfigurationContainer { * 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 @@ class TaskRecord extends ConfigurationContainer { * 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 @@ class TaskRecord extends ConfigurationContainer { final ActivityRecord ret = r; for (++activityNdx; activityNdx < numActivities; ++activityNdx) { - r = mActivities.get(activityNdx); + r = getChildAt(activityNdx); if (r.finishing) { continue; } @@ -1591,8 +1591,8 @@ class TaskRecord extends ConfigurationContainer { */ 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 @@ class TaskRecord extends ConfigurationContainer { // 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 @@ class TaskRecord extends ConfigurationContainer { 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 @@ class TaskRecord extends ConfigurationContainer { */ 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 @@ class TaskRecord extends ConfigurationContainer { // 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 @@ class TaskRecord extends ConfigurationContainer { } 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 @@ class TaskRecord extends ConfigurationContainer { 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 @@ class TaskRecord extends ConfigurationContainer { 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 @@ class TaskRecord extends ConfigurationContainer { 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/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index f4b76729b7ea..0cb4826fdfc3 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 class WindowManagerInternal { 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 6f9f2c0f9708..c48528042348 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.SurfaceControl; 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 @@ public class WindowManagerService extends IWindowManager.Stub 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; @@ -1578,7 +1582,6 @@ public class WindowManagerService extends IWindowManager.Stub win.attach(); mWindowMap.put(client.asBinder(), win); - win.initAppOpsState(); final boolean suspended = mPmInternal.isPackageSuspended(win.getOwningPackage(), @@ -7310,6 +7313,16 @@ public class WindowManagerService extends IWindowManager.Stub } @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; @@ -7592,7 +7605,7 @@ public class WindowManagerService extends IWindowManager.Stub } private void onPointerDownOutsideFocusLocked(IBinder touchedToken) { - final WindowState touchedWindow = windowForClientLocked(null, touchedToken, false); + final WindowState touchedWindow = mInputToWindowMap.get(touchedToken); if (touchedWindow == null || !touchedWindow.canReceiveKeys()) { return; } @@ -7665,9 +7678,9 @@ public class WindowManagerService extends IWindowManager.Stub 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 56e08b2843b5..7ff9b7057653 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mClientChannel = null; } mWmService.mKeyInterceptionInfoForToken.remove(mInputWindowHandle.token); + mWmService.mInputToWindowMap.remove(mInputWindowHandle.token); mInputWindowHandle.token = null; } @@ -3375,6 +3377,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } } + @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 1328273c9560..eac372f5da81 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -1348,13 +1348,16 @@ class WindowStateAnimator { // 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: @@ -1384,7 +1387,9 @@ class WindowStateAnimator { + " 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 { @@ -1395,7 +1400,6 @@ class WindowStateAnimator { mWin.getDisplayContent().adjustForImeIfNeeded(); } - Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER); return mWin.isAnimating(); } diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java index 56f6d4b02e32..0cfdebc6792d 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.H.REPORT_WINDOWS_CHANGE 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 @@ class WindowSurfacePlacer { /** 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 @@ class WindowSurfacePlacer { return; } - Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmLayout"); mInLayout = true; boolean recoveringMemory = false; @@ -198,8 +200,6 @@ class WindowSurfacePlacer { 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 fb2fdab19a54..dd2629d31768 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 @@ public: 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 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO } 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 handleInputChannelDisposed(JNIEnv* env, } 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 @@ static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */, 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 @@ static const JNINativeMethod gInputManagerMethods[] = { { "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 9ceb7706628a..906b5688d51f 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 android { 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 @@ int enableFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath, jbyteArra 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 @@ int measureFsverity(JNIEnv* env, jobject /* clazz */, jstring filePath) { 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 47790ce68dc1..91c05a858ce4 100644 --- a/services/devicepolicy/Android.bp +++ b/services/devicepolicy/Android.bp @@ -5,4 +5,13 @@ java_library_static { 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 9569ac8dfdd1..6f643c91bee8 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.DevicePolicyManagerInternal; 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.admin.SystemUpdatePolicy; 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 android.view.inputmethod.InputMethodInfo; 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.StatLogger; 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return LocalServices.getService(LockSettingsInternal.class); } + IPlatformCompat getIPlatformCompat() { + return IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); + } + boolean hasUserSetupCompleted(DevicePolicyData userData) { return userData.mUserSetupComplete; } @@ -2222,6 +2239,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { .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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + /** + * 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { */ 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { 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)); - 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); + 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; } - 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { // 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { /** * 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 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } 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 0838fbc536c1..7cfbcc87edc3 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 @@ class RemoteBugreportUtils { 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 4cf98d32f8de..88859a71d135 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -921,7 +921,6 @@ public final class SystemServer { 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"); @@ -1856,7 +1855,7 @@ public final class SystemServer { t.traceEnd(); } - if (!disableSlices) { + if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_SLICES_DISABLED)) { t.traceBegin("StartSliceManagerService"); mSystemServiceManager.startService(SLICE_MANAGER_SERVICE_CLASS); t.traceEnd(); 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 000000000000..4fa882265e53 --- /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 6e8b86add2c4..7b7b8e6c628a 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.verify; 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.junit.Assert.assertNull; 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.app.usage.UsageStatsManagerInternal; 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 @@ public class AlarmManagerServiceTest { private long mAppStandbyWindow; private AlarmManagerService mService; private UsageStatsManagerInternal.AppIdleStateChangeListener mAppStandbyListener; + private AlarmManagerService.ChargingReceiver mChargingReceiver; @Mock private ContentResolver mMockResolver; @Mock @@ -290,6 +293,13 @@ public class AlarmManagerServiceTest { 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 @@ public class AlarmManagerServiceTest { } 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 d0158e0c819f..247a3581c78c 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.test.mock.MockContentResolver; 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.CountDownLatch; 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 2aa625a657ef..9c9730501a78 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 @@ package com.android.server; 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_INACTIVE; 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.anyBoolean; 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.ConnectivityManager; 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.After; 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 @@ import org.mockito.quality.Strictness; @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 @@ public class DeviceIdleControllerTest { @Mock private ContentResolver mContentResolver; @Mock - private DeviceIdleController.MyHandler mHandler; - @Mock private IActivityManager mIActivityManager; @Mock private LocationManager mLocationManager; @@ -171,6 +176,23 @@ public class DeviceIdleControllerTest { @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 @@ public class DeviceIdleControllerTest { } } + 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 @@ public class DeviceIdleControllerTest { 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 @@ public class DeviceIdleControllerTest { 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/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java index edf82ee30e2d..597d337c2450 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.content.pm.ServiceInfo; 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 @@ public class AccessibilityServiceConnectionTest { "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; 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 000000000000..d70e1648f719 --- /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_removeServiceAndAddToCrashed() { + when(mMockConnection.getComponentName()).thenReturn(COMPONENT_NAME); + mUserState.addServiceLocked(mMockConnection); + + mUserState.serviceDisconnectedLocked(mMockConnection); + + assertFalse(mUserState.getBoundServicesLocked().contains(mMockConnection)); + assertTrue(mUserState.getCrashedServicesLocked().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/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java index deb6f71c695b..8da927dcb4ab 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 @@ public class UiAutomationManagerTest { MessageCapturingHandler mMessageCapturingHandler; - @Mock AccessibilityManagerService.UserState mMockUserState; @Mock Context mMockContext; @Mock AccessibilityServiceInfo mMockServiceInfo; @Mock ResolveInfo mMockResolveInfo; 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 9a1fd9cf0e12..aeccfc5310e9 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_HIGH; 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 android.util.Pair; 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 @@ public class DevicePolicyManagerTest extends DpmTestBase { .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 @@ public class DevicePolicyManagerTest extends DpmTestBase { 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 index 1fcd0ef07410..baf1ed00c7b5 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java @@ -127,4 +127,57 @@ public class RuleEvaluatorTest { 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/RuleTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/RuleTest.java index d1fa0f97b071..048ee707d8fe 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,6 +19,7 @@ package com.android.server.integrity.model; 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; @@ -83,4 +84,20 @@ public class RuleTest { 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 f9ac02271a27..537287d18cca 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.ComponentName; 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 @@ public abstract class BaseLockSettingsServiceTests extends AndroidTestCase { 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 c00d33b431c3..b60111ea3333 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 @@ 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 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.filters.SmallTest; 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.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.List; + /** * Test class for {@link LockSettingsShellCommand}. * @@ -87,24 +90,30 @@ public class LockSettingsShellCommandTest { 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 class LockSettingsShellCommandTest { 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 class LockSettingsShellCommandTest { 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 class LockSettingsShellCommandTest { 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 2a169b775ca3..cb5189712685 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 @@ public class LockSettingsStorageTests extends AndroidTestCase { 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 0776589ea47f..42ca42aecf70 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 android.platform.test.annotations.Presubmit; 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 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { // 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 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { 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 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { 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 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { 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 fe7a376d9e8d..25b41db1aea3 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.BatteryManager; 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 @@ public class ConnOnActivityStartTest { 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 @@ public class ConnOnActivityStartTest { 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 @@ public class ConnOnActivityStartTest { @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 @@ public class ConnOnActivityStartTest { } } - 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 ba12b7393048..8a489047f179 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.NetworkStatsHistory; 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 @@ public class NetworkPolicyManagerServiceTest { // 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 @@ public class NetworkPolicyManagerServiceTest { // 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 @@ public class NetworkPolicyManagerServiceTest { 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 @@ public class NetworkPolicyManagerServiceTest { 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 @@ public class NetworkPolicyManagerServiceTest { 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 @@ public class NetworkPolicyManagerServiceTest { 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 @@ public class NetworkPolicyManagerServiceTest { { 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 @@ public class NetworkPolicyManagerServiceTest { { 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 @@ public class NetworkPolicyManagerServiceTest { 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 @@ public class NetworkPolicyManagerServiceTest { 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 43bcd4fc8436..7c2a0978fe31 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 @@ public class PackageInstallerSessionTest { /* 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 4ffcf8fa18b7..12ba219d0365 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 static org.mockito.Mockito.mock; 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.ArrayList; 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 @@ public class AppStandbyControllerTests { 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 @@ public class AppStandbyControllerTests { 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 @@ public class AppStandbyControllerTests { } @Override - boolean isCharging() { - return mIsCharging; - } - - @Override boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException { return mPowerSaveWhitelistExceptIdle.contains(packageName); } @@ -228,8 +218,7 @@ public class AppStandbyControllerTests { 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 @@ public class AppStandbyControllerTests { } 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 @@ public class AppStandbyControllerTests { 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 @@ public class AppStandbyControllerTests { 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 @@ public class AppStandbyControllerTests { 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 @@ public class AppStandbyControllerTests { @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 @@ public class AppStandbyControllerTests { @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 8c3373faa0d4..3ae5674e63a3 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_HIGH; 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 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { 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 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.mScreenOn = false; mService.mInCallStateOffHook = false; mService.mNotificationPulseEnabled = true; + + mChannel = new NotificationChannel("test", "test", IMPORTANCE_HIGH); } // @@ -174,13 +178,18 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { 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 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { 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 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { 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 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { 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 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { 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 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { 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 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // 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 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { } 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 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { 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 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); - verifyBeepLooped(); + verifyBeepUnlooped(); verifyNeverVibrate(); verify(mAccessibilityService, times(1)).sendAccessibilityEvent(any(), anyInt()); assertTrue(r.isInterruptive()); @@ -438,7 +473,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); - verifyBeep(); + verifyBeepLooped(); assertTrue(r.isInterruptive()); assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @@ -492,7 +527,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); - verifyBeepLooped(); + verifyBeepUnlooped(); assertTrue(r.isInterruptive()); } @@ -533,7 +568,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { // 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 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); verifyNeverVibrate(); - verifyBeepLooped(); + verifyBeepUnlooped(); assertTrue(r.isInterruptive()); assertNotEquals(-1, r.getLastAudiblyAlertedMs()); } @@ -821,7 +856,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(summary); - verifyBeepLooped(); + verifyBeepUnlooped(); // summaries are never interruptive for notification counts assertFalse(summary.isInterruptive()); assertNotEquals(-1, summary.getLastAudiblyAlertedMs()); @@ -833,7 +868,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(nonGroup); - verifyBeepLooped(); + verifyBeepUnlooped(); assertTrue(nonGroup.isInterruptive()); assertNotEquals(-1, nonGroup.getLastAudiblyAlertedMs()); } @@ -856,7 +891,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(child); - verifyBeepLooped(); + verifyBeepUnlooped(); assertTrue(child.isInterruptive()); assertNotEquals(-1, child.getLastAudiblyAlertedMs()); } @@ -867,7 +902,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(nonGroup); - verifyBeepLooped(); + verifyBeepUnlooped(); assertTrue(nonGroup.isInterruptive()); assertNotEquals(-1, nonGroup.getLastAudiblyAlertedMs()); } @@ -878,7 +913,7 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(group); - verifyBeepLooped(); + verifyBeepUnlooped(); assertTrue(group.isInterruptive()); assertNotEquals(-1, group.getLastAudiblyAlertedMs()); } @@ -1293,7 +1328,11 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { @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 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { mService.buzzBeepBlinkLocked(r); - verifyBeepLooped(); + verifyBeepUnlooped(); } @Test @@ -1326,7 +1365,11 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { @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 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { @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 @@ public class BuzzBeepBlinkTest extends UiServiceTestCase { 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 4ea2fc0d398e..cd0f4f185927 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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 aaaa7a5ab596..2836e69f79da 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.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMor 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 @@ public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { mActivityMetricsLogger.notifyActivityLaunching(intent); - verifyAsync(mLaunchObserver).onIntentStarted(eq(intent)); + verifyAsync(mLaunchObserver).onIntentStarted(eq(intent), anyLong()); verifyNoMoreInteractions(mLaunchObserver); } @@ -163,12 +164,12 @@ public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { 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 @@ public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { } @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 @@ public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { 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 c2a05c243540..2835c1fb59ae 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 @@ public class ActivityStackTests extends ActivityTestsBase { 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 @@ public class ActivityStackTests extends ActivityTestsBase { // 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 @@ public class ActivityStackTests extends ActivityTestsBase { // 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 @@ public class ActivityStackTests extends ActivityTestsBase { mStack.handleAppDiedLocked(activity.app); - assertEquals(1, mTask.mActivities.size()); + assertEquals(1, mTask.getChildCount()); assertEquals(1, mStack.getAllTasks().size()); } @@ -950,7 +950,7 @@ public class ActivityStackTests extends ActivityTestsBase { mStack.handleAppDiedLocked(activity.app); - assertThat(mTask.mActivities).isEmpty(); + assertFalse(mTask.hasChild()); assertThat(mStack.getAllTasks()).isEmpty(); } @@ -964,7 +964,7 @@ public class ActivityStackTests extends ActivityTestsBase { mStack.handleAppDiedLocked(activity.app); - assertEquals(1, mTask.mActivities.size()); + assertEquals(1, mTask.getChildCount()); assertEquals(1, mStack.getAllTasks().size()); } @@ -978,7 +978,7 @@ public class ActivityStackTests extends ActivityTestsBase { 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 77fbdcf8ff52..d43fe63c04d1 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 @@ class ActivityTestsBase extends SystemServiceTestsBase { 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 @@ class ActivityTestsBase extends SystemServiceTestsBase { 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 650a911f4a20..b174251d5ecf 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 @@ public class AppChangeTransitionTests extends WindowTestsBase { 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 605d52045d50..14939cccda1b 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 @@ public class AppTransitionControllerTest extends WindowTestsBase { @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 72d9bd0aaf8a..9d53676e05e4 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 @@ public class AppTransitionTests extends WindowTestsBase { 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 26617355cfbf..b4c978facdf5 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 @@ public class AppWindowTokenTests extends WindowTestsBase { TaskStack mStack; Task mTask; - AppWindowToken mToken; + ActivityRecord mToken; private final String mPackageName = getInstrumentation().getTargetContext().getPackageName(); @@ -410,7 +410,7 @@ public class AppWindowTokenTests extends WindowTestsBase { } 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/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 2ba3cbdadad4..f12c349ffd85 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 @@ public class DisplayContentTests extends WindowTestsBase { 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 @@ public class DisplayContentTests extends WindowTestsBase { // 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 @@ public class DisplayContentTests extends WindowTestsBase { 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 @@ public class DisplayContentTests extends WindowTestsBase { // 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 304df22bfd7b..452e06fc881b 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 @@ public class DragDropControllerTests extends WindowTestsBase { * 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/TaskStackContainersTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskStackContainersTests.java index 92ddb35afd04..eef680be9410 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 @@ public class TaskStackContainersTests extends WindowTestsBase { // 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 2eb6ea4dfee9..d045073b5f78 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 class TaskStackTests extends WindowTestsBase { 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 class TaskStackTests extends WindowTestsBase { 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/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java index 09e5027c1faa..6a9413716a89 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 @@ public class TestIWindow extends IWindow.Stub { @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/WindowTestUtils.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestUtils.java index c627c1938438..f44c969b442b 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 @@ package com.android.server.wm; 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 @@ class WindowTestUtils { } /** 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 4c4b21e49165..1fce46c69b7a 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 @@ class WindowTestsBase extends SystemServiceTestsBase { } } - 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 @@ class WindowTestsBase extends SystemServiceTestsBase { 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 ecee709054ee..2cd207f72d6f 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.FileReader; 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 @@ public class UsageStatsService extends SystemService implements event.mPackage = packageName; reportEventOrAddToQueue(userId, event); } - - @Override - public void onParoleStateChanged(boolean isParoleOn) { - - } }; public UsageStatsService(Context context) { @@ -1426,7 +1419,7 @@ public class UsageStatsService extends SystemService implements 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 @@ public class UsageStatsService extends SystemService implements } @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 @@ public class UsageStatsService extends SystemService implements @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 a8063209b56f..2ff26b8a5cde 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 15785d4d44b9..ebe2584c2d32 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 dadc66af3306..949dff75b723 100644 --- a/startop/apps/test/README.md +++ b/startop/apps/test/README.md @@ -24,3 +24,14 @@ spent in view inflation to make it easier to focus on the time spent in view 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 000000000000..a2dc2cf03d69 --- /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 c8d9fde0bdaf..75ea69b81e02 100644 --- a/startop/apps/test/src/SystemServerBenchmarkActivity.java +++ b/startop/apps/test/src/SystemServerBenchmarkActivity.java @@ -31,13 +31,25 @@ import android.widget.Button; 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); + + benchmarkList = findViewById(R.id.benchmark_list); - public Benchmark(ViewGroup parent, CharSequence name, Runnable thunk) { - Context context = parent.getContext(); + 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 @@ class Benchmark { 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); - } - }); - - new Benchmark(benchmarkList, "getPackagesForUid", () -> { - pm.getPackagesForUid(app.uid); + SystemServerBenchmarks.runBenchmarkInBackground(thunk, (resultMean, resultStdev) -> { + mean.setText(String.format("%.3f", resultMean / 1e6)); + stdev.setText(String.format("%.3f", resultStdev / 1e6)); }); - } 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 000000000000..5918503c87ea --- /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 acf994610182..59f4d56d3f46 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 com.android.server.wm.ActivityMetricsLaunchObserver.Temperature; 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 abstract class AppLaunchEvent implements Parcelable { 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 abstract class AppLaunchEvent implements Parcelable { 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 @@ public abstract class AppLaunchEvent implements Parcelable { 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 @@ public abstract class AppLaunchEvent implements Parcelable { @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 @@ public abstract class AppLaunchEvent implements Parcelable { @Override protected String toStringBody() { - return ", " + activityRecordSnapshot.toString(); + return ", " + new String(activityRecordSnapshot); } @Override @@ -200,7 +209,7 @@ public abstract class AppLaunchEvent implements Parcelable { @Override protected String toStringBody() { - return ", temperature=" + Integer.toString(temperature); + return super.toStringBody() + ", temperature=" + Integer.toString(temperature); } @Override @@ -216,18 +225,39 @@ public abstract class AppLaunchEvent implements Parcelable { } 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 @@ public abstract class AppLaunchEvent implements Parcelable { @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 @@ public abstract class AppLaunchEvent implements Parcelable { @Override protected String toStringBody() { - return ", " + activityRecordSnapshot.toString(); + return super.toStringBody() + ", " + new String(activityRecordSnapshot); } @Override @@ -275,6 +305,42 @@ public abstract class AppLaunchEvent implements Parcelable { } } + 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 @@ public abstract class AppLaunchEvent implements Parcelable { 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 902da4c0f72e..f753548516ef 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 @@ public class IorapForwardingService extends SystemService { // 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 @@ public class IorapForwardingService extends SystemService { } @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, + 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.ActivityLaunchFinished(mSequenceId, activity)) + 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 000000000000..51e407d4cbff --- /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/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java index 5e71416a0510..3f348a4bda8c 100644 --- a/telecomm/java/android/telecom/Call.java +++ b/telecomm/java/android/telecom/Call.java @@ -728,6 +728,7 @@ public final class Call { } /** {@hide} */ + @TestApi public String getTelecomCallId() { return mTelecomCallId; } @@ -2137,6 +2138,9 @@ public final class Call { } 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 0d97567ed213..ef1c790dcc83 100644 --- a/telecomm/java/android/telecom/CallScreeningService.java +++ b/telecomm/java/android/telecom/CallScreeningService.java @@ -374,6 +374,8 @@ public abstract class CallScreeningService extends Service { 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 0cc052ef340d..2ecdb3035685 100644 --- a/telecomm/java/android/telecom/Phone.java +++ b/telecomm/java/android/telecom/Phone.java @@ -21,7 +21,6 @@ import android.annotation.UnsupportedAppUsage; 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 final class Phone { 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 @@ public final class Phone { } 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 @@ public final class Phone { checkCallTree(parcelableCall); call.internalUpdate(parcelableCall, mCallByTelecomCallId); fireCallAdded(call); - } + } final void internalRemoveCall(Call call) { mCallByTelecomCallId.remove(call.internalGetCallId()); @@ -164,12 +173,28 @@ public final class Phone { } 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/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 1d5c18d6aae3..047b220a7a23 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -526,6 +526,15 @@ public class CarrierConfigManager { "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 @@ public class CarrierConfigManager { "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 class CarrierConfigManager { 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 @@ public class CarrierConfigManager { 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 @@ public class CarrierConfigManager { 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 d5e447e6c73d..46eb9df8bdad 100644 --- a/telephony/java/android/telephony/CellBroadcastService.java +++ b/telephony/java/android/telephony/CellBroadcastService.java @@ -21,6 +21,7 @@ import android.annotation.SystemApi; 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 @@ public abstract class CellBroadcastService extends Service { /** * 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 @@ public abstract class CellBroadcastService extends Service { /** * 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/CellInfo.java b/telephony/java/android/telephony/CellInfo.java index 7edc91cd67e8..18687d400faf 100644 --- a/telephony/java/android/telephony/CellInfo.java +++ b/telephony/java/android/telephony/CellInfo.java @@ -189,11 +189,15 @@ public abstract class CellInfo implements Parcelable { mTimeStamp = ts; } - /** @hide */ + /** + * @return a {@link CellIdentity} instance. + */ @NonNull public abstract CellIdentity getCellIdentity(); - /** @hide */ + /** + * @return a {@link CellSignalStrength} instance. + */ @NonNull public abstract CellSignalStrength getCellSignalStrength(); diff --git a/telephony/java/android/telephony/CellInfoNr.java b/telephony/java/android/telephony/CellInfoNr.java index 9775abd5075c..cea83230391d 100644 --- a/telephony/java/android/telephony/CellInfoNr.java +++ b/telephony/java/android/telephony/CellInfoNr.java @@ -19,6 +19,8 @@ package android.telephony; import android.annotation.NonNull; import android.os.Parcel; +import dalvik.annotation.codegen.CovariantReturnType; + import java.util.Objects; /** @@ -46,6 +48,7 @@ public final class CellInfoNr extends CellInfo { /** * @return a {@link CellIdentityNr} instance. */ + @CovariantReturnType(returnType = CellIdentityNr.class, presentAfter = 29) @Override @NonNull public CellIdentity getCellIdentity() { @@ -55,6 +58,7 @@ public final class CellInfoNr extends CellInfo { /** * @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 cdf01dab0059..4dc54f0fef58 100644 --- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java +++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java @@ -245,9 +245,12 @@ public final class CellSignalStrengthWcdma extends CellSignalStrength implements } /** - * 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 eff64a2e35ba..bcd6cc546eed 100644 --- a/telephony/java/android/telephony/ICellBroadcastService.aidl +++ b/telephony/java/android/telephony/ICellBroadcastService.aidl @@ -28,5 +28,5 @@ interface ICellBroadcastService { 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 ee3c1b1be6d9..60732a3db59a 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 @@ import java.util.List; 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/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index f4330fa0b725..2d35f8eae816 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -1814,6 +1814,36 @@ public final class SmsManager { // 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 1b876575a742..8425ec13b282 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -47,22 +47,20 @@ import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Looper; import android.os.Message; import android.os.ParcelUuid; 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; import android.util.Log; -import com.android.internal.telephony.IOnSubscriptionsChangedListener; import com.android.internal.telephony.ISetOpportunisticDataCallback; import com.android.internal.telephony.ISub; -import com.android.internal.telephony.ITelephonyRegistry; import com.android.internal.telephony.PhoneConstants; import com.android.internal.util.Preconditions; @@ -924,20 +922,24 @@ public class SubscriptionManager { OnSubscriptionsChangedListenerHandler(Looper looper) { super(looper); } - - @Override - public void handleMessage(Message msg) { - if (DBG) { - log("handleMessage: invoke the overriden onSubscriptionsChanged()"); - } - OnSubscriptionsChangedListener.this.onSubscriptionsChanged(); - } } - private final Handler mHandler; + /** + * Posted executor callback on the handler associated with a given looper. + * The looper can be the calling thread's looper or the looper passed from the + * constructor {@link #OnSubscriptionsChangedListener(Looper)}. + */ + private final HandlerExecutor mExecutor; + + /** + * @hide + */ + public HandlerExecutor getHandlerExecutor() { + return mExecutor; + } public OnSubscriptionsChangedListener() { - mHandler = new OnSubscriptionsChangedListenerHandler(); + mExecutor = new HandlerExecutor(new OnSubscriptionsChangedListenerHandler()); } /** @@ -946,7 +948,7 @@ public class SubscriptionManager { * @hide */ public OnSubscriptionsChangedListener(Looper looper) { - mHandler = new OnSubscriptionsChangedListenerHandler(looper); + mExecutor = new HandlerExecutor(new OnSubscriptionsChangedListenerHandler(looper)); } /** @@ -958,18 +960,6 @@ public class SubscriptionManager { if (DBG) log("onSubscriptionsChanged: NOT OVERRIDDEN"); } - /** - * The callback methods need to be called on the handler thread where - * this object was created. If the binder did that for us it'd be nice. - */ - IOnSubscriptionsChangedListener callback = new IOnSubscriptionsChangedListener.Stub() { - @Override - public void onSubscriptionsChanged() { - if (DBG) log("callback: received, sendEmptyMessage(0) to handler"); - mHandler.sendEmptyMessage(0); - } - }; - private void log(String s) { Rlog.d(LOG_TAG, s); } @@ -1011,21 +1001,19 @@ public class SubscriptionManager { * onSubscriptionsChanged overridden. */ public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) { + if (listener == null) return; String pkgName = mContext != null ? mContext.getOpPackageName() : "<unknown>"; if (DBG) { logd("register OnSubscriptionsChangedListener pkgName=" + pkgName + " listener=" + listener); } - try { - // We use the TelephonyRegistry as it runs in the system and thus is always - // available. Where as SubscriptionController could crash and not be available - ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService( - "telephony.registry")); - if (tr != null) { - tr.addOnSubscriptionsChangedListener(pkgName, listener.callback); - } - } catch (RemoteException ex) { - Log.e(LOG_TAG, "Remote exception ITelephonyRegistry " + ex); + // We use the TelephonyRegistry as it runs in the system and thus is always + // available. Where as SubscriptionController could crash and not be available + TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager) + mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE); + if (telephonyRegistryManager != null) { + telephonyRegistryManager.addOnSubscriptionsChangedListener(listener, + listener.mExecutor); } } @@ -1037,21 +1025,18 @@ public class SubscriptionManager { * @param listener that is to be unregistered. */ public void removeOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) { + if (listener == null) return; String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "<unknown>"; if (DBG) { logd("unregister OnSubscriptionsChangedListener pkgForDebug=" + pkgForDebug + " listener=" + listener); } - try { - // We use the TelephonyRegistry as it runs in the system and thus is always - // available where as SubscriptionController could crash and not be available - ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService( - "telephony.registry")); - if (tr != null) { - tr.removeOnSubscriptionsChangedListener(pkgForDebug, listener.callback); - } - } catch (RemoteException ex) { - Log.e(LOG_TAG, "Remote exception ITelephonyRegistry " + ex); + // We use the TelephonyRegistry as it runs in the system and thus is always + // available where as SubscriptionController could crash and not be available + TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager) + mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE); + if (telephonyRegistryManager != null) { + telephonyRegistryManager.removeOnSubscriptionsChangedListener(listener); } } @@ -1070,7 +1055,6 @@ public class SubscriptionManager { * for #onOpportunisticSubscriptionsChanged to be invoked. */ public static class OnOpportunisticSubscriptionsChangedListener { - private Executor mExecutor; /** * Callback invoked when there is any change to any SubscriptionInfo. Typically * this method would invoke {@link #getActiveSubscriptionInfoList} @@ -1079,27 +1063,6 @@ public class SubscriptionManager { if (DBG) log("onOpportunisticSubscriptionsChanged: NOT OVERRIDDEN"); } - private void setExecutor(Executor executor) { - mExecutor = executor; - } - - /** - * The callback methods need to be called on the handler thread where - * this object was created. If the binder did that for us it'd be nice. - */ - IOnSubscriptionsChangedListener callback = new IOnSubscriptionsChangedListener.Stub() { - @Override - public void onSubscriptionsChanged() { - final long identity = Binder.clearCallingIdentity(); - try { - if (DBG) log("onOpportunisticSubscriptionsChanged callback received."); - mExecutor.execute(() -> onOpportunisticSubscriptionsChanged()); - } finally { - Binder.restoreCallingIdentity(identity); - } - } - }; - private void log(String s) { Rlog.d(LOG_TAG, s); } @@ -1126,18 +1089,13 @@ public class SubscriptionManager { + " listener=" + listener); } - listener.setExecutor(executor); - - try { - // We use the TelephonyRegistry as it runs in the system and thus is always - // available. Where as SubscriptionController could crash and not be available - ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService( - "telephony.registry")); - if (tr != null) { - tr.addOnOpportunisticSubscriptionsChangedListener(pkgName, listener.callback); - } - } catch (RemoteException ex) { - Log.e(LOG_TAG, "Remote exception ITelephonyRegistry " + ex); + // We use the TelephonyRegistry as it runs in the system and thus is always + // available where as SubscriptionController could crash and not be available + TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager) + mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE); + if (telephonyRegistryManager != null) { + telephonyRegistryManager.addOnOpportunisticSubscriptionsChangedListener( + listener, executor); } } @@ -1157,16 +1115,10 @@ public class SubscriptionManager { logd("unregister OnOpportunisticSubscriptionsChangedListener pkgForDebug=" + pkgForDebug + " listener=" + listener); } - try { - // We use the TelephonyRegistry as it runs in the system and thus is always - // available where as SubscriptionController could crash and not be available - ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService( - "telephony.registry")); - if (tr != null) { - tr.removeOnSubscriptionsChangedListener(pkgForDebug, listener.callback); - } - } catch (RemoteException ex) { - Log.e(LOG_TAG, "Remote exception ITelephonyRegistry " + ex); + TelephonyRegistryManager telephonyRegistryManager = (TelephonyRegistryManager) + mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE); + if (telephonyRegistryManager != null) { + telephonyRegistryManager.removeOnOpportunisticSubscriptionsChangedListener(listener); } } @@ -2101,13 +2053,13 @@ public class SubscriptionManager { /** @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 +2372,12 @@ public class SubscriptionManager { * @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 +2422,10 @@ public class SubscriptionManager { */ 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 +2457,10 @@ public class SubscriptionManager { */ 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 ec2050fb1a44..e24eb2696c6c 100644 --- a/telephony/java/android/telephony/SubscriptionPlan.java +++ b/telephony/java/android/telephony/SubscriptionPlan.java @@ -24,6 +24,7 @@ import android.annotation.Nullable; 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.Retention; 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 @@ public final class SubscriptionPlan implements Parcelable { 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 @@ public final class SubscriptionPlan implements Parcelable { dataLimitBehavior = source.readInt(); dataUsageBytes = source.readLong(); dataUsageTime = source.readLong(); + networkTypes = source.createIntArray(); } @Override @@ -109,6 +114,7 @@ public final class SubscriptionPlan implements Parcelable { dest.writeInt(dataLimitBehavior); dest.writeLong(dataUsageBytes); dest.writeLong(dataUsageTime); + dest.writeIntArray(networkTypes); } @Override @@ -121,13 +127,14 @@ public final class SubscriptionPlan implements Parcelable { .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 @@ public final class SubscriptionPlan implements Parcelable { && 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 @@ public final class SubscriptionPlan implements Parcelable { } /** + * 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 @@ public final class SubscriptionPlan implements Parcelable { 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 2442023ee27c..03e57e728610 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -65,7 +65,6 @@ import android.telecom.PhoneAccountHandle; 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 @@ public class TelephonyManager { }; /** @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 @@ public class TelephonyManager { /** * 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 @@ public class TelephonyManager { // 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 @@ public class TelephonyManager { 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 @@ public class TelephonyManager { 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. diff --git a/telephony/java/android/telephony/ims/ImsReasonInfo.java b/telephony/java/android/telephony/ims/ImsReasonInfo.java index 1e0d9a786acc..10251d707c22 100644 --- a/telephony/java/android/telephony/ims/ImsReasonInfo.java +++ b/telephony/java/android/telephony/ims/ImsReasonInfo.java @@ -885,6 +885,12 @@ public final class ImsReasonInfo implements Parcelable { */ 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 @@ public final class ImsReasonInfo implements Parcelable { 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 175769bd34e4..36ece958d501 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 @@ public class ImsSmsImplBase { * * @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 @@ public class ImsSmsImplBase { * 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 @@ public class ImsSmsImplBase { * * @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 @@ public class ImsSmsImplBase { * {@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 @@ public class ImsSmsImplBase { * 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 @@ public class ImsSmsImplBase { * 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 @@ public class ImsSmsImplBase { * 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 @@ public class ImsSmsImplBase { * 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 @@ public class ImsSmsImplBase { * 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 @@ public class ImsSmsImplBase { * 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 @@ public class ImsSmsImplBase { * 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 @@ public class ImsSmsImplBase { * 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 @@ public class ImsSmsImplBase { } /** - * 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 668a6af08145..9e786ce32792 100644 --- a/telephony/java/com/android/internal/telephony/DctConstants.java +++ b/telephony/java/com/android/internal/telephony/DctConstants.java @@ -111,7 +111,7 @@ public class DctConstants { 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 fd7ec561faef..39e00cc50f98 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2063,4 +2063,9 @@ interface ITelephony { * 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/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java index 4654437fb49c..b357fa43008f 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 @@ public class SmsMessage extends SmsMessageBase { } /** + * @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 2787b2451470..c924ab350dc6 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 @@ public final class SmsEnvelope { // 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/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java index fcd4701c7630..5053ceedc703 100644 --- a/test-mock/src/android/test/mock/MockContext.java +++ b/test-mock/src/android/test/mock/MockContext.java @@ -758,7 +758,7 @@ public class MockContext extends Context { /** {@hide} */ @Override - public Context createContextAsUser(UserHandle user) { + public Context createContextAsUser(UserHandle user, @CreatePackageOptions int flags) { 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 b0c7251e77f5..02dfd732a716 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 @@ int write_block_to_device(const char* device_path, uint64_t block_offset, 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 -1; + 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; +} - const char* block_device = argv[1]; - const char* file_name = argv[2]; - uint64_t byte_offset = strtoull(argv[3], nullptr, 10); +int main(int argc, const char** argv) { + std::unique_ptr<Args> args = parse_args(argc, argv); + if (args == nullptr) { + return -1; + } - 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 @@ int main(int argc, const char** argv) { 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 @@ int main(int argc, const char** argv) { 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 761c5ceb2413..2445a6a52c08 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.Test; 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 @@ public class ApkVerityTest extends BaseHostJUnit4Test { 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 0e90deaadd61..929f122e261e 100755 --- a/tests/Codegen/runTest.sh +++ b/tests/Codegen/runTest.sh @@ -17,11 +17,13 @@ else 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 10eba6a899ad..325c1c09dd8c 100644 --- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java +++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassBase.java @@ -32,13 +32,17 @@ public class HierrarchicalDataClassBase implements Parcelable { - // 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 @@ public class HierrarchicalDataClassBase implements Parcelable { @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 @@ public class HierrarchicalDataClassBase implements Parcelable { /** @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 @@ public class HierrarchicalDataClassBase implements Parcelable { } @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 1085a6a1636a..6c92009f8533 100644 --- a/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java +++ b/tests/Codegen/src/com/android/codegentest/HierrarchicalDataClassChild.java @@ -46,13 +46,17 @@ public class HierrarchicalDataClassChild extends HierrarchicalDataClassBase { - // 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 @@ public class HierrarchicalDataClassChild extends HierrarchicalDataClassBase { @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 @@ public class HierrarchicalDataClassChild extends HierrarchicalDataClassBase { /** @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 @@ public class HierrarchicalDataClassChild extends HierrarchicalDataClassBase { } @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 75ef963c7995..36def8a8dfb1 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 @@ public class ParcelAllTheThingsDataClass implements Parcelable { @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 @@ public class ParcelAllTheThingsDataClass implements Parcelable { @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 @@ public class ParcelAllTheThingsDataClass implements Parcelable { 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 @@ public class ParcelAllTheThingsDataClass implements Parcelable { return mSparseIntArray; } + @DataClass.Generated.Member + public @SuppressWarnings({ "WeakerAccess" }) @Nullable Boolean getNullableBoolean() { + return mNullableBoolean; + } + @Override @DataClass.Generated.Member public String toString() { @@ -139,16 +154,20 @@ public class ParcelAllTheThingsDataClass implements Parcelable { "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 @@ public class ParcelAllTheThingsDataClass implements Parcelable { dest.writeMap(mStringMap); dest.writeSparseArray(mSparseArray); dest.writeSparseIntArray(mSparseIntArray); + if (mNullableBoolean != null) dest.writeBoolean(mNullableBoolean); } @Override @@ -165,10 +185,11 @@ public class ParcelAllTheThingsDataClass implements Parcelable { /** @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 @@ public class ParcelAllTheThingsDataClass implements Parcelable { 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 @@ public class ParcelAllTheThingsDataClass implements Parcelable { 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 @@ public class ParcelAllTheThingsDataClass implements Parcelable { } @Override - public ParcelAllTheThingsDataClass createFromParcel(Parcel in) { + public ParcelAllTheThingsDataClass createFromParcel(@NonNull Parcel in) { return new ParcelAllTheThingsDataClass(in); } }; @@ -233,6 +256,7 @@ public class ParcelAllTheThingsDataClass implements Parcelable { 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 @@ public class ParcelAllTheThingsDataClass implements Parcelable { 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 @@ public class ParcelAllTheThingsDataClass implements Parcelable { if ((mBuilderFieldsSet & 0x40) == 0) { mSparseIntArray = null; } + if ((mBuilderFieldsSet & 0x80) == 0) { + mNullableBoolean = null; + } ParcelAllTheThingsDataClass o = new ParcelAllTheThingsDataClass( mStringArray, mIntArray, @@ -361,12 +396,13 @@ public class ParcelAllTheThingsDataClass implements Parcelable { 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 @@ public class ParcelAllTheThingsDataClass implements Parcelable { } @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 14010a9cfb1d..c444d61a0fba 100644 --- a/tests/Codegen/src/com/android/codegentest/SampleDataClass.java +++ b/tests/Codegen/src/com/android/codegentest/SampleDataClass.java @@ -342,13 +342,17 @@ public final class SampleDataClass implements Parcelable { - // 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 @@ public final class SampleDataClass implements Parcelable { @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 @@ public final class SampleDataClass implements Parcelable { @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 @@ public final class SampleDataClass implements Parcelable { /** @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 @@ public final class SampleDataClass implements Parcelable { @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 @@ public final class SampleDataClass implements Parcelable { /** @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 @@ public final class SampleDataClass implements Parcelable { } @Override - public SampleDataClass createFromParcel(Parcel in) { + public SampleDataClass createFromParcel(@NonNull Parcel in) { return new SampleDataClass(in); } }; @@ -1868,8 +1872,8 @@ public final class SampleDataClass implements Parcelable { } @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 b5f6c73a8aef..55feae7200ea 100644 --- a/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java +++ b/tests/Codegen/src/com/android/codegentest/SampleWithCustomBuilder.java @@ -85,13 +85,17 @@ public class SampleWithCustomBuilder implements Parcelable { - // 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 @@ public class SampleWithCustomBuilder implements Parcelable { @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 @@ public class SampleWithCustomBuilder implements Parcelable { /** @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 @@ public class SampleWithCustomBuilder implements Parcelable { } @Override - public SampleWithCustomBuilder createFromParcel(Parcel in) { + public SampleWithCustomBuilder createFromParcel(@NonNull Parcel in) { return new SampleWithCustomBuilder(in); } }; @@ -249,8 +253,8 @@ public class SampleWithCustomBuilder implements Parcelable { } @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 0ce8aba9b28f..b967f19f9f7e 100644 --- a/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java +++ b/tests/Codegen/src/com/android/codegentest/StaleDataclassDetectorFalsePositivesTest.java @@ -51,18 +51,22 @@ public class StaleDataclassDetectorFalsePositivesTest { - // 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 55a61471dfb8..f34c43248e1a 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 231d045bd817..085c53cf45ea 100644 --- a/tests/RollbackTest/Android.bp +++ b/tests/RollbackTest/Android.bp @@ -19,7 +19,6 @@ android_test { 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 9e6ac8ef679b..8b97f616b1a8 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 @@ public class StagedRollbackTest { * 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 @@ public class StagedRollbackTest { 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 @@ public class StagedRollbackTest { * 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 @@ public class StagedRollbackTest { 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 @@ public class StagedRollbackTest { * 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 @@ public class StagedRollbackTest { * 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 @@ public class StagedRollbackTest { 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,39 +188,45 @@ public class StagedRollbackTest { + metadataApkPath); } + /** + * Verify the rollback is available. + */ @Test - public void assertNetworkStackRollbackAvailable() throws Exception { + public void testNativeWatchdogTriggersRollback_Phase2() throws Exception { RollbackManager rm = RollbackUtils.getRollbackManager(); assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), - getNetworkStackPackageName())).isNotNull(); + MODULE_META_DATA_PACKAGE)).isNotNull(); } + /** + * Verify the rollback is committed after crashing. + */ @Test - public void assertNetworkStackRollbackCommitted() throws Exception { + public void testNativeWatchdogTriggersRollback_Phase3() throws Exception { RollbackManager rm = RollbackUtils.getRollbackManager(); assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), - getNetworkStackPackageName())).isNotNull(); + MODULE_META_DATA_PACKAGE)).isNotNull(); } @Test - public void assertNoNetworkStackRollbackCommitted() throws Exception { + public void assertNetworkStackRollbackAvailable() throws Exception { RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), - getNetworkStackPackageName())).isNull(); + assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), + getNetworkStackPackageName())).isNotNull(); } @Test - public void assertModuleMetadataRollbackAvailable() throws Exception { + public void assertNetworkStackRollbackCommitted() throws Exception { RollbackManager rm = RollbackUtils.getRollbackManager(); - assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), - MODULE_META_DATA_PACKAGE)).isNotNull(); + assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), + getNetworkStackPackageName())).isNotNull(); } @Test - public void assertModuleMetadataRollbackCommitted() throws Exception { + public void assertNoNetworkStackRollbackCommitted() throws Exception { RollbackManager rm = RollbackUtils.getRollbackManager(); assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), - MODULE_META_DATA_PACKAGE)).isNotNull(); + getNetworkStackPackageName())).isNull(); } private String getNetworkStackPackageName() { 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 bc98f06ebc56..20430274d074 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 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { */ @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 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { 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 bffbbfda08ee..cf3fba8bef91 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -5709,7 +5709,6 @@ public class ConnectivityServiceTest { } @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/tools/codegen/src/com/android/codegen/FieldInfo.kt b/tools/codegen/src/com/android/codegen/FieldInfo.kt index ba00264f5f5e..1a7fd6e241aa 100644 --- a/tools/codegen/src/com/android/codegen/FieldInfo.kt +++ b/tools/codegen/src/com/android/codegen/FieldInfo.kt @@ -191,7 +191,7 @@ data class FieldInfo( * 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 5a95676c1dc8..431f378a8811 100644 --- a/tools/codegen/src/com/android/codegen/Generators.kt +++ b/tools/codegen/src/com/android/codegen/Generators.kt @@ -417,7 +417,7 @@ fun ClassPrinter.generateParcelable() { 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 @@ fun ClassPrinter.generateParcelable() { +"/** @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 @@ fun ClassPrinter.generateParcelable() { } +"@Override" - "public $ClassName createFromParcel($Parcel in)" { + "public $ClassName createFromParcel(@$NonNull $Parcel in)" { +"return new $ClassName(in);" } rmEmptyLine() @@ -611,7 +611,7 @@ fun ClassPrinter.generateEqualsHashcode() { 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 @@ private fun ClassPrinter.generateFieldValidation(field: FieldInfo) = field.run { 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 @@ fun ClassPrinter.appendValidateCall(annotation: AnnotationExpr, valueToValidate: 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 @@ fun ClassPrinter.generateForEachField() { 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 @@ fun ClassPrinter.generateForEachField() { +"/** @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 24cf4690dae1..d6953c00fc0b 100644 --- a/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt +++ b/tools/codegen/src/com/android/codegen/InputSignaturesComputation.kt @@ -87,6 +87,14 @@ private fun ClassPrinter.appendExpr(sb: StringBuilder, ex: Expression?) { 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 039f7b2fc627..ce83d3dc8e51 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 GENERATED_WARNING_PREFIX = "Code below generated by $CODEGEN_NAME" 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 @@ fun main(args: Array<String>) { // // 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 47f774f07f16..3eb9e7bb68c6 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 a1f068afa29a..e703397214eb 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 abort(msg: String): Nothing { } 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 14eead853f50..1390f63248f9 100644 --- a/tools/streaming_proto/Android.bp +++ b/tools/streaming_proto/Android.bp @@ -29,7 +29,7 @@ cc_defaults { "-Werror", ], - shared_libs: ["libprotoc"], + static_libs: ["libprotoc"], } cc_binary_host { |