diff options
817 files changed, 21781 insertions, 5642 deletions
diff --git a/Android.bp b/Android.bp index 07e6b36b5d87..040bb6e2a719 100644 --- a/Android.bp +++ b/Android.bp @@ -368,17 +368,12 @@ java_defaults { // TODO(b/120066492): remove default_television.xml when the build system // propagates "required" properly. "default_television.xml", - "framework-platform-compat-config", // TODO(b/120066492): remove gps_debug and protolog.conf.json when the build // system propagates "required" properly. "gps_debug.conf", - "icu4j-platform-compat-config", "protolog.conf.json.gz", - "services-platform-compat-config", - "TeleService-platform-compat-config", - "documents-ui-compat-config", - "calendar-provider-compat-config", - "contacts-provider-platform-compat-config", + // any install dependencies should go into framework-minus-apex-install-dependencies + // rather than here to avoid bloating incremental build time ], libs: [ "androidx.annotation_annotation", @@ -486,6 +481,20 @@ java_library { apex_available: ["//apex_available:platform"], } +java_library { + name: "framework-minus-apex-install-dependencies", + required: [ + "framework-minus-apex", + "framework-platform-compat-config", + "services-platform-compat-config", + "icu4j-platform-compat-config", + "TeleService-platform-compat-config", + "documents-ui-compat-config", + "calendar-provider-compat-config", + "contacts-provider-platform-compat-config", + ], +} + platform_compat_config { name: "framework-platform-compat-config", src: ":framework-minus-apex", diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java index 3910a0827aad..7712aeef4161 100644 --- a/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java +++ b/apct-tests/perftests/core/src/android/libcore/regression/ExpensiveObjectsPerfTest.java @@ -52,7 +52,7 @@ public class ExpensiveObjectsPerfTest { } } - @Test(timeout = 900) + @Test(timeout = 900000) public void timeClonedDateFormatTimeInstance() { DateFormat df = DateFormat.getTimeInstance(DateFormat.SHORT); BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); @@ -72,7 +72,7 @@ public class ExpensiveObjectsPerfTest { } } - @Test + @Test(timeout = 900000) public void timeNewCollator() { BenchmarkState state = mPerfStatusReporter.getBenchmarkState(); while (state.keepRunning()) { diff --git a/apct-tests/perftests/inputmethod/OWNERS b/apct-tests/perftests/inputmethod/OWNERS index 5deb2ce8f24b..cbd94ba6b467 100644 --- a/apct-tests/perftests/inputmethod/OWNERS +++ b/apct-tests/perftests/inputmethod/OWNERS @@ -1 +1,2 @@ +# Bug component: 34867 include /core/java/android/view/inputmethod/OWNERS diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java deleted file mode 100644 index 42fb24f570d2..000000000000 --- a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java +++ /dev/null @@ -1,253 +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 android.wm; - -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.ActivityManager; -import android.app.ActivityTaskManager; -import android.app.IActivityTaskManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Rect; -import android.os.RemoteException; -import android.os.SystemClock; -import android.perftests.utils.ManualBenchmarkState; -import android.perftests.utils.ManualBenchmarkState.ManualBenchmarkTest; -import android.perftests.utils.PerfManualStatusReporter; -import android.util.Pair; -import android.view.IRecentsAnimationController; -import android.view.IRecentsAnimationRunner; -import android.view.RemoteAnimationTarget; -import android.window.TaskSnapshot; - -import androidx.test.filters.LargeTest; -import androidx.test.runner.lifecycle.Stage; - -import org.junit.AfterClass; -import org.junit.Assume; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; - -@RunWith(Parameterized.class) -@LargeTest -public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase - implements ManualBenchmarkState.CustomizedIterationListener { - private static Intent sRecentsIntent; - - @Rule - public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter(); - - @Rule - public final PerfTestActivityRule mActivityRule = - new PerfTestActivityRule(true /* launchActivity */); - - private long mMeasuredTimeNs; - - /** - * Used to skip each test method if there is error. It cannot be raised in static setup because - * that will break the amount of target method count. - */ - private static Exception sSetUpClassException; - - @Parameterized.Parameter(0) - public int intervalBetweenOperations; - - @Parameterized.Parameters(name = "interval{0}ms") - public static Collection<Object[]> getParameters() { - return Arrays.asList(new Object[][] { - { 0 }, - { 100 }, - { 300 }, - }); - } - - @BeforeClass - public static void setUpClass() { - // Get the permission to invoke startRecentsActivity. - getUiAutomation().adoptShellPermissionIdentity(); - - final Context context = getInstrumentation().getContext(); - final PackageManager pm = context.getPackageManager(); - final ComponentName defaultHome = pm.getHomeActivities(new ArrayList<>()); - - try { - final ComponentName recentsComponent = - ComponentName.unflattenFromString(context.getResources().getString( - com.android.internal.R.string.config_recentsComponentName)); - final int enabledState = pm.getComponentEnabledSetting(recentsComponent); - Assume.assumeThat(enabledState, anyOf( - is(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT), - is(PackageManager.COMPONENT_ENABLED_STATE_ENABLED))); - - final boolean homeIsRecents = - recentsComponent.getPackageName().equals(defaultHome.getPackageName()); - sRecentsIntent = - new Intent().setComponent(homeIsRecents ? defaultHome : recentsComponent); - } catch (Exception e) { - sSetUpClassException = e; - } - } - - @AfterClass - public static void tearDownClass() { - sSetUpClassException = null; - try { - // Recents activity may stop app switches. Restore the state to avoid affecting - // the next test. - ActivityManager.resumeAppSwitches(); - } catch (RemoteException ignored) { - } - getUiAutomation().dropShellPermissionIdentity(); - } - - @Before - public void setUp() { - Assume.assumeNoException(sSetUpClassException); - } - - /** Simulate the timing of touch. */ - private void makeInterval() { - SystemClock.sleep(intervalBetweenOperations); - } - - /** - * <pre> - * Steps: - * (1) Start recents activity (only make it visible). - * (2) Finish animation, take turns to execute (a), (b). - * (a) Move recents activity to top. - * ({@link com.android.server.wm.RecentsAnimationController#REORDER_MOVE_TO_TOP}) - * Move test app to top by startActivityFromRecents. - * (b) Cancel (it is similar to swipe a little distance and give up to enter recents). - * ({@link com.android.server.wm.RecentsAnimationController#REORDER_MOVE_TO_ORIGINAL_POSITION}) - * (3) Loop (1). - * </pre> - */ - @Test - @ManualBenchmarkTest( - warmupDurationNs = TIME_1_S_IN_NS, - targetTestDurationNs = TIME_5_S_IN_NS, - statsReport = @StatsReport(flags = StatsReport.FLAG_ITERATION | StatsReport.FLAG_MEAN - | StatsReport.FLAG_COEFFICIENT_VAR)) - public void testRecentsAnimation() throws Throwable { - final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState(); - state.setCustomizedIterations(getProfilingIterations(), this); - final IActivityTaskManager atm = ActivityTaskManager.getService(); - - final ArrayList<Pair<String, Boolean>> finishCases = new ArrayList<>(); - // Real launch the recents activity. - finishCases.add(new Pair<>("finishMoveToTop", true)); - // Return to the original top. - finishCases.add(new Pair<>("finishCancel", false)); - - // Ensure startRecentsActivity won't be called before finishing the animation. - final Semaphore recentsSemaphore = new Semaphore(1); - - final int testActivityTaskId = mActivityRule.getActivity().getTaskId(); - final IRecentsAnimationRunner.Stub anim = new IRecentsAnimationRunner.Stub() { - int mIteration; - - @Override - public void onAnimationStart(IRecentsAnimationController controller, - RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, - Rect homeContentInsets, Rect minimizedHomeBounds) throws RemoteException { - final Pair<String, Boolean> finishCase = finishCases.get(mIteration++ % 2); - final boolean moveRecentsToTop = finishCase.second; - makeInterval(); - - long startTime = SystemClock.elapsedRealtimeNanos(); - controller.finish(moveRecentsToTop, false /* sendUserLeaveHint */); - final long elapsedTimeNsOfFinish = SystemClock.elapsedRealtimeNanos() - startTime; - mMeasuredTimeNs += elapsedTimeNsOfFinish; - state.addExtraResult(finishCase.first, elapsedTimeNsOfFinish); - - if (moveRecentsToTop) { - mActivityRule.waitForIdleSync(Stage.STOPPED); - - startTime = SystemClock.elapsedRealtimeNanos(); - atm.startActivityFromRecents(testActivityTaskId, null /* options */); - final long elapsedTimeNs = SystemClock.elapsedRealtimeNanos() - startTime; - mMeasuredTimeNs += elapsedTimeNs; - state.addExtraResult("startFromRecents", elapsedTimeNs); - - mActivityRule.waitForIdleSync(Stage.RESUMED); - } - - makeInterval(); - recentsSemaphore.release(); - } - - @Override - public void onAnimationCanceled(int[] taskIds, TaskSnapshot[] taskSnapshots) - throws RemoteException { - Assume.assumeNoException( - new AssertionError("onAnimationCanceled should not be called")); - } - - @Override - public void onTasksAppeared(RemoteAnimationTarget[] app) throws RemoteException { - /* no-op */ - } - }; - - recentsSemaphore.tryAcquire(); - while (state.keepRunning(mMeasuredTimeNs)) { - mMeasuredTimeNs = 0; - - final long startTime = SystemClock.elapsedRealtimeNanos(); - atm.startRecentsActivity(sRecentsIntent, 0 /* eventTime */, anim); - final long elapsedTimeNsOfStart = SystemClock.elapsedRealtimeNanos() - startTime; - mMeasuredTimeNs += elapsedTimeNsOfStart; - state.addExtraResult("start", elapsedTimeNsOfStart); - - // Ensure the animation callback is done. - Assume.assumeTrue(recentsSemaphore.tryAcquire( - sIsProfilingMethod() ? 10 * TIME_5_S_IN_NS : TIME_5_S_IN_NS, - TimeUnit.NANOSECONDS)); - } - } - - @Override - public void onStart(int iteration) { - startProfiling(RecentsAnimationPerfTest.class.getSimpleName() - + "_interval_" + intervalBetweenOperations - + "_MethodTracing_" + iteration + ".trace"); - } - - @Override - public void onFinished(int iteration) { - stopProfiling(); - } -} diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index 7c8dd92f96d8..9a0053f8add6 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -380,7 +380,7 @@ java_api_library { ":non-updatable-current.txt", ], defaults: ["android-non-updatable_from_text_defaults"], - dep_api_srcs: "android_stubs_current.from-text", + full_api_surface_stub: "android_stubs_current.from-text", } java_api_library { @@ -391,7 +391,7 @@ java_api_library { ":non-updatable-system-current.txt", ], defaults: ["android-non-updatable_from_text_defaults"], - dep_api_srcs: "android_system_stubs_current.from-text", + full_api_surface_stub: "android_system_stubs_current.from-text", } java_api_library { @@ -403,7 +403,7 @@ java_api_library { ":non-updatable-test-current.txt", ], defaults: ["android-non-updatable_from_text_defaults"], - dep_api_srcs: "android_test_stubs_current.from-text", + full_api_surface_stub: "android_test_stubs_current.from-text", } java_api_library { @@ -415,7 +415,7 @@ java_api_library { ":non-updatable-module-lib-current.txt", ], defaults: ["android-non-updatable_from_text_defaults"], - dep_api_srcs: "android_module_lib_stubs_current_full.from-text", + full_api_surface_stub: "android_module_lib_stubs_current_full.from-text", } java_defaults { diff --git a/core/api/current.txt b/core/api/current.txt index 00e3effa461c..c2bc5a2426af 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -3286,8 +3286,10 @@ package android.accessibilityservice { public abstract class AccessibilityService extends android.app.Service { ctor public AccessibilityService(); - method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl); - method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl); + method @Deprecated public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl); + method public void attachAccessibilityOverlayToDisplay(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); + method @Deprecated public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl); + method public void attachAccessibilityOverlayToWindow(int, @NonNull android.view.SurfaceControl, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer); method public boolean clearCache(); method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo); method public final void disableSelf(); @@ -3399,6 +3401,9 @@ package android.accessibilityservice { field public static final int GLOBAL_ACTION_RECENTS = 3; // 0x3 field public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9; // 0x9 field public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7; // 0x7 + field public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; // 0x1 + field public static final int OVERLAY_RESULT_INVALID = 2; // 0x2 + field public static final int OVERLAY_RESULT_SUCCESS = 0; // 0x0 field public static final String SERVICE_INTERFACE = "android.accessibilityservice.AccessibilityService"; field public static final String SERVICE_META_DATA = "android.accessibilityservice"; field public static final int SHOW_MODE_AUTO = 0; // 0x0 @@ -17437,6 +17442,7 @@ package android.graphics.fonts { ctor public FontFamily.Builder(@NonNull android.graphics.fonts.Font); method @NonNull public android.graphics.fonts.FontFamily.Builder addFont(@NonNull android.graphics.fonts.Font); method @NonNull public android.graphics.fonts.FontFamily build(); + method @Nullable public android.graphics.fonts.FontFamily buildVariableFamily(); } public final class FontStyle { @@ -17528,17 +17534,21 @@ package android.graphics.text { public final class LineBreakConfig { method public int getLineBreakStyle(); method public int getLineBreakWordStyle(); + method @NonNull public android.graphics.text.LineBreakConfig merge(@NonNull android.graphics.text.LineBreakConfig); field public static final int LINE_BREAK_STYLE_LOOSE = 1; // 0x1 field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0 field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2 field public static final int LINE_BREAK_STYLE_STRICT = 3; // 0x3 + field public static final int LINE_BREAK_STYLE_UNSPECIFIED = -1; // 0xffffffff field public static final int LINE_BREAK_WORD_STYLE_NONE = 0; // 0x0 field public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1; // 0x1 + field public static final int LINE_BREAK_WORD_STYLE_UNSPECIFIED = -1; // 0xffffffff } public static final class LineBreakConfig.Builder { ctor public LineBreakConfig.Builder(); method @NonNull public android.graphics.text.LineBreakConfig build(); + method @NonNull public android.graphics.text.LineBreakConfig.Builder merge(@NonNull android.graphics.text.LineBreakConfig); method @NonNull public android.graphics.text.LineBreakConfig.Builder setLineBreakStyle(int); method @NonNull public android.graphics.text.LineBreakConfig.Builder setLineBreakWordStyle(int); } @@ -17613,13 +17623,18 @@ package android.graphics.text { method public float getAdvance(); method public float getAscent(); method public float getDescent(); + method public boolean getFakeBold(@IntRange(from=0) int); + method public boolean getFakeItalic(@IntRange(from=0) int); method @NonNull public android.graphics.fonts.Font getFont(@IntRange(from=0) int); method @IntRange(from=0) public int getGlyphId(@IntRange(from=0) int); method public float getGlyphX(@IntRange(from=0) int); method public float getGlyphY(@IntRange(from=0) int); + method public float getItalicOverride(@IntRange(from=0) int); method public float getOffsetX(); method public float getOffsetY(); + method public float getWeightOverride(@IntRange(from=0) int); method @IntRange(from=0) public int glyphCount(); + field public static final float NO_OVERRIDE = 1.4E-45f; } public class TextRunShaper { @@ -48397,6 +48412,11 @@ package android.text.style { method public void writeToParcel(@NonNull android.os.Parcel, int); } + public class LineBreakConfigSpan { + ctor public LineBreakConfigSpan(@NonNull android.graphics.text.LineBreakConfig); + method @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig(); + } + public interface LineHeightSpan extends android.text.style.ParagraphStyle android.text.style.WrapTogetherSpan { method public void chooseHeight(CharSequence, int, int, int, int, android.graphics.Paint.FontMetricsInt); } @@ -54926,6 +54946,7 @@ package android.view.animation { public class AnimationUtils { ctor public AnimationUtils(); method public static long currentAnimationTimeMillis(); + method public static long getExpectedPresentationTimeMillis(); method public static long getExpectedPresentationTimeNanos(); method public static android.view.animation.Animation loadAnimation(android.content.Context, @AnimRes int) throws android.content.res.Resources.NotFoundException; method public static android.view.animation.Interpolator loadInterpolator(android.content.Context, @AnimRes @InterpolatorRes int) throws android.content.res.Resources.NotFoundException; @@ -55760,7 +55781,7 @@ package android.view.inputmethod { field public static final String SERVICE_INTERFACE = "android.view.InputMethod"; field public static final String SERVICE_META_DATA = "android.view.im"; field public static final int SHOW_EXPLICIT = 1; // 0x1 - field public static final int SHOW_FORCED = 2; // 0x2 + field @Deprecated public static final int SHOW_FORCED = 2; // 0x2 } public static interface InputMethod.SessionCallback { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 070822ef49ed..cf24f201315c 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -15221,7 +15221,7 @@ package android.telephony.data { method @NonNull public android.telephony.data.DataCallResponse.Builder setGatewayAddresses(@NonNull java.util.List<java.net.InetAddress>); method @NonNull public android.telephony.data.DataCallResponse.Builder setHandoverFailureMode(int); method @NonNull public android.telephony.data.DataCallResponse.Builder setId(int); - method @NonNull public android.telephony.data.DataCallResponse.Builder setInterfaceName(@NonNull String); + method @NonNull public android.telephony.data.DataCallResponse.Builder setInterfaceName(@Nullable String); method @NonNull public android.telephony.data.DataCallResponse.Builder setLinkStatus(int); method @Deprecated @NonNull public android.telephony.data.DataCallResponse.Builder setMtu(int); method @NonNull public android.telephony.data.DataCallResponse.Builder setMtuV4(int); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 1dfd4f9fa484..7c3d8fb856a4 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -540,6 +540,7 @@ package android.app.admin { field public static final String PERMITTED_INPUT_METHODS_POLICY = "permittedInputMethods"; field public static final String PERSONAL_APPS_SUSPENDED_POLICY = "personalAppsSuspended"; field public static final String SCREEN_CAPTURE_DISABLED_POLICY = "screenCaptureDisabled"; + field public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling"; } public class DevicePolicyManager { @@ -1060,6 +1061,7 @@ package android.content.pm { method public boolean isMain(); method public boolean isManagedProfile(); method @Deprecated public boolean isPrimary(); + method public boolean isPrivateProfile(); method public boolean isProfile(); method public boolean isQuietModeEnabled(); method public boolean isRestricted(); @@ -2681,7 +2683,7 @@ package android.os.vibrator.persistence { method public static void serialize(@NonNull android.os.VibrationEffect, @NonNull java.io.Writer) throws java.io.IOException, android.os.vibrator.persistence.VibrationXmlSerializer.SerializationFailedException; } - public static final class VibrationXmlSerializer.SerializationFailedException extends java.lang.IllegalStateException { + public static final class VibrationXmlSerializer.SerializationFailedException extends java.lang.RuntimeException { } } @@ -3334,6 +3336,15 @@ package android.text { field @NonNull public static final android.os.Parcelable.Creator<android.text.FontConfig.NamedFamilyList> CREATOR; } + public class MeasuredParagraph { + method @NonNull public static android.text.MeasuredParagraph buildForStaticLayoutTest(@NonNull android.text.TextPaint, @Nullable android.graphics.text.LineBreakConfig, @NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.text.TextDirectionHeuristic, int, boolean, @Nullable android.text.MeasuredParagraph.StyleRunCallback); + } + + public static interface MeasuredParagraph.StyleRunCallback { + method public void onAppendReplacementRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, @FloatRange(from=0) @Px float); + method public void onAppendStyleRun(@NonNull android.graphics.Paint, @Nullable android.graphics.text.LineBreakConfig, @IntRange(from=0) int, boolean); + } + public static final class Selection.MemoryTextWatcher implements android.text.TextWatcher { ctor public Selection.MemoryTextWatcher(); method public void afterTextChanged(android.text.Editable); @@ -3659,6 +3670,7 @@ package android.view.accessibility { public final class AccessibilityWindowInfo implements android.os.Parcelable { method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger); + field public static final int UNDEFINED_WINDOW_ID = -1; // 0xffffffff } } diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index c395e15d23e9..a78b50c68c2d 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -81,6 +81,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; import java.util.function.Consumer; +import java.util.function.IntConsumer; /** * Accessibility services should only be used to assist users with disabilities in using @@ -785,6 +786,39 @@ public abstract class AccessibilityService extends Service { public static final String KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP = "screenshot_timestamp"; + + /** + * Annotations for result codes of attaching accessibility overlays. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + prefix = {"OVERLAY_RESULT_"}, + value = { + OVERLAY_RESULT_SUCCESS, + OVERLAY_RESULT_INTERNAL_ERROR, + OVERLAY_RESULT_INVALID, + }) + public @interface AttachOverlayResult {} + + /** Result code indicating the overlay was successfully attached. */ + public static final int OVERLAY_RESULT_SUCCESS = 0; + + /** + * Result code indicating the overlay could not be attached due to an internal + * error and not + * because of problems with the input. + */ + public static final int OVERLAY_RESULT_INTERNAL_ERROR = 1; + + /** + * Result code indicating the overlay could not be attached because the + * specified display or + * window id was invalid. + */ + public static final int OVERLAY_RESULT_INVALID = 2; + private int mConnectionId = AccessibilityInteractionClient.NO_ID; @UnsupportedAppUsage @@ -3435,75 +3469,154 @@ public abstract class AccessibilityService extends Service { } /** - * <p>Attaches a {@link android.view.SurfaceControl} containing an accessibility - * overlay to the - * specified display. This type of overlay should be used for content that does - * not need to - * track the location and size of Views in the currently active app e.g. service - * configuration - * or general service UI.</p> - * <p>Generally speaking, an accessibility overlay will be a {@link android.view.View}. - * To embed the View into a {@link android.view.SurfaceControl}, create a - * {@link android.view.SurfaceControlViewHost} and attach the View using - * {@link android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by - * calling <code> viewHost.getSurfacePackage().getSurfaceControl()</code>.</p> - * <p>To remove this overlay and free the associated - * resources, use - * <code> new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.</p> - * <p>If the specified overlay has already been attached to the specified display - * this method does nothing. - * If the specified overlay has already been attached to a previous display this - * function will transfer the overlay to the new display. - * Services can attach multiple overlays. Use - * <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>. - * to coordinate the order of the overlays on screen.</p> + * Attaches a {@link android.view.SurfaceControl} containing an accessibility overlay to the + * specified display. This type of overlay should be used for content that does not need to + * track the location and size of Views in the currently active app e.g. service configuration + * or general service UI. + * + * <p>Generally speaking, an accessibility overlay will be a {@link android.view.View}. To embed + * the View into a {@link android.view.SurfaceControl}, create a {@link + * android.view.SurfaceControlViewHost} and attach the View using {@link + * android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by calling + * <code> viewHost.getSurfacePackage().getSurfaceControl()</code>. + * + * <p>To remove this overlay and free the associated resources, use <code> + * new SurfaceControl.Transaction().reparent(sc, null).apply();</code>. + * + * <p>If the specified overlay has already been attached to the specified display this method + * does nothing. If the specified overlay has already been attached to a previous display this + * function will transfer the overlay to the new display. Services can attach multiple overlays. + * Use <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>. to + * coordinate the order of the overlays on screen. * * @param displayId the display to which the SurfaceControl should be attached. - * @param sc the SurfaceControl containing the overlay content + * @param sc the SurfaceControl containing the overlay content + * + * @deprecated Use + * {@link #attachAccessibilityOverlayToDisplay(int, SurfaceControl, Executor, IntConsumer)} + * instead. */ + @Deprecated public void attachAccessibilityOverlayToDisplay(int displayId, @NonNull SurfaceControl sc) { Preconditions.checkNotNull(sc, "SurfaceControl cannot be null"); - final IAccessibilityServiceConnection connection = - AccessibilityInteractionClient.getConnection(mConnectionId); - if (connection == null) { - return; - } - try { - connection.attachAccessibilityOverlayToDisplay(displayId, sc); - } catch (RemoteException re) { - re.rethrowFromSystemServer(); - } + AccessibilityInteractionClient.getInstance(this) + .attachAccessibilityOverlayToDisplay(mConnectionId, displayId, sc, null, null); + } + + /** + * Attaches a {@link android.view.SurfaceControl} containing an accessibility overlay to the + * specified display. This type of overlay should be used for content that does not need to + * track the location and size of Views in the currently active app e.g. service configuration + * or general service UI. + * + * <p>Generally speaking, an accessibility overlay will be a {@link android.view.View}. To embed + * the View into a {@link android.view.SurfaceControl}, create a {@link + * android.view.SurfaceControlViewHost} and attach the View using {@link + * android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by calling + * <code> viewHost.getSurfacePackage().getSurfaceControl()</code>. + * + * <p>To remove this overlay and free the associated resources, use <code> + * new SurfaceControl.Transaction().reparent(sc, null).apply();</code>. + * + * <p>If the specified overlay has already been attached to the specified display this method + * does nothing. If the specified overlay has already been attached to a previous display this + * function will transfer the overlay to the new display. Services can attach multiple overlays. + * Use <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>. to + * coordinate the order of the overlays on screen. + * + * @param displayId the display to which the SurfaceControl should be attached. + * @param sc the SurfaceControl containing the overlay content + * @param executor Executor on which to run the callback. + * @param callback The callback invoked when attaching the overlay has succeeded or failed. The + * callback is a {@link java.util.function.IntConsumer} of the result status code. + * @see OVERLAY_RESULT_SUCCESS + * @see OVERLAY_RESULT_INVALID + * @see OVERLAY_RESULT_INTERNAL_ERROR + */ + public void attachAccessibilityOverlayToDisplay( + int displayId, + @NonNull SurfaceControl sc, + @NonNull @CallbackExecutor Executor executor, + @NonNull IntConsumer callback) { + Preconditions.checkNotNull(sc, "SurfaceControl cannot be null"); + AccessibilityInteractionClient.getInstance(this) + .attachAccessibilityOverlayToDisplay( + mConnectionId, displayId, sc, executor, callback); } /** - * <p>Attaches an accessibility overlay {@link android.view.SurfaceControl} to the - * specified - * window. This method should be used when you want the overlay to move and - * resize as the parent window moves and resizes.</p> - * <p>Generally speaking, an accessibility overlay will be a {@link android.view.View}. - * To embed the View into a {@link android.view.SurfaceControl}, create a - * {@link android.view.SurfaceControlViewHost} and attach the View using - * {@link android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by - * calling <code> viewHost.getSurfacePackage().getSurfaceControl()</code>.</p> - * <p>To remove this overlay and free the associated resources, use - * <code> new SurfaceControl.Transaction().reparent(sc, null).apply();</code>.</p> - * <p>If the specified overlay has already been attached to the specified window - * this method does nothing. - * If the specified overlay has already been attached to a previous window this - * function will transfer the overlay to the new window. - * Services can attach multiple overlays. Use - * <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>. - * to coordinate the order of the overlays on screen.</p> - * - * @param accessibilityWindowId The window id, from - * {@link AccessibilityWindowInfo#getId()}. - * @param sc the SurfaceControl containing the overlay - * content + * Attaches an accessibility overlay {@link android.view.SurfaceControl} to the specified + * window. This method should be used when you want the overlay to move and resize as the parent + * window moves and resizes. + * + * <p>Generally speaking, an accessibility overlay will be a {@link android.view.View}. To embed + * the View into a {@link android.view.SurfaceControl}, create a {@link + * android.view.SurfaceControlViewHost} and attach the View using {@link + * android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by calling + * <code> viewHost.getSurfacePackage().getSurfaceControl()</code>. + * + * <p>To remove this overlay and free the associated resources, use <code> + * new SurfaceControl.Transaction().reparent(sc, null).apply();</code>. + * + * <p>If the specified overlay has already been attached to the specified window this method + * does nothing. If the specified overlay has already been attached to a previous window this + * function will transfer the overlay to the new window. Services can attach multiple overlays. + * Use <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>. to + * coordinate the order of the overlays on screen. + * + * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}. + * @param sc the SurfaceControl containing the overlay content + * + * @deprecated Use + * {@link #attachAccessibilityOverlayToWindow(int, SurfaceControl, Executor,IntConsumer)} + * instead. */ + @Deprecated public void attachAccessibilityOverlayToWindow( int accessibilityWindowId, @NonNull SurfaceControl sc) { Preconditions.checkNotNull(sc, "SurfaceControl cannot be null"); AccessibilityInteractionClient.getInstance(this) - .attachAccessibilityOverlayToWindow(mConnectionId, accessibilityWindowId, sc); + .attachAccessibilityOverlayToWindow( + mConnectionId, accessibilityWindowId, sc, null, null); + } + + /** + * Attaches an accessibility overlay {@link android.view.SurfaceControl} to the specified + * window. This method should be used when you want the overlay to move and resize as the parent + * window moves and resizes. + * + * <p>Generally speaking, an accessibility overlay will be a {@link android.view.View}. To embed + * the View into a {@link android.view.SurfaceControl}, create a {@link + * android.view.SurfaceControlViewHost} and attach the View using {@link + * android.view.SurfaceControlViewHost#setView}. Then obtain the SurfaceControl by calling + * <code> viewHost.getSurfacePackage().getSurfaceControl()</code>. + * + * <p>To remove this overlay and free the associated resources, use <code> + * new SurfaceControl.Transaction().reparent(sc, null).apply();</code>. + * + * <p>If the specified overlay has already been attached to the specified window this method + * does nothing. If the specified overlay has already been attached to a previous window this + * function will transfer the overlay to the new window. Services can attach multiple overlays. + * Use <code> new SurfaceControl.Transaction().setLayer(sc, layer).apply();</code>. to + * coordinate the order of the overlays on screen. + * + * @param accessibilityWindowId The window id, from {@link AccessibilityWindowInfo#getId()}. + * @param sc the SurfaceControl containing the overlay content + * @param executor Executor on which to run the callback. + * @param callback The callback invoked when attaching the overlay has succeeded or failed. The + * callback is a {@link java.util.function.IntConsumer} of the result status code. + * @see OVERLAY_RESULT_SUCCESS + * @see OVERLAY_RESULT_INVALID + * @see OVERLAY_RESULT_INTERNAL_ERROR + */ + public void attachAccessibilityOverlayToWindow( + int accessibilityWindowId, + @NonNull SurfaceControl sc, + @NonNull @CallbackExecutor Executor executor, + @NonNull IntConsumer callback) { + Preconditions.checkNotNull(sc, "SurfaceControl cannot be null"); + AccessibilityInteractionClient.getInstance(this) + .attachAccessibilityOverlayToWindow( + mConnectionId, accessibilityWindowId, sc, executor, callback); } } diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index e99932dc0661..96716dbbaca1 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -157,7 +157,7 @@ interface IAccessibilityServiceConnection { void setInstalledAndEnabledServices(in List<AccessibilityServiceInfo> infos); List<AccessibilityServiceInfo> getInstalledAndEnabledServices(); - void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl sc); + void attachAccessibilityOverlayToDisplay(int interactionId, int displayId, in SurfaceControl sc, IAccessibilityInteractionConnectionCallback callback); - void attachAccessibilityOverlayToWindow(int accessibilityWindowId, in SurfaceControl sc); + void attachAccessibilityOverlayToWindow(int interactionId, int accessibilityWindowId, in SurfaceControl sc, IAccessibilityInteractionConnectionCallback callback); }
\ No newline at end of file diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java index 70c3d7ae3f82..8ac507c727c7 100644 --- a/core/java/android/animation/AnimatorSet.java +++ b/core/java/android/animation/AnimatorSet.java @@ -1691,7 +1691,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim return 1; } // When neither event happens at INFINITE time: - return (int) (t1 - t2); + return t1 - t2 > 0 ? 1 : -1; } }); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 0bdf0a08f389..02aeac7502fe 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -3156,13 +3156,17 @@ public class Activity extends ContextThemeWrapper /** * Called by the system when the device configuration changes while your - * activity is running. Note that this will <em>only</em> be called if - * you have selected configurations you would like to handle with the + * activity is running. Note that this will only be called if you have + * selected configurations you would like to handle with the * {@link android.R.attr#configChanges} attribute in your manifest. If * any configuration change occurs that is not selected to be reported * by that attribute, then instead of reporting it the system will stop * and restart the activity (to have it launched with the new - * configuration). + * configuration). The only exception is if a size-based configuration + * is not large enough to be considered significant, in which case the + * system will not recreate the activity and will instead call this + * method. For details on this see the documentation on + * <a href="{@docRoot}guide/topics/resources/runtime-changes.html">size-based config change</a>. * * <p>At the time that this function has been called, your Resources * object will have been updated to return resource values matching the diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index f4a29ed3a92e..0e3a6959bed7 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -91,6 +91,7 @@ import android.graphics.drawable.LayerDrawable; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.IRemoteCallback; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; @@ -3918,4 +3919,24 @@ public class ApplicationPackageManager extends PackageManager { throw e.rethrowFromSystemServer(); } } + + @Override + public void registerPackageMonitorCallback(@NonNull IRemoteCallback callback, int userId) { + Objects.requireNonNull(callback); + try { + mPM.registerPackageMonitorCallback(callback, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + public void unregisterPackageMonitorCallback(@NonNull IRemoteCallback callback) { + Objects.requireNonNull(callback); + try { + mPM.unregisterPackageMonitorCallback(callback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 4e2b6fa56b17..d189bab85195 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -24,7 +24,6 @@ import android.app.GrantedUriPermission; import android.app.IApplicationThread; import android.app.IActivityClientController; import android.app.IActivityController; -import android.app.IAppTask; import android.app.IAssistDataReceiver; import android.app.IInstrumentationWatcher; import android.app.IProcessObserver; @@ -107,14 +106,6 @@ interface IActivityTaskManager { in ProfilerInfo profilerInfo, in Bundle options, int userId); boolean startNextMatchingActivity(in IBinder callingActivity, in Intent intent, in Bundle options); - - /** - * The DreamActivity has to be started in a special way that does not involve the PackageParser. - * The DreamActivity is a framework component inserted in the dream application process. Hence, - * it is not declared in the application's manifest and cannot be parsed. startDreamActivity - * creates the activity and starts it without reaching out to the PackageParser. - */ - boolean startDreamActivity(in Intent intent); int startActivityIntentSender(in IApplicationThread caller, in IIntentSender target, in IBinder whitelistToken, in Intent fillInIntent, in String resolvedType, in IBinder resultTo, in String resultWho, int requestCode, diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl index fbb0748fc01f..63cae63e1e50 100644 --- a/core/java/android/app/IUiAutomationConnection.aidl +++ b/core/java/android/app/IUiAutomationConnection.aidl @@ -24,6 +24,8 @@ import android.view.SurfaceControl; import android.view.WindowContentFrameStats; import android.view.WindowAnimationFrameStats; import android.os.ParcelFileDescriptor; +import android.window.ScreenCapture.ScreenCaptureListener; +import android.window.ScreenCapture.LayerCaptureArgs; import java.util.List; @@ -43,8 +45,8 @@ interface IUiAutomationConnection { void injectInputEventToInputFilter(in InputEvent event); void syncInputTransactions(boolean waitForAnimations); boolean setRotation(int rotation); - Bitmap takeScreenshot(in Rect crop); - Bitmap takeSurfaceControlScreenshot(in SurfaceControl surfaceControl); + boolean takeScreenshot(in Rect crop, in ScreenCaptureListener listener); + boolean takeSurfaceControlScreenshot(in SurfaceControl surfaceControl, in ScreenCaptureListener listener); boolean clearWindowContentFrameStats(int windowId); WindowContentFrameStats getWindowContentFrameStats(int windowId); void clearWindowAnimationFrameStats(); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 785470f2f22e..79b68c1456c7 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -571,6 +571,12 @@ public class NotificationManager { */ public static final int BUBBLE_PREFERENCE_SELECTED = 2; + /** + * Maximum length of the component name of a registered NotificationListenerService. + * @hide + */ + public static int MAX_SERVICE_COMPONENT_NAME_LENGTH = 500; + @UnsupportedAppUsage private static INotificationManager sService; diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 1e80552f6b82..8647dd29b71a 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -382,8 +382,10 @@ public final class SystemServiceRegistry { registerService(Context.SELECTION_TOOLBAR_SERVICE, SelectionToolbarManager.class, new CachedServiceFetcher<SelectionToolbarManager>() { @Override - public SelectionToolbarManager createService(ContextImpl ctx) { - IBinder b = ServiceManager.getService(Context.SELECTION_TOOLBAR_SERVICE); + public SelectionToolbarManager createService(ContextImpl ctx) + throws ServiceNotFoundException { + IBinder b = ServiceManager.getServiceOrThrow( + Context.SELECTION_TOOLBAR_SERVICE); return new SelectionToolbarManager(ctx.getOuterContext(), ISelectionToolbarManager.Stub.asInterface(b)); }}); diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index c4e49954f745..2b5175ca6659 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -242,6 +242,18 @@ public class TaskInfo { public boolean isLetterboxDoubleTapEnabled; /** + * Whether the user aspect ratio settings button is enabled + * @hide + */ + public boolean topActivityEligibleForUserAspectRatioButton; + + /** + * Hint about the letterbox state of the top activity. + * @hide + */ + public boolean topActivityBoundsLetterboxed; + + /** * Whether the update comes from a letterbox double-tap action from the user or not. * @hide */ @@ -460,7 +472,8 @@ public class TaskInfo { public boolean hasCompatUI() { return hasCameraCompatControl() || topActivityInSizeCompat || topActivityEligibleForLetterboxEducation - || isLetterboxDoubleTapEnabled; + || isLetterboxDoubleTapEnabled + || topActivityEligibleForUserAspectRatioButton; } /** @@ -510,6 +523,8 @@ public class TaskInfo { && supportsMultiWindow == that.supportsMultiWindow && displayAreaFeatureId == that.displayAreaFeatureId && isFromLetterboxDoubleTap == that.isFromLetterboxDoubleTap + && topActivityEligibleForUserAspectRatioButton + == that.topActivityEligibleForUserAspectRatioButton && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition && topActivityLetterboxWidth == that.topActivityLetterboxWidth && topActivityLetterboxHeight == that.topActivityLetterboxHeight @@ -543,6 +558,8 @@ public class TaskInfo { && taskId == that.taskId && topActivityInSizeCompat == that.topActivityInSizeCompat && isFromLetterboxDoubleTap == that.isFromLetterboxDoubleTap + && topActivityEligibleForUserAspectRatioButton + == that.topActivityEligibleForUserAspectRatioButton && topActivityEligibleForLetterboxEducation == that.topActivityEligibleForLetterboxEducation && topActivityLetterboxVerticalPosition == that.topActivityLetterboxVerticalPosition @@ -606,6 +623,8 @@ public class TaskInfo { displayAreaFeatureId = source.readInt(); cameraCompatControlState = source.readInt(); isLetterboxDoubleTapEnabled = source.readBoolean(); + topActivityEligibleForUserAspectRatioButton = source.readBoolean(); + topActivityBoundsLetterboxed = source.readBoolean(); isFromLetterboxDoubleTap = source.readBoolean(); topActivityLetterboxVerticalPosition = source.readInt(); topActivityLetterboxHorizontalPosition = source.readInt(); @@ -660,6 +679,8 @@ public class TaskInfo { dest.writeInt(displayAreaFeatureId); dest.writeInt(cameraCompatControlState); dest.writeBoolean(isLetterboxDoubleTapEnabled); + dest.writeBoolean(topActivityEligibleForUserAspectRatioButton); + dest.writeBoolean(topActivityBoundsLetterboxed); dest.writeBoolean(isFromLetterboxDoubleTap); dest.writeInt(topActivityLetterboxVerticalPosition); dest.writeInt(topActivityLetterboxHorizontalPosition); @@ -701,8 +722,11 @@ public class TaskInfo { + " topActivityInSizeCompat=" + topActivityInSizeCompat + " topActivityEligibleForLetterboxEducation= " + topActivityEligibleForLetterboxEducation - + " topActivityLetterboxed= " + isLetterboxDoubleTapEnabled - + " isFromDoubleTap= " + isFromLetterboxDoubleTap + + " isLetterboxDoubleTapEnabled= " + isLetterboxDoubleTapEnabled + + " topActivityEligibleForUserAspectRatioButton= " + + topActivityEligibleForUserAspectRatioButton + + " topActivityBoundsLetterboxed= " + topActivityBoundsLetterboxed + + " isFromLetterboxDoubleTap= " + isFromLetterboxDoubleTap + " topActivityLetterboxVerticalPosition= " + topActivityLetterboxVerticalPosition + " topActivityLetterboxHorizontalPosition= " + topActivityLetterboxHorizontalPosition diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index b613faefe06d..b0180c1857b6 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -37,6 +37,7 @@ import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; +import android.hardware.HardwareBuffer; import android.hardware.display.DisplayManagerGlobal; import android.os.Build; import android.os.Handler; @@ -71,6 +72,8 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.inputmethod.EditorInfo; +import android.window.ScreenCapture; +import android.window.ScreenCapture.ScreenshotHardwareBuffer; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -1190,17 +1193,12 @@ public final class UiAutomation { Point displaySize = new Point(); display.getRealSize(displaySize); - int rotation = display.getRotation(); - // Take the screenshot - Bitmap screenShot = null; + ScreenCapture.SynchronousScreenCaptureListener syncScreenCapture = + ScreenCapture.createSyncCaptureListener(); try { - // Calling out without a lock held. - screenShot = mUiAutomationConnection.takeScreenshot( - new Rect(0, 0, displaySize.x, displaySize.y)); - if (screenShot == null) { - Log.e(LOG_TAG, "mUiAutomationConnection.takeScreenshot() returned null for display " - + mDisplayId); + if (!mUiAutomationConnection.takeScreenshot( + new Rect(0, 0, displaySize.x, displaySize.y), syncScreenCapture)) { return null; } } catch (RemoteException re) { @@ -1208,10 +1206,23 @@ public final class UiAutomation { return null; } - // Optimization - screenShot.setHasAlpha(false); + final ScreenshotHardwareBuffer screenshotBuffer = + syncScreenCapture.getBuffer(); + Bitmap screenShot = screenshotBuffer.asBitmap(); + if (screenShot == null) { + Log.e(LOG_TAG, "mUiAutomationConnection.takeScreenshot() returned null for display " + + mDisplayId); + return null; + } + Bitmap swBitmap; + try (HardwareBuffer buffer = screenshotBuffer.getHardwareBuffer()) { + swBitmap = screenShot.copy(Bitmap.Config.ARGB_8888, false); + } + screenShot.recycle(); - return screenShot; + // Optimization + swBitmap.setHasAlpha(false); + return swBitmap; } /** @@ -1248,12 +1259,27 @@ public final class UiAutomation { // Apply a sync transaction to ensure SurfaceFlinger is flushed before capturing a // screenshot. new SurfaceControl.Transaction().apply(true); + ScreenCapture.SynchronousScreenCaptureListener syncScreenCapture = + ScreenCapture.createSyncCaptureListener(); try { - return mUiAutomationConnection.takeSurfaceControlScreenshot(sc); + if (!mUiAutomationConnection.takeSurfaceControlScreenshot(sc, syncScreenCapture)) { + return null; + } + } catch (RemoteException re) { Log.e(LOG_TAG, "Error while taking screenshot!", re); return null; } + ScreenCapture.ScreenshotHardwareBuffer captureBuffer = + syncScreenCapture.getBuffer(); + Bitmap screenShot = captureBuffer.asBitmap(); + Bitmap swBitmap; + try (HardwareBuffer buffer = captureBuffer.getHardwareBuffer()) { + swBitmap = screenShot.copy(Bitmap.Config.ARGB_8888, false); + } + + screenShot.recycle(); + return swBitmap; } /** diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index 34f0964cf823..52949d6d1fbd 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -25,7 +25,6 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; -import android.graphics.Bitmap; import android.graphics.Rect; import android.hardware.input.InputManager; import android.hardware.input.InputManagerGlobal; @@ -51,8 +50,6 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.IAccessibilityManager; import android.window.ScreenCapture; import android.window.ScreenCapture.CaptureArgs; -import android.window.ScreenCapture.ScreenshotHardwareBuffer; -import android.window.ScreenCapture.SynchronousScreenCaptureListener; import libcore.io.IoUtils; @@ -224,56 +221,54 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { } @Override - public Bitmap takeScreenshot(Rect crop) { + public boolean takeScreenshot(Rect crop, ScreenCapture.ScreenCaptureListener listener) { synchronized (mLock) { throwIfCalledByNotTrustedUidLocked(); throwIfShutdownLocked(); throwIfNotConnectedLocked(); } + final long identity = Binder.clearCallingIdentity(); try { final CaptureArgs captureArgs = new CaptureArgs.Builder<>() .setSourceCrop(crop) .build(); - SynchronousScreenCaptureListener syncScreenCapture = - ScreenCapture.createSyncCaptureListener(); - mWindowManager.captureDisplay(DEFAULT_DISPLAY, captureArgs, - syncScreenCapture); - final ScreenshotHardwareBuffer screenshotBuffer = - syncScreenCapture.getBuffer(); - return screenshotBuffer == null ? null : screenshotBuffer.asBitmap(); + mWindowManager.captureDisplay(DEFAULT_DISPLAY, captureArgs, listener); } catch (RemoteException re) { re.rethrowAsRuntimeException(); } finally { Binder.restoreCallingIdentity(identity); } - return null; + + return true; } @Nullable @Override - public Bitmap takeSurfaceControlScreenshot(@NonNull SurfaceControl surfaceControl) { + public boolean takeSurfaceControlScreenshot(@NonNull SurfaceControl surfaceControl, + ScreenCapture.ScreenCaptureListener listener) { synchronized (mLock) { throwIfCalledByNotTrustedUidLocked(); throwIfShutdownLocked(); throwIfNotConnectedLocked(); } - ScreenCapture.ScreenshotHardwareBuffer captureBuffer; final long identity = Binder.clearCallingIdentity(); try { - captureBuffer = ScreenCapture.captureLayers( + ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(surfaceControl) - .setChildrenOnly(false) - .build()); + .setChildrenOnly(false) + .build(); + int status = ScreenCapture.captureLayers(args, listener); + + if (status != 0) { + return false; + } } finally { Binder.restoreCallingIdentity(identity); } - if (captureBuffer == null) { - return null; - } - return captureBuffer.asBitmap(); + return true; } @Override diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java index 74132a3783be..e3a05af8fa35 100644 --- a/core/java/android/app/WallpaperInfo.java +++ b/core/java/android/app/WallpaperInfo.java @@ -142,9 +142,13 @@ public final class WallpaperInfo implements Parcelable { mShowMetadataInPreview = sa.getBoolean( com.android.internal.R.styleable.Wallpaper_showMetadataInPreview, false); + + // Watch wallpapers support ambient mode by default. + final boolean defSupportsAmbientMode = + pm.hasSystemFeature(PackageManager.FEATURE_WATCH); mSupportsAmbientMode = sa.getBoolean( com.android.internal.R.styleable.Wallpaper_supportsAmbientMode, - false); + defSupportsAmbientMode); mShouldUseDefaultUnfoldTransition = sa.getBoolean( com.android.internal.R.styleable .Wallpaper_shouldUseDefaultUnfoldTransition, true); diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java index aeac59b12a2e..ad0af72c72b4 100644 --- a/core/java/android/app/admin/DevicePolicyIdentifiers.java +++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java @@ -160,6 +160,14 @@ public final class DevicePolicyIdentifiers { public static final String CROSS_PROFILE_WIDGET_PROVIDER_POLICY = "crossProfileWidgetProvider"; /** + * String identifier for {@link DevicePolicyManager#setUsbDataSignalingEnabled}. + * + * @hide + */ + @TestApi + public static final String USB_DATA_SIGNALING_POLICY = "usbDataSignaling"; + + /** * @hide */ public static final String USER_RESTRICTION_PREFIX = "userRestriction_"; diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index da5e40aedbd2..33b8b03e3258 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -12993,7 +12993,7 @@ public class DevicePolicyManager { } /** - * Called by device owners or profile owners of an organization-owned managed profile to to set + * Called by device owners or profile owners of an organization-owned managed profile to set * a local system update policy. When a new policy is set, * {@link #ACTION_SYSTEM_UPDATE_POLICY_CHANGED} is broadcast. * <p> @@ -14787,12 +14787,12 @@ public class DevicePolicyManager { } /** - * Called by the system to find out whether the current user's IME was set by the device/profile - * owner or the user. + * Returns true if the current user's IME was set by an admin. * - * @return {@code true} if the user's IME was set by the device or profile owner, {@code false} - * otherwise. - * @throws SecurityException if the caller is not the device owner/profile owner. + * <p>Requires the caller to be the system server, a device owner or profile owner, or a holder + * of the QUERY_ADMIN_POLICY permission. + * + * @throws SecurityException if the caller is not authorized * * @hide */ @@ -16585,10 +16585,28 @@ public class DevicePolicyManager { * {@link #canUsbDataSignalingBeDisabled()} to check whether enabling or disabling USB data * signaling is supported on the device. * + * Starting from {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, after the USB data signaling + * policy has been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String, + * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was + * successfully set or not. This callback will contain: + * <ul> + * li> The policy identifier {@link DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY} + * <li> The {@link TargetUser} that this policy relates to + * <li> The {@link PolicyUpdateResult}, which will be + * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the + * reason the policy failed to be set + * e.g. {@link PolicyUpdateResult#RESULT_FAILURE_CONFLICTING_ADMIN_POLICY}) + * </ul> + * If there has been a change to the policy, + * {@link PolicyUpdateReceiver#onPolicyChanged(Context, String, Bundle, TargetUser, + * PolicyUpdateResult)} will notify the admin of this change. This callback will contain the + * same parameters as PolicyUpdateReceiver#onPolicySetResult and the {@link PolicyUpdateResult} + * will contain the reason why the policy changed. + * * @param enabled whether USB data signaling should be enabled or not. * @throws SecurityException if the caller is not permitted to set this policy * @throws IllegalStateException if disabling USB data signaling is not supported or - * if USB data signaling fails to be enabled/disabled. + * if USB data signaling fails to be enabled/disabled. */ @RequiresPermission(value = MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING, conditional = true) public void setUsbDataSignalingEnabled(boolean enabled) { @@ -16624,25 +16642,6 @@ public class DevicePolicyManager { } /** - * Called by the system to check whether USB data signaling is currently enabled for this user. - * - * @param userId which user to check for. - * @return {@code true} if USB data signaling is enabled, {@code false} otherwise. - * @hide - */ - public boolean isUsbDataSignalingEnabledForUser(@UserIdInt int userId) { - throwIfParentInstance("isUsbDataSignalingEnabledForUser"); - if (mService != null) { - try { - return mService.isUsbDataSignalingEnabledForUser(userId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - return true; - } - - /** * Returns whether enabling or disabling USB data signaling is supported on the device. * * @return {@code true} if the device supports enabling and disabling USB data signaling. diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java index 0e78275fa30b..8dd50f0c42e8 100644 --- a/core/java/android/app/admin/DevicePolicyManagerInternal.java +++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java @@ -250,6 +250,16 @@ public abstract class DevicePolicyManagerInternal { public abstract ComponentName getProfileOwnerAsUser(@UserIdInt int userId); /** + * Returns the device owner component for the device, or {@code null} if there is not one. + * + * @deprecated added temporarily to support Android Role permission granting. + * Please contact Android Enterprise Device Policy team before calling this function. + */ + @Deprecated + @Nullable + public abstract ComponentName getDeviceOwnerComponent(boolean callingUserOnly); + + /** * Returns the user id of the device owner, or {@link UserHandle#USER_NULL} if there is not one. */ @UserIdInt diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 003e804831a4..95ec89e5f444 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -565,7 +565,6 @@ interface IDevicePolicyManager { void setUsbDataSignalingEnabled(String callerPackage, boolean enabled); boolean isUsbDataSignalingEnabled(String callerPackage); - boolean isUsbDataSignalingEnabledForUser(int userId); boolean canUsbDataSignalingBeDisabled(); void setMinimumRequiredWifiSecurityLevel(String callerPackageName, int level); diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java index 3d76b28a3ccc..d7195a76d873 100644 --- a/core/java/android/content/BroadcastReceiver.java +++ b/core/java/android/content/BroadcastReceiver.java @@ -65,7 +65,7 @@ public abstract class BroadcastReceiver { * thread of your app. * * <p>Note on threading: the state inside of this class is not itself - * thread-safe, however you can use it from any thread if you properly + * thread-safe. However, you can use it from any thread if you make * sure that you do not have races. Typically this means you will hand * the entire object to another thread, which will be solely responsible * for setting any results and finally calling {@link #finish()}. diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 5d076d487b30..6d82922484bc 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1413,7 +1413,7 @@ public abstract class Context { * </ul> * <p> * If a shared storage device is emulated (as determined by - * {@link Environment#isExternalStorageEmulated(File)}), it's contents are + * {@link Environment#isExternalStorageEmulated(File)}), its contents are * backed by a private user data partition, which means there is little * benefit to storing data here instead of the private directories returned * by {@link #getFilesDir()}, etc. @@ -1501,7 +1501,7 @@ public abstract class Context { * </ul> * <p> * If a shared storage device is emulated (as determined by - * {@link Environment#isExternalStorageEmulated(File)}), it's contents are + * {@link Environment#isExternalStorageEmulated(File)}), its contents are * backed by a private user data partition, which means there is little * benefit to storing data here instead of the private directories returned * by {@link #getFilesDir()}, etc. @@ -1812,7 +1812,7 @@ public abstract class Context { * </ul> * <p> * If a shared storage device is emulated (as determined by - * {@link Environment#isExternalStorageEmulated(File)}), it's contents are + * {@link Environment#isExternalStorageEmulated(File)}), its contents are * backed by a private user data partition, which means there is little * benefit to storing data here instead of the private directory returned by * {@link #getCacheDir()}. diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index bf25ae8b43a7..7c9ccbad10a2 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -55,6 +55,7 @@ import android.content.pm.dex.IArtManager; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; +import android.os.IRemoteCallback; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.content.IntentSender; @@ -794,6 +795,11 @@ interface IPackageManager { void setSplashScreenTheme(String packageName, String themeName, int userId); + int getUserMinAspectRatio(String packageName, int userId); + + @EnforcePermission("INSTALL_PACKAGES") + void setUserMinAspectRatio(String packageName, int userId, int aspectRatio); + List<String> getMimeGroup(String packageName, String group); boolean isAutoRevokeWhitelisted(String packageName); @@ -818,4 +824,8 @@ interface IPackageManager { boolean[] canPackageQuery(String sourcePackageName, in String[] targetPackageNames, int userId); boolean waitForHandler(long timeoutMillis, boolean forBackgroundHandler); + + void registerPackageMonitorCallback(IRemoteCallback callback, int userId); + + void unregisterPackageMonitorCallback(IRemoteCallback callback); } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 3e442e592e10..679dd48f3713 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -3091,10 +3091,6 @@ public class PackageInstaller { * The update ownership enforcement can only be enabled on initial installation. Set * this to {@code true} on package update is a no-op. * - * Apps may opt themselves out of update ownership by setting the - * <a href="https://developer.android.com/guide/topics/manifest/manifest-element.html#allowupdateownership">android:alllowUpdateOwnership</a> - * attribute in their manifest to <code>false</code>. - * * Note: To enable the update ownership enforcement, the installer must have the * {@link android.Manifest.permission#ENFORCE_UPDATE_OWNERSHIP ENFORCE_UPDATE_OWNERSHIP} * permission. diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index e5468a58e4be..2948bd9b8868 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -65,6 +65,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.Parcel; import android.os.Parcelable; import android.os.PersistableBundle; @@ -1431,6 +1432,7 @@ public abstract class PackageManager { INSTALL_ALLOW_DOWNGRADE, INSTALL_STAGED, INSTALL_REQUEST_UPDATE_OWNERSHIP, + INSTALL_DONT_EXTRACT_BASELINE_PROFILES, }) @Retention(RetentionPolicy.SOURCE) public @interface InstallFlags {} @@ -1645,6 +1647,13 @@ public abstract class PackageManager { */ public static final int INSTALL_FROM_MANAGED_USER_OR_PROFILE = 1 << 26; + /** + * Flag parameter for {@link PackageInstaller.SessionParams} to indicate that do not extract + * the baseline profiles when parsing the apk + * @hide + */ + public static final int INSTALL_DONT_EXTRACT_BASELINE_PROFILES = 1 << 27; + /** @hide */ @IntDef(flag = true, value = { DONT_KILL_APP, @@ -2342,6 +2351,64 @@ public abstract class PackageManager { */ public static final int INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST = -130; + /** + * App minimum aspect ratio set by the user which will override app-defined aspect ratio. + * + * @hide + */ + @IntDef(prefix = { "USER_MIN_ASPECT_RATIO_" }, value = { + USER_MIN_ASPECT_RATIO_UNSET, + USER_MIN_ASPECT_RATIO_SPLIT_SCREEN, + USER_MIN_ASPECT_RATIO_DISPLAY_SIZE, + USER_MIN_ASPECT_RATIO_4_3, + USER_MIN_ASPECT_RATIO_16_9, + USER_MIN_ASPECT_RATIO_3_2, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface UserMinAspectRatio {} + + /** + * No aspect ratio override has been set by user. + * + * @hide + */ + public static final int USER_MIN_ASPECT_RATIO_UNSET = 0; + + /** + * Aspect ratio override code: user forces app to split screen aspect ratio. This is adjusted to + * half of the screen without the split screen divider. + * + * @hide + */ + public static final int USER_MIN_ASPECT_RATIO_SPLIT_SCREEN = 1; + + /** + * Aspect ratio override code: user forces app to the aspect ratio of the device display size. + * This will be the portrait aspect ratio of the device if the app is portrait or the landscape + * aspect ratio of the device if the app is landscape. + * + * @hide + */ + public static final int USER_MIN_ASPECT_RATIO_DISPLAY_SIZE = 2; + + /** + * Aspect ratio override code: user forces app to 4:3 min aspect ratio + * @hide + */ + public static final int USER_MIN_ASPECT_RATIO_4_3 = 3; + + /** + * Aspect ratio override code: user forces app to 16:9 min aspect ratio + * @hide + */ + public static final int USER_MIN_ASPECT_RATIO_16_9 = 4; + + /** + * Aspect ratio override code: user forces app to 3:2 min aspect ratio + * @hide + */ + public static final int USER_MIN_ASPECT_RATIO_3_2 = 5; + /** @hide */ @IntDef(flag = true, prefix = { "DELETE_" }, value = { DELETE_KEEP_DATA, @@ -2576,6 +2643,13 @@ public abstract class PackageManager { public static final String EXTRA_MOVE_ID = "android.content.pm.extra.MOVE_ID"; /** + * Extra field name for notifying package change event. Currently, it is used by PackageMonitor. + * @hide + */ + public static final String EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT = + "android.content.pm.extra.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT"; + + /** * Usable by the required verifier as the {@code verificationCode} argument * for {@link PackageManager#verifyPendingInstall} to indicate that it will * allow the installation to proceed without any of the optional verifiers @@ -11039,4 +11113,28 @@ public abstract class PackageManager { throw new UnsupportedOperationException( "relinquishUpdateOwnership not implemented in subclass"); } + + /** + * Register for notifications of package changes such as install, removal and other events. + * + * @param callback the callback to register for receiving the change events + * @param userId The id of registered user + * @hide + */ + public void registerPackageMonitorCallback(@NonNull IRemoteCallback callback, int userId) { + throw new UnsupportedOperationException( + "registerPackageMonitorCallback not implemented in subclass"); + } + + /** + * Unregister for notifications of package changes such as install, removal and other events. + * + * @param callback the callback to unregister for receiving the change events + * @see #registerPackageMonitorCallback(IRemoteCallback, int) + * @hide + */ + public void unregisterPackageMonitorCallback(@NonNull IRemoteCallback callback) { + throw new UnsupportedOperationException( + "unregisterPackageMonitorCallback not implemented in subclass"); + } } diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index 7f42c1a08f9c..9f4459d6591b 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -390,6 +390,10 @@ public class UserInfo implements Parcelable { return UserManager.isUserTypeCommunalProfile(userType); } + public boolean isPrivateProfile() { + return UserManager.isUserTypePrivateProfile(userType); + } + @UnsupportedAppUsage public boolean isEnabled() { return (flags & FLAG_DISABLED) != FLAG_DISABLED; diff --git a/core/java/android/content/pm/dex/DexMetadataHelper.java b/core/java/android/content/pm/dex/DexMetadataHelper.java index e75aa065d3d3..3b53c25d1d8b 100644 --- a/core/java/android/content/pm/dex/DexMetadataHelper.java +++ b/core/java/android/content/pm/dex/DexMetadataHelper.java @@ -23,6 +23,9 @@ import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; import android.content.pm.parsing.result.ParseInput; import android.content.pm.parsing.result.ParseResult; +import android.content.res.AssetFileDescriptor; +import android.content.res.AssetManager; +import android.os.ParcelFileDescriptor.AutoCloseInputStream; import android.os.SystemProperties; import android.util.ArrayMap; import android.util.JsonReader; @@ -33,6 +36,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.security.VerityUtils; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -44,6 +48,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; /** * Helper class used to compute and validate the location of dex metadata files. @@ -62,6 +67,11 @@ public class DexMetadataHelper { private static final String DEX_METADATA_FILE_EXTENSION = ".dm"; + private static final String PROFILE_FILE_NAME = "primary.prof"; + private static final String PROFILE_METADATA_FILE_NAME = "primary.profm"; + private static final String BASELINE_PROFILE_SOURCE_RELATIVE_PATH = "dexopt/baseline.prof"; + private static final String BASELINE_PROFILE_METADATA_RELATIVE_PATH = "dexopt/baseline.profm"; + private DexMetadataHelper() {} /** Return true if the given file is a dex metadata file. */ @@ -313,4 +323,76 @@ public class DexMetadataHelper { } } + /** + * Extract the baseline profiles from the assets directory in the apk. Then create a + * ZIP archive with the (.dm) file extension (e.g. foo.dm from foo.apk) to include the + * baseline profiles and put the DexMetadata file in the same directory with the apk. + * + * @param assetManager The {@link AssetManager} to use. + * @param apkPath The path of the apk + * @return {@code true} if the extraction is successful. Otherwise, return {@code false}. + * + * @see #buildDexMetadataPathForApk(String) + */ + public static boolean extractBaselineProfilesToDexMetadataFileFromApk(AssetManager assetManager, + String apkPath) { + if (!ApkLiteParseUtils.isApkPath(apkPath)) { + if (DEBUG) { + Log.d(TAG, "It is not an apk file: " + apkPath); + } + return false; + } + + // get the name of the DexMetadata file from the path of the apk + final File dmFile = new File(buildDexMetadataPathForApk(apkPath)); + boolean success = false; + + // load profile and profile metadata from assets directory in the apk + try (InputStream profileIs = openStreamFromAssets(assetManager, + BASELINE_PROFILE_SOURCE_RELATIVE_PATH); + InputStream profileMetadataIs = openStreamFromAssets(assetManager, + BASELINE_PROFILE_METADATA_RELATIVE_PATH)) { + // Create the zip archive file and write the baseline profiles into it. + try (FileOutputStream fos = new FileOutputStream(dmFile)) { + try (ZipOutputStream zipOs = new ZipOutputStream(fos)) { + zipOs.putNextEntry(new ZipEntry(PROFILE_FILE_NAME)); + zipOs.write(profileIs.readAllBytes()); + zipOs.closeEntry(); + + zipOs.putNextEntry(new ZipEntry(PROFILE_METADATA_FILE_NAME)); + zipOs.write(profileMetadataIs.readAllBytes()); + zipOs.closeEntry(); + success = true; + } + } + } catch (IOException e) { + if (DEBUG) { + Log.e(TAG, "Extract baseline profiles from apk failed: " + e.getMessage()); + } + } finally { + if (!success) { + if (dmFile.exists()) { + dmFile.delete(); + } + } + } + return success; + } + + /** + * Loads an {@link AutoCloseInputStream} from assets with the path. + * + * @param assetManager The {@link AssetManager} to use. + * @param path The source file's relative path. + * @return An AutoCloseInputStream in case the file was successfully read. + * @throws IOException If anything goes wrong while opening or reading the file. + */ + private static AutoCloseInputStream openStreamFromAssets(AssetManager assetManager, String path) + throws IOException { + AssetFileDescriptor descriptor = assetManager.openFd(path); + // Based on the java doc of AssetFileDescriptor#createInputStream, it will always return + // an AutoCloseInputStream. It should be fine we cast it from FileInputStream to + // AutoCloseInputStream here. + return (AutoCloseInputStream) descriptor.createInputStream(); + } } diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java index 653e243f5e06..4089cfe91eda 100644 --- a/core/java/android/content/res/ApkAssets.java +++ b/core/java/android/content/res/ApkAssets.java @@ -15,6 +15,8 @@ */ package android.content.res; +import static android.content.res.Resources.ID_NULL; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -386,7 +388,7 @@ public final class ApkAssets { synchronized (this) { long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName); try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) { - XmlResourceParser parser = block.newParser(); + XmlResourceParser parser = block.newParser(ID_NULL, new Validator()); // If nativeOpenXml doesn't throw, it will always return a valid native pointer, // which makes newParser always return non-null. But let's be careful. if (parser == null) { diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index cc06ea721bd1..b225de402f17 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -16,6 +16,8 @@ package android.content.res; +import static android.content.res.Resources.ID_NULL; + import android.annotation.AnyRes; import android.annotation.ArrayRes; import android.annotation.AttrRes; @@ -1089,7 +1091,7 @@ public final class AssetManager implements AutoCloseable { public @NonNull XmlResourceParser openXmlResourceParser(int cookie, @NonNull String fileName) throws IOException { try (XmlBlock block = openXmlBlockAsset(cookie, fileName)) { - XmlResourceParser parser = block.newParser(); + XmlResourceParser parser = block.newParser(ID_NULL, new Validator()); // If openXmlBlockAsset doesn't throw, it will always return an XmlBlock object with // a valid native pointer, which makes newParser always return non-null. But let's // be careful. diff --git a/core/java/android/content/res/Element.java b/core/java/android/content/res/Element.java new file mode 100644 index 000000000000..a6fea6ad750a --- /dev/null +++ b/core/java/android/content/res/Element.java @@ -0,0 +1,405 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; + +import android.annotation.NonNull; +import android.util.ArrayMap; +import android.util.Pools.SimplePool; + +import androidx.annotation.StyleableRes; + +import com.android.internal.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.util.Iterator; +import java.util.Map; + +/** + * Defines the string attribute length and child tag count restrictions for a xml element. + * + * {@hide} + */ +public class Element { + private static final int DEFAULT_MAX_STRING_ATTR_LENGTH = 32_768; + private static final int MAX_POOL_SIZE = 128; + + private static final String ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android"; + + protected static final String TAG_ACTION = "action"; + protected static final String TAG_ACTIVITY = "activity"; + protected static final String TAG_ADOPT_PERMISSIONS = "adopt-permissions"; + protected static final String TAG_ACTIVITY_ALIAS = "activity-alias"; + protected static final String TAG_APPLICATION = "application"; + protected static final String TAG_ATTRIBUTION = "attribution"; + protected static final String TAG_CATEGORY = "category"; + protected static final String TAG_COMPATIBLE_SCREENS = "compatible-screens"; + protected static final String TAG_DATA = "data"; + protected static final String TAG_EAT_COMMENT = "eat-comment"; + protected static final String TAG_FEATURE_GROUP = "feature-group"; + protected static final String TAG_GRANT_URI_PERMISSION = "grant-uri-permission"; + protected static final String TAG_INSTRUMENTATION = "instrumentation"; + protected static final String TAG_INTENT = "intent"; + protected static final String TAG_INTENT_FILTER = "intent-filter"; + protected static final String TAG_KEY_SETS = "key-sets"; + protected static final String TAG_LAYOUT = "layout"; + protected static final String TAG_MANIFEST = "manifest"; + protected static final String TAG_META_DATA = "meta-data"; + protected static final String TAG_ORIGINAL_PACKAGE = "original-package"; + protected static final String TAG_OVERLAY = "overlay"; + protected static final String TAG_PACKAGE = "package"; + protected static final String TAG_PACKAGE_VERIFIER = "package-verifier"; + protected static final String TAG_PATH_PERMISSION = "path-permission"; + protected static final String TAG_PERMISSION = "permission"; + protected static final String TAG_PERMISSION_GROUP = "permission-group"; + protected static final String TAG_PERMISSION_TREE = "permission-tree"; + protected static final String TAG_PROFILEABLE = "profileable"; + protected static final String TAG_PROTECTED_BROADCAST = "protected-broadcast"; + protected static final String TAG_PROPERTY = "property"; + protected static final String TAG_PROVIDER = "provider"; + protected static final String TAG_QUERIES = "queries"; + protected static final String TAG_RECEIVER = "receiver"; + protected static final String TAG_RESTRICT_UPDATE = "restrict-update"; + protected static final String TAG_SCREEN = "screen"; + protected static final String TAG_SERVICE = "service"; + protected static final String TAG_SUPPORT_SCREENS = "supports-screens"; + protected static final String TAG_SUPPORTS_GL_TEXTURE = "supports-gl-texture"; + protected static final String TAG_SUPPORTS_INPUT = "supports-input"; + protected static final String TAG_SUPPORTS_SCREENS = "supports-screens"; + protected static final String TAG_USES_CONFIGURATION = "uses-configuration"; + protected static final String TAG_USES_FEATURE = "uses-feature"; + protected static final String TAG_USES_GL_TEXTURE = "uses-gl-texture"; + protected static final String TAG_USES_LIBRARY = "uses-library"; + protected static final String TAG_USES_NATIVE_LIBRARY = "uses-native-library"; + protected static final String TAG_USES_PERMISSION = "uses-permission"; + protected static final String TAG_USES_PERMISSION_SDK_23 = "uses-permission-sdk-23"; + protected static final String TAG_USES_PERMISSION_SDK_M = "uses-permission-sdk-m"; + protected static final String TAG_USES_SDK = "uses-sdk"; + protected static final String TAG_USES_SPLIT = "uses-split"; + + protected static final String TAG_ATTR_BACKUP_AGENT = "backupAgent"; + protected static final String TAG_ATTR_CATEGORY = "category"; + protected static final String TAG_ATTR_HOST = "host"; + protected static final String TAG_ATTR_MANAGE_SPACE_ACTIVITY = "manageSpaceActivity"; + protected static final String TAG_ATTR_MIMETYPE = "mimeType"; + protected static final String TAG_ATTR_NAME = "name"; + protected static final String TAG_ATTR_PACKAGE = "package"; + protected static final String TAG_ATTR_PATH = "path"; + protected static final String TAG_ATTR_PATH_ADVANCED_PATTERN = "pathAdvancedPattern"; + protected static final String TAG_ATTR_PATH_PATTERN = "pathPattern"; + protected static final String TAG_ATTR_PATH_PREFIX = "pathPrefix"; + protected static final String TAG_ATTR_PATH_SUFFIX = "pathSuffix"; + protected static final String TAG_ATTR_PARENT_ACTIVITY_NAME = "parentActivityName"; + protected static final String TAG_ATTR_PERMISSION = "permission"; + protected static final String TAG_ATTR_PERMISSION_GROUP = "permissionGroup"; + protected static final String TAG_ATTR_PORT = "port"; + protected static final String TAG_ATTR_PROCESS = "process"; + protected static final String TAG_ATTR_READ_PERMISSION = "readPermission"; + protected static final String TAG_ATTR_REQUIRED_ACCOUNT_TYPE = "requiredAccountType"; + protected static final String TAG_ATTR_REQUIRED_SYSTEM_PROPERTY_NAME = + "requiredSystemPropertyName"; + protected static final String TAG_ATTR_REQUIRED_SYSTEM_PROPERTY_VALUE = + "requiredSystemPropertyValue"; + protected static final String TAG_ATTR_RESTRICTED_ACCOUNT_TYPE = "restrictedAccountType"; + protected static final String TAG_ATTR_SCHEME = "scheme"; + protected static final String TAG_ATTR_SHARED_USER_ID = "sharedUserId"; + protected static final String TAG_ATTR_TARGET_ACTIVITY = "targetActivity"; + protected static final String TAG_ATTR_TARGET_NAME = "targetName"; + protected static final String TAG_ATTR_TARGET_PACKAGE = "targetPackage"; + protected static final String TAG_ATTR_TARGET_PROCESSES = "targetProcesses"; + protected static final String TAG_ATTR_TASK_AFFINITY = "taskAffinity"; + protected static final String TAG_ATTR_VALUE = "value"; + protected static final String TAG_ATTR_VERSION_NAME = "versionName"; + protected static final String TAG_ATTR_WRITE_PERMISSION = "writePermission"; + + private static final String[] ACTIVITY_STR_ATTR_NAMES = {TAG_ATTR_NAME, + TAG_ATTR_PARENT_ACTIVITY_NAME, TAG_ATTR_PERMISSION, TAG_ATTR_PROCESS, + TAG_ATTR_TASK_AFFINITY}; + private static final String[] ACTIVITY_ALIAS_STR_ATTR_NAMES = {TAG_ATTR_NAME, + TAG_ATTR_PERMISSION, TAG_ATTR_TARGET_ACTIVITY}; + private static final String[] APPLICATION_STR_ATTR_NAMES = {TAG_ATTR_BACKUP_AGENT, + TAG_ATTR_MANAGE_SPACE_ACTIVITY, TAG_ATTR_NAME, TAG_ATTR_PERMISSION, TAG_ATTR_PROCESS, + TAG_ATTR_REQUIRED_ACCOUNT_TYPE, TAG_ATTR_RESTRICTED_ACCOUNT_TYPE, + TAG_ATTR_TASK_AFFINITY}; + private static final String[] DATA_STR_ATTR_NAMES = {TAG_ATTR_SCHEME, TAG_ATTR_HOST, + TAG_ATTR_PORT, TAG_ATTR_PATH, TAG_ATTR_PATH_PATTERN, TAG_ATTR_PATH_PREFIX, + TAG_ATTR_PATH_SUFFIX, TAG_ATTR_PATH_ADVANCED_PATTERN, TAG_ATTR_MIMETYPE}; + private static final String[] GRANT_URI_PERMISSION_STR_ATTR_NAMES = {TAG_ATTR_PATH, + TAG_ATTR_PATH_PATTERN, TAG_ATTR_PATH_PREFIX}; + private static final String[] INSTRUMENTATION_STR_ATTR_NAMES = {TAG_ATTR_NAME, + TAG_ATTR_TARGET_PACKAGE, TAG_ATTR_TARGET_PROCESSES}; + private static final String[] MANIFEST_STR_ATTR_NAMES = {TAG_ATTR_PACKAGE, + TAG_ATTR_SHARED_USER_ID, TAG_ATTR_VERSION_NAME}; + private static final String[] OVERLAY_STR_ATTR_NAMES = {TAG_ATTR_CATEGORY, + TAG_ATTR_REQUIRED_SYSTEM_PROPERTY_NAME, TAG_ATTR_REQUIRED_SYSTEM_PROPERTY_VALUE, + TAG_ATTR_TARGET_PACKAGE, TAG_ATTR_TARGET_NAME}; + private static final String[] PATH_PERMISSION_STR_ATTR_NAMES = {TAG_ATTR_PATH, + TAG_ATTR_PATH_PREFIX, TAG_ATTR_PATH_PATTERN, TAG_ATTR_PERMISSION, + TAG_ATTR_READ_PERMISSION, TAG_ATTR_WRITE_PERMISSION}; + private static final String[] PERMISSION_STR_ATTR_NAMES = {TAG_ATTR_NAME, + TAG_ATTR_PERMISSION_GROUP}; + private static final String[] PROVIDER_STR_ATTR_NAMES = {TAG_ATTR_NAME, TAG_ATTR_PERMISSION, + TAG_ATTR_PROCESS, TAG_ATTR_READ_PERMISSION, TAG_ATTR_WRITE_PERMISSION}; + private static final String[] RECEIVER_SERVICE_STR_ATTR_NAMES = {TAG_ATTR_NAME, + TAG_ATTR_PERMISSION, TAG_ATTR_PROCESS}; + private static final String[] NAME_ATTR = {TAG_ATTR_NAME}; + private static final String[] NAME_VALUE_ATTRS = {TAG_ATTR_NAME, TAG_ATTR_VALUE}; + + private String[] mStringAttrNames = new String[0]; + private final Map<String, TagCounter> mTagCounters = new ArrayMap<>(); + + private String mTag; + + private static final ThreadLocal<SimplePool<Element>> sPool = + ThreadLocal.withInitial(() -> new SimplePool<>(MAX_POOL_SIZE)); + + @NonNull + static Element obtain(@NonNull String tag) { + Element element = sPool.get().acquire(); + if (element == null) { + element = new Element(); + } + element.init(tag); + return element; + } + + void recycle() { + mStringAttrNames = new String[0]; + Iterator<Map.Entry<String, TagCounter>> it = mTagCounters.entrySet().iterator(); + while (it.hasNext()) { + it.next().getValue().recycle(); + it.remove(); + } + mTag = null; + sPool.get().release(this); + } + + private void init(String tag) { + this.mTag = tag; + switch (tag) { + case TAG_ACTION: + case TAG_CATEGORY: + case TAG_PACKAGE: + case TAG_PERMISSION_GROUP: + case TAG_PERMISSION_TREE: + case TAG_SUPPORTS_GL_TEXTURE: + case TAG_USES_FEATURE: + case TAG_USES_LIBRARY: + case TAG_USES_NATIVE_LIBRARY: + case TAG_USES_PERMISSION: + case TAG_USES_PERMISSION_SDK_23: + case TAG_USES_SDK: + setStringAttrNames(NAME_ATTR); + break; + case TAG_ACTIVITY: + setStringAttrNames(ACTIVITY_STR_ATTR_NAMES); + addTagCounter(1000, TAG_LAYOUT); + addTagCounter(8000, TAG_META_DATA); + addTagCounter(20000, TAG_INTENT_FILTER); + break; + case TAG_ACTIVITY_ALIAS: + setStringAttrNames(ACTIVITY_ALIAS_STR_ATTR_NAMES); + addTagCounter(8000, TAG_META_DATA); + addTagCounter(20000, TAG_INTENT_FILTER); + break; + case TAG_APPLICATION: + setStringAttrNames(APPLICATION_STR_ATTR_NAMES); + addTagCounter(100, TAG_PROFILEABLE); + addTagCounter(100, TAG_USES_NATIVE_LIBRARY); + addTagCounter(1000, TAG_RECEIVER); + addTagCounter(1000, TAG_SERVICE); + addTagCounter(4000, TAG_ACTIVITY_ALIAS); + addTagCounter(4000, TAG_USES_LIBRARY); + addTagCounter(8000, TAG_PROVIDER); + addTagCounter(8000, TAG_META_DATA); + addTagCounter(40000, TAG_ACTIVITY); + break; + case TAG_COMPATIBLE_SCREENS: + addTagCounter(4000, TAG_SCREEN); + break; + case TAG_DATA: + setStringAttrNames(DATA_STR_ATTR_NAMES); + break; + case TAG_GRANT_URI_PERMISSION: + setStringAttrNames(GRANT_URI_PERMISSION_STR_ATTR_NAMES); + break; + case TAG_INSTRUMENTATION: + setStringAttrNames(INSTRUMENTATION_STR_ATTR_NAMES); + break; + case TAG_INTENT: + case TAG_INTENT_FILTER: + addTagCounter(20000, TAG_ACTION); + addTagCounter(40000, TAG_CATEGORY); + addTagCounter(40000, TAG_DATA); + break; + case TAG_MANIFEST: + setStringAttrNames(MANIFEST_STR_ATTR_NAMES); + addTagCounter(100, TAG_APPLICATION); + addTagCounter(100, TAG_OVERLAY); + addTagCounter(100, TAG_INSTRUMENTATION); + addTagCounter(100, TAG_PERMISSION_GROUP); + addTagCounter(100, TAG_PERMISSION_TREE); + addTagCounter(100, TAG_SUPPORTS_GL_TEXTURE); + addTagCounter(100, TAG_SUPPORTS_SCREENS); + addTagCounter(100, TAG_USES_CONFIGURATION); + addTagCounter(100, TAG_USES_PERMISSION_SDK_23); + addTagCounter(100, TAG_USES_SDK); + addTagCounter(200, TAG_COMPATIBLE_SCREENS); + addTagCounter(200, TAG_QUERIES); + addTagCounter(400, TAG_ATTRIBUTION); + addTagCounter(400, TAG_USES_FEATURE); + addTagCounter(2000, TAG_PERMISSION); + addTagCounter(20000, TAG_USES_PERMISSION); + break; + case TAG_META_DATA: + case TAG_PROPERTY: + setStringAttrNames(NAME_VALUE_ATTRS); + break; + case TAG_OVERLAY: + setStringAttrNames(OVERLAY_STR_ATTR_NAMES); + break; + case TAG_PATH_PERMISSION: + setStringAttrNames(PATH_PERMISSION_STR_ATTR_NAMES); + break; + case TAG_PERMISSION: + setStringAttrNames(PERMISSION_STR_ATTR_NAMES); + break; + case TAG_PROVIDER: + setStringAttrNames(PROVIDER_STR_ATTR_NAMES); + addTagCounter(100, TAG_GRANT_URI_PERMISSION); + addTagCounter(100, TAG_PATH_PERMISSION); + addTagCounter(8000, TAG_META_DATA); + addTagCounter(20000, TAG_INTENT_FILTER); + break; + case TAG_QUERIES: + addTagCounter(1000, TAG_PACKAGE); + addTagCounter(2000, TAG_INTENT); + addTagCounter(8000, TAG_PROVIDER); + break; + case TAG_RECEIVER: + case TAG_SERVICE: + setStringAttrNames(RECEIVER_SERVICE_STR_ATTR_NAMES); + addTagCounter(8000, TAG_META_DATA); + addTagCounter(20000, TAG_INTENT_FILTER); + break; + } + } + + private void setStringAttrNames(String[] attrNames) { + mStringAttrNames = attrNames; + } + + private static String getAttrNamespace(String attrName) { + if (attrName.equals(TAG_ATTR_PACKAGE)) { + return null; + } + return ANDROID_NAMESPACE; + } + + private static int getAttrStringMaxLength(String attrName) { + switch (attrName) { + case TAG_ATTR_HOST: + case TAG_ATTR_PACKAGE: + case TAG_ATTR_PERMISSION_GROUP: + case TAG_ATTR_PORT: + case TAG_ATTR_REQUIRED_SYSTEM_PROPERTY_VALUE: + case TAG_ATTR_SCHEME: + case TAG_ATTR_SHARED_USER_ID: + case TAG_ATTR_TARGET_PACKAGE: + return 256; + case TAG_ATTR_MIMETYPE: + return 512; + case TAG_ATTR_BACKUP_AGENT: + case TAG_ATTR_CATEGORY: + case TAG_ATTR_MANAGE_SPACE_ACTIVITY: + case TAG_ATTR_NAME: + case TAG_ATTR_PARENT_ACTIVITY_NAME: + case TAG_ATTR_PERMISSION: + case TAG_ATTR_PROCESS: + case TAG_ATTR_READ_PERMISSION: + case TAG_ATTR_REQUIRED_ACCOUNT_TYPE: + case TAG_ATTR_RESTRICTED_ACCOUNT_TYPE: + case TAG_ATTR_TARGET_ACTIVITY: + case TAG_ATTR_TARGET_NAME: + case TAG_ATTR_TARGET_PROCESSES: + case TAG_ATTR_TASK_AFFINITY: + case TAG_ATTR_WRITE_PERMISSION: + return 1024; + case TAG_ATTR_PATH: + case TAG_ATTR_PATH_ADVANCED_PATTERN: + case TAG_ATTR_PATH_PATTERN: + case TAG_ATTR_PATH_PREFIX: + case TAG_ATTR_PATH_SUFFIX: + case TAG_ATTR_VERSION_NAME: + return 4000; + default: + return DEFAULT_MAX_STRING_ATTR_LENGTH; + } + } + + private static int getResStringMaxLength(@StyleableRes int index) { + switch (index) { + case R.styleable.AndroidManifestData_host: + case R.styleable.AndroidManifestData_port: + case R.styleable.AndroidManifestData_scheme: + return 255; + case R.styleable.AndroidManifestData_mimeType: + return 512; + default: + return DEFAULT_MAX_STRING_ATTR_LENGTH; + } + } + + private void addTagCounter(int max, String tag) { + mTagCounters.put(tag, TagCounter.obtain(max)); + } + + boolean hasChild(String tag) { + return mTagCounters.containsKey(tag); + } + + void validateStringAttrs(@NonNull XmlPullParser attrs) throws XmlPullParserException { + for (int i = 0; i < mStringAttrNames.length; i++) { + String attrName = mStringAttrNames[i]; + String val = attrs.getAttributeValue(getAttrNamespace(attrName), attrName); + if (val != null && val.length() > getAttrStringMaxLength(attrName)) { + throw new XmlPullParserException("String length limit exceeded for " + + "attribute " + attrName + " in " + mTag); + } + } + } + + void validateResStringAttr(@StyleableRes int index, CharSequence stringValue) + throws XmlPullParserException { + if (stringValue != null && stringValue.length() > getResStringMaxLength(index)) { + throw new XmlPullParserException("String length limit exceeded for " + + "attribute in " + mTag); + } + } + + void seen(@NonNull Element element) throws XmlPullParserException { + if (mTagCounters.containsKey(element.mTag)) { + TagCounter counter = mTagCounters.get(element.mTag); + counter.increment(); + if (!counter.isValid()) { + throw new XmlPullParserException("The number of child " + element.mTag + + " elements exceeded the max allowed in " + this.mTag); + } + } + } +} diff --git a/core/java/android/content/res/TagCounter.java b/core/java/android/content/res/TagCounter.java new file mode 100644 index 000000000000..0e5510f19635 --- /dev/null +++ b/core/java/android/content/res/TagCounter.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; + +import android.annotation.NonNull; +import android.util.Pools.SimplePool; + +/** + * Counter used to track the number of tags seen during manifest validation. + * + * {@hide} + */ +public class TagCounter { + private static final int MAX_POOL_SIZE = 512; + private static final int DEFAULT_MAX_COUNT = 512; + + private static final ThreadLocal<SimplePool<TagCounter>> sPool = + ThreadLocal.withInitial(() -> new SimplePool<>(MAX_POOL_SIZE)); + + private int mMaxValue; + private int mCount; + + @NonNull + static TagCounter obtain(int max) { + TagCounter counter = sPool.get().acquire(); + if (counter == null) { + counter = new TagCounter(); + } + counter.setMaxValue(max); + return counter; + } + + void recycle() { + mCount = 0; + mMaxValue = DEFAULT_MAX_COUNT; + sPool.get().release(this); + } + + public TagCounter() { + mMaxValue = DEFAULT_MAX_COUNT; + mCount = 0; + } + + private void setMaxValue(int maxValue) { + this.mMaxValue = maxValue; + } + + void increment() { + mCount += 1; + } + + public boolean isValid() { + return mCount <= mMaxValue; + } + + int value() { + return mCount; + } +} diff --git a/core/java/android/content/res/ThemedResourceCache.java b/core/java/android/content/res/ThemedResourceCache.java index a7cd168690b4..690dfcf9619b 100644 --- a/core/java/android/content/res/ThemedResourceCache.java +++ b/core/java/android/content/res/ThemedResourceCache.java @@ -137,8 +137,10 @@ abstract class ThemedResourceCache<T> { */ @UnsupportedAppUsage public void onConfigurationChange(@Config int configChanges) { - prune(configChanges); - mGeneration++; + synchronized (this) { + pruneLocked(configChanges); + mGeneration++; + } } /** @@ -214,22 +216,20 @@ abstract class ThemedResourceCache<T> { * simply prune missing weak references * @return {@code true} if the cache is completely empty after pruning */ - private boolean prune(@Config int configChanges) { - synchronized (this) { - if (mThemedEntries != null) { - for (int i = mThemedEntries.size() - 1; i >= 0; i--) { - if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) { - mThemedEntries.removeAt(i); - } + private boolean pruneLocked(@Config int configChanges) { + if (mThemedEntries != null) { + for (int i = mThemedEntries.size() - 1; i >= 0; i--) { + if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) { + mThemedEntries.removeAt(i); } } + } - pruneEntriesLocked(mNullThemedEntries, configChanges); - pruneEntriesLocked(mUnthemedEntries, configChanges); + pruneEntriesLocked(mNullThemedEntries, configChanges); + pruneEntriesLocked(mUnthemedEntries, configChanges); - return mThemedEntries == null && mNullThemedEntries == null - && mUnthemedEntries == null; - } + return mThemedEntries == null && mNullThemedEntries == null + && mUnthemedEntries == null; } private boolean pruneEntriesLocked(@Nullable LongSparseArray<WeakReference<T>> entries, diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index 9eb9cd51284a..2e84636202be 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -35,6 +35,8 @@ import com.android.internal.util.XmlUtils; import dalvik.system.VMRuntime; +import org.xmlpull.v1.XmlPullParserException; + import java.util.Arrays; /** @@ -1397,7 +1399,15 @@ public class TypedArray implements AutoCloseable { } return null; } - return mAssets.getPooledStringForCookie(cookie, data[index + STYLE_DATA]); + CharSequence value = mAssets.getPooledStringForCookie(cookie, data[index + STYLE_DATA]); + if (mXml != null && mXml.mValidator != null) { + try { + mXml.mValidator.validateAttr(mXml, index, value); + } catch (XmlPullParserException e) { + throw new RuntimeException("Failed to validate resource string: " + e.getMessage()); + } + } + return value; } /** @hide */ diff --git a/core/java/android/content/res/Validator.java b/core/java/android/content/res/Validator.java new file mode 100644 index 000000000000..daa7dfef3b8a --- /dev/null +++ b/core/java/android/content/res/Validator.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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; + +import static android.content.res.Element.TAG_MANIFEST; + +import android.annotation.NonNull; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.util.ArrayDeque; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * Validates manifest files by ensuring that tag counts and the length of string attributes are + * restricted. + * + * {@hide} + */ +public class Validator { + + private static final int MAX_TAG_COUNT = 100000; + + private final ArrayDeque<Element> mElements = new ArrayDeque<>(); + private final Map<String, TagCounter> mTagCounters = new HashMap<>(); + + private void cleanUp() { + while (!mElements.isEmpty()) { + mElements.pop().recycle(); + } + Iterator<Map.Entry<String, TagCounter>> it = mTagCounters.entrySet().iterator(); + while (it.hasNext()) { + it.next().getValue().recycle(); + it.remove(); + } + } + + private void seen(String tag) throws XmlPullParserException { + mTagCounters.putIfAbsent(tag, TagCounter.obtain(MAX_TAG_COUNT)); + TagCounter counter = mTagCounters.get(tag); + counter.increment(); + if (!counter.isValid()) { + throw new XmlPullParserException("The number of " + tag + + " tags exceeded " + MAX_TAG_COUNT); + } + } + + /** + * Validates the elements and it's attributes as the XmlPullParser traverses the xml. + */ + public void validate(@NonNull XmlPullParser parser) throws XmlPullParserException { + int eventType = parser.getEventType(); + int depth = parser.getDepth(); + // The mElement size should equal to the parser depth-1 when the parser eventType is + // START_TAG. If depth - mElement.size() is larger than 1 then that means + // validation for the previous element was skipped so we should skip validation for all + // descendant elements as well + if (depth > mElements.size() + 1) { + return; + } + if (eventType == XmlPullParser.START_TAG) { + try { + String tag = parser.getName(); + // only validate manifests + if (depth == 0 && mElements.size() == 0 && !TAG_MANIFEST.equals(tag)) { + return; + } + Element parent = mElements.peek(); + if (parent == null || parent.hasChild(tag)) { + seen(tag); + Element element = Element.obtain(tag); + element.validateStringAttrs(parser); + if (parent != null) { + parent.seen(element); + } + mElements.push(element); + } + } catch (XmlPullParserException e) { + cleanUp(); + throw e; + } + } else if (eventType == XmlPullParser.END_TAG && depth == mElements.size()) { + mElements.pop().recycle(); + } else if (eventType == XmlPullParser.END_DOCUMENT) { + cleanUp(); + } + } + + /** + * Validates the resource string of a manifest tag attribute. + */ + public void validateAttr(@NonNull XmlPullParser parser, int index, CharSequence stringValue) + throws XmlPullParserException { + if (parser.getDepth() > mElements.size()) { + return; + } + mElements.peek().validateResStringAttr(index, stringValue); + } +} diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java index 16fd1f7277a7..3afc830fa14d 100644 --- a/core/java/android/content/res/XmlBlock.java +++ b/core/java/android/content/res/XmlBlock.java @@ -97,6 +97,18 @@ public final class XmlBlock implements AutoCloseable { } /** + * Returns a XmlResourceParser that validates the xml using the given validator. + */ + public XmlResourceParser newParser(@AnyRes int resId, Validator validator) { + synchronized (this) { + if (mNative != 0) { + return new Parser(nativeCreateParseState(mNative, resId), this, validator); + } + return null; + } + } + + /** * Reference Error.h UNEXPECTED_NULL */ private static final int ERROR_NULL_DOCUMENT = Integer.MIN_VALUE + 8; @@ -108,12 +120,19 @@ public final class XmlBlock implements AutoCloseable { @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public final class Parser implements XmlResourceParser { + Validator mValidator; + Parser(long parseState, XmlBlock block) { mParseState = parseState; mBlock = block; block.mOpenCount++; } + Parser(long parseState, XmlBlock block, Validator validator) { + this(parseState, block); + mValidator = validator; + } + @AnyRes public int getSourceResId() { return nativeGetSourceResId(mParseState); @@ -329,6 +348,9 @@ public final class XmlBlock implements AutoCloseable { break; } mEventType = ev; + if (mValidator != null) { + mValidator.validate(this); + } if (ev == END_DOCUMENT) { // Automatically close the parse when we reach the end of // a document, since the standard XmlPullParser interface diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index d48e20e128b9..6baf91d720c3 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -34,6 +34,7 @@ import android.hardware.camera2.impl.CameraExtensionUtils; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.params.ExtensionSessionConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; +import android.os.Binder; import android.os.ConditionVariable; import android.os.IBinder; import android.os.RemoteException; @@ -48,7 +49,6 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -346,28 +346,29 @@ public final class CameraExtensionCharacteristics { } } - public long registerClient(Context ctx) { + public boolean registerClient(Context ctx, IBinder token) { synchronized (mLock) { connectToProxyLocked(ctx); - if (mProxy != null) { - try { - return mProxy.registerClient(); - } catch (RemoteException e) { - Log.e(TAG, "Failed to initialize extension! Extension service does " - + " not respond!"); - return -1; - } - } else { - return -1; + if (mProxy == null) { + return false; } + + try { + return mProxy.registerClient(token); + } catch (RemoteException e) { + Log.e(TAG, "Failed to initialize extension! Extension service does " + + " not respond!"); + } + + return false; } } - public void unregisterClient(long clientId) { + public void unregisterClient(IBinder token) { synchronized (mLock) { if (mProxy != null) { try { - mProxy.unregisterClient(clientId); + mProxy.unregisterClient(token); } catch (RemoteException e) { Log.e(TAG, "Failed to de-initialize extension! Extension service does" + " not respond!"); @@ -438,15 +439,15 @@ public final class CameraExtensionCharacteristics { /** * @hide */ - public static long registerClient(Context ctx) { - return CameraExtensionManagerGlobal.get().registerClient(ctx); + public static boolean registerClient(Context ctx, IBinder token) { + return CameraExtensionManagerGlobal.get().registerClient(ctx, token); } /** * @hide */ - public static void unregisterClient(long clientId) { - CameraExtensionManagerGlobal.get().unregisterClient(clientId); + public static void unregisterClient(IBinder token) { + CameraExtensionManagerGlobal.get().unregisterClient(token); } /** @@ -564,8 +565,9 @@ public final class CameraExtensionCharacteristics { */ public @NonNull List<Integer> getSupportedExtensions() { ArrayList<Integer> ret = new ArrayList<>(); - long clientId = registerClient(mContext); - if (clientId < 0) { + final IBinder token = new Binder(TAG + "#getSupportedExtensions:" + mCameraId); + boolean success = registerClient(mContext, token); + if (!success) { return Collections.unmodifiableList(ret); } @@ -576,7 +578,7 @@ public final class CameraExtensionCharacteristics { } } } finally { - unregisterClient(clientId); + unregisterClient(token); } return Collections.unmodifiableList(ret); @@ -599,8 +601,9 @@ public final class CameraExtensionCharacteristics { * supported device-specific extension */ public boolean isPostviewAvailable(@Extension int extension) { - long clientId = registerClient(mContext); - if (clientId < 0) { + final IBinder token = new Binder(TAG + "#isPostviewAvailable:" + mCameraId); + boolean success = registerClient(mContext, token); + if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } @@ -623,7 +626,7 @@ public final class CameraExtensionCharacteristics { Log.e(TAG, "Failed to query the extension for postview availability! Extension " + "service does not respond!"); } finally { - unregisterClient(clientId); + unregisterClient(token); } return false; @@ -656,9 +659,9 @@ public final class CameraExtensionCharacteristics { @NonNull public List<Size> getPostviewSupportedSizes(@Extension int extension, @NonNull Size captureSize, int format) { - - long clientId = registerClient(mContext); - if (clientId < 0) { + final IBinder token = new Binder(TAG + "#getPostviewSupportedSizes:" + mCameraId); + boolean success = registerClient(mContext, token); + if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } @@ -719,7 +722,7 @@ public final class CameraExtensionCharacteristics { + "service does not respond!"); return Collections.emptyList(); } finally { - unregisterClient(clientId); + unregisterClient(token); } } @@ -756,8 +759,9 @@ public final class CameraExtensionCharacteristics { // TODO: Revisit this code once the Extension preview processor output format // ambiguity is resolved in b/169799538. - long clientId = registerClient(mContext); - if (clientId < 0) { + final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId); + boolean success = registerClient(mContext, token); + if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } @@ -787,7 +791,7 @@ public final class CameraExtensionCharacteristics { + " not respond!"); return new ArrayList<>(); } finally { - unregisterClient(clientId); + unregisterClient(token); } } @@ -814,8 +818,9 @@ public final class CameraExtensionCharacteristics { public @NonNull List<Size> getExtensionSupportedSizes(@Extension int extension, int format) { try { - long clientId = registerClient(mContext); - if (clientId < 0) { + final IBinder token = new Binder(TAG + "#getExtensionSupportedSizes:" + mCameraId); + boolean success = registerClient(mContext, token); + if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } @@ -867,7 +872,7 @@ public final class CameraExtensionCharacteristics { } } } finally { - unregisterClient(clientId); + unregisterClient(token); } } catch (RemoteException e) { Log.e(TAG, "Failed to query the extension supported sizes! Extension service does" @@ -888,7 +893,6 @@ public final class CameraExtensionCharacteristics { * @param format device-specific extension output format * @return the range of estimated minimal and maximal capture latency in milliseconds * or null if no capture latency info can be provided - * * @throws IllegalArgumentException in case of format different from {@link ImageFormat#JPEG} / * {@link ImageFormat#YUV_420_888}; or unsupported extension. */ @@ -903,8 +907,9 @@ public final class CameraExtensionCharacteristics { throw new IllegalArgumentException("Unsupported format: " + format); } - long clientId = registerClient(mContext); - if (clientId < 0) { + final IBinder token = new Binder(TAG + "#getEstimatedCaptureLatencyRangeMillis:" + mCameraId); + boolean success = registerClient(mContext, token); + if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } @@ -952,7 +957,7 @@ public final class CameraExtensionCharacteristics { Log.e(TAG, "Failed to query the extension capture latency! Extension service does" + " not respond!"); } finally { - unregisterClient(clientId); + unregisterClient(token); } return null; @@ -968,8 +973,9 @@ public final class CameraExtensionCharacteristics { * @throws IllegalArgumentException in case of an unsupported extension. */ public boolean isCaptureProcessProgressAvailable(@Extension int extension) { - long clientId = registerClient(mContext); - if (clientId < 0) { + final IBinder token = new Binder(TAG + "#isCaptureProcessProgressAvailable:" + mCameraId); + boolean success = registerClient(mContext, token); + if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } @@ -992,7 +998,7 @@ public final class CameraExtensionCharacteristics { Log.e(TAG, "Failed to query the extension progress callbacks! Extension service does" + " not respond!"); } finally { - unregisterClient(clientId); + unregisterClient(token); } return false; @@ -1013,8 +1019,9 @@ public final class CameraExtensionCharacteristics { */ @NonNull public Set<CaptureRequest.Key> getAvailableCaptureRequestKeys(@Extension int extension) { - long clientId = registerClient(mContext); - if (clientId < 0) { + final IBinder token = new Binder(TAG + "#getAvailableCaptureRequestKeys:" + mCameraId); + boolean success = registerClient(mContext, token); + if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } @@ -1033,10 +1040,11 @@ public final class CameraExtensionCharacteristics { } else { Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = initializeExtension(extension); - extenders.second.onInit(mCameraId, mCharacteristicsMapNative.get(mCameraId)); + extenders.second.onInit(token, mCameraId, + mCharacteristicsMapNative.get(mCameraId)); extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); captureRequestMeta = extenders.second.getAvailableCaptureRequestKeys(); - extenders.second.onDeInit(); + extenders.second.onDeInit(token); } if (captureRequestMeta != null) { @@ -1067,7 +1075,7 @@ public final class CameraExtensionCharacteristics { } catch (RemoteException e) { throw new IllegalStateException("Failed to query the available capture request keys!"); } finally { - unregisterClient(clientId); + unregisterClient(token); } return Collections.unmodifiableSet(ret); @@ -1092,8 +1100,9 @@ public final class CameraExtensionCharacteristics { */ @NonNull public Set<CaptureResult.Key> getAvailableCaptureResultKeys(@Extension int extension) { - long clientId = registerClient(mContext); - if (clientId < 0) { + final IBinder token = new Binder(TAG + "#getAvailableCaptureResultKeys:" + mCameraId); + boolean success = registerClient(mContext, token); + if (!success) { throw new IllegalArgumentException("Unsupported extensions"); } @@ -1111,10 +1120,11 @@ public final class CameraExtensionCharacteristics { } else { Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders = initializeExtension(extension); - extenders.second.onInit(mCameraId, mCharacteristicsMapNative.get(mCameraId)); + extenders.second.onInit(token, mCameraId, + mCharacteristicsMapNative.get(mCameraId)); extenders.second.init(mCameraId, mCharacteristicsMapNative.get(mCameraId)); captureResultMeta = extenders.second.getAvailableCaptureResultKeys(); - extenders.second.onDeInit(); + extenders.second.onDeInit(token); } if (captureResultMeta != null) { @@ -1126,7 +1136,7 @@ public final class CameraExtensionCharacteristics { } CameraCharacteristics resultChars = new CameraCharacteristics(captureResultMeta); Object crKey = CaptureResult.Key.class; - Class<CaptureResult.Key<?>> crKeyTyped = (Class<CaptureResult.Key<?>>)crKey; + Class<CaptureResult.Key<?>> crKeyTyped = (Class<CaptureResult.Key<?>>) crKey; ret.addAll(resultChars.getAvailableKeyList(CaptureResult.class, crKeyTyped, resultKeys, /*includeSynthetic*/ true)); @@ -1145,7 +1155,7 @@ public final class CameraExtensionCharacteristics { } catch (RemoteException e) { throw new IllegalStateException("Failed to query the available capture result keys!"); } finally { - unregisterClient(clientId); + unregisterClient(token); } return Collections.unmodifiableSet(ret); diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 85f8ca66715b..a098362f16aa 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -67,6 +67,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; @@ -178,22 +179,20 @@ public final class CameraManager { boolean folded = ArrayUtils.contains(mFoldedDeviceStates, state); mFoldedDeviceState = folded; - ArrayList<WeakReference<DeviceStateListener>> invalidListeners = new ArrayList<>(); - for (WeakReference<DeviceStateListener> listener : mDeviceStateListeners) { - DeviceStateListener callback = listener.get(); + Iterator<WeakReference<DeviceStateListener>> it = mDeviceStateListeners.iterator(); + while(it.hasNext()) { + DeviceStateListener callback = it.next().get(); if (callback != null) { callback.onDeviceStateChanged(folded); } else { - invalidListeners.add(listener); + it.remove(); } } - if (!invalidListeners.isEmpty()) { - mDeviceStateListeners.removeAll(invalidListeners); - } } public synchronized void addDeviceStateListener(DeviceStateListener listener) { listener.onDeviceStateChanged(mFoldedDeviceState); + mDeviceStateListeners.removeIf(l -> l.get() == null); mDeviceStateListeners.add(new WeakReference<>(listener)); } diff --git a/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl b/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl index b52c650086f4..3b7d801afd85 100644 --- a/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl +++ b/core/java/android/hardware/camera2/extension/ICameraExtensionsProxyService.aidl @@ -20,11 +20,13 @@ import android.hardware.camera2.extension.IPreviewExtenderImpl; import android.hardware.camera2.extension.IImageCaptureExtenderImpl; import android.hardware.camera2.extension.IInitializeSessionCallback; +import android.os.IBinder; + /** @hide */ interface ICameraExtensionsProxyService { - long registerClient(); - void unregisterClient(long clientId); + boolean registerClient(in IBinder token); + void unregisterClient(in IBinder token); boolean advancedExtensionsSupported(); void initializeSession(in IInitializeSessionCallback cb); void releaseSession(); diff --git a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl index 754f8f6625c9..5a2241820534 100644 --- a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl +++ b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl @@ -24,11 +24,13 @@ import android.hardware.camera2.extension.LatencyRange; import android.hardware.camera2.extension.Size; import android.hardware.camera2.extension.SizeList; +import android.os.IBinder; + /** @hide */ interface IImageCaptureExtenderImpl { - void onInit(in String cameraId, in CameraMetadataNative cameraCharacteristics); - void onDeInit(); + void onInit(in IBinder token, in String cameraId, in CameraMetadataNative cameraCharacteristics); + void onDeInit(in IBinder token); @nullable CaptureStageImpl onPresetSession(); @nullable CaptureStageImpl onEnableSession(); @nullable CaptureStageImpl onDisableSession(); diff --git a/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl index 01046d01233c..9ea8a7407049 100644 --- a/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl +++ b/core/java/android/hardware/camera2/extension/IPreviewExtenderImpl.aidl @@ -22,11 +22,13 @@ import android.hardware.camera2.extension.IPreviewImageProcessorImpl; import android.hardware.camera2.extension.IRequestUpdateProcessorImpl; import android.hardware.camera2.extension.SizeList; +import android.os.IBinder; + /** @hide */ interface IPreviewExtenderImpl { - void onInit(in String cameraId, in CameraMetadataNative cameraCharacteristics); - void onDeInit(); + void onInit(in IBinder token, in String cameraId, in CameraMetadataNative cameraCharacteristics); + void onDeInit(in IBinder token); @nullable CaptureStageImpl onPresetSession(); @nullable CaptureStageImpl onEnableSession(); @nullable CaptureStageImpl onDisableSession(); diff --git a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl index 13b93a8c5e92..0581ec08a131 100644 --- a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl +++ b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl @@ -25,13 +25,15 @@ import android.hardware.camera2.extension.LatencyPair; import android.hardware.camera2.extension.LatencyRange; import android.hardware.camera2.extension.OutputSurface; +import android.os.IBinder; + /** @hide */ interface ISessionProcessorImpl { - CameraSessionConfig initSession(in String cameraId, + CameraSessionConfig initSession(in IBinder token, in String cameraId, in Map<String, CameraMetadataNative> charsMap, in OutputSurface previewSurface, in OutputSurface imageCaptureSurface, in OutputSurface postviewSurface); - void deInitSession(); + void deInitSession(in IBinder token); void onCaptureSessionStart(IRequestProcessorImpl requestProcessor); void onCaptureSessionEnd(); int startRepeating(in ICaptureCallback callback); diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java index 65d4b433f132..ae700a0a3c41 100644 --- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java @@ -60,6 +60,7 @@ import android.media.ImageReader; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; +import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.util.Size; @@ -79,7 +80,6 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes private final Executor mExecutor; private CameraDevice mCameraDevice; private final Map<String, CameraMetadataNative> mCharacteristicsMap; - private final long mExtensionClientId; private final Handler mHandler; private final HandlerThread mHandlerThread; private final CameraExtensionSession.StateCallback mCallbacks; @@ -90,6 +90,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes private final HashMap<Integer, ImageReader> mReaderMap = new HashMap<>(); private RequestProcessor mRequestProcessor = new RequestProcessor(); private final int mSessionId; + private final IBinder mToken; private Surface mClientRepeatingRequestSurface; private Surface mClientCaptureSurface; @@ -114,8 +115,9 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes @NonNull Map<String, CameraCharacteristics> characteristicsMap, @NonNull Context ctx, @NonNull ExtensionSessionConfiguration config, int sessionId) throws CameraAccessException, RemoteException { - long clientId = CameraExtensionCharacteristics.registerClient(ctx); - if (clientId < 0) { + final IBinder token = new Binder(TAG + " : " + sessionId); + boolean success = CameraExtensionCharacteristics.registerClient(ctx, token); + if (!success) { throw new UnsupportedOperationException("Unsupported extension!"); } @@ -202,11 +204,10 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes IAdvancedExtenderImpl extender = CameraExtensionCharacteristics.initializeAdvancedExtension( config.getExtension()); extender.init(cameraId, characteristicsMapNative); - - CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(clientId, - extender, cameraDevice, characteristicsMapNative, repeatingRequestSurface, + CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(extender, + cameraDevice, characteristicsMapNative, repeatingRequestSurface, burstCaptureSurface, postviewSurface, config.getStateCallback(), - config.getExecutor(), sessionId); + config.getExecutor(), sessionId, token); ret.mStatsAggregator.setClientName(ctx.getOpPackageName()); ret.mStatsAggregator.setExtensionType(config.getExtension()); @@ -216,15 +217,13 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes return ret; } - private CameraAdvancedExtensionSessionImpl(long extensionClientId, - @NonNull IAdvancedExtenderImpl extender, + private CameraAdvancedExtensionSessionImpl(@NonNull IAdvancedExtenderImpl extender, @NonNull CameraDeviceImpl cameraDevice, Map<String, CameraMetadataNative> characteristicsMap, @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface, @Nullable Surface postviewSurface, @NonNull StateCallback callback, @NonNull Executor executor, - int sessionId) { - mExtensionClientId = extensionClientId; + int sessionId, @NonNull IBinder token) { mAdvancedExtender = extender; mCameraDevice = cameraDevice; mCharacteristicsMap = characteristicsMap; @@ -240,6 +239,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes mSessionClosed = false; mInitializeHandler = new InitializeSessionHandler(); mSessionId = sessionId; + mToken = token; mInterfaceLock = cameraDevice.mInterfaceLock; mStatsAggregator = new ExtensionSessionStatsAggregator(mCameraDevice.getId(), @@ -260,7 +260,8 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes OutputSurface postviewSurface = initializeParcelable(mClientPostviewSurface); mSessionProcessor = mAdvancedExtender.getSessionProcessor(); - CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mCameraDevice.getId(), + CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mToken, + mCameraDevice.getId(), mCharacteristicsMap, previewSurface, captureSurface, postviewSurface); List<CameraOutputConfig> outputConfigs = sessionConfig.outputConfigs; ArrayList<OutputConfiguration> outputList = new ArrayList<>(); @@ -526,7 +527,11 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes synchronized (mInterfaceLock) { if (mInitialized) { try { - mCaptureSession.stopRepeating(); + try { + mCaptureSession.stopRepeating(); + } catch (IllegalStateException e) { + // OK: already be closed, nothing else to do + } mSessionProcessor.stopRepeating(); mSessionProcessor.onCaptureSessionEnd(); mSessionClosed = true; @@ -565,7 +570,7 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes if (!mSessionClosed) { mSessionProcessor.onCaptureSessionEnd(); } - mSessionProcessor.deInitSession(); + mSessionProcessor.deInitSession(mToken); } catch (RemoteException e) { Log.e(TAG, "Failed to de-initialize session processor, extension service" + " does not respond!") ; @@ -573,12 +578,10 @@ public final class CameraAdvancedExtensionSessionImpl extends CameraExtensionSes mSessionProcessor = null; } - if (mExtensionClientId >= 0) { - CameraExtensionCharacteristics.unregisterClient(mExtensionClientId); - if (mInitialized || (mCaptureSession != null)) { - notifyClose = true; - CameraExtensionCharacteristics.releaseSession(); - } + CameraExtensionCharacteristics.unregisterClient(mToken); + if (mInitialized || (mCaptureSession != null)) { + notifyClose = true; + CameraExtensionCharacteristics.releaseSession(); } mInitialized = false; diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java index 9ebef0b59ece..1db4808b6430 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -56,6 +56,7 @@ import android.media.ImageWriter; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; +import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.util.LongSparseArray; @@ -79,7 +80,6 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { private final Executor mExecutor; private final CameraDevice mCameraDevice; - private final long mExtensionClientId; private final IImageCaptureExtenderImpl mImageExtender; private final IPreviewExtenderImpl mPreviewExtender; private final Handler mHandler; @@ -91,6 +91,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { private final Set<CaptureRequest.Key> mSupportedRequestKeys; private final Set<CaptureResult.Key> mSupportedResultKeys; private final ExtensionSessionStatsAggregator mStatsAggregator; + private final IBinder mToken; private boolean mCaptureResultsSupported; private CameraCaptureSession mCaptureSession = null; @@ -111,6 +112,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { private int mPreviewProcessorType = IPreviewExtenderImpl.PROCESSOR_TYPE_NONE; private boolean mInitialized; + private boolean mSessionClosed; // Enable/Disable internal preview/(repeating request). Extensions expect // that preview/(repeating request) is enabled and active at any point in time. // In case the client doesn't explicitly enable repeating requests, the framework @@ -135,8 +137,9 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { @NonNull ExtensionSessionConfiguration config, int sessionId) throws CameraAccessException, RemoteException { - long clientId = CameraExtensionCharacteristics.registerClient(ctx); - if (clientId < 0) { + final IBinder token = new Binder(TAG + " : " + sessionId); + boolean success = CameraExtensionCharacteristics.registerClient(ctx, token); + if (!success) { throw new UnsupportedOperationException("Unsupported extension!"); } @@ -224,15 +227,16 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } extenders.first.init(cameraId, characteristicsMap.get(cameraId).getNativeMetadata()); - extenders.first.onInit(cameraId, characteristicsMap.get(cameraId).getNativeMetadata()); + extenders.first.onInit(token, cameraId, + characteristicsMap.get(cameraId).getNativeMetadata()); extenders.second.init(cameraId, characteristicsMap.get(cameraId).getNativeMetadata()); - extenders.second.onInit(cameraId, characteristicsMap.get(cameraId).getNativeMetadata()); + extenders.second.onInit(token, cameraId, + characteristicsMap.get(cameraId).getNativeMetadata()); CameraExtensionSessionImpl session = new CameraExtensionSessionImpl( extenders.second, extenders.first, supportedPreviewSizes, - clientId, cameraDevice, repeatingRequestSurface, burstCaptureSurface, @@ -240,6 +244,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { config.getStateCallback(), config.getExecutor(), sessionId, + token, extensionChars.getAvailableCaptureRequestKeys(config.getExtension()), extensionChars.getAvailableCaptureResultKeys(config.getExtension())); @@ -254,7 +259,6 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { public CameraExtensionSessionImpl(@NonNull IImageCaptureExtenderImpl imageExtender, @NonNull IPreviewExtenderImpl previewExtender, @NonNull List<Size> previewSizes, - long extensionClientId, @NonNull android.hardware.camera2.impl.CameraDeviceImpl cameraDevice, @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface, @@ -262,9 +266,9 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { @NonNull StateCallback callback, @NonNull Executor executor, int sessionId, + @NonNull IBinder token, @NonNull Set<CaptureRequest.Key> requestKeys, @Nullable Set<CaptureResult.Key> resultKeys) { - mExtensionClientId = extensionClientId; mImageExtender = imageExtender; mPreviewExtender = previewExtender; mCameraDevice = cameraDevice; @@ -278,8 +282,10 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); mInitialized = false; + mSessionClosed = false; mInitializeHandler = new InitializeSessionHandler(); mSessionId = sessionId; + mToken = token; mSupportedRequestKeys = requestKeys; mSupportedResultKeys = resultKeys; mCaptureResultsSupported = !resultKeys.isEmpty(); @@ -775,7 +781,12 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { synchronized (mInterfaceLock) { if (mInitialized) { mInternalRepeatingRequestEnabled = false; - mCaptureSession.stopRepeating(); + try { + mCaptureSession.stopRepeating(); + } catch (IllegalStateException e) { + // OK: already be closed, nothing else to do + mSessionClosed = true; + } ArrayList<CaptureStageImpl> captureStageList = new ArrayList<>(); try { @@ -793,13 +804,14 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { Log.e(TAG, "Failed to disable extension! Extension service does not " + "respond!"); } - if (!captureStageList.isEmpty()) { + if (!captureStageList.isEmpty() && !mSessionClosed) { CaptureRequest disableRequest = createRequest(mCameraDevice, captureStageList, mCameraRepeatingSurface, CameraDevice.TEMPLATE_PREVIEW); mCaptureSession.capture(disableRequest, new CloseRequestHandler(mRepeatingRequestImageCallback), mHandler); } + mSessionClosed = true; mStatsAggregator.commit(/*isFinal*/true); // Commit stats before closing session mCaptureSession.close(); } @@ -854,19 +866,22 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { mHandlerThread.quit(); try { - mPreviewExtender.onDeInit(); - mImageExtender.onDeInit(); + if (!mSessionClosed) { + // return value is omitted. nothing can do after session is closed. + mPreviewExtender.onDisableSession(); + mImageExtender.onDisableSession(); + } + mPreviewExtender.onDeInit(mToken); + mImageExtender.onDeInit(mToken); } catch (RemoteException e) { Log.e(TAG, "Failed to release extensions! Extension service does not" + " respond!"); } - if (mExtensionClientId >= 0) { - CameraExtensionCharacteristics.unregisterClient(mExtensionClientId); - if (mInitialized || (mCaptureSession != null)) { - notifyClose = true; - CameraExtensionCharacteristics.releaseSession(); - } + CameraExtensionCharacteristics.unregisterClient(mToken); + if (mInitialized || (mCaptureSession != null)) { + notifyClose = true; + CameraExtensionCharacteristics.releaseSession(); } mInitialized = false; diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 76efce56dcf0..022f3c4c3a20 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -1762,6 +1762,24 @@ public final class DisplayManager { * 123,1,critical,0.8,default;123,1,moderate,0.6,id_2;456,2,moderate,0.9,critical,0.7 */ String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data"; + + /** + * Key for new power controller feature flag. If enabled new DisplayPowerController will + * be used. + * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)} + * with {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace. + * @hide + */ + String KEY_NEW_POWER_CONTROLLER = "use_newly_structured_display_power_controller"; + + /** + * Key for normal brightness mode controller feature flag. + * It enables NormalBrightnessModeController. + * Read value via {@link android.provider.DeviceConfig#getBoolean(String, String, boolean)} + * with {@link android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER} as the namespace. + * @hide + */ + String KEY_USE_NORMAL_BRIGHTNESS_MODE_CONTROLLER = "use_normal_brightness_mode_controller"; } /** diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 2f9c2073cd38..a9c4818393a8 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -52,6 +52,8 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowInsets.Type.statusBars; +import static java.lang.annotation.RetentionPolicy.SOURCE; + import android.annotation.AnyThread; import android.annotation.CallSuper; import android.annotation.DrawableRes; @@ -158,7 +160,6 @@ import com.android.internal.inputmethod.InputMethodNavButtonFlags; import com.android.internal.inputmethod.InputMethodPrivilegedOperations; import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry; import com.android.internal.inputmethod.SoftInputShowHideReason; -import com.android.internal.util.Preconditions; import com.android.internal.util.RingBuffer; import org.xmlpull.v1.XmlPullParserException; @@ -481,53 +482,43 @@ public class InputMethodService extends AbstractInputMethodService { public static final int BACK_DISPOSITION_ADJUST_NOTHING = 3; /** - * Enum values to be used for {@link #setBackDisposition(int)}. + * Enum flag to be used for {@link #setBackDisposition(int)}. * * @hide */ - @IntDef(prefix = { "BACK_DISPOSITION_" }, value = { - BACK_DISPOSITION_DEFAULT, - BACK_DISPOSITION_WILL_NOT_DISMISS, - BACK_DISPOSITION_WILL_DISMISS, - BACK_DISPOSITION_ADJUST_NOTHING, - }) - @Retention(RetentionPolicy.SOURCE) + @Retention(SOURCE) + @IntDef(value = {BACK_DISPOSITION_DEFAULT, BACK_DISPOSITION_WILL_NOT_DISMISS, + BACK_DISPOSITION_WILL_DISMISS, BACK_DISPOSITION_ADJUST_NOTHING}, + prefix = "BACK_DISPOSITION_") public @interface BackDispositionMode {} /** - * Enum flags to be used for {@link #setImeWindowStatus}, representing the current state of the - * IME window visibility. - * * @hide + * The IME is active. It may or may not be visible. */ - @IntDef(flag = true, prefix = { "IME_" }, value = { - IME_ACTIVE, - IME_VISIBLE, - IME_VISIBLE_IMPERCEPTIBLE, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface ImeWindowVisibility {} + public static final int IME_ACTIVE = 0x1; /** - * The IME is active. It may or may not be visible. * @hide + * The IME is perceptibly visible to the user. */ - public static final int IME_ACTIVE = 0x1; + public static final int IME_VISIBLE = 0x2; /** - * The IME is perceptibly visible to the user. * @hide + * The IME is active and ready with views but set invisible. + * This flag cannot be combined with {@link #IME_VISIBLE}. */ - public static final int IME_VISIBLE = 0x2; + public static final int IME_INVISIBLE = 0x4; /** + * @hide * The IME is visible, but not yet perceptible to the user (e.g. fading in) * by {@link android.view.WindowInsetsController}. * * @see InputMethodManager#reportPerceptible - * @hide */ - public static final int IME_VISIBLE_IMPERCEPTIBLE = 0x4; + public static final int IME_VISIBLE_IMPERCEPTIBLE = 0x8; // Min and max values for back disposition. private static final int BACK_DISPOSITION_MIN = BACK_DISPOSITION_DEFAULT; @@ -640,18 +631,9 @@ public class InputMethodService extends AbstractInputMethodService { int mStatusIcon; - /** - * Latest value reported of back disposition mode. - */ @BackDispositionMode int mBackDisposition; - /** - * Latest value reported of IME window visibility flags. - */ - @ImeWindowVisibility - private int mImeWindowVisibility; - private Object mLock = new Object(); @GuardedBy("mLock") private boolean mNotifyUserActionSent; @@ -1228,14 +1210,8 @@ public class InputMethodService extends AbstractInputMethodService { mImeSurfaceRemoverRunnable = null; } - private void setImeWindowStatus(@ImeWindowVisibility int vis, - @BackDispositionMode int backDisposition) { - if (vis == mImeWindowVisibility && backDisposition == mBackDisposition) { - return; - } - mImeWindowVisibility = Preconditions.checkFlagsArgument(vis, IME_ACTIVE | IME_VISIBLE); - mBackDisposition = backDisposition; - mPrivOps.setImeWindowStatusAsync(mImeWindowVisibility, mBackDisposition); + private void setImeWindowStatus(int visibilityFlags, int backDisposition) { + mPrivOps.setImeWindowStatusAsync(visibilityFlags, backDisposition); } /** Set region of the keyboard to be avoided from back gesture */ @@ -1909,11 +1885,15 @@ public class InputMethodService extends AbstractInputMethodService { * @param disposition disposition mode to be set */ public void setBackDisposition(@BackDispositionMode int disposition) { - if (disposition < BACK_DISPOSITION_MIN || disposition > BACK_DISPOSITION_MAX) { + if (disposition == mBackDisposition) { + return; + } + if (disposition > BACK_DISPOSITION_MAX || disposition < BACK_DISPOSITION_MIN) { Log.e(TAG, "Invalid back disposition value (" + disposition + ") specified."); return; } - setImeWindowStatus(mImeWindowVisibility, disposition); + mBackDisposition = disposition; + setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition); } /** @@ -2887,8 +2867,14 @@ public class InputMethodService extends AbstractInputMethodService { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.showWindow"); mDecorViewWasVisible = mDecorViewVisible; mInShowWindow = true; + final int previousImeWindowStatus = + (mDecorViewVisible ? IME_ACTIVE : 0) | (isInputViewShown() + ? (!mWindowVisible ? IME_INVISIBLE : IME_VISIBLE) : 0); startViews(prepareWindow(showInput)); - setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition); + final int nextImeWindowStatus = mapToImeWindowStatus(); + if (previousImeWindowStatus != nextImeWindowStatus) { + setImeWindowStatus(nextImeWindowStatus, mBackDisposition); + } mNavigationBarController.onWindowShown(); // compute visibility @@ -4099,9 +4085,9 @@ public class InputMethodService extends AbstractInputMethodService { }; } - @ImeWindowVisibility private int mapToImeWindowStatus() { - return IME_ACTIVE | (mDecorViewVisible ? IME_VISIBLE : 0); + return IME_ACTIVE + | (isInputViewShown() ? IME_VISIBLE : 0); } private boolean isAutomotive() { diff --git a/core/java/android/inputmethodservice/OWNERS b/core/java/android/inputmethodservice/OWNERS index d7db7c741364..9805ce82dcfc 100644 --- a/core/java/android/inputmethodservice/OWNERS +++ b/core/java/android/inputmethodservice/OWNERS @@ -3,4 +3,5 @@ set noparent include /services/core/java/com/android/server/inputmethod/OWNERS -per-file *InlineSuggestion* = file:/core/java/android/service/autofill/OWNERS +# Bug component: 1195602 = per-file *InlineSuggestion* +per-file *InlineSuggestion* = file:/core/java/android/widget/inline/OWNERS diff --git a/core/java/android/net/OWNERS b/core/java/android/net/OWNERS index b989488f9015..feeef55a957b 100644 --- a/core/java/android/net/OWNERS +++ b/core/java/android/net/OWNERS @@ -5,3 +5,4 @@ include platform/frameworks/base:/services/core/java/com/android/server/net/OWNE per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS per-file SSL*,Uri*,Url* = prb@google.com,oth@google.com,narayan@google.com,ngeoffray@google.com per-file SntpClient* = file:/services/core/java/com/android/server/timedetector/OWNERS +per-file Uri.java,Uri.aidl = varunshah@google.com diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index 9e97216ac632..c111138d6550 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -26,8 +26,6 @@ import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; import android.annotation.UserIdInt; import android.app.Activity; -import android.app.ActivityThread; -import android.app.OnActivityPausedListener; import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -1473,17 +1471,11 @@ public final class NfcAdapter { if (activity == null || intent == null) { throw new NullPointerException(); } - if (!activity.isResumed()) { - throw new IllegalStateException("Foreground dispatch can only be enabled " + - "when your activity is resumed"); - } try { TechListParcel parcel = null; if (techLists != null && techLists.length > 0) { parcel = new TechListParcel(techLists); } - ActivityThread.currentActivityThread().registerOnActivityPausedListener(activity, - mForegroundDispatchListener); sService.setForegroundDispatch(intent, filters, parcel); } catch (RemoteException e) { attemptDeadServiceRecovery(e); @@ -1511,25 +1503,8 @@ public final class NfcAdapter { throw new UnsupportedOperationException(); } } - ActivityThread.currentActivityThread().unregisterOnActivityPausedListener(activity, - mForegroundDispatchListener); - disableForegroundDispatchInternal(activity, false); - } - - OnActivityPausedListener mForegroundDispatchListener = new OnActivityPausedListener() { - @Override - public void onPaused(Activity activity) { - disableForegroundDispatchInternal(activity, true); - } - }; - - void disableForegroundDispatchInternal(Activity activity, boolean force) { try { sService.setForegroundDispatch(null, null, null); - if (!force && !activity.isResumed()) { - throw new IllegalStateException("You must disable foreground dispatching " + - "while your activity is still resumed"); - } } catch (RemoteException e) { attemptDeadServiceRecovery(e); } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 5072876a04dc..8596e543a148 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -54,6 +54,7 @@ import android.view.Display; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BatteryStatsHistoryIterator; +import com.android.internal.os.CpuScalingPolicies; import com.google.android.collect.Lists; @@ -1008,9 +1009,11 @@ public abstract class BatteryStats { * @param cluster the index of the CPU cluster. * @param step the index of the CPU speed. This is not the actual speed of the CPU. * @param which one of STATS_SINCE_CHARGED, STATS_SINCE_UNPLUGGED, or STATS_CURRENT. - * @see com.android.internal.os.PowerProfile#getNumCpuClusters() - * @see com.android.internal.os.PowerProfile#getNumSpeedStepsInCpuCluster(int) + * @see com.android.internal.os.CpuScalingPolicies#getPolicies + * @see com.android.internal.os.CpuScalingPolicies#getFrequencies + * @deprecated Unused except in tests */ + @Deprecated public abstract long getTimeAtCpuSpeed(int cluster, int step, int which); /** @@ -1648,11 +1651,9 @@ public abstract class BatteryStats { public abstract long getNextMaxDailyDeadline(); /** - * Returns the total number of frequencies across all CPU clusters. + * Returns the CPU scaling policies. */ - public abstract int getCpuFreqCount(); - - public abstract long[] getCpuFreqs(); + public abstract CpuScalingPolicies getCpuScalingPolicies(); public final static class HistoryTag { public String string; @@ -3390,8 +3391,8 @@ public abstract class BatteryStats { * cluster1-speeed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ... * </pre> * - * @see com.android.internal.os.PowerProfile#getNumCpuClusters() - * @see com.android.internal.os.PowerProfile#getNumSpeedStepsInCpuCluster(int) + * @see com.android.internal.os.CpuScalingPolicies#getPolicies + * @see com.android.internal.os.CpuScalingPolicies#getFrequencies */ @Nullable public abstract long[] getSystemServiceTimeAtCpuSpeeds(); @@ -4677,12 +4678,14 @@ public abstract class BatteryStats { proportionalAttributionCalculator.getProportionalPowerMah(consumer))); } - final long[] cpuFreqs = getCpuFreqs(); - if (cpuFreqs != null) { + final CpuScalingPolicies scalingPolicies = getCpuScalingPolicies(); + if (scalingPolicies != null) { sb.setLength(0); - for (int i = 0; i < cpuFreqs.length; ++i) { - if (i != 0) sb.append(','); - sb.append(cpuFreqs[i]); + for (int policy : scalingPolicies.getPolicies()) { + for (int frequency : scalingPolicies.getFrequencies(policy)) { + if (sb.length() != 0) sb.append(','); + sb.append(frequency); + } } dumpLine(pw, 0 /* uid */, category, GLOBAL_CPU_FREQ_DATA, sb.toString()); } @@ -4994,11 +4997,12 @@ public abstract class BatteryStats { } // If the cpuFreqs is null, then don't bother checking for cpu freq times. - if (cpuFreqs != null) { + if (scalingPolicies != null) { final long[] cpuFreqTimeMs = u.getCpuFreqTimes(which); // If total cpuFreqTimes is null, then we don't need to check for // screenOffCpuFreqTimes. - if (cpuFreqTimeMs != null && cpuFreqTimeMs.length == cpuFreqs.length) { + if (cpuFreqTimeMs != null + && cpuFreqTimeMs.length == scalingPolicies.getScalingStepCount()) { sb.setLength(0); for (int i = 0; i < cpuFreqTimeMs.length; ++i) { if (i != 0) sb.append(','); @@ -5018,7 +5022,8 @@ public abstract class BatteryStats { cpuFreqTimeMs.length, sb.toString()); } - final long[] timesInFreqMs = new long[getCpuFreqCount()]; + final long[] timesInFreqMs = + new long[getCpuScalingPolicies().getScalingStepCount()]; for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) { if (u.getCpuFreqTimes(timesInFreqMs, procState)) { sb.setLength(0); @@ -6000,14 +6005,18 @@ public abstract class BatteryStats { } } - final long[] cpuFreqs = getCpuFreqs(); - if (cpuFreqs != null) { + final CpuScalingPolicies scalingPolicies = getCpuScalingPolicies(); + if (scalingPolicies != null) { sb.setLength(0); - sb.append(" CPU freqs:"); - for (int i = 0; i < cpuFreqs.length; ++i) { - sb.append(' ').append(cpuFreqs[i]); + sb.append(" CPU scaling: "); + for (int policy : scalingPolicies.getPolicies()) { + sb.append(" policy").append(policy).append(':'); + for (int frequency : scalingPolicies.getFrequencies(policy)) { + sb.append(' ').append(frequency); + } } - pw.println(sb.toString()); + + pw.println(sb); pw.println(); } @@ -6628,7 +6637,7 @@ public abstract class BatteryStats { pw.println(sb.toString()); } - final long[] timesInFreqMs = new long[getCpuFreqCount()]; + final long[] timesInFreqMs = new long[getCpuScalingPolicies().getScalingStepCount()]; for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) { if (u.getCpuFreqTimes(timesInFreqMs, procState)) { sb.setLength(0); @@ -8078,12 +8087,13 @@ public abstract class BatteryStats { proto.write(UidProto.Cpu.USER_DURATION_MS, roundUsToMs(u.getUserCpuTimeUs(which))); proto.write(UidProto.Cpu.SYSTEM_DURATION_MS, roundUsToMs(u.getSystemCpuTimeUs(which))); - final long[] cpuFreqs = getCpuFreqs(); - if (cpuFreqs != null) { + final CpuScalingPolicies scalingPolicies = getCpuScalingPolicies(); + if (scalingPolicies != null) { final long[] cpuFreqTimeMs = u.getCpuFreqTimes(which); // If total cpuFreqTimes is null, then we don't need to check for // screenOffCpuFreqTimes. - if (cpuFreqTimeMs != null && cpuFreqTimeMs.length == cpuFreqs.length) { + if (cpuFreqTimeMs != null + && cpuFreqTimeMs.length == scalingPolicies.getScalingStepCount()) { long[] screenOffCpuFreqTimeMs = u.getScreenOffCpuFreqTimes(which); if (screenOffCpuFreqTimeMs == null) { screenOffCpuFreqTimeMs = new long[cpuFreqTimeMs.length]; @@ -8100,8 +8110,9 @@ public abstract class BatteryStats { } } - final long[] timesInFreqMs = new long[getCpuFreqCount()]; - final long[] timesInFreqScreenOffMs = new long[getCpuFreqCount()]; + final int stepCount = getCpuScalingPolicies().getScalingStepCount(); + final long[] timesInFreqMs = new long[stepCount]; + final long[] timesInFreqScreenOffMs = new long[stepCount]; for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) { if (u.getCpuFreqTimes(timesInFreqMs, procState)) { if (!u.getScreenOffCpuFreqTimes(timesInFreqScreenOffMs, procState)) { @@ -8571,10 +8582,12 @@ public abstract class BatteryStats { dumpDurationSteps(proto, SystemProto.DISCHARGE_STEP, getDischargeLevelStepTracker()); // CPU frequencies (GLOBAL_CPU_FREQ_DATA) - final long[] cpuFreqs = getCpuFreqs(); - if (cpuFreqs != null) { - for (long i : cpuFreqs) { - proto.write(SystemProto.CPU_FREQUENCY, i); + final CpuScalingPolicies scalingPolicies = getCpuScalingPolicies(); + if (scalingPolicies != null) { + for (int policy : scalingPolicies.getPolicies()) { + for (int frequency : scalingPolicies.getFrequencies(policy)) { + proto.write(SystemProto.CPU_FREQUENCY, frequency); + } } } diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index c9073fa4b72c..7664bada2c28 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -112,9 +112,19 @@ public class GraphicsEnvironment { private static final int ANGLE_GL_DRIVER_ALL_ANGLE_OFF = 0; // Values for ANGLE_GL_DRIVER_SELECTION_VALUES - private static final String ANGLE_GL_DRIVER_CHOICE_DEFAULT = "default"; - private static final String ANGLE_GL_DRIVER_CHOICE_ANGLE = "angle"; - private static final String ANGLE_GL_DRIVER_CHOICE_NATIVE = "native"; + private enum AngleDriverChoice { + DEFAULT("default"), + ANGLE("angle"), + NATIVE("native"); + + public final String choice; + + AngleDriverChoice(String choice) { + this.choice = choice; + } + } + + private static final String PROPERTY_RO_ANGLE_SUPPORTED = "ro.gfx.angle.supported"; private ClassLoader mClassLoader; private String mLibrarySearchPaths; @@ -193,15 +203,16 @@ public class GraphicsEnvironment { } /** - * Query to determine if ANGLE should be used + * Query to determine the ANGLE driver choice. */ - private boolean shouldUseAngle(Context context, Bundle coreSettings, String packageName) { + private AngleDriverChoice queryAngleChoice(Context context, Bundle coreSettings, + String packageName) { if (TextUtils.isEmpty(packageName)) { Log.v(TAG, "No package name specified; use the system driver"); - return false; + return AngleDriverChoice.DEFAULT; } - return shouldUseAngleInternal(context, coreSettings, packageName); + return queryAngleChoiceInternal(context, coreSettings, packageName); } private int getVulkanVersion(PackageManager pm) { @@ -422,10 +433,11 @@ public class GraphicsEnvironment { * forces a choice; * 3) Use ANGLE if isAngleEnabledByGameMode() returns true; */ - private boolean shouldUseAngleInternal(Context context, Bundle bundle, String packageName) { + private AngleDriverChoice queryAngleChoiceInternal(Context context, Bundle bundle, + String packageName) { // Make sure we have a good package name if (TextUtils.isEmpty(packageName)) { - return false; + return AngleDriverChoice.DEFAULT; } // Check the semi-global switch (i.e. once system has booted enough) for whether ANGLE @@ -440,7 +452,7 @@ public class GraphicsEnvironment { } if (allUseAngle == ANGLE_GL_DRIVER_ALL_ANGLE_ON) { Log.v(TAG, "Turn on ANGLE for all applications."); - return true; + return AngleDriverChoice.ANGLE; } // Get the per-application settings lists @@ -463,7 +475,7 @@ public class GraphicsEnvironment { + optInPackages.size() + ", " + "number of values: " + optInValues.size()); - return mEnabledByGameMode; + return mEnabledByGameMode ? AngleDriverChoice.ANGLE : AngleDriverChoice.DEFAULT; } // See if this application is listed in the per-application settings list @@ -471,7 +483,7 @@ public class GraphicsEnvironment { if (pkgIndex < 0) { Log.v(TAG, packageName + " is not listed in per-application setting"); - return mEnabledByGameMode; + return mEnabledByGameMode ? AngleDriverChoice.ANGLE : AngleDriverChoice.DEFAULT; } mAngleOptInIndex = pkgIndex; @@ -481,14 +493,14 @@ public class GraphicsEnvironment { Log.v(TAG, "ANGLE Developer option for '" + packageName + "' " + "set to: '" + optInValue + "'"); - if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_ANGLE)) { - return true; - } else if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) { - return false; + if (optInValue.equals(AngleDriverChoice.ANGLE.choice)) { + return AngleDriverChoice.ANGLE; + } else if (optInValue.equals(AngleDriverChoice.NATIVE.choice)) { + return AngleDriverChoice.NATIVE; } else { // The user either chose default or an invalid value; go with the default driver or what // the game mode indicates - return mEnabledByGameMode; + return mEnabledByGameMode ? AngleDriverChoice.ANGLE : AngleDriverChoice.DEFAULT; } } @@ -501,10 +513,12 @@ public class GraphicsEnvironment { final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY); if (resolveInfos.size() != 1) { - Log.e(TAG, "Invalid number of ANGLE packages. Required: 1, Found: " + Log.v(TAG, "Invalid number of ANGLE packages. Required: 1, Found: " + resolveInfos.size()); - for (ResolveInfo resolveInfo : resolveInfos) { - Log.e(TAG, "Found ANGLE package: " + resolveInfo.activityInfo.packageName); + if (DEBUG) { + for (ResolveInfo resolveInfo : resolveInfos) { + Log.d(TAG, "Found ANGLE package: " + resolveInfo.activityInfo.packageName); + } } return ""; } @@ -539,26 +553,48 @@ public class GraphicsEnvironment { } /** - * Determine whether ANGLE should be used, set it up if so, and pass ANGLE details down to - * the C++ GraphicsEnv class. - * - * If ANGLE will be used, GraphicsEnv::setAngleInfo() will be called to enable ANGLE to be - * properly used. + * Determine whether ANGLE should be used, attempt to set up from apk first, if ANGLE can be + * set up from apk, pass ANGLE details down to the C++ GraphicsEnv class via + * GraphicsEnv::setAngleInfo(). If apk setup fails, attempt to set up to use system ANGLE. + * Return false if both fail. * - * @param context - * @param bundle - * @param pm + * @param context - Context of the application. + * @param bundle - Bundle of the application. + * @param packageManager - PackageManager of the application process. * @param packageName - package name of the application. - * @return true: ANGLE setup successfully - * false: ANGLE not setup (not on allowlist, ANGLE not present, etc.) + * @return true: can set up to use ANGLE successfully. + * false: can not set up to use ANGLE (not on allowlist, ANGLE not present, etc.) */ - private boolean setupAngle(Context context, Bundle bundle, PackageManager pm, + private boolean setupAngle(Context context, Bundle bundle, PackageManager packageManager, String packageName) { - if (!shouldUseAngle(context, bundle, packageName)) { + final AngleDriverChoice angleDriverChoice = queryAngleChoice(context, bundle, packageName); + if (angleDriverChoice == AngleDriverChoice.DEFAULT) { return false; } + if (queryAngleChoice(context, bundle, packageName) == AngleDriverChoice.NATIVE) { + nativeSetAngleInfo("", true, packageName, null); + return false; + } + + return setupAngleFromApk(context, bundle, packageManager, packageName) + || setupAngleFromSystem(context, bundle, packageName); + } + + /** + * Attempt to set up ANGLE from the packaged apk, if the apk can be found, pass ANGLE details to + * the C++ GraphicsEnv class. + * + * @param context - Context of the application. + * @param bundle - Bundle of the application. + * @param packageManager - PackageManager of the application process. + * @param packageName - package name of the application. + * @return true: can set up to use ANGLE apk. + * false: can not set up to use ANGLE apk (ANGLE apk not present, etc.) + */ + private boolean setupAngleFromApk(Context context, Bundle bundle, PackageManager packageManager, + String packageName) { ApplicationInfo angleInfo = null; // If the developer has specified a debug package over ADB, attempt to find it @@ -567,7 +603,7 @@ public class GraphicsEnvironment { Log.v(TAG, "ANGLE debug package enabled: " + anglePkgName); try { // Note the debug package does not have to be pre-installed - angleInfo = pm.getApplicationInfo(anglePkgName, 0); + angleInfo = packageManager.getApplicationInfo(anglePkgName, 0); } catch (PackageManager.NameNotFoundException e) { // If the debug package is specified but not found, abort. Log.v(TAG, "ANGLE debug package '" + anglePkgName + "' not installed"); @@ -577,7 +613,7 @@ public class GraphicsEnvironment { // Otherwise, check to see if ANGLE is properly installed if (angleInfo == null) { - anglePkgName = getAnglePackageName(pm); + anglePkgName = getAnglePackageName(packageManager); if (TextUtils.isEmpty(anglePkgName)) { Log.v(TAG, "Failed to find ANGLE package."); return false; @@ -586,7 +622,7 @@ public class GraphicsEnvironment { Log.v(TAG, "ANGLE package enabled: " + anglePkgName); try { // Production ANGLE libraries must be pre-installed as a system app - angleInfo = pm.getApplicationInfo(anglePkgName, + angleInfo = packageManager.getApplicationInfo(anglePkgName, PackageManager.MATCH_SYSTEM_ONLY); } catch (PackageManager.NameNotFoundException e) { Log.v(TAG, "ANGLE package '" + anglePkgName + "' not installed"); @@ -607,15 +643,39 @@ public class GraphicsEnvironment { Log.d(TAG, "ANGLE package libs: " + paths); } - // If we make it to here, ANGLE will be used. Call setAngleInfo() with the package name, - // and features to use. + // If we make it to here, ANGLE apk will be used. Call nativeSetAngleInfo() with the + // application package name and ANGLE features to use. final String[] features = getAngleEglFeatures(context, bundle); - setAngleInfo(paths, packageName, ANGLE_GL_DRIVER_CHOICE_ANGLE, features); + nativeSetAngleInfo(paths, false, packageName, features); return true; } /** + * Attempt to set up ANGLE from system, if the apk can be found, pass ANGLE details to + * the C++ GraphicsEnv class. + * + * @param context - Context of the application. + * @param bundle - Bundle of the application. + * @param packageName - package name of the application. + * @return true: can set up to use system ANGLE. + * false: can not set up to use system ANGLE because it doesn't exist. + */ + private boolean setupAngleFromSystem(Context context, Bundle bundle, String packageName) { + final boolean systemAngleSupported = SystemProperties + .getBoolean(PROPERTY_RO_ANGLE_SUPPORTED, false); + if (!systemAngleSupported) { + return false; + } + + // If we make it to here, system ANGLE will be used. Call nativeSetAngleInfo() with + // the application package name and ANGLE features to use. + final String[] features = getAngleEglFeatures(context, bundle); + nativeSetAngleInfo("system", false, packageName, features); + return true; + } + + /** * Determine if the "ANGLE In Use" dialog box should be shown. */ private boolean shouldShowAngleInUseDialogBox(Context context) { @@ -651,7 +711,9 @@ public class GraphicsEnvironment { final Intent intent = new Intent(ACTION_ANGLE_FOR_ANDROID_TOAST_MESSAGE); final String anglePkg = getAnglePackageName(context.getPackageManager()); - intent.setPackage(anglePkg); + if (anglePkg.isEmpty()) { + return; + } context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { @Override @@ -890,8 +952,8 @@ public class GraphicsEnvironment { private static native void setDriverPathAndSphalLibraries(String path, String sphalLibraries); private static native void setGpuStats(String driverPackageName, String driverVersionName, long driverVersionCode, long driverBuildTime, String appPackageName, int vulkanVersion); - private static native void setAngleInfo(String path, String packageName, - String devOptIn, String[] features); + private static native void nativeSetAngleInfo(String path, boolean useNativeDriver, + String packageName, String[] features); private static native boolean setInjectLayersPrSetDumpable(); private static native void nativeToggleAngleAsSystemDriver(boolean enabled); diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING index cc5426631a7b..5c4aa4a233fc 100644 --- a/core/java/android/os/TEST_MAPPING +++ b/core/java/android/os/TEST_MAPPING @@ -52,8 +52,20 @@ ], "name": "FrameworksServicesTests", "options": [ - { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }, - { "include-filter": "com.android.server.power.stats.BatteryStatsTests" } + { "include-filter": "com.android.server.am.BatteryStatsServiceTest" } + ] + }, + { + "file_patterns": [ + "BatteryStats[^/]*\\.java", + "BatteryUsageStats[^/]*\\.java", + "PowerComponents\\.java", + "[^/]*BatteryConsumer[^/]*\\.java" + ], + "name": "FrameworksServicesTests", + "options": [ + { "include-filter": "com.android.server.power.stats" }, + { "exclude-filter": "com.android.server.power.stats.BatteryStatsTests" } ] }, { diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 84dc79b92c55..ba1f979b4514 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -159,6 +159,13 @@ public class UserManager { @SystemApi public static final String USER_TYPE_PROFILE_CLONE = "android.os.usertype.profile.CLONE"; + + /** + * User type representing a private profile. + * @hide + */ + public static final String USER_TYPE_PROFILE_PRIVATE = "android.os.usertype.profile.PRIVATE"; + /** * User type representing a generic profile for testing purposes. Only on debuggable builds. * @hide @@ -2858,6 +2865,16 @@ public class UserManager { } /** + * Returns whether the user type is a + * {@link UserManager#USER_TYPE_PROFILE_PRIVATE private profile}. + * + * @hide + */ + public static boolean isUserTypePrivateProfile(@Nullable String userType) { + return USER_TYPE_PROFILE_PRIVATE.equals(userType); + } + + /** * @hide * @deprecated Use {@link #isRestrictedProfile()} */ @@ -3146,6 +3163,24 @@ public class UserManager { } /** + * Checks if the context user is a private profile. + * + * @return whether the context user is a private profile. + * + * @see android.os.UserManager#USER_TYPE_PROFILE_PRIVATE + * @hide + */ + @UserHandleAware( + requiresAnyOfPermissionsIfNotCallerProfileGroup = { + android.Manifest.permission.MANAGE_USERS, + android.Manifest.permission.QUERY_USERS, + android.Manifest.permission.INTERACT_ACROSS_USERS}) + @SuppressAutoDoc + public boolean isPrivateProfile() { + return isUserTypePrivateProfile(getProfileType()); + } + + /** * Checks if the context user is an ephemeral user. * * @return whether the context user is an ephemeral user. diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java index 22676276f748..d41a428355ea 100644 --- a/core/java/android/os/vibrator/persistence/VibrationXmlParser.java +++ b/core/java/android/os/vibrator/persistence/VibrationXmlParser.java @@ -93,6 +93,16 @@ public final class VibrationXmlParser { private static final String TAG = "VibrationXmlParser"; /** + * The MIME type for a xml holding a vibration. + * + * <p>This should match the type registered at android.mime.types. + * + * @hide + */ + public static final String APPLICATION_VIBRATION_XML_MIME_TYPE = + "application/vnd.android.haptics.vibration+xml"; + + /** * Allows {@link VibrationEffect} instances created via non-public APIs to be parsed/serialized. * * <p>Note that the XML schema for non-public APIs is not backwards compatible. This is intended @@ -111,6 +121,24 @@ public final class VibrationXmlParser { public @interface Flags {} /** + * Returns whether this parser supports parsing files of the given MIME type. + * + * <p>Returns false for {@code null} value. + * + * <p><em>Note: MIME type matching in the Android framework is case-sensitive, unlike the formal + * RFC definitions. As a result, you should always write these elements with lower case letters, + * or use {@link android.content.Intent#normalizeMimeType} to ensure that they are converted to + * lower case.</em> + * + * @hide + */ + public static boolean isSupportedMimeType(@Nullable String mimeType) { + // NOTE: prefer using MimeTypeFilter.matches() if MIME_TYPE_VIBRATION_XML becomes a filter + // or if more than one MIME type is supported by this parser. + return APPLICATION_VIBRATION_XML_MIME_TYPE.equals(mimeType); + } + + /** * Parses XML content from given input stream into a {@link VibrationEffect}. * * <p>This parser fails silently and returns {@code null} if the content of the input stream diff --git a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java index 554d70339b90..1cdfa4f74dbd 100644 --- a/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java +++ b/core/java/android/os/vibrator/persistence/VibrationXmlSerializer.java @@ -81,12 +81,11 @@ public final class VibrationXmlSerializer { * Serializes a {@link VibrationEffect} to XML and writes output to given {@link Writer}. * * <p>This method will only write into the {@link Writer} if the effect can successfully - * be represented by the XML serialization. It will return {@code false} otherwise, and not - * write any data. + * be represented by the XML serialization. It will throw an exception otherwise. * * @throws SerializationFailedException serialization of input effect failed, no data was - * written into given {@link Writer} - * @throws IOException error writing to given {@link Writer} + * written into given {@link Writer}. + * @throws IOException error writing to given {@link Writer}. * * @hide */ @@ -139,10 +138,14 @@ public final class VibrationXmlSerializer { /** * Exception thrown when a {@link VibrationEffect} instance serialization fails. * + * <p>The serialization can fail if a given vibration cannot be represented using the public + * format, or if it uses hidden APIs that are not supported for serialization (e.g. + * {@link VibrationEffect.WaveformBuilder}). + * * @hide */ @TestApi - public static final class SerializationFailedException extends IllegalStateException { + public static final class SerializationFailedException extends RuntimeException { SerializationFailedException(VibrationEffect effect, Throwable cause) { super("Serialization failed for vibration effect " + effect, cause); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index fd28446f3947..7a0556840275 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -12696,6 +12696,26 @@ public final class Settings { public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on"; /** + * The duration in milliseconds of each action, separated by commas. Ex: + * + * "18000,18000,18000,18000,0" + * + * See com.android.internal.telephony.data.DataStallRecoveryManager for more info + * @hide + */ + public static final String DSRM_DURATION_MILLIS = "dsrm_duration_millis"; + + /** + * The list of DSRM enabled actions, separated by commas. Ex: + * + * "true,true,false,true,true" + * + * See com.android.internal.telephony.data.DataStallRecoveryManager for more info + * @hide + */ + public static final String DSRM_ENABLED_ACTIONS = "dsrm_enabled_actions"; + + /** * Whether the wifi data connection should remain active even when higher * priority networks like Ethernet are active, to keep both networks. * In the case where higher priority networks are connected, wifi will be diff --git a/core/java/android/service/autofill/augmented/OWNERS b/core/java/android/service/autofill/augmented/OWNERS index a08863276da7..4187b10f1ed2 100644 --- a/core/java/android/service/autofill/augmented/OWNERS +++ b/core/java/android/service/autofill/augmented/OWNERS @@ -1,9 +1,3 @@ # Bug component: 351486 -joannechung@google.com -adamhe@google.com -tymtsai@google.com -lpeter@google.com -augale@google.com -svetoslavganov@android.com -svetoslavganov@google.com +wangqi@google.com diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 5e7f5d62e256..9b19937444bd 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -26,7 +26,6 @@ import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.TestApi; import android.app.Activity; -import android.app.ActivityTaskManager; import android.app.AlarmManager; import android.app.Service; import android.compat.annotation.UnsupportedAppUsage; @@ -1268,9 +1267,7 @@ public class DreamService extends Service implements Window.Callback { fetchDreamLabel(this, serviceInfo, isPreviewMode)); try { - if (!ActivityTaskManager.getService().startDreamActivity(i)) { - detach(); - } + mDreamManager.startDreamActivity(i); } catch (SecurityException e) { Log.w(mTag, "Received SecurityException trying to start DreamActivity. " diff --git a/core/java/android/service/dreams/IDreamManager.aidl b/core/java/android/service/dreams/IDreamManager.aidl index 609425c95b0d..dd8b3deabc01 100644 --- a/core/java/android/service/dreams/IDreamManager.aidl +++ b/core/java/android/service/dreams/IDreamManager.aidl @@ -17,6 +17,7 @@ package android.service.dreams; import android.content.ComponentName; +import android.content.Intent; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.IBinder; @@ -45,4 +46,5 @@ interface IDreamManager { void setDreamComponentsForUser(int userId, in ComponentName[] componentNames); void setSystemDreamComponent(in ComponentName componentName); void registerDreamOverlayService(in ComponentName componentName); + void startDreamActivity(in Intent intent); } diff --git a/core/java/android/service/wallpaper/IWallpaperService.aidl b/core/java/android/service/wallpaper/IWallpaperService.aidl index da56b4b2f574..3262f3a9fdfb 100644 --- a/core/java/android/service/wallpaper/IWallpaperService.aidl +++ b/core/java/android/service/wallpaper/IWallpaperService.aidl @@ -16,6 +16,7 @@ package android.service.wallpaper; +import android.app.WallpaperInfo; import android.graphics.Rect; import android.service.wallpaper.IWallpaperConnection; @@ -25,6 +26,7 @@ import android.service.wallpaper.IWallpaperConnection; oneway interface IWallpaperService { void attach(IWallpaperConnection connection, IBinder windowToken, int windowType, boolean isPreview, - int reqWidth, int reqHeight, in Rect padding, int displayId, int which); + int reqWidth, int reqHeight, in Rect padding, int displayId, int which, + in WallpaperInfo info); void detach(IBinder windowToken); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index dbc1be141571..cc3a662870bb 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -1606,8 +1606,13 @@ public abstract class WallpaperService extends Service { if (!mDestroyed) { mDisplayState = mDisplay == null ? Display.STATE_UNKNOWN : mDisplay.getCommittedState(); - boolean displayVisible = Display.isOnState(mDisplayState) && !mIsScreenTurningOn; - boolean visible = mVisible && displayVisible; + boolean displayFullyOn = Display.isOnState(mDisplayState) && !mIsScreenTurningOn; + boolean supportsAmbientMode = + mIWallpaperEngine.mInfo == null + ? false + : mIWallpaperEngine.mInfo.supportsAmbientMode(); + // Report visibility only if display is fully on or wallpaper supports ambient mode. + boolean visible = mVisible && (displayFullyOn || supportsAmbientMode); if (DEBUG) { Log.v( TAG, @@ -2060,7 +2065,7 @@ public abstract class WallpaperService extends Service { } private void updateFrozenState(boolean frozenRequested) { - if (mIWallpaperEngine.mWallpaperManager.getWallpaperInfo() == null + if (mIWallpaperEngine.mInfo == null // Procees the unfreeze command in case the wallaper became static while // being paused. && frozenRequested) { @@ -2355,6 +2360,7 @@ public abstract class WallpaperService extends Service { final DisplayManager mDisplayManager; final Display mDisplay; final WallpaperManager mWallpaperManager; + @Nullable final WallpaperInfo mInfo; Engine mEngine; @SetWallpaperFlags int mWhich; @@ -2362,7 +2368,7 @@ public abstract class WallpaperService extends Service { IWallpaperEngineWrapper(WallpaperService service, IWallpaperConnection conn, IBinder windowToken, int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding, - int displayId, @SetWallpaperFlags int which) { + int displayId, @SetWallpaperFlags int which, @Nullable WallpaperInfo info) { mWallpaperManager = getSystemService(WallpaperManager.class); mCaller = new HandlerCaller(service, service.onProvideEngineLooper(), this, true); mConnection = conn; @@ -2374,6 +2380,7 @@ public abstract class WallpaperService extends Service { mDisplayPadding.set(padding); mDisplayId = displayId; mWhich = which; + mInfo = info; // Create a display context before onCreateEngine. mDisplayManager = getSystemService(DisplayManager.class); @@ -2447,8 +2454,7 @@ public abstract class WallpaperService extends Service { Trace.beginSection("WPMS.mConnection.engineShown"); try { mConnection.engineShown(this); - Log.d(TAG, "Wallpaper has updated the surface:" - + mWallpaperManager.getWallpaperInfo()); + Log.d(TAG, "Wallpaper has updated the surface:" + mInfo); } catch (RemoteException e) { Log.w(TAG, "Wallpaper host disappeared", e); } @@ -2702,11 +2708,11 @@ public abstract class WallpaperService extends Service { @Override public void attach(IWallpaperConnection conn, IBinder windowToken, int windowType, boolean isPreview, int reqWidth, int reqHeight, Rect padding, - int displayId, @SetWallpaperFlags int which) { + int displayId, @SetWallpaperFlags int which, @Nullable WallpaperInfo info) { Trace.beginSection("WPMS.ServiceWrapper.attach"); IWallpaperEngineWrapper engineWrapper = new IWallpaperEngineWrapper(mTarget, conn, windowToken, windowType, - isPreview, reqWidth, reqHeight, padding, displayId, which); + isPreview, reqWidth, reqHeight, padding, displayId, which, info); synchronized (mActiveEngines) { mActiveEngines.put(windowToken, engineWrapper); } diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java index bedd4097d2db..5b4f195d537b 100644 --- a/core/java/android/text/MeasuredParagraph.java +++ b/core/java/android/text/MeasuredParagraph.java @@ -20,6 +20,9 @@ import android.annotation.FloatRange; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.Px; +import android.annotation.SuppressLint; +import android.annotation.TestApi; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.text.LineBreakConfig; @@ -28,6 +31,7 @@ import android.text.AutoGrowArray.ByteArray; import android.text.AutoGrowArray.FloatArray; import android.text.AutoGrowArray.IntArray; import android.text.Layout.Directions; +import android.text.style.LineBreakConfigSpan; import android.text.style.MetricAffectingSpan; import android.text.style.ReplacementSpan; import android.util.Pools.SynchronizedPool; @@ -57,6 +61,7 @@ import java.util.Arrays; * MeasuredParagraph is NOT a thread safe object. * @hide */ +@TestApi public class MeasuredParagraph { private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC'; @@ -73,6 +78,7 @@ public class MeasuredParagraph { * Recycle the MeasuredParagraph. * * Do not call any methods after you call this method. + * @hide */ public void recycle() { release(); @@ -126,11 +132,14 @@ public class MeasuredParagraph { private @Nullable MeasuredText mMeasuredText; // Following three objects are for avoiding object allocation. - private @NonNull TextPaint mCachedPaint = new TextPaint(); + private final @NonNull TextPaint mCachedPaint = new TextPaint(); private @Nullable Paint.FontMetricsInt mCachedFm; + private final @NonNull LineBreakConfig.Builder mLineBreakConfigBuilder = + new LineBreakConfig.Builder(); /** * Releases internal buffers. + * @hide */ public void release() { reset(); @@ -158,6 +167,7 @@ public class MeasuredParagraph { * Returns the length of the paragraph. * * This is always available. + * @hide */ public int getTextLength() { return mTextLength; @@ -167,6 +177,7 @@ public class MeasuredParagraph { * Returns the characters to be measured. * * This is always available. + * @hide */ public @NonNull char[] getChars() { return mCopiedBuffer; @@ -176,6 +187,7 @@ public class MeasuredParagraph { * Returns the paragraph direction. * * This is always available. + * @hide */ public @Layout.Direction int getParagraphDir() { return mParaDir; @@ -185,6 +197,7 @@ public class MeasuredParagraph { * Returns the directions. * * This is always available. + * @hide */ public Directions getDirections(@IntRange(from = 0) int start, // inclusive @IntRange(from = 0) int end) { // exclusive @@ -202,6 +215,7 @@ public class MeasuredParagraph { * * This is available only if the MeasuredParagraph is computed with buildForMeasurement. * Returns 0 in other cases. + * @hide */ public @FloatRange(from = 0.0f) float getWholeWidth() { return mWholeWidth; @@ -212,6 +226,7 @@ public class MeasuredParagraph { * * This is available only if the MeasuredParagraph is computed with buildForMeasurement. * Returns empty array in other cases. + * @hide */ public @NonNull FloatArray getWidths() { return mWidths; @@ -224,6 +239,7 @@ public class MeasuredParagraph { * * This is available only if the MeasuredParagraph is computed with buildForStaticLayout. * Returns empty array in other cases. + * @hide */ public @NonNull IntArray getSpanEndCache() { return mSpanEndCache; @@ -236,6 +252,7 @@ public class MeasuredParagraph { * * This is available only if the MeasuredParagraph is computed with buildForStaticLayout. * Returns empty array in other cases. + * @hide */ public @NonNull IntArray getFontMetrics() { return mFontMetrics; @@ -246,6 +263,7 @@ public class MeasuredParagraph { * * This is available only if the MeasuredParagraph is computed with buildForStaticLayout. * Returns null in other cases. + * @hide */ public MeasuredText getMeasuredText() { return mMeasuredText; @@ -259,6 +277,7 @@ public class MeasuredParagraph { * * @param start the inclusive start offset of the target region in the text * @param end the exclusive end offset of the target region in the text + * @hide */ public float getWidth(int start, int end) { if (mMeasuredText == null) { @@ -280,6 +299,7 @@ public class MeasuredParagraph { * at (0, 0). * * This is available only if the MeasuredParagraph is computed with buildForStaticLayout. + * @hide */ public void getBounds(@IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull Rect bounds) { @@ -290,6 +310,7 @@ public class MeasuredParagraph { * Retrieves the font metrics for the given range. * * This is available only if the MeasuredParagraph is computed with buildForStaticLayout. + * @hide */ public void getFontMetricsInt(@IntRange(from = 0) int start, @IntRange(from = 0) int end, @NonNull Paint.FontMetricsInt fmi) { @@ -300,6 +321,7 @@ public class MeasuredParagraph { * Returns a width of the character at the offset. * * This is available only if the MeasuredParagraph is computed with buildForStaticLayout. + * @hide */ public float getCharWidthAt(@IntRange(from = 0) int offset) { return mMeasuredText.getCharWidthAt(offset); @@ -318,6 +340,7 @@ public class MeasuredParagraph { * @param recycle pass existing MeasuredParagraph if you want to recycle it. * * @return measured text + * @hide */ public static @NonNull MeasuredParagraph buildForBidi(@NonNull CharSequence text, @IntRange(from = 0) int start, @@ -343,6 +366,7 @@ public class MeasuredParagraph { * @param recycle pass existing MeasuredParagraph if you want to recycle it. * * @return measured text + * @hide */ public static @NonNull MeasuredParagraph buildForMeasurement(@NonNull TextPaint paint, @NonNull CharSequence text, @@ -361,25 +385,53 @@ public class MeasuredParagraph { if (mt.mSpanned == null) { // No style change by MetricsAffectingSpan. Just measure all text. mt.applyMetricsAffectingSpan( - paint, null /* lineBreakConfig */, null /* spans */, start, end, - null /* native builder ptr */); + paint, null /* lineBreakConfig */, null /* spans */, null /* lbcSpans */, + start, end, null /* native builder ptr */, null); } else { // There may be a MetricsAffectingSpan. Split into span transitions and apply styles. int spanEnd; for (int spanStart = start; spanStart < end; spanStart = spanEnd) { - spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class); + int maSpanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, + MetricAffectingSpan.class); + int lbcSpanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, + LineBreakConfigSpan.class); + spanEnd = Math.min(maSpanEnd, lbcSpanEnd); MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class); + LineBreakConfigSpan[] lbcSpans = mt.mSpanned.getSpans(spanStart, spanEnd, + LineBreakConfigSpan.class); spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); + lbcSpans = TextUtils.removeEmptySpans(lbcSpans, mt.mSpanned, + LineBreakConfigSpan.class); mt.applyMetricsAffectingSpan( - paint, null /* line break config */, spans, spanStart, spanEnd, - null /* native builder ptr */); + paint, null /* line break config */, spans, lbcSpans, spanStart, spanEnd, + null /* native builder ptr */, null); } } return mt; } /** + * A test interface for observing the style run calculation. + * @hide + */ + @TestApi + public interface StyleRunCallback { + /** + * Called when a single style run is identified. + */ + void onAppendStyleRun(@NonNull Paint paint, + @Nullable LineBreakConfig lineBreakConfig, @IntRange(from = 0) int length, + boolean isRtl); + + /** + * Called when a single replacement run is identified. + */ + void onAppendReplacementRun(@NonNull Paint paint, + @IntRange(from = 0) int length, @Px @FloatRange(from = 0) float width); + } + + /** * Generates new MeasuredParagraph for StaticLayout. * * If recycle is null, this returns new instance. If recycle is not null, this fills computed @@ -397,6 +449,7 @@ public class MeasuredParagraph { * @param recycle pass existing MeasuredParagraph if you want to recycle it. * * @return measured text + * @hide */ public static @NonNull MeasuredParagraph buildForStaticLayout( @NonNull TextPaint paint, @@ -409,6 +462,57 @@ public class MeasuredParagraph { boolean computeLayout, @Nullable MeasuredParagraph hint, @Nullable MeasuredParagraph recycle) { + return buildForStaticLayoutInternal(paint, lineBreakConfig, text, start, end, textDir, + hyphenationMode, computeLayout, hint, recycle, null); + } + + /** + * Generates new MeasuredParagraph for StaticLayout. + * + * If recycle is null, this returns new instance. If recycle is not null, this fills computed + * result to recycle and returns recycle. + * + * @param paint the paint to be used for rendering the text. + * @param lineBreakConfig the line break configuration for text wrapping. + * @param text the character sequence to be measured + * @param start the inclusive start offset of the target region in the text + * @param end the exclusive end offset of the target region in the text + * @param textDir the text direction + * @param hyphenationMode a hyphenation mode + * @param computeLayout true if need to compute full layout, otherwise false. + * + * @return measured text + * @hide + */ + @SuppressLint("ExecutorRegistration") + @TestApi + @NonNull + public static MeasuredParagraph buildForStaticLayoutTest( + @NonNull TextPaint paint, + @Nullable LineBreakConfig lineBreakConfig, + @NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextDirectionHeuristic textDir, + int hyphenationMode, + boolean computeLayout, + @Nullable StyleRunCallback testCallback) { + return buildForStaticLayoutInternal(paint, lineBreakConfig, text, start, end, textDir, + hyphenationMode, computeLayout, null, null, testCallback); + } + + private static @NonNull MeasuredParagraph buildForStaticLayoutInternal( + @NonNull TextPaint paint, + @Nullable LineBreakConfig lineBreakConfig, + @NonNull CharSequence text, + @IntRange(from = 0) int start, + @IntRange(from = 0) int end, + @NonNull TextDirectionHeuristic textDir, + int hyphenationMode, + boolean computeLayout, + @Nullable MeasuredParagraph hint, + @Nullable MeasuredParagraph recycle, + @Nullable StyleRunCallback testCallback) { final MeasuredParagraph mt = recycle == null ? obtain() : recycle; mt.resetAndAnalyzeBidi(text, start, end, textDir); final MeasuredText.Builder builder; @@ -426,23 +530,29 @@ public class MeasuredParagraph { } else { if (mt.mSpanned == null) { // No style change by MetricsAffectingSpan. Just measure all text. - mt.applyMetricsAffectingSpan(paint, lineBreakConfig, null /* spans */, start, end, - builder); + mt.applyMetricsAffectingSpan(paint, lineBreakConfig, null /* spans */, null, + start, end, builder, testCallback); mt.mSpanEndCache.append(end); } else { // There may be a MetricsAffectingSpan. Split into span transitions and apply // styles. int spanEnd; for (int spanStart = start; spanStart < end; spanStart = spanEnd) { - spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, + int maSpanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class); + int lbcSpanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, + LineBreakConfigSpan.class); + spanEnd = Math.min(maSpanEnd, lbcSpanEnd); MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class); + LineBreakConfigSpan[] lbcSpans = mt.mSpanned.getSpans(spanStart, spanEnd, + LineBreakConfigSpan.class); spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class); - // TODO: Update line break config with spans. - mt.applyMetricsAffectingSpan(paint, lineBreakConfig, spans, spanStart, spanEnd, - builder); + lbcSpans = TextUtils.removeEmptySpans(lbcSpans, mt.mSpanned, + LineBreakConfigSpan.class); + mt.applyMetricsAffectingSpan(paint, lineBreakConfig, spans, lbcSpans, spanStart, + spanEnd, builder, testCallback); mt.mSpanEndCache.append(spanEnd); } } @@ -519,7 +629,8 @@ public class MeasuredParagraph { @IntRange(from = 0) int start, // inclusive, in copied buffer @IntRange(from = 0) int end, // exclusive, in copied buffer @NonNull TextPaint paint, - @Nullable MeasuredText.Builder builder) { + @Nullable MeasuredText.Builder builder, + @Nullable StyleRunCallback testCallback) { // Use original text. Shouldn't matter. // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for // backward compatibility? or Should we initialize them for getFontMetricsInt? @@ -535,13 +646,17 @@ public class MeasuredParagraph { } else { builder.appendReplacementRun(paint, end - start, width); } + if (testCallback != null) { + testCallback.onAppendReplacementRun(paint, end - start, width); + } } private void applyStyleRun(@IntRange(from = 0) int start, // inclusive, in copied buffer @IntRange(from = 0) int end, // exclusive, in copied buffer @NonNull TextPaint paint, @Nullable LineBreakConfig config, - @Nullable MeasuredText.Builder builder) { + @Nullable MeasuredText.Builder builder, + @Nullable StyleRunCallback testCallback) { if (mLtrWithoutBidi) { // If the whole text is LTR direction, just apply whole region. @@ -552,6 +667,9 @@ public class MeasuredParagraph { } else { builder.appendStyleRun(paint, config, end - start, false /* isRtl */); } + if (testCallback != null) { + testCallback.onAppendStyleRun(paint, config, end - start, false); + } } else { // If there is multiple bidi levels, split into individual bidi level and apply style. byte level = mLevels.get(start); @@ -568,6 +686,9 @@ public class MeasuredParagraph { } else { builder.appendStyleRun(paint, config, levelEnd - levelStart, isRtl); } + if (testCallback != null) { + testCallback.onAppendStyleRun(paint, config, levelEnd - levelStart, isRtl); + } if (levelEnd == end) { break; } @@ -582,9 +703,11 @@ public class MeasuredParagraph { @NonNull TextPaint paint, @Nullable LineBreakConfig lineBreakConfig, @Nullable MetricAffectingSpan[] spans, + @Nullable LineBreakConfigSpan[] lbcSpans, @IntRange(from = 0) int start, // inclusive, in original text buffer @IntRange(from = 0) int end, // exclusive, in original text buffer - @Nullable MeasuredText.Builder builder) { + @Nullable MeasuredText.Builder builder, + @Nullable StyleRunCallback testCallback) { mCachedPaint.set(paint); // XXX paint should not have a baseline shift, but... mCachedPaint.baselineShift = 0; @@ -609,6 +732,14 @@ public class MeasuredParagraph { } } + if (lbcSpans != null) { + mLineBreakConfigBuilder.reset(lineBreakConfig); + for (LineBreakConfigSpan lbcSpan : lbcSpans) { + mLineBreakConfigBuilder.merge(lbcSpan.getLineBreakConfig()); + } + lineBreakConfig = mLineBreakConfigBuilder.build(); + } + final int startInCopiedBuffer = start - mTextStart; final int endInCopiedBuffer = end - mTextStart; @@ -618,10 +749,10 @@ public class MeasuredParagraph { if (replacement != null) { applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer, mCachedPaint, - builder); + builder, testCallback); } else { applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, mCachedPaint, - lineBreakConfig, builder); + lineBreakConfig, builder, testCallback); } if (needFontMetrics) { @@ -690,6 +821,7 @@ public class MeasuredParagraph { /** * This only works if the MeasuredParagraph is computed with buildForStaticLayout. + * @hide */ public @IntRange(from = 0) int getMemoryUsage() { return mMeasuredText.getMemoryUsage(); diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java index f31a690c7774..fd97801208ac 100644 --- a/core/java/android/text/PrecomputedText.java +++ b/core/java/android/text/PrecomputedText.java @@ -329,22 +329,17 @@ public class PrecomputedText implements Spannable { @Override public int hashCode() { // TODO: implement MinikinPaint::hashCode and use it to keep consistency with equals. - int lineBreakStyle = (mLineBreakConfig != null) - ? mLineBreakConfig.getLineBreakStyle() : LineBreakConfig.LINE_BREAK_STYLE_NONE; return Objects.hash(mPaint.getTextSize(), mPaint.getTextScaleX(), mPaint.getTextSkewX(), mPaint.getLetterSpacing(), mPaint.getWordSpacing(), mPaint.getFlags(), mPaint.getTextLocales(), mPaint.getTypeface(), mPaint.getFontVariationSettings(), mPaint.isElegantTextHeight(), mTextDir, - mBreakStrategy, mHyphenationFrequency, lineBreakStyle); + mBreakStrategy, mHyphenationFrequency, + LineBreakConfig.getResolvedLineBreakStyle(mLineBreakConfig), + LineBreakConfig.getResolvedLineBreakWordStyle(mLineBreakConfig)); } @Override public String toString() { - int lineBreakStyle = (mLineBreakConfig != null) - ? mLineBreakConfig.getLineBreakStyle() : LineBreakConfig.LINE_BREAK_STYLE_NONE; - int lineBreakWordStyle = (mLineBreakConfig != null) - ? mLineBreakConfig.getLineBreakWordStyle() - : LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; return "{" + "textSize=" + mPaint.getTextSize() + ", textScaleX=" + mPaint.getTextScaleX() @@ -357,8 +352,9 @@ public class PrecomputedText implements Spannable { + ", textDir=" + mTextDir + ", breakStrategy=" + mBreakStrategy + ", hyphenationFrequency=" + mHyphenationFrequency - + ", lineBreakStyle=" + lineBreakStyle - + ", lineBreakWordStyle=" + lineBreakWordStyle + + ", lineBreakStyle=" + LineBreakConfig.getResolvedLineBreakStyle(mLineBreakConfig) + + ", lineBreakWordStyle=" + + LineBreakConfig.getResolvedLineBreakWordStyle(mLineBreakConfig) + "}"; } }; diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 6371da4f3776..ab9cff078ac4 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -25,7 +25,6 @@ import android.graphics.Paint; import android.graphics.text.LineBreakConfig; import android.graphics.text.LineBreaker; import android.os.Build; -import android.os.SystemProperties; import android.text.style.LeadingMarginSpan; import android.text.style.LeadingMarginSpan.LeadingMarginSpan2; import android.text.style.LineHeightSpan; @@ -33,7 +32,6 @@ import android.text.style.TabStopSpan; import android.util.Log; import android.util.Pools.SynchronizedPool; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; @@ -75,13 +73,6 @@ public class StaticLayout extends Layout { * default values. */ public final static class Builder { - // The content length threshold to enable LINE_BREAK_WORD_STYLE_PHRASE. - private static final int DEFAULT_LINECOUNT_THRESHOLD_FOR_PHRASE = 3; - - // The property of content length threshold to enable LINE_BREAK_WORD_STYLE_PHRASE. - private static final String PROPERTY_LINECOUNT_THRESHOLD_FOR_PHRASE = - "android.phrase.linecount.threshold"; - private Builder() {} /** @@ -440,55 +431,11 @@ public class StaticLayout extends Layout { */ @NonNull public StaticLayout build() { - reviseLineBreakConfig(); StaticLayout result = new StaticLayout(this); Builder.recycle(this); return result; } - private void reviseLineBreakConfig() { - boolean autoPhraseBreaking = mLineBreakConfig.getAutoPhraseBreaking(); - int wordStyle = mLineBreakConfig.getLineBreakWordStyle(); - if (autoPhraseBreaking) { - if (wordStyle != LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) { - if (shouldEnablePhraseBreaking()) { - mLineBreakConfig = LineBreakConfig.getLineBreakConfig( - mLineBreakConfig.getLineBreakStyle(), - LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE, - mLineBreakConfig.getAutoPhraseBreaking()); - } - } - } - } - - private boolean shouldEnablePhraseBreaking() { - if (TextUtils.isEmpty(mText) || mWidth <= 0) { - return false; - } - int lineLimit = SystemProperties.getInt( - PROPERTY_LINECOUNT_THRESHOLD_FOR_PHRASE, - DEFAULT_LINECOUNT_THRESHOLD_FOR_PHRASE); - double desiredWidth = (double) Layout.getDesiredWidth(mText, mStart, - mEnd, mPaint, mTextDir); - int lineCount = (int) Math.ceil(desiredWidth / mWidth); - if (lineCount > 0 && lineCount <= lineLimit) { - return true; - } - return false; - } - - /** - * Get the line break word style. - * - * @return The current line break word style. - * - * @hide - */ - @VisibleForTesting - public int getLineBreakWordStyle() { - return mLineBreakConfig.getLineBreakWordStyle(); - } - private CharSequence mText; private int mStart; private int mEnd; diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java index 511c9746c84c..518a5498d6ed 100644 --- a/core/java/android/text/format/DateUtils.java +++ b/core/java/android/text/format/DateUtils.java @@ -743,7 +743,7 @@ public class DateUtils * @param startMillis the start time in UTC milliseconds * @param endMillis the end time in UTC milliseconds * @param flags a bit mask of options - * @param timeZone the time zone to compute the string in. Use null for local + * @param timeZone the id of the time zone to compute the string in. Use null for local * or if the FORMAT_UTC flag is being used. * * @return the formatter with the formatted date/time range appended to the string buffer. diff --git a/core/java/android/text/style/LineBreakConfigSpan.java b/core/java/android/text/style/LineBreakConfigSpan.java new file mode 100644 index 000000000000..90a79c6b3f94 --- /dev/null +++ b/core/java/android/text/style/LineBreakConfigSpan.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.text.style; + +import android.annotation.NonNull; +import android.graphics.text.LineBreakConfig; + +import java.util.Objects; + +/** + * LineBreakSpan for changing line break style of the specific region of the text. + */ +public class LineBreakConfigSpan { + private final LineBreakConfig mLineBreakConfig; + + /** + * Construct a new {@link LineBreakConfigSpan} + * @param lineBreakConfig a line break config + */ + public LineBreakConfigSpan(@NonNull LineBreakConfig lineBreakConfig) { + mLineBreakConfig = lineBreakConfig; + } + + /** + * Gets an associated line break config. + * @return associated line break config. + */ + public @NonNull LineBreakConfig getLineBreakConfig() { + return mLineBreakConfig; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof LineBreakConfigSpan)) return false; + LineBreakConfigSpan that = (LineBreakConfigSpan) o; + return Objects.equals(mLineBreakConfig, that.mLineBreakConfig); + } + + @Override + public int hashCode() { + return Objects.hash(mLineBreakConfig); + } + + @Override + public String toString() { + return "LineBreakConfigSpan{mLineBreakConfig=" + mLineBreakConfig + '}'; + } +} diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index ff7d8bb956ca..827600c83fae 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -86,9 +86,6 @@ public class FeatureFlagUtils { public static final String SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST = "settings_need_connected_ble_device_for_broadcast"; - /** @hide */ - public static final String SETTINGS_AUTO_TEXT_WRAPPING = "settings_auto_text_wrapping"; - /** * Enable new language and keyboard settings UI * @hide @@ -152,6 +149,13 @@ public class FeatureFlagUtils { */ public static final String SETTINGS_BIOMETRICS2_ENROLLMENT = "settings_biometrics2_enrollment"; + /** + * Flag to enable/disable FingerprintSettings v2 + * @hide + */ + public static final String SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS = + "settings_biometrics2_fingerprint"; + /** Flag to enable/disable entire page in Accessibility -> Hearing aids * @hide */ @@ -225,7 +229,6 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true"); DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true"); DEFAULT_FLAGS.put(SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, "true"); - DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false"); DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "true"); DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "true"); DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "true"); @@ -243,6 +246,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_SHOW_UDFPS_ENROLL_IN_SETTINGS, "true"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_LOCKSCREEN_TRANSFER_API, "true"); DEFAULT_FLAGS.put(SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, "true"); + DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false"); } private static final Set<String> PERSISTENT_FLAGS; @@ -253,7 +257,6 @@ public class FeatureFlagUtils { PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN); PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS); PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME); - PERSISTENT_FLAGS.add(SETTINGS_AUTO_TEXT_WRAPPING); PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_UI); PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY); PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD); diff --git a/core/java/android/util/LongSparseArray.java b/core/java/android/util/LongSparseArray.java index eefb15604bbc..2f14b2537cf9 100644 --- a/core/java/android/util/LongSparseArray.java +++ b/core/java/android/util/LongSparseArray.java @@ -209,6 +209,39 @@ public class LongSparseArray<E> implements Cloneable { } /** + * Returns the index of the first element whose key is greater than or equal to the given key. + * + * @param key The key for which to search the array. + * @return The smallest {@code index} for which {@code (keyAt(index) >= key)} is + * {@code true}, or {@link #size() size} if no such {@code index} exists. + * @hide + */ + public int firstIndexOnOrAfter(long key) { + if (mGarbage) { + gc(); + } + final int index = Arrays.binarySearch(mKeys, 0, size(), key); + return (index >= 0) ? index : -index - 1; + } + + /** + * Returns the index of the last element whose key is less than or equal to the given key. + * + * @param key The key for which to search the array. + * @return The largest {@code index} for which {@code (keyAt(index) <= key)} is + * {@code true}, or {@code -1} if no such {@code index} exists. + * @hide + */ + public int lastIndexOnOrBefore(long key) { + final int index = firstIndexOnOrAfter(key); + + if (index < size() && keyAt(index) == key) { + return index; + } + return index - 1; + } + + /** * Adds a mapping from the specified key to the specified value, * replacing the previous mapping from the specified key if there * was one. diff --git a/core/java/android/util/TimeSparseArray.java b/core/java/android/util/TimeSparseArray.java deleted file mode 100644 index 1f49fa24a64a..000000000000 --- a/core/java/android/util/TimeSparseArray.java +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy - * of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ - -package android.util; - -import java.util.Objects; - -/** - * An array that indexes by a long timestamp, representing milliseconds since the epoch. - * @param <E> The type of values this container maps to a timestamp. - * - * {@hide} - */ -public class TimeSparseArray<E> extends LongSparseArray<E> { - private static final String TAG = TimeSparseArray.class.getSimpleName(); - - private boolean mWtfReported; - - /** - * Finds the index of the first element whose timestamp is greater or equal to - * the given time. - * - * @param time The timestamp for which to search the array. - * @return The smallest {@code index} for which {@code (keyAt(index) >= timeStamp)} is - * {@code true}, or {@link #size() size} if no such {@code index} exists. - */ - public int closestIndexOnOrAfter(long time) { - final int size = size(); - int result = size; - int lo = 0; - int hi = size - 1; - while (lo <= hi) { - final int mid = lo + ((hi - lo) / 2); - final long key = keyAt(mid); - - if (time > key) { - lo = mid + 1; - } else if (time < key) { - hi = mid - 1; - result = mid; - } else { - return mid; - } - } - return result; - } - - /** - * {@inheritDoc} - * - * <p> This container can store only one value for each timestamp. And so ideally, the caller - * should ensure that there are no collisions. Reporting a {@link Slog#wtf(String, String)} - * if that happens, as that will lead to the previous value being overwritten. - */ - @Override - public void put(long key, E value) { - final int index = indexOfKey(key); - if (index >= 0) { - final E curValue = valueAt(index); - if (Objects.equals(curValue, value)) { - Log.w(TAG, "Overwriting value at " + key + " by equal value " + value); - } else if (!mWtfReported) { - Slog.wtf(TAG, "Overwriting value " + curValue + " by " + value + " at " + key); - mWtfReported = true; - } - } - super.put(key, value); - } - - /** - * Finds the index of the first element whose timestamp is less than or equal to - * the given time. - * - * @param time The timestamp for which to search the array. - * @return The largest {@code index} for which {@code (keyAt(index) <= timeStamp)} is - * {@code true}, or -1 if no such {@code index} exists. - */ - public int closestIndexOnOrBefore(long time) { - final int index = closestIndexOnOrAfter(time); - - if (index < size() && keyAt(index) == time) { - return index; - } - return index - 1; - } -} diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 6901b7218741..3e615394f7de 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -1968,21 +1968,42 @@ public final class AccessibilityInteractionController { } /** Attaches an accessibility overlay to the specified window. */ - public void attachAccessibilityOverlayToWindowClientThread(SurfaceControl sc) { + public void attachAccessibilityOverlayToWindowClientThread( + SurfaceControl sc, + int interactionId, + IAccessibilityInteractionConnectionCallback callback) { mHandler.sendMessage( obtainMessage( AccessibilityInteractionController ::attachAccessibilityOverlayToWindowUiThread, this, - sc)); + sc, + interactionId, + callback)); } - private void attachAccessibilityOverlayToWindowUiThread(SurfaceControl sc) { + private void attachAccessibilityOverlayToWindowUiThread( + SurfaceControl sc, + int interactionId, + IAccessibilityInteractionConnectionCallback callback) { SurfaceControl parent = mViewRootImpl.getSurfaceControl(); - if (parent.isValid()) { - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - t.reparent(sc, parent).apply(); - t.close(); + if (!parent.isValid()) { + try { + callback.sendAttachOverlayResult( + AccessibilityService.OVERLAY_RESULT_INTERNAL_ERROR, interactionId); + return; + } catch (RemoteException re) { + /* ignore - the other side will time out */ + } + } + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.reparent(sc, parent).apply(); + t.close(); + try { + callback.sendAttachOverlayResult( + AccessibilityService.OVERLAY_RESULT_SUCCESS, interactionId); + } catch (RemoteException re) { + /* ignore - the other side will time out */ } } } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index a9a5888207aa..072a7f5ea304 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -93,6 +93,11 @@ interface IWindowManager * Only use {@link DisplayRotation#mUserRotation} as the display rotation. */ const int FIXED_TO_USER_ROTATION_ENABLED = 2; + /** + * If auto-rotation is not supported, {@link DisplayRotation#mUserRotation} will be used. + * Otherwise the behavior is same as {link #FIXED_TO_USER_ROTATION_DISABLED}. + */ + const int FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION = 3; /** * ===== NOTICE ===== diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 5019b85ca503..c1eacb535d57 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -797,7 +797,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } WindowInsets insets = state.calculateInsets(mFrame, mState /* ignoringVisibilityState*/, - mLastInsets.isRound(), mLastInsets.shouldAlwaysConsumeSystemBars(), + mLastInsets.isRound(), false /* alwaysConsumeSystemBars */, mLastLegacySoftInputMode, mLastLegacyWindowFlags, mLastLegacySystemUiFlags, mWindowType, mLastWindowingMode, null /* idSideMap */); mHost.dispatchWindowInsetsAnimationProgress(insets, @@ -841,7 +841,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return mLastDispatchedState; } - @VisibleForTesting public boolean onStateChanged(InsetsState state) { boolean stateChanged = false; if (!CAPTION_ON_SHELL) { diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index e10184976abe..64411866f020 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -68,10 +68,16 @@ public class InsetsSource implements Parcelable { */ public static final int FLAG_INSETS_ROUNDED_CORNER = 1 << 1; + /** + * Controls whether the insets provided by this source should be forcibly consumed. + */ + public static final int FLAG_FORCE_CONSUMING = 1 << 2; + @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = "FLAG_", value = { FLAG_SUPPRESS_SCRIM, FLAG_INSETS_ROUNDED_CORNER, + FLAG_FORCE_CONSUMING, }) public @interface Flags {} @@ -328,6 +334,9 @@ public class InsetsSource implements Parcelable { if ((flags & FLAG_INSETS_ROUNDED_CORNER) != 0) { joiner.add("INSETS_ROUNDED_CORNER"); } + if ((flags & FLAG_FORCE_CONSUMING) != 0) { + joiner.add("FORCE_CONSUMING"); + } return joiner.toString(); } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index dceae90822b8..c13b9ab0abd1 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -16,6 +16,7 @@ package android.view; +import static android.view.InsetsSource.FLAG_FORCE_CONSUMING; import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER; import static android.view.InsetsStateProto.DISPLAY_CUTOUT; import static android.view.InsetsStateProto.DISPLAY_FRAME; @@ -144,12 +145,18 @@ public class InsetsState implements Parcelable { boolean[] typeVisibilityMap = new boolean[Type.SIZE]; final Rect relativeFrame = new Rect(frame); final Rect relativeFrameMax = new Rect(frame); + @InsetsType int forceConsumingTypes = 0; @InsetsType int suppressScrimTypes = 0; for (int i = mSources.size() - 1; i >= 0; i--) { final InsetsSource source = mSources.valueAt(i); + final @InsetsType int type = source.getType(); + + if ((source.getFlags() & InsetsSource.FLAG_FORCE_CONSUMING) != 0) { + forceConsumingTypes |= type; + } if ((source.getFlags() & InsetsSource.FLAG_SUPPRESS_SCRIM) != 0) { - suppressScrimTypes |= source.getType(); + suppressScrimTypes |= type; } processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap, @@ -157,7 +164,7 @@ public class InsetsState implements Parcelable { // IME won't be reported in max insets as the size depends on the EditorInfo of the IME // target. - if (source.getType() != WindowInsets.Type.ime()) { + if (type != WindowInsets.Type.ime()) { InsetsSource ignoringVisibilitySource = ignoringVisibilityState != null ? ignoringVisibilityState.peekSource(source.getId()) : source; @@ -178,13 +185,13 @@ public class InsetsState implements Parcelable { if ((legacyWindowFlags & FLAG_FULLSCREEN) != 0) { compatInsetsTypes &= ~statusBars(); } - if (clearsCompatInsets(windowType, legacyWindowFlags, windowingMode) - && !alwaysConsumeSystemBars) { - compatInsetsTypes = 0; + if (clearsCompatInsets(windowType, legacyWindowFlags, windowingMode)) { + // Clear all types but forceConsumingTypes. + compatInsetsTypes &= forceConsumingTypes; } return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound, - alwaysConsumeSystemBars, suppressScrimTypes, calculateRelativeCutout(frame), + forceConsumingTypes, suppressScrimTypes, calculateRelativeCutout(frame), calculateRelativeRoundedCorners(frame), calculateRelativePrivacyIndicatorBounds(frame), calculateRelativeDisplayShape(frame), @@ -290,9 +297,8 @@ public class InsetsState implements Parcelable { public Insets calculateVisibleInsets(Rect frame, int windowType, int windowingMode, @SoftInputModeFlags int softInputMode, int windowFlags) { - if (clearsCompatInsets(windowType, windowFlags, windowingMode)) { - return Insets.NONE; - } + final boolean clearsCompatInsets = clearsCompatInsets( + windowType, windowFlags, windowingMode); final int softInputAdjustMode = softInputMode & SOFT_INPUT_MASK_ADJUST; final int visibleInsetsTypes = softInputAdjustMode != SOFT_INPUT_ADJUST_NOTHING ? systemBars() | ime() @@ -303,6 +309,9 @@ public class InsetsState implements Parcelable { if ((source.getType() & visibleInsetsTypes) == 0) { continue; } + if (clearsCompatInsets && !source.hasFlags(FLAG_FORCE_CONSUMING)) { + continue; + } insets = Insets.max(source.calculateVisibleInsets(frame), insets); } return insets; diff --git a/core/java/android/view/RoundScrollbarRenderer.java b/core/java/android/view/RoundScrollbarRenderer.java index df9e23e427f8..5f6d5e29570e 100644 --- a/core/java/android/view/RoundScrollbarRenderer.java +++ b/core/java/android/view/RoundScrollbarRenderer.java @@ -21,25 +21,37 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; +import android.util.DisplayMetrics; /** * Helper class for drawing round scroll bars on round Wear devices. */ class RoundScrollbarRenderer { // The range of the scrollbar position represented as an angle in degrees. - private static final int SCROLLBAR_ANGLE_RANGE = 90; - private static final int MAX_SCROLLBAR_ANGLE_SWIPE = 16; - private static final int MIN_SCROLLBAR_ANGLE_SWIPE = 6; - private static final float WIDTH_PERCENTAGE = 0.02f; - private static final int DEFAULT_THUMB_COLOR = 0xFFE8EAED; + private static final float SCROLLBAR_ANGLE_RANGE = 28.8f; + private static final float MAX_SCROLLBAR_ANGLE_SWIPE = 26.3f; // 90% + private static final float MIN_SCROLLBAR_ANGLE_SWIPE = 3.1f; // 10% + private static final float THUMB_WIDTH_DP = 4f; + private static final float OUTER_PADDING_DP = 2f; + private static final int DEFAULT_THUMB_COLOR = 0xFFFFFFFF; private static final int DEFAULT_TRACK_COLOR = 0x4CFFFFFF; + // Rate at which the scrollbar will resize itself when the size of the view changes + private static final float RESIZING_RATE = 0.8f; + // Threshold at which the scrollbar will stop resizing smoothly and jump to the correct size + private static final int RESIZING_THRESHOLD_PX = 20; + private final Paint mThumbPaint = new Paint(); private final Paint mTrackPaint = new Paint(); private final RectF mRect = new RectF(); private final View mParent; private final int mMaskThickness; + private float mPreviousMaxScroll = 0; + private float mMaxScrollDiff = 0; + private float mPreviousCurrentScroll = 0; + private float mCurrentScrollDiff = 0; + public RoundScrollbarRenderer(View parent) { // Paints for the round scrollbar. // Set up the thumb paint @@ -61,19 +73,50 @@ class RoundScrollbarRenderer { com.android.internal.R.dimen.circular_display_mask_thickness); } - public void drawRoundScrollbars(Canvas canvas, float alpha, Rect bounds) { + public void drawRoundScrollbars(Canvas canvas, float alpha, Rect bounds, boolean drawToLeft) { if (alpha == 0) { return; } // Get information about the current scroll state of the parent view. float maxScroll = mParent.computeVerticalScrollRange(); float scrollExtent = mParent.computeVerticalScrollExtent(); - if (scrollExtent <= 0 || maxScroll <= scrollExtent) { + float newScroll = mParent.computeVerticalScrollOffset(); + + if (scrollExtent <= 0) { + if (!mParent.canScrollVertically(1) && !mParent.canScrollVertically(-1)) { + return; + } else { + scrollExtent = 0; + } + } else if (maxScroll <= scrollExtent) { return; } - float currentScroll = Math.max(0, mParent.computeVerticalScrollOffset()); - float linearThumbLength = mParent.computeVerticalScrollExtent(); - float thumbWidth = mParent.getWidth() * WIDTH_PERCENTAGE; + + // Make changes to the VerticalScrollRange happen gradually + if (Math.abs(maxScroll - mPreviousMaxScroll) > RESIZING_THRESHOLD_PX + && mPreviousMaxScroll != 0) { + mMaxScrollDiff += maxScroll - mPreviousMaxScroll; + mCurrentScrollDiff += newScroll - mPreviousCurrentScroll; + } + + mPreviousMaxScroll = maxScroll; + mPreviousCurrentScroll = newScroll; + + if (Math.abs(mMaxScrollDiff) > RESIZING_THRESHOLD_PX + || Math.abs(mCurrentScrollDiff) > RESIZING_THRESHOLD_PX) { + mMaxScrollDiff *= RESIZING_RATE; + mCurrentScrollDiff *= RESIZING_RATE; + + maxScroll -= mMaxScrollDiff; + newScroll -= mCurrentScrollDiff; + } else { + mMaxScrollDiff = 0; + mCurrentScrollDiff = 0; + } + + float currentScroll = Math.max(0, newScroll); + float linearThumbLength = scrollExtent; + float thumbWidth = dpToPx(THUMB_WIDTH_DP); mThumbPaint.setStrokeWidth(thumbWidth); mTrackPaint.setStrokeWidth(thumbWidth); @@ -85,9 +128,9 @@ class RoundScrollbarRenderer { sweepAngle = clamp(sweepAngle, MIN_SCROLLBAR_ANGLE_SWIPE, MAX_SCROLLBAR_ANGLE_SWIPE); // Normalize the start angle so that it falls on the track. float startAngle = (currentScroll * (SCROLLBAR_ANGLE_RANGE - sweepAngle)) - / (maxScroll - linearThumbLength) - SCROLLBAR_ANGLE_RANGE / 2; - startAngle = clamp(startAngle, -SCROLLBAR_ANGLE_RANGE / 2, - SCROLLBAR_ANGLE_RANGE / 2 - sweepAngle); + / (maxScroll - linearThumbLength) - SCROLLBAR_ANGLE_RANGE / 2f; + startAngle = clamp(startAngle, -SCROLLBAR_ANGLE_RANGE / 2f, + SCROLLBAR_ANGLE_RANGE / 2f - sweepAngle); // Draw the track and the thumb. float inset = thumbWidth / 2 + mMaskThickness; @@ -96,9 +139,26 @@ class RoundScrollbarRenderer { bounds.top + inset, bounds.right - inset, bounds.bottom - inset); - canvas.drawArc(mRect, -SCROLLBAR_ANGLE_RANGE / 2, SCROLLBAR_ANGLE_RANGE, false, - mTrackPaint); - canvas.drawArc(mRect, startAngle, sweepAngle, false, mThumbPaint); + + if (drawToLeft) { + canvas.drawArc(mRect, 180 + SCROLLBAR_ANGLE_RANGE / 2f, -SCROLLBAR_ANGLE_RANGE, false, + mTrackPaint); + canvas.drawArc(mRect, 180 - startAngle, -sweepAngle, false, mThumbPaint); + } else { + canvas.drawArc(mRect, -SCROLLBAR_ANGLE_RANGE / 2f, SCROLLBAR_ANGLE_RANGE, false, + mTrackPaint); + canvas.drawArc(mRect, startAngle, sweepAngle, false, mThumbPaint); + } + } + + void getRoundVerticalScrollBarBounds(Rect bounds) { + float padding = dpToPx(OUTER_PADDING_DP); + final int width = mParent.mRight - mParent.mLeft; + final int height = mParent.mBottom - mParent.mTop; + bounds.left = mParent.mScrollX + (int) padding; + bounds.top = mParent.mScrollY + (int) padding; + bounds.right = mParent.mScrollX + width - (int) padding; + bounds.bottom = mParent.mScrollY + height - (int) padding; } private static float clamp(float val, float min, float max) { @@ -127,4 +187,9 @@ class RoundScrollbarRenderer { mTrackPaint.setColor(trackColor); } } + + private float dpToPx(float dp) { + return dp * ((float) mParent.getContext().getResources().getDisplayMetrics().densityDpi) + / DisplayMetrics.DENSITY_DEFAULT; + } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 71e9052627b9..565ed61ce30d 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -8662,15 +8662,43 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Dispatches an {@link AccessibilityEvent} to the {@link View} first and then - * to its children for adding their text content to the event. Note that the - * event text is populated in a separate dispatch path since we add to the + * Dispatches an {@link AccessibilityEvent} to the {@link View} to add the text content of the + * view and its children. + * <p> + * <b>Note:</b> This method should only be used with event.setText(). + * Avoid mutating other event state in this method. In general, put UI metadata in the node for + * services to easily query. + * <ul> + * <li> If you are modifying other event properties, you may be eliminating semantics + * accessibility services may want. Instead, send a separate event using + * {@link #sendAccessibilityEvent(int)} and override + * {@link #onInitializeAccessibilityEvent(AccessibilityEvent)}. + * </li> + * <li>If you are checking for type {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED} + * to generate window/title announcements, you may be causing disruptive announcements + * (or making no announcements at all). Instead, follow the practices described in + * {@link View#announceForAccessibility(CharSequence)}. <b>Note:</b> this does not suggest + * calling announceForAccessibility(), but using the suggestions listed in its + * documentation. + * </li> + * <li>If you are making changes based on the state of accessibility, such as checking for + * an event type to trigger a UI update, while well-intentioned, you are creating brittle, + * less well-maintained code that works for some users but not others. Instead, leverage + * existing code for equitable experiences and less technical debt. See + * {@link AccessibilityManager#isEnabled()} for an example. + * </li> + * </ul> + * <p> + * Note that the event text is populated in a separate dispatch path + * ({@link #onPopulateAccessibilityEvent(AccessibilityEvent)}) since we add to the * event not only the text of the source but also the text of all its descendants. + * <p> * A typical implementation will call - * {@link #onPopulateAccessibilityEvent(AccessibilityEvent)} on the this view + * {@link #onPopulateAccessibilityEvent(AccessibilityEvent)} on this view * and then call the {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)} - * on each child. Override this method if custom population of the event text - * content is required. + * on each child or the first child that is visible. Override this method if custom population + * of the event text content is required. + * * <p> * If an {@link AccessibilityDelegate} has been specified via calling * {@link #setAccessibilityDelegate(AccessibilityDelegate)} its @@ -8714,9 +8742,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Called from {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)} * giving a chance to this View to populate the accessibility event with its - * text content. While this method is free to modify event - * attributes other than text content, doing so should normally be performed in - * {@link #onInitializeAccessibilityEvent(AccessibilityEvent)}. + * text content. + * <p> + * <b>Note:</b> This method should only be used with event.setText(). + * Avoid mutating other event state in this method. Instead, follow the practices described in + * {@link #dispatchPopulateAccessibilityEvent(AccessibilityEvent)}. In general, put UI + * metadata in the node for services to easily query, than in transient events. * <p> * Example: Adding formatted date string to an accessibility event in addition * to the text added by the super implementation: @@ -11256,26 +11287,36 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Sets the id of a view before which this one is visited in accessibility traversal. - * A screen-reader must visit the content of this view before the content of the one - * it precedes. For example, if view B is set to be before view A, then a screen-reader - * will traverse the entire content of B before traversing the entire content of A, - * regardles of what traversal strategy it is using. + * Sets the id of a view that screen readers are requested to visit after this view. + * * <p> - * Views that do not have specified before/after relationships are traversed in order - * determined by the screen-reader. - * </p> + * + * For example, if view B should be visited before view A, with + * B.setAccessibilityTraversalBefore(A), this requests that screen readers visit and traverse + * view B before visiting view A. + * * <p> - * Setting that this view is before a view that is not important for accessibility - * or if this view is not important for accessibility will have no effect as the - * screen-reader is not aware of unimportant views. - * </p> + * <b>Note:</b> Views are visited in the order determined by the screen reader. Avoid + * explicitly manipulating focus order, as this may result in inconsistent user + * experiences between apps. Instead, use other semantics, such as restructuring the view + * hierarchy layout, to communicate order. + * + * <p> + * Setting this view to be after a view that is not important for accessibility, + * or if this view is not important for accessibility, means this method will have no effect if + * the service is not aware of unimportant views. + * + * <p> + * To avoid a risk of loops, set clear relationships between views. For example, if focus order + * should be B -> A, and B.setAccessibilityTraversalBefore(A), then also call + * A.setAccessibilityTraversalAfter(B). * * @param beforeId The id of a view this one precedes in accessibility traversal. * * @attr ref android.R.styleable#View_accessibilityTraversalBefore * * @see #setImportantForAccessibility(int) + * @see #setAccessibilityTraversalAfter(int) */ @RemotableViewMethod public void setAccessibilityTraversalBefore(@IdRes int beforeId) { @@ -11302,26 +11343,34 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Sets the id of a view after which this one is visited in accessibility traversal. - * A screen-reader must visit the content of the other view before the content of this - * one. For example, if view B is set to be after view A, then a screen-reader - * will traverse the entire content of A before traversing the entire content of B, - * regardles of what traversal strategy it is using. + * Sets the id of a view that screen readers are requested to visit before this view. + * * <p> - * Views that do not have specified before/after relationships are traversed in order - * determined by the screen-reader. - * </p> + * For example, if view B should be visited after A, with B.setAccessibilityTraversalAfter(A), + * then this requests that screen readers visit and traverse view A before visiting view B. + * * <p> - * Setting that this view is after a view that is not important for accessibility - * or if this view is not important for accessibility will have no effect as the - * screen-reader is not aware of unimportant views. - * </p> + * <b>Note:</b> Views are visited in the order determined by the screen reader. Avoid + * explicitly manipulating focus order, as this may result in inconsistent user + * experiences between apps. Instead, use other semantics, such as restructuring the view + * hierarchy layout, to communicate order. * - * @param afterId The id of a view this one succedees in accessibility traversal. + * <p> + * Setting this view to be after a view that is not important for accessibility, + * or if this view is not important for accessibility, means this method will have no effect if + * the service is not aware of unimportant views. + * + * <p> + * To avoid a risk of loops, set clear relationships between views. For example, if focus order + * should be B -> A, and B.setAccessibilityTraversalBefore(A), then also call + * A.setAccessibilityTraversalAfter(B). + * + * @param afterId The id of a view this one succeeds in accessibility traversal. * * @attr ref android.R.styleable#View_accessibilityTraversalAfter * * @see #setImportantForAccessibility(int) + * @see #setAccessibilityTraversalBefore(int) */ @RemotableViewMethod public void setAccessibilityTraversalAfter(@IdRes int afterId) { @@ -14619,19 +14668,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * to the view's content description or text, or to the content descriptions * or text of the view's children (where applicable). * <p> - * For example, in a login screen with a TextView that displays an "incorrect - * password" notification, that view should be marked as a live region with - * mode {@link #ACCESSIBILITY_LIVE_REGION_POLITE}. + * To indicate that the user should be notified of changes, use + * {@link #ACCESSIBILITY_LIVE_REGION_POLITE}. Announcements from this region are queued and + * do not disrupt ongoing speech. + * <p> + * For example, selecting an option in a dropdown menu may update a panel below with the updated + * content. This panel may be marked as a live region with + * {@link #ACCESSIBILITY_LIVE_REGION_POLITE} to notify users of the change. + * <p> + * For notifying users about errors, such as in a login screen with text that displays an + * "incorrect password" notification, that view should send an AccessibilityEvent of type + * {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR} and set + * {@link AccessibilityNodeInfo#setError(CharSequence)} instead. Custom widgets should expose + * error-setting methods that support accessibility automatically. For example, instead of + * explicitly sending this event when using a TextView, use + * {@link android.widget.TextView#setError(CharSequence)}. * <p> * To disable change notifications for this view, use * {@link #ACCESSIBILITY_LIVE_REGION_NONE}. This is the default live region * mode for most views. * <p> - * To indicate that the user should be notified of changes, use - * {@link #ACCESSIBILITY_LIVE_REGION_POLITE}. - * <p> * If the view's changes should interrupt ongoing speech and notify the user - * immediately, use {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}. + * immediately, use {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}. This may result in disruptive + * announcements from an accessibility service, so it should generally be used only to convey + * information that is time-sensitive or critical for use of the application. Examples may + * include an incoming call or an emergency alert. * <p> * <b>Note:</b> Use {@link androidx.core.view.ViewCompat#setAccessibilityLiveRegion(View, int)} * for backwards-compatibility. </aside> @@ -21190,21 +21251,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (mRoundScrollbarRenderer == null) { getStraightVerticalScrollBarBounds(bounds, touchBounds); } else { - getRoundVerticalScrollBarBounds(bounds != null ? bounds : touchBounds); + mRoundScrollbarRenderer.getRoundVerticalScrollBarBounds( + bounds != null ? bounds : touchBounds); } } - private void getRoundVerticalScrollBarBounds(Rect bounds) { - final int width = mRight - mLeft; - final int height = mBottom - mTop; - // Do not take padding into account as we always want the scrollbars - // to hug the screen for round wearable devices. - bounds.left = mScrollX; - bounds.top = mScrollY; - bounds.right = bounds.left + width; - bounds.bottom = mScrollY + height; - } - private void getStraightVerticalScrollBarBounds(@Nullable Rect drawBounds, @Nullable Rect touchBounds) { final Rect bounds = drawBounds != null ? drawBounds : touchBounds; @@ -21314,8 +21365,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (drawVerticalScrollBar) { final Rect bounds = cache.mScrollBarBounds; getVerticalScrollBarBounds(bounds, null); + boolean shouldDrawScrollbarAtLeft = + (mVerticalScrollbarPosition == SCROLLBAR_POSITION_LEFT) + || (mVerticalScrollbarPosition == SCROLLBAR_POSITION_DEFAULT + && isLayoutRtl()); + mRoundScrollbarRenderer.drawRoundScrollbars( - canvas, (float) cache.scrollBar.getAlpha() / 255f, bounds); + canvas, (float) cache.scrollBar.getAlpha() / 255f, bounds, + shouldDrawScrollbarAtLeft); if (invalidate) { invalidate(); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 5eaf089b4caa..1d90270bf73f 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -3859,7 +3859,15 @@ public final class ViewRootImpl implements ViewParent, mWmsRequestSyncGroupState = WMS_SYNC_PENDING; mWmsRequestSyncGroup = new SurfaceSyncGroup("wmsSync-" + mTag, t -> { mWmsRequestSyncGroupState = WMS_SYNC_MERGED; - reportDrawFinished(t, seqId); + // See b/286355097. If the current process is not system, then invoking finishDraw on + // any thread is fine since once it calls into system process, finishDrawing will run + // on a different thread. However, when the current process is system, the finishDraw in + // system server will be run on the current thread, which could result in a deadlock. + if (mWindowSession instanceof Binder) { + reportDrawFinished(t, seqId); + } else { + mHandler.postAtFrontOfQueue(() -> reportDrawFinished(t, seqId)); + } }); if (DEBUG_BLAST) { Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName()); @@ -10903,12 +10911,16 @@ public final class ViewRootImpl implements ViewParent, } } - public void attachAccessibilityOverlayToWindow(SurfaceControl sc) { + public void attachAccessibilityOverlayToWindow( + SurfaceControl sc, + int interactionId, + IAccessibilityInteractionConnectionCallback callback) { ViewRootImpl viewRootImpl = mViewRootImpl.get(); if (viewRootImpl != null) { viewRootImpl .getAccessibilityInteractionController() - .attachAccessibilityOverlayToWindowClientThread(sc); + .attachAccessibilityOverlayToWindowClientThread( + sc, interactionId, callback); } } } diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 4acaea849586..57a41619ff8d 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -84,13 +84,7 @@ public final class WindowInsets { @Nullable private final PrivacyIndicatorBounds mPrivacyIndicatorBounds; @Nullable private final DisplayShape mDisplayShape; - /** - * In multi-window we force show the navigation bar. Because we don't want that the surface size - * changes in this mode, we instead have a flag whether the navigation bar size should always - * be consumed, so the app is treated like there is no virtual navigation bar at all. - */ - private final boolean mAlwaysConsumeSystemBars; - + private final @InsetsType int mForceConsumingTypes; private final @InsetsType int mSuppressScrimTypes; private final boolean mSystemWindowInsetsConsumed; private final boolean mStableInsetsConsumed; @@ -117,7 +111,7 @@ public final class WindowInsets { static { CONSUMED = new WindowInsets(createCompatTypeMap(null), createCompatTypeMap(null), - createCompatVisibilityMap(createCompatTypeMap(null)), false, false, 0, null, + createCompatVisibilityMap(createCompatTypeMap(null)), false, 0, 0, null, null, null, null, systemBars(), false); } @@ -137,7 +131,8 @@ public final class WindowInsets { @Nullable Insets[] typeMaxInsetsMap, boolean[] typeVisibilityMap, boolean isRound, - boolean alwaysConsumeSystemBars, @InsetsType int suppressScrimTypes, + @InsetsType int forceConsumingTypes, + @InsetsType int suppressScrimTypes, DisplayCutout displayCutout, RoundedCorners roundedCorners, PrivacyIndicatorBounds privacyIndicatorBounds, @@ -155,7 +150,7 @@ public final class WindowInsets { mTypeVisibilityMap = typeVisibilityMap; mIsRound = isRound; - mAlwaysConsumeSystemBars = alwaysConsumeSystemBars; + mForceConsumingTypes = forceConsumingTypes; mSuppressScrimTypes = suppressScrimTypes; mCompatInsetsTypes = compatInsetsTypes; mCompatIgnoreVisibility = compatIgnoreVisibility; @@ -178,7 +173,7 @@ public final class WindowInsets { this(src.mSystemWindowInsetsConsumed ? null : src.mTypeInsetsMap, src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap, src.mTypeVisibilityMap, src.mIsRound, - src.mAlwaysConsumeSystemBars, src.mSuppressScrimTypes, + src.mForceConsumingTypes, src.mSuppressScrimTypes, displayCutoutCopyConstructorArgument(src), src.mRoundedCorners, src.mPrivacyIndicatorBounds, @@ -235,7 +230,7 @@ public final class WindowInsets { /** @hide */ @UnsupportedAppUsage public WindowInsets(Rect systemWindowInsets) { - this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, 0, + this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, 0, 0, null, null, null, null, systemBars(), false /* compatIgnoreVisibility */); } @@ -556,7 +551,7 @@ public final class WindowInsets { return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap, mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, - mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes, + mIsRound, mForceConsumingTypes, mSuppressScrimTypes, null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, mCompatInsetsTypes, mCompatIgnoreVisibility); } @@ -607,7 +602,7 @@ public final class WindowInsets { public WindowInsets consumeSystemWindowInsets() { return new WindowInsets(null, null, mTypeVisibilityMap, - mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes, + mIsRound, mForceConsumingTypes, mSuppressScrimTypes, // If the system window insets types contain displayCutout, we should also consume // it. (mCompatInsetsTypes & displayCutout()) != 0 @@ -895,8 +890,8 @@ public final class WindowInsets { /** * @hide */ - public boolean shouldAlwaysConsumeSystemBars() { - return mAlwaysConsumeSystemBars; + public @InsetsType int getForceConsumingTypes() { + return mForceConsumingTypes; } /** @@ -930,6 +925,8 @@ public final class WindowInsets { result.append("\n "); result.append(mDisplayShape != null ? "displayShape=" + mDisplayShape : ""); result.append("\n "); + result.append("forceConsumingTypes=" + Type.toString(mForceConsumingTypes)); + result.append("\n "); result.append("suppressScrimTypes=" + Type.toString(mSuppressScrimTypes)); result.append("\n "); result.append("compatInsetsTypes=" + Type.toString(mCompatInsetsTypes)); @@ -1027,7 +1024,7 @@ public final class WindowInsets { ? null : insetInsets(mTypeMaxInsetsMap, left, top, right, bottom), mTypeVisibilityMap, - mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes, + mIsRound, mForceConsumingTypes, mSuppressScrimTypes, mDisplayCutoutConsumed ? null : mDisplayCutout == null @@ -1050,7 +1047,7 @@ public final class WindowInsets { WindowInsets that = (WindowInsets) o; return mIsRound == that.mIsRound - && mAlwaysConsumeSystemBars == that.mAlwaysConsumeSystemBars + && mForceConsumingTypes == that.mForceConsumingTypes && mSuppressScrimTypes == that.mSuppressScrimTypes && mSystemWindowInsetsConsumed == that.mSystemWindowInsetsConsumed && mStableInsetsConsumed == that.mStableInsetsConsumed @@ -1068,7 +1065,7 @@ public final class WindowInsets { public int hashCode() { return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap), Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners, - mAlwaysConsumeSystemBars, mSuppressScrimTypes, mSystemWindowInsetsConsumed, + mForceConsumingTypes, mSuppressScrimTypes, mSystemWindowInsetsConsumed, mStableInsetsConsumed, mDisplayCutoutConsumed, mPrivacyIndicatorBounds, mDisplayShape); } @@ -1134,7 +1131,7 @@ public final class WindowInsets { private DisplayShape mDisplayShape = DisplayShape.NONE; private boolean mIsRound; - private boolean mAlwaysConsumeSystemBars; + private @InsetsType int mForceConsumingTypes; private @InsetsType int mSuppressScrimTypes; private PrivacyIndicatorBounds mPrivacyIndicatorBounds = new PrivacyIndicatorBounds(); @@ -1162,7 +1159,7 @@ public final class WindowInsets { mDisplayCutout = displayCutoutCopyConstructorArgument(insets); mRoundedCorners = insets.mRoundedCorners; mIsRound = insets.mIsRound; - mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars; + mForceConsumingTypes = insets.mForceConsumingTypes; mSuppressScrimTypes = insets.mSuppressScrimTypes; mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds; mDisplayShape = insets.mDisplayShape; @@ -1433,7 +1430,15 @@ public final class WindowInsets { /** @hide */ @NonNull public Builder setAlwaysConsumeSystemBars(boolean alwaysConsumeSystemBars) { - mAlwaysConsumeSystemBars = alwaysConsumeSystemBars; + // TODO (b/277891341): Remove this and related usages. This has been replaced by + // #setForceConsumingTypes. + return this; + } + + /** @hide */ + @NonNull + public Builder setForceConsumingTypes(@InsetsType int forceConsumingTypes) { + mForceConsumingTypes = forceConsumingTypes; return this; } @@ -1453,7 +1458,7 @@ public final class WindowInsets { public WindowInsets build() { return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap, mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, - mIsRound, mAlwaysConsumeSystemBars, mSuppressScrimTypes, mDisplayCutout, + mIsRound, mForceConsumingTypes, mSuppressScrimTypes, mDisplayCutout, mRoundedCorners, mPrivacyIndicatorBounds, mDisplayShape, systemBars(), false /* compatIgnoreVisibility */); } diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index d9fcfb578232..f14ec37ca402 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -62,6 +62,7 @@ import java.util.List; import java.util.Queue; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.IntConsumer; /** * This class is a singleton that performs accessibility interaction @@ -158,11 +159,15 @@ public final class AccessibilityInteractionClient private final SparseArray<Pair<Executor, AccessibilityService.TakeScreenshotCallback>> mTakeScreenshotOfWindowCallbacks = new SparseArray<>(); + // SparseArray of interaction ID -> overlay executor+callback. + private final SparseArray<Pair<Executor, IntConsumer>> mAttachAccessibilityOverlayCallbacks = + new SparseArray<>(); private Message mSameThreadMessage; private int mInteractionIdWaitingForPrefetchResult = -1; private int mConnectionIdWaitingForPrefetchResult; private String[] mPackageNamesForNextPrefetchResult; + private Handler mMainHandler = new Handler(Looper.getMainLooper()); /** * @return The client for the current thread. @@ -830,7 +835,7 @@ public final class AccessibilityInteractionClient interactionId)); connection.takeScreenshotOfWindow(accessibilityWindowId, interactionId, listener, this); - new Handler(Looper.getMainLooper()).postDelayed(() -> { + mMainHandler.postDelayed(() -> { synchronized (mInstanceLock) { // Notify failure if we still haven't sent a response after timeout. if (mTakeScreenshotOfWindowCallbacks.contains(interactionId)) { @@ -1621,7 +1626,11 @@ public final class AccessibilityInteractionClient private void logTraceClient( IAccessibilityServiceConnection connection, String method, String params) { - logTrace(connection, method, params, Binder.getCallingUid(), + logTrace( + connection, + method, + params, + Binder.getCallingUid(), Arrays.asList(Thread.currentThread().getStackTrace()), new HashSet<String>(Arrays.asList("getStackTrace", "logTraceClient")), FLAGS_ACCESSIBILITY_INTERACTION_CLIENT); @@ -1629,18 +1638,105 @@ public final class AccessibilityInteractionClient /** Attaches an accessibility overlay to the specified window. */ public void attachAccessibilityOverlayToWindow( - int connectionId, int accessibilityWindowId, SurfaceControl sc) { + int connectionId, + int accessibilityWindowId, + SurfaceControl sc, + @NonNull @CallbackExecutor Executor executor, + @NonNull IntConsumer callback) { + synchronized (mInstanceLock) { + try { + IAccessibilityServiceConnection connection = getConnection(connectionId); + if (connection == null) { + executor.execute( + () -> + callback.accept( + AccessibilityService.OVERLAY_RESULT_INTERNAL_ERROR)); + return; + } + final int interactionId = mInteractionIdCounter.getAndIncrement(); + mAttachAccessibilityOverlayCallbacks.put( + interactionId, Pair.create(executor, callback)); + connection.attachAccessibilityOverlayToWindow( + interactionId, accessibilityWindowId, sc, this); + mMainHandler.postDelayed( + () -> { + synchronized (mInstanceLock) { + // Notify failure if we still haven't sent a response after timeout. + if (mAttachAccessibilityOverlayCallbacks.contains(interactionId)) { + sendAttachOverlayResult( + AccessibilityService.OVERLAY_RESULT_INTERNAL_ERROR, + interactionId); + } + } + }, + TIMEOUT_INTERACTION_MILLIS); + } catch (RemoteException re) { + re.rethrowFromSystemServer(); + } + } + } + + /** Attaches an accessibility overlay to the specified display. */ + public void attachAccessibilityOverlayToDisplay( + int connectionId, + int displayId, + SurfaceControl sc, + @NonNull @CallbackExecutor Executor executor, + @NonNull IntConsumer callback) { synchronized (mInstanceLock) { try { IAccessibilityServiceConnection connection = getConnection(connectionId); if (connection == null) { - Log.e(LOG_TAG, "Error while getting service connection."); + executor.execute( + () -> + callback.accept( + AccessibilityService.OVERLAY_RESULT_INTERNAL_ERROR)); return; } - connection.attachAccessibilityOverlayToWindow(accessibilityWindowId, sc); + final int interactionId = mInteractionIdCounter.getAndIncrement(); + mAttachAccessibilityOverlayCallbacks.put( + interactionId, Pair.create(executor, callback)); + connection.attachAccessibilityOverlayToDisplay(interactionId, displayId, sc, this); + mMainHandler.postDelayed( + () -> { + // Notify failure if we still haven't sent a response after timeout. + if (mAttachAccessibilityOverlayCallbacks.contains(interactionId)) { + sendAttachOverlayResult( + AccessibilityService.OVERLAY_RESULT_INTERNAL_ERROR, + interactionId); + } + }, + TIMEOUT_INTERACTION_MILLIS); } catch (RemoteException re) { re.rethrowFromSystemServer(); } } } + + /** + * Sends a result code for an attach window overlay request to the requesting client. + * + * @param result The result code from {@link AccessibilityService.OverlayResult}. + * @param interactionId The interaction id of the request. + */ + @Override + public void sendAttachOverlayResult( + @AccessibilityService.AttachOverlayResult int result, int interactionId) { + synchronized (mInstanceLock) { + if (mAttachAccessibilityOverlayCallbacks.contains(interactionId)) { + final Pair<Executor, IntConsumer> pair = + mAttachAccessibilityOverlayCallbacks.get(interactionId); + if (pair == null) { + return; + } + final Executor executor = pair.first; + final IntConsumer callback = pair.second; + if (executor == null || callback == null) { + return; + } + executor.execute(() -> callback.accept(result)); + mAttachAccessibilityOverlayCallbacks.remove(interactionId); + } + } + } } diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java index fa0052cf664a..09b2f9c1ee15 100644 --- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java +++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java @@ -99,6 +99,7 @@ public final class AccessibilityWindowInfo implements Parcelable { /** @hide */ public static final int UNDEFINED_CONNECTION_ID = -1; /** @hide */ + @TestApi public static final int UNDEFINED_WINDOW_ID = -1; /** @hide */ public static final int ANY_WINDOW_ID = -2; diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl index 0eeba7ccf46a..a43acf9147f3 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl @@ -66,5 +66,6 @@ oneway interface IAccessibilityInteractionConnection { void takeScreenshotOfWindow(int interactionId, in ScreenCapture.ScreenCaptureListener listener, IAccessibilityInteractionConnectionCallback callback); - void attachAccessibilityOverlayToWindow(in SurfaceControl sc); + + void attachAccessibilityOverlayToWindow(in SurfaceControl sc, int interactionId, in IAccessibilityInteractionConnectionCallback callback); } diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl index 456bf5893ea0..fe595193047c 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl @@ -68,4 +68,9 @@ oneway interface IAccessibilityInteractionConnectionCallback { * Sends an error code for a window screenshot request to the requesting client. */ void sendTakeScreenshotOfWindowError(int errorCode, int interactionId); + + /** + * Sends an result code for an attach overlay request to the requesting client. + */ + void sendAttachOverlayResult(int result, int interactionId); } diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index f31a43f5924f..8ba8b8cca5ed 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -30,6 +30,7 @@ import android.content.res.Resources.Theme; import android.content.res.XmlResourceParser; import android.os.SystemClock; import android.util.AttributeSet; +import android.util.TimeUtils; import android.util.Xml; import android.view.InflateException; @@ -156,6 +157,18 @@ public class AnimationUtils { } /** + * The expected presentation time of a frame in the {@link SystemClock#uptimeMillis()}. + * Developers should prefer using this method over {@link #currentAnimationTimeMillis()} + * because it offers a more accurate time for the calculating animation progress. + * + * @return the expected presentation time of a frame in the + * {@link SystemClock#uptimeMillis()} time base. + */ + public static long getExpectedPresentationTimeMillis() { + return getExpectedPresentationTimeNanos() / TimeUtils.NANOS_PER_MS; + } + + /** * Loads an {@link Animation} object from a resource * * @param context Application context used to access resources diff --git a/core/java/android/view/autofill/OWNERS b/core/java/android/view/autofill/OWNERS index 622b0e208812..37c6f5bf3425 100644 --- a/core/java/android/view/autofill/OWNERS +++ b/core/java/android/view/autofill/OWNERS @@ -4,3 +4,6 @@ simranjit@google.com haoranzhang@google.com skxu@google.com yunicorn@google.com + +# Bug component: 543785 = per-file *Augmented* +per-file *Augmented* = wangqi@google.com diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index 92380ed7a7bc..6340388b5d5b 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -282,7 +282,11 @@ public interface InputMethod { * Flag for {@link #showSoftInput}: this show has been forced to * happen by the user. If set, the input method should remain visible * until deliberated dismissed by the user in its UI. + * + * @deprecated {@link InputMethodManager#SHOW_FORCED} is deprecated and + * should no longer be used by apps. IMEs likewise should no longer react to this flag. */ + @Deprecated public static final int SHOW_FORCED = 0x00002; /** diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 3f308e6fccee..fb94d49276cb 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2018,7 +2018,7 @@ public final class InputMethodManager { * * @deprecated Use {@link #showSoftInput} without this flag instead. Using this flag can lead * to the soft input remaining visible even when the calling application is closed. The - * use of this flag can make the soft input remains visible globally. Starting in + * use of this flag can make the soft input remain visible globally. Starting in * {@link Build.VERSION_CODES#TIRAMISU Android T}, this flag only has an effect while the * caller is currently focused. */ diff --git a/core/java/android/view/textclassifier/OWNERS b/core/java/android/view/textclassifier/OWNERS index a205be2f39d0..224ed71f3401 100644 --- a/core/java/android/view/textclassifier/OWNERS +++ b/core/java/android/view/textclassifier/OWNERS @@ -1,8 +1,3 @@ # Bug component: 709498 -mns@google.com -toki@google.com -augale@google.com -joannechung@google.com -tonymak@google.com -licha@google.com +wangqi@google.com diff --git a/core/java/android/view/translation/OWNERS b/core/java/android/view/translation/OWNERS index b772ad3f7cab..977bda16c2d0 100644 --- a/core/java/android/view/translation/OWNERS +++ b/core/java/android/view/translation/OWNERS @@ -1,7 +1,3 @@ # Bug component: 994311 -augale@google.com -joannechung@google.com -markpun@google.com -lpeter@google.com -tymtsai@google.com +wangqi@google.com diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 95ba36ab80f9..cc50f793abc8 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -156,7 +156,6 @@ import android.text.util.Linkify; import android.util.ArraySet; import android.util.AttributeSet; import android.util.DisplayMetrics; -import android.util.FeatureFlagUtils; import android.util.IntArray; import android.util.Log; import android.util.SparseIntArray; @@ -831,11 +830,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE; private int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE; - // The auto option for LINE_BREAK_WORD_STYLE_PHRASE may not be applied in recycled view due to - // one-way flag flipping. This is a tentative limitation during experiment and will not have the - // issue once this is finalized to LINE_BREAK_WORD_STYLE_PHRASE_AUTO option. - private boolean mUserSpeficiedLineBreakwordStyle = false; - // This is used to reflect the current user preference for changing font weight and making text // more bold. private int mFontWeightAdjustment; @@ -1546,9 +1540,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; case com.android.internal.R.styleable.TextView_lineBreakWordStyle: - if (a.hasValue(attr)) { - mUserSpeficiedLineBreakwordStyle = true; - } mLineBreakWordStyle = a.getInt(attr, LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE); break; @@ -4350,7 +4341,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle: attributes.mHasLineBreakWordStyle = true; - mUserSpeficiedLineBreakwordStyle = true; attributes.mLineBreakWordStyle = appearance.getInt(attr, attributes.mLineBreakWordStyle); break; @@ -5086,7 +5076,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @param lineBreakWordStyle The line-break word style for the text. */ public void setLineBreakWordStyle(@LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) { - mUserSpeficiedLineBreakwordStyle = true; if (mLineBreakWordStyle != lineBreakWordStyle) { mLineBreakWordStyle = lineBreakWordStyle; if (mLayout != null) { @@ -5122,12 +5111,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @see PrecomputedText */ public @NonNull PrecomputedText.Params getTextMetricsParams() { - final boolean autoPhraseBreaking = - !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); return new PrecomputedText.Params(new TextPaint(mTextPaint), - LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, - autoPhraseBreaking), + LineBreakConfig.getLineBreakConfig(mLineBreakStyle, mLineBreakWordStyle), getTextDirectionHeuristic(), mBreakStrategy, mHyphenationFrequency); } @@ -5145,9 +5130,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mBreakStrategy = params.getBreakStrategy(); mHyphenationFrequency = params.getHyphenationFrequency(); LineBreakConfig lineBreakConfig = params.getLineBreakConfig(); - mLineBreakStyle = lineBreakConfig.getLineBreakStyle(); - mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle(); - mUserSpeficiedLineBreakwordStyle = true; + mLineBreakStyle = LineBreakConfig.getResolvedLineBreakStyle(lineBreakConfig); + mLineBreakWordStyle = LineBreakConfig.getResolvedLineBreakWordStyle(lineBreakConfig); if (mLayout != null) { nullLayouts(); requestLayout(); @@ -7077,13 +7061,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mTextDir == null) { mTextDir = getTextDirectionHeuristic(); } - final boolean autoPhraseBreaking = - !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); final @PrecomputedText.Params.CheckResultUsableResult int checkResult = precomputed.getParams().checkResultUsable(getPaint(), mTextDir, mBreakStrategy, mHyphenationFrequency, LineBreakConfig.getLineBreakConfig( - mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking)); + mLineBreakStyle, mLineBreakWordStyle)); switch (checkResult) { case PrecomputedText.Params.UNUSABLE: throw new IllegalArgumentException( @@ -10640,9 +10621,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } // TODO: code duplication with makeSingleLayout() if (mHintLayout == null) { - final boolean autoPhraseBreaking = - !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, mHint.length(), mTextPaint, hintWidth) .setAlignment(alignment) @@ -10655,7 +10633,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setJustificationMode(mJustificationMode) .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( - mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking)); + mLineBreakStyle, mLineBreakWordStyle)); if (shouldEllipsize) { builder.setEllipsize(mEllipsize) .setEllipsizedWidth(ellipsisWidth); @@ -10704,7 +10682,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean useSaved) { Layout result = null; if (useDynamicLayout()) { - final boolean autoPhraseBreaking = isAutoPhraseBreakingEnabled(); final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint, wantWidth) .setDisplayText(mTransformed) @@ -10717,7 +10694,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( - mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking)) + mLineBreakStyle, mLineBreakWordStyle)) .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null) .setEllipsizedWidth(ellipsisWidth); result = builder.build(); @@ -10762,7 +10739,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } if (result == null) { - final boolean autoPhraseBreaking = isAutoPhraseBreakingEnabled(); StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, 0, mTransformed.length(), mTextPaint, wantWidth) .setAlignment(alignment) @@ -10775,7 +10751,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setJustificationMode(mJustificationMode) .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( - mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking)); + mLineBreakStyle, mLineBreakWordStyle)); if (shouldEllipsize) { builder.setEllipsize(effectiveEllipsize) .setEllipsizedWidth(ellipsisWidth); @@ -10785,11 +10761,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return result; } - private boolean isAutoPhraseBreakingEnabled() { - return !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); - } - @UnsupportedAppUsage private boolean compressText(float width) { if (isHardwareAccelerated()) return false; @@ -11138,9 +11109,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain( text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right)); - final boolean autoPhraseBreaking = - !mUserSpeficiedLineBreakwordStyle && FeatureFlagUtils.isEnabled(mContext, - FeatureFlagUtils.SETTINGS_AUTO_TEXT_WRAPPING); layoutBuilder.setAlignment(getLayoutAlignment()) .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) .setIncludePad(getIncludeFontPadding()) @@ -11151,7 +11119,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) .setTextDirection(getTextDirectionHeuristic()) .setLineBreakConfig(LineBreakConfig.getLineBreakConfig( - mLineBreakStyle, mLineBreakWordStyle, autoPhraseBreaking)); + mLineBreakStyle, mLineBreakWordStyle)); final StaticLayout layout = layoutBuilder.build(); diff --git a/core/java/android/widget/inline/OWNERS b/core/java/android/widget/inline/OWNERS index 9a30e826a24f..73651daa562c 100644 --- a/core/java/android/widget/inline/OWNERS +++ b/core/java/android/widget/inline/OWNERS @@ -1,3 +1,3 @@ -# Bug component: 351486 +# Bug component: 1195602 -include /core/java/android/view/autofill/OWNERS +wangqi@google.com diff --git a/core/java/android/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java index 954f68633e57..2858f0a1a725 100644 --- a/core/java/android/window/WindowMetricsController.java +++ b/core/java/android/window/WindowMetricsController.java @@ -145,13 +145,13 @@ public final class WindowMetricsController { for (int i = 0; i < possibleDisplayInfos.size(); i++) { currentDisplayInfo = possibleDisplayInfos.get(i); - // Calculate max bounds for this rotation and state. - Rect maxBounds = new Rect(0, 0, currentDisplayInfo.logicalWidth, - currentDisplayInfo.logicalHeight); + // Calculate max bounds for natural rotation and state. + Rect maxBounds = new Rect(0, 0, currentDisplayInfo.getNaturalWidth(), + currentDisplayInfo.getNaturalHeight()); - // Calculate insets for the rotated max bounds. + // Calculate insets for the natural max bounds. final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0; - // Initialize insets based upon display rotation. Note any window-provided insets + // Initialize insets based on Surface.ROTATION_0. Note any window-provided insets // will not be set. windowInsets = getWindowInsetsFromServerForDisplay( currentDisplayInfo.displayId, null /* token */, diff --git a/core/java/android/window/WindowProviderService.java b/core/java/android/window/WindowProviderService.java index f2ae973500af..611da3cec5c6 100644 --- a/core/java/android/window/WindowProviderService.java +++ b/core/java/android/window/WindowProviderService.java @@ -34,6 +34,7 @@ import android.content.res.Configuration; import android.hardware.display.DisplayManager; import android.os.Bundle; import android.os.IBinder; +import android.util.Log; import android.view.Display; import android.view.WindowManager; import android.view.WindowManager.LayoutParams.WindowType; @@ -52,6 +53,8 @@ import android.view.WindowManagerImpl; @UiContext public abstract class WindowProviderService extends Service implements WindowProvider { + private static final String TAG = WindowProviderService.class.getSimpleName(); + private final Bundle mOptions; private final WindowTokenClient mWindowToken = new WindowTokenClient(); private final WindowContextController mController = new WindowContextController(mWindowToken); @@ -194,8 +197,16 @@ public abstract class WindowProviderService extends Service implements WindowPro public final Context createServiceBaseContext(ActivityThread mainThread, LoadedApk packageInfo) { final Context context = super.createServiceBaseContext(mainThread, packageInfo); - final Display display = context.getSystemService(DisplayManager.class) - .getDisplay(getInitialDisplayId()); + final DisplayManager displayManager = context.getSystemService(DisplayManager.class); + final int initialDisplayId = getInitialDisplayId(); + Display display = displayManager.getDisplay(initialDisplayId); + // Fallback to use the default display if the initial display to start WindowProviderService + // is detached. + if (display == null) { + Log.e(TAG, "Display with id " + initialDisplayId + " not found, falling back to " + + "DEFAULT_DISPLAY"); + display = displayManager.getDisplay(DEFAULT_DISPLAY); + } return context.createTokenContext(mWindowToken, display); } diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index 7452daa4908c..65b59790e327 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -56,6 +56,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.telecom.TelecomManager; +import android.util.Log; import android.util.Slog; import android.view.View; import android.widget.Button; @@ -124,16 +125,19 @@ public class IntentForwarderActivity extends Activity { String className = intentReceived.getComponent().getClassName(); final int targetUserId; final String userMessage; + final UserInfo managedProfile; if (className.equals(FORWARD_INTENT_TO_PARENT)) { userMessage = getForwardToPersonalMessage(); targetUserId = getProfileParent(); + managedProfile = null; getMetricsLogger().write( new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE) .setSubtype(MetricsEvent.PARENT_PROFILE)); } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) { userMessage = getForwardToWorkMessage(); - targetUserId = getManagedProfile(); + managedProfile = getManagedProfile(); + targetUserId = managedProfile == null ? UserHandle.USER_NULL : managedProfile.id; getMetricsLogger().write( new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE) @@ -142,6 +146,7 @@ public class IntentForwarderActivity extends Activity { Slog.wtf(TAG, IntentForwarderActivity.class.getName() + " cannot be called directly"); userMessage = null; targetUserId = UserHandle.USER_NULL; + managedProfile = null; } if (targetUserId == UserHandle.USER_NULL) { // This covers the case where there is no parent / managed profile. @@ -185,27 +190,49 @@ public class IntentForwarderActivity extends Activity { finish(); // When switching to the work profile, ask the user for consent before launching } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) { - maybeShowUserConsentMiniResolver(result, newIntent, targetUserId); + maybeShowUserConsentMiniResolver(result, newIntent, managedProfile); } }, getApplicationContext().getMainExecutor()); } private void maybeShowUserConsentMiniResolver( - ResolveInfo target, Intent launchIntent, int targetUserId) { + ResolveInfo target, Intent launchIntent, UserInfo managedProfile) { if (target == null || isIntentForwarderResolveInfo(target) || !isDeviceProvisioned()) { finish(); return; } - if (launchIntent.getBooleanExtra(EXTRA_SKIP_USER_CONFIRMATION, /* defaultValue= */ false) - && getCallingPackage() != null - && PERMISSION_GRANTED == getPackageManager().checkPermission( - INTERACT_ACROSS_USERS, getCallingPackage())) { + int targetUserId = managedProfile == null ? UserHandle.USER_NULL : managedProfile.id; + String callingPackage = getCallingPackage(); + boolean privilegedCallerAskedToSkipUserConsent = + launchIntent.getBooleanExtra( + EXTRA_SKIP_USER_CONFIRMATION, /* defaultValue= */ false) + && callingPackage != null + && PERMISSION_GRANTED == getPackageManager().checkPermission( + INTERACT_ACROSS_USERS, callingPackage); + + DevicePolicyManager devicePolicyManager = + getSystemService(DevicePolicyManager.class); + ComponentName profileOwnerName = devicePolicyManager.getProfileOwnerAsUser(targetUserId); + boolean intentToLaunchProfileOwner = profileOwnerName != null + && profileOwnerName.getPackageName().equals(target.getComponentInfo().packageName); + + if (privilegedCallerAskedToSkipUserConsent || intentToLaunchProfileOwner) { + Log.i("IntentForwarderActivity", String.format( + "Skipping user consent for redirection into the managed profile for intent [%s]" + + ", privilegedCallerAskedToSkipUserConsent=[%s]" + + ", intentToLaunchProfileOwner=[%s]", + launchIntent, privilegedCallerAskedToSkipUserConsent, + intentToLaunchProfileOwner)); startActivityAsCaller(launchIntent, targetUserId); finish(); return; } + Log.i("IntentForwarderActivity", String.format( + "Showing user consent for redirection into the managed profile for intent [%s] and " + + " calling package [%s]", + launchIntent, callingPackage)); int layoutId = R.layout.miniresolver; setContentView(layoutId); @@ -245,8 +272,7 @@ public class IntentForwarderActivity extends Activity { View telephonyInfo = findViewById(R.id.miniresolver_info_section); - DevicePolicyManager devicePolicyManager = - getSystemService(DevicePolicyManager.class); + // Additional information section is work telephony specific. Therefore, it is only shown // for telephony related intents, when all sim subscriptions are in the work profile. if ((isDialerIntent(launchIntent) || isTextMessageIntent(launchIntent)) @@ -507,20 +533,18 @@ public class IntentForwarderActivity extends Activity { } /** - * Returns the userId of the managed profile for this device or UserHandle.USER_NULL if there is - * no managed profile. + * Returns the managed profile for this device or null if there is no managed profile. * - * TODO: Remove the assumption that there is only one managed profile - * on the device. + * TODO: Remove the assumption that there is only one managed profile on the device. */ - private int getManagedProfile() { + @Nullable private UserInfo getManagedProfile() { List<UserInfo> relatedUsers = mInjector.getUserManager().getProfiles(UserHandle.myUserId()); for (UserInfo userInfo : relatedUsers) { - if (userInfo.isManagedProfile()) return userInfo.id; + if (userInfo.isManagedProfile()) return userInfo; } Slog.wtf(TAG, FORWARD_INTENT_TO_MANAGED_PROFILE + " has been called, but there is no managed profile"); - return UserHandle.USER_NULL; + return null; } /** diff --git a/core/java/com/android/internal/content/PackageMonitor.java b/core/java/com/android/internal/content/PackageMonitor.java index 76e0e34ef7d3..d78689e9955b 100644 --- a/core/java/com/android/internal/content/PackageMonitor.java +++ b/core/java/com/android/internal/content/PackageMonitor.java @@ -22,15 +22,23 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; +import android.os.HandlerExecutor; +import android.os.IRemoteCallback; import android.os.Looper; +import android.os.RemoteException; import android.os.UserHandle; +import android.util.Log; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import java.util.Objects; +import java.util.concurrent.Executor; /** * Helper class for monitoring the state of packages: adding, removing, @@ -41,8 +49,7 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { final IntentFilter mPackageFilt; final IntentFilter mNonDataFilt; - final IntentFilter mExternalFilt; - + Context mRegisteredContext; Handler mRegisteredHandler; String[] mDisappearingPackages; @@ -55,15 +62,16 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { String[] mTempArray = new String[1]; + PackageMonitorCallback mPackageMonitorCallback; + @UnsupportedAppUsage public PackageMonitor() { final boolean isCore = UserHandle.isCore(android.os.Process.myUid()); mPackageFilt = new IntentFilter(); - mPackageFilt.addAction(Intent.ACTION_PACKAGE_ADDED); - mPackageFilt.addAction(Intent.ACTION_PACKAGE_REMOVED); - mPackageFilt.addAction(Intent.ACTION_PACKAGE_CHANGED); + // Settings app sends the broadcast mPackageFilt.addAction(Intent.ACTION_QUERY_PACKAGE_RESTART); + // AMS sends the broadcast mPackageFilt.addAction(Intent.ACTION_PACKAGE_RESTARTED); mPackageFilt.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); mPackageFilt.addDataScheme("package"); @@ -72,20 +80,11 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { } mNonDataFilt = new IntentFilter(); - mNonDataFilt.addAction(Intent.ACTION_UID_REMOVED); + // UserController sends the broadcast mNonDataFilt.addAction(Intent.ACTION_USER_STOPPED); - mNonDataFilt.addAction(Intent.ACTION_PACKAGES_SUSPENDED); - mNonDataFilt.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED); if (isCore) { mNonDataFilt.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); } - - mExternalFilt = new IntentFilter(); - mExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); - mExternalFilt.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); - if (isCore) { - mExternalFilt.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); - } } @UnsupportedAppUsage @@ -101,7 +100,16 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { } public void register(Context context, UserHandle user, - boolean externalStorage, Handler handler) { + boolean externalStorage, Handler handler) { + // Remove until all using code are updated to new method. + register(context, user, handler); + } + + + /** + * Register for notifications of package changes such as install, removal and other events. + */ + public void register(Context context, UserHandle user, Handler handler) { if (mRegisteredContext != null) { throw new IllegalStateException("Already registered"); } @@ -110,15 +118,17 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { if (user != null) { context.registerReceiverAsUser(this, user, mPackageFilt, null, mRegisteredHandler); context.registerReceiverAsUser(this, user, mNonDataFilt, null, mRegisteredHandler); - if (externalStorage) { - context.registerReceiverAsUser(this, user, mExternalFilt, null, - mRegisteredHandler); - } } else { context.registerReceiver(this, mPackageFilt, null, mRegisteredHandler); context.registerReceiver(this, mNonDataFilt, null, mRegisteredHandler); - if (externalStorage) { - context.registerReceiver(this, mExternalFilt, null, mRegisteredHandler); + } + if (mPackageMonitorCallback == null) { + PackageManager pm = mRegisteredContext.getPackageManager(); + if (pm != null) { + mPackageMonitorCallback = new PackageMonitorCallback(this, + new HandlerExecutor(mRegisteredHandler)); + int userId = user != null ? user.getIdentifier() : mRegisteredContext.getUserId(); + pm.registerPackageMonitorCallback(mPackageMonitorCallback, userId); } } } @@ -133,9 +143,15 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { throw new IllegalStateException("Not registered"); } mRegisteredContext.unregisterReceiver(this); + + PackageManager pm = mRegisteredContext.getPackageManager(); + if (pm != null && mPackageMonitorCallback != null) { + pm.unregisterPackageMonitorCallback(mPackageMonitorCallback); + } + mPackageMonitorCallback = null; mRegisteredContext = null; } - + public void onBeginPackageChanges() { } @@ -327,9 +343,18 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { String pkg = uri != null ? uri.getSchemeSpecificPart() : null; return pkg; } - + @Override public void onReceive(Context context, Intent intent) { + doHandlePackageEvent(intent); + } + + /** + * Handle the package related event + * @param intent the intent that contains package related event information + */ + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) + public void doHandlePackageEvent(Intent intent) { mChangeUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); if (mChangeUserId == UserHandle.USER_NULL) { @@ -337,11 +362,11 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { return; } onBeginPackageChanges(); - + mDisappearingPackages = mAppearingPackages = null; mSomePackagesChanged = false; mModifiedComponents = null; - + String action = intent.getAction(); if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { String pkg = getPackageName(intent); @@ -465,4 +490,30 @@ public abstract class PackageMonitor extends android.content.BroadcastReceiver { onFinishPackageChanges(); mChangeUserId = UserHandle.USER_NULL; } + + private static final class PackageMonitorCallback extends IRemoteCallback.Stub { + + private final PackageMonitor mPackageMonitor; + private final Executor mExecutor; + + PackageMonitorCallback(PackageMonitor monitor, Executor executor) { + mPackageMonitor = monitor; + mExecutor = executor; + } + + @Override + public void sendResult(Bundle data) throws RemoteException { + onHandlePackageMonitorCallback(data); + } + + private void onHandlePackageMonitorCallback(Bundle bundle) { + Intent intent = bundle.getParcelable( + PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, Intent.class); + if (intent == null) { + Log.w(TAG, "No intent is set for PackageMonitorCallback"); + return; + } + mExecutor.execute(() -> mPackageMonitor.doHandlePackageEvent(intent)); + } + } } diff --git a/core/java/com/android/internal/infra/ServiceConnector.java b/core/java/com/android/internal/infra/ServiceConnector.java index cb162674eb16..6489c8ed30ae 100644 --- a/core/java/com/android/internal/infra/ServiceConnector.java +++ b/core/java/com/android/internal/infra/ServiceConnector.java @@ -745,6 +745,10 @@ public interface ServiceConnector<I extends IInterface> { boolean mAsync = false; private String mDebugName; { + // The timeout handler must be set before any calls to set timeouts on the + // AndroidFuture, to ensure they are posted on the proper thread. + setTimeoutHandler(getJobHandler()); + long requestTimeout = getRequestTimeoutMs(); if (requestTimeout > 0) { orTimeout(requestTimeout, TimeUnit.MILLISECONDS); diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java index 1a3804900665..66e3333acf7c 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java @@ -20,7 +20,6 @@ import android.annotation.AnyThread; import android.annotation.DrawableRes; import android.annotation.NonNull; import android.annotation.Nullable; -import android.inputmethodservice.InputMethodService; import android.net.Uri; import android.os.IBinder; import android.os.RemoteException; @@ -106,10 +105,14 @@ public final class InputMethodPrivilegedOperations { * * @param vis visibility flags * @param backDisposition disposition flags + * @see android.inputmethodservice.InputMethodService#IME_ACTIVE + * @see android.inputmethodservice.InputMethodService#IME_VISIBLE + * @see android.inputmethodservice.InputMethodService#IME_INVISIBLE + * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_DEFAULT + * @see android.inputmethodservice.InputMethodService#BACK_DISPOSITION_ADJUST_NOTHING */ @AnyThread - public void setImeWindowStatusAsync(@InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition) { + public void setImeWindowStatusAsync(int vis, int backDisposition) { final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); if (ops == null) { return; diff --git a/core/java/com/android/internal/os/CpuScalingPolicies.java b/core/java/com/android/internal/os/CpuScalingPolicies.java new file mode 100644 index 000000000000..6dbe8ab5f567 --- /dev/null +++ b/core/java/com/android/internal/os/CpuScalingPolicies.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.os; + +import android.annotation.NonNull; +import android.util.SparseArray; + +import libcore.util.EmptyArray; + +import java.util.Arrays; + +/** + * CPU scaling policies: the policy IDs and corresponding supported scaling for those + * policies. + */ +public class CpuScalingPolicies { + private final SparseArray<int[]> mCpusByPolicy; + private final SparseArray<int[]> mFreqsByPolicy; + private final int[] mPolicies; + private final int mScalingStepCount; + + public CpuScalingPolicies(@NonNull SparseArray<int[]> cpusByPolicy, + @NonNull SparseArray<int[]> freqsByPolicy) { + mCpusByPolicy = cpusByPolicy; + mFreqsByPolicy = freqsByPolicy; + + mPolicies = new int[cpusByPolicy.size()]; + for (int i = 0; i < mPolicies.length; i++) { + mPolicies[i] = cpusByPolicy.keyAt(i); + } + + Arrays.sort(mPolicies); + + int count = 0; + for (int i = freqsByPolicy.size() - 1; i >= 0; i--) { + count += freqsByPolicy.valueAt(i).length; + } + mScalingStepCount = count; + } + + /** + * Returns available policies (aka clusters). + */ + @NonNull + public int[] getPolicies() { + return mPolicies; + } + + /** + * CPUs covered by the specified policy. + */ + @NonNull + public int[] getRelatedCpus(int policy) { + return mCpusByPolicy.get(policy, EmptyArray.INT); + } + + /** + * Scaling frequencies supported for the specified policy. + */ + @NonNull + public int[] getFrequencies(int policy) { + return mFreqsByPolicy.get(policy, EmptyArray.INT); + } + + /** + * Returns the overall number of supported scaling steps: grand total of available frequencies + * across all scaling policies. + */ + public int getScalingStepCount() { + return mScalingStepCount; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (int policy : mPolicies) { + sb.append("policy").append(policy) + .append("\n CPUs: ").append(Arrays.toString(mCpusByPolicy.get(policy))) + .append("\n freqs: ").append(Arrays.toString(mFreqsByPolicy.get(policy))) + .append("\n"); + } + return sb.toString(); + } +} diff --git a/core/java/com/android/internal/os/CpuScalingPolicyReader.java b/core/java/com/android/internal/os/CpuScalingPolicyReader.java new file mode 100644 index 000000000000..c96089a5c9c9 --- /dev/null +++ b/core/java/com/android/internal/os/CpuScalingPolicyReader.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.os; + +import android.annotation.NonNull; +import android.os.FileUtils; +import android.util.IntArray; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.annotations.VisibleForTesting; + +import libcore.util.EmptyArray; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Captures a CPU scaling policies such as available scaling frequencies as well as + * CPUs (cores) for each policy. + * + * See <a + * href="https://www.kernel.org/doc/html/latest/admin-guide/pm/cpufreq.html + * #policy-interface-in-sysfs">Policy Interface in sysfs</a> + */ +public class CpuScalingPolicyReader { + private static final String TAG = "CpuScalingPolicyReader"; + private static final String CPUFREQ_DIR = "/sys/devices/system/cpu/cpufreq"; + private static final Pattern POLICY_PATTERN = Pattern.compile("policy(\\d+)"); + private static final String FILE_NAME_RELATED_CPUS = "related_cpus"; + private static final String FILE_NAME_SCALING_AVAILABLE_FREQUENCIES = + "scaling_available_frequencies"; + private static final String FILE_NAME_SCALING_BOOST_FREQUENCIES = "scaling_boost_frequencies"; + private static final String FILE_NAME_CPUINFO_CUR_FREQ = "cpuinfo_cur_freq"; + + private final String mCpuFreqDir; + + public CpuScalingPolicyReader() { + this(CPUFREQ_DIR); + } + + @VisibleForTesting + public CpuScalingPolicyReader(String cpuFreqDir) { + mCpuFreqDir = cpuFreqDir; + } + + /** + * Reads scaling policy info from sysfs files in /sys/devices/system/cpu/cpufreq + */ + @NonNull + public CpuScalingPolicies read() { + SparseArray<int[]> cpusByPolicy = new SparseArray<>(); + SparseArray<int[]> freqsByPolicy = new SparseArray<>(); + + File cpuFreqDir = new File(mCpuFreqDir); + File[] policyDirs = cpuFreqDir.listFiles(); + if (policyDirs != null) { + for (File policyDir : policyDirs) { + Matcher matcher = POLICY_PATTERN.matcher(policyDir.getName()); + if (matcher.matches()) { + int[] relatedCpus = readIntsFromFile( + new File(policyDir, FILE_NAME_RELATED_CPUS)); + if (relatedCpus.length == 0) { + continue; + } + + int[] availableFreqs = readIntsFromFile( + new File(policyDir, FILE_NAME_SCALING_AVAILABLE_FREQUENCIES)); + int[] boostFreqs = readIntsFromFile( + new File(policyDir, FILE_NAME_SCALING_BOOST_FREQUENCIES)); + int[] freqs; + if (boostFreqs.length == 0) { + freqs = availableFreqs; + } else { + freqs = Arrays.copyOf(availableFreqs, + availableFreqs.length + boostFreqs.length); + System.arraycopy(boostFreqs, 0, freqs, availableFreqs.length, + boostFreqs.length); + } + if (freqs.length == 0) { + freqs = readIntsFromFile(new File(policyDir, FILE_NAME_CPUINFO_CUR_FREQ)); + if (freqs.length == 0) { + freqs = new int[]{0}; // Unknown frequency + } + } + int policy = Integer.parseInt(matcher.group(1)); + cpusByPolicy.put(policy, relatedCpus); + freqsByPolicy.put(policy, freqs); + } + } + } + + if (cpusByPolicy.size() == 0) { + // There just has to be at least one CPU - otherwise, what's executing this code? + cpusByPolicy.put(0, new int[]{0}); + freqsByPolicy.put(0, new int[]{0}); + } + + return new CpuScalingPolicies(cpusByPolicy, freqsByPolicy); + } + + @NonNull + private static int[] readIntsFromFile(File file) { + if (!file.exists()) { + return EmptyArray.INT; + } + + IntArray intArray = new IntArray(16); + try { + String contents = FileUtils.readTextFile(file, 0, null).trim(); + String[] strings = contents.split(" "); + intArray.clear(); + for (String s : strings) { + try { + intArray.add(Integer.parseInt(s)); + } catch (NumberFormatException e) { + Slog.e(TAG, "Unexpected file format " + file + + ": " + contents, e); + } + } + return intArray.toArray(); + } catch (IOException e) { + Slog.e(TAG, "Cannot read " + file, e); + return EmptyArray.INT; + } + } +} diff --git a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java index c801be0ce3e7..21e0dc59db01 100644 --- a/core/java/com/android/internal/os/KernelCpuUidTimeReader.java +++ b/core/java/com/android/internal/os/KernelCpuUidTimeReader.java @@ -16,9 +16,7 @@ package com.android.internal.os; import static com.android.internal.os.KernelCpuProcStringReader.asLongs; -import static com.android.internal.util.Preconditions.checkNotNull; -import android.annotation.NonNull; import android.annotation.Nullable; import android.os.StrictMode; import android.util.IntArray; @@ -29,11 +27,9 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.KernelCpuProcStringReader.ProcFileIterator; import com.android.internal.os.KernelCpuUidBpfMapReader.BpfMapIterator; -import java.io.BufferedReader; import java.io.FileWriter; import java.io.IOException; import java.nio.CharBuffer; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -352,7 +348,7 @@ public abstract class KernelCpuUidTimeReader<T> { private int mFreqCount = 0; private int mErrors = 0; private boolean mPerClusterTimesAvailable; - private boolean mAllUidTimesAvailable = true; + private boolean mAllUidTimesAvailable; public KernelCpuUidFreqTimeReader(boolean throttle) { this(throttle, Clock.SYSTEM_CLOCK); @@ -376,10 +372,22 @@ public abstract class KernelCpuUidTimeReader<T> { } /** + * Initializes the reader. Should be called during the system-ready boot phase. + */ + public void onSystemReady() { + if (mBpfTimesAvailable && mCpuFreqs == null) { + readFreqsThroughBpf(); + // By extension: if we can read CPU frequencies through eBPF, we can also + // read per-UID CPU time-in-state + mAllUidTimesAvailable = mCpuFreqs != null; + } + } + + /** * @return Whether per-cluster times are available. */ public boolean perClusterTimesAvailable() { - return mPerClusterTimesAvailable; + return mBpfTimesAvailable; } /** @@ -396,59 +404,6 @@ public abstract class KernelCpuUidTimeReader<T> { return mLastTimes; } - /** - * Reads a list of CPU frequencies from /proc/uid_time_in_state. Uses a given PowerProfile - * to determine if per-cluster times are available. - * - * @param powerProfile The PowerProfile to compare against. - * @return A long[] of CPU frequencies in Hz. - */ - public long[] readFreqs(@NonNull PowerProfile powerProfile) { - checkNotNull(powerProfile); - if (mCpuFreqs != null) { - // No need to read cpu freqs more than once. - return mCpuFreqs; - } - if (!mAllUidTimesAvailable) { - return null; - } - if (mBpfTimesAvailable) { - readFreqsThroughBpf(); - } - if (mCpuFreqs == null) { - final int oldMask = StrictMode.allowThreadDiskReadsMask(); - try (BufferedReader reader = Files.newBufferedReader(mProcFilePath)) { - if (readFreqs(reader.readLine()) == null) { - return null; - } - } catch (IOException e) { - if (++mErrors >= MAX_ERROR_COUNT) { - mAllUidTimesAvailable = false; - } - Slog.e(mTag, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e); - return null; - } finally { - StrictMode.setThreadPolicyMask(oldMask); - } - } - // Check if the freqs in the proc file correspond to per-cluster freqs. - final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs(); - final int numClusters = powerProfile.getNumCpuClusters(); - if (numClusterFreqs.size() == numClusters) { - mPerClusterTimesAvailable = true; - for (int i = 0; i < numClusters; ++i) { - if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) { - mPerClusterTimesAvailable = false; - break; - } - } - } else { - mPerClusterTimesAvailable = false; - } - Slog.i(mTag, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable); - return mCpuFreqs; - } - private long[] readFreqsThroughBpf() { if (!mBpfTimesAvailable || mBpfReader == null) { return null; diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java index 2bcc645e6a9a..503e689c0d5d 100644 --- a/core/java/com/android/internal/os/PowerProfile.java +++ b/core/java/com/android/internal/os/PowerProfile.java @@ -26,6 +26,7 @@ import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.util.IndentingPrintWriter; import android.util.Slog; +import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; @@ -294,6 +295,8 @@ public class PowerProfile { private static final long SUBSYSTEM_FIELDS_MASK = 0xFFFF_FFFF; + private static final int DEFAULT_CPU_POWER_BRACKET_NUMBER = 3; + /** * A map from Power Use Item to its power consumption. */ @@ -357,6 +360,8 @@ public class PowerProfile { readPowerValuesFromXml(context, xmlId); } initCpuClusters(); + initCpuScalingPolicies(); + initCpuPowerBrackets(DEFAULT_CPU_POWER_BRACKET_NUMBER); initDisplays(); initModem(); } @@ -452,9 +457,7 @@ public class PowerProfile { private static final String CPU_CLUSTER_POWER_COUNT = "cpu.cluster_power.cluster"; private static final String CPU_CORE_SPEED_PREFIX = "cpu.core_speeds.cluster"; private static final String CPU_CORE_POWER_PREFIX = "cpu.core_power.cluster"; - private static final String CPU_POWER_BRACKETS_PREFIX = "cpu.power_brackets.cluster"; - - private static final int DEFAULT_CPU_POWER_BRACKET_NUMBER = 3; + private static final String CPU_POWER_BRACKETS_PREFIX = "cpu.power_brackets.policy"; private void initCpuClusters() { if (sPowerArrayMap.containsKey(CPU_PER_CLUSTER_CORE_COUNT)) { @@ -476,8 +479,82 @@ public class PowerProfile { mCpuClusters[0] = new CpuClusterKey(CPU_CORE_SPEED_PREFIX + 0, CPU_CLUSTER_POWER_COUNT + 0, CPU_CORE_POWER_PREFIX + 0, numCpus); } + } - initCpuPowerBrackets(DEFAULT_CPU_POWER_BRACKET_NUMBER); + private SparseArray<CpuScalingPolicyPower> mCpuScalingPolicies; + private static final String CPU_SCALING_POLICY_POWER_POLICY = "cpu.scaling_policy_power.policy"; + private static final String CPU_SCALING_STEP_POWER_POLICY = "cpu.scaling_step_power.policy"; + + private void initCpuScalingPolicies() { + int policyCount = 0; + for (String key : sPowerItemMap.keySet()) { + if (key.startsWith(CPU_SCALING_POLICY_POWER_POLICY)) { + int policy = + Integer.parseInt(key.substring(CPU_SCALING_POLICY_POWER_POLICY.length())); + policyCount = Math.max(policyCount, policy + 1); + } + } + for (String key : sPowerArrayMap.keySet()) { + if (key.startsWith(CPU_SCALING_STEP_POWER_POLICY)) { + int policy = + Integer.parseInt(key.substring(CPU_SCALING_STEP_POWER_POLICY.length())); + policyCount = Math.max(policyCount, policy + 1); + } + } + + if (policyCount > 0) { + mCpuScalingPolicies = new SparseArray<>(policyCount); + for (int policy = 0; policy < policyCount; policy++) { + Double policyPower = sPowerItemMap.get(CPU_SCALING_POLICY_POWER_POLICY + policy); + Double[] stepPower = sPowerArrayMap.get(CPU_SCALING_STEP_POWER_POLICY + policy); + if (policyPower != null || stepPower != null) { + double[] primitiveStepPower; + if (stepPower != null) { + primitiveStepPower = new double[stepPower.length]; + for (int i = 0; i < stepPower.length; i++) { + primitiveStepPower[i] = stepPower[i]; + } + } else { + primitiveStepPower = new double[0]; + } + mCpuScalingPolicies.put(policy, new CpuScalingPolicyPower( + policyPower != null ? policyPower : 0, primitiveStepPower)); + } + } + } else { + // Legacy power_profile.xml + int cpuId = 0; + for (CpuClusterKey cpuCluster : mCpuClusters) { + policyCount = cpuId + 1; + cpuId += cpuCluster.numCpus; + } + + if (policyCount > 0) { + mCpuScalingPolicies = new SparseArray<>(policyCount); + cpuId = 0; + for (CpuClusterKey cpuCluster : mCpuClusters) { + double clusterPower = getAveragePower(cpuCluster.clusterPowerKey); + double[] stepPower; + int numSteps = getNumElements(cpuCluster.corePowerKey); + if (numSteps != 0) { + stepPower = new double[numSteps]; + for (int step = 0; step < numSteps; step++) { + stepPower[step] = getAveragePower(cpuCluster.corePowerKey, step); + } + } else { + stepPower = new double[1]; + } + mCpuScalingPolicies.put(cpuId, + new CpuScalingPolicyPower(clusterPower, stepPower)); + cpuId += cpuCluster.numCpus; + } + } else { + mCpuScalingPolicies = new SparseArray<>(1); + mCpuScalingPolicies.put(0, + new CpuScalingPolicyPower(getAveragePower(POWER_CPU_ACTIVE), + new double[]{0})); + } + } } /** @@ -487,33 +564,38 @@ public class PowerProfile { public void initCpuPowerBrackets(int defaultCpuPowerBracketNumber) { boolean anyBracketsSpecified = false; boolean allBracketsSpecified = true; - for (int cluster = 0; cluster < mCpuClusters.length; cluster++) { - final int steps = getNumSpeedStepsInCpuCluster(cluster); - mCpuClusters[cluster].powerBrackets = new int[steps]; - if (sPowerArrayMap.get(CPU_POWER_BRACKETS_PREFIX + cluster) != null) { + for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) { + int policy = mCpuScalingPolicies.keyAt(i); + CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i); + final int steps = cpuScalingPolicyPower.stepPower.length; + cpuScalingPolicyPower.powerBrackets = new int[steps]; + if (sPowerArrayMap.get(CPU_POWER_BRACKETS_PREFIX + policy) != null) { anyBracketsSpecified = true; } else { allBracketsSpecified = false; } } - if (anyBracketsSpecified && !allBracketsSpecified) { throw new RuntimeException( - "Power brackets should be specified for all clusters or no clusters"); + "Power brackets should be specified for all scaling policies or none"); } mCpuPowerBracketCount = 0; if (allBracketsSpecified) { - for (int cluster = 0; cluster < mCpuClusters.length; cluster++) { - final Double[] data = sPowerArrayMap.get(CPU_POWER_BRACKETS_PREFIX + cluster); - if (data.length != mCpuClusters[cluster].powerBrackets.length) { + for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) { + int policy = mCpuScalingPolicies.keyAt(i); + CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i); + final Double[] data = sPowerArrayMap.get(CPU_POWER_BRACKETS_PREFIX + policy); + if (data.length != cpuScalingPolicyPower.powerBrackets.length) { throw new RuntimeException( - "Wrong number of items in " + CPU_POWER_BRACKETS_PREFIX + cluster); + "Wrong number of items in " + CPU_POWER_BRACKETS_PREFIX + policy + + ", expected: " + + cpuScalingPolicyPower.powerBrackets.length); } - for (int i = 0; i < data.length; i++) { - final int bracket = (int) Math.round(data[i]); - mCpuClusters[cluster].powerBrackets[i] = bracket; + for (int j = 0; j < data.length; j++) { + final int bracket = (int) Math.round(data[j]); + cpuScalingPolicyPower.powerBrackets[j] = bracket; if (bracket > mCpuPowerBracketCount) { mCpuPowerBracketCount = bracket; } @@ -524,10 +606,12 @@ public class PowerProfile { double minPower = Double.MAX_VALUE; double maxPower = Double.MIN_VALUE; int stateCount = 0; - for (int cluster = 0; cluster < mCpuClusters.length; cluster++) { - final int steps = getNumSpeedStepsInCpuCluster(cluster); + for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) { + int policy = mCpuScalingPolicies.keyAt(i); + CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i); + final int steps = cpuScalingPolicyPower.stepPower.length; for (int step = 0; step < steps; step++) { - final double power = getAveragePowerForCpuCore(cluster, step); + final double power = getAveragePowerForCpuScalingStep(policy, step); if (power < minPower) { minPower = power; } @@ -541,10 +625,11 @@ public class PowerProfile { if (stateCount <= defaultCpuPowerBracketNumber) { mCpuPowerBracketCount = stateCount; int bracket = 0; - for (int cluster = 0; cluster < mCpuClusters.length; cluster++) { - final int steps = getNumSpeedStepsInCpuCluster(cluster); + for (int i = 0; i < mCpuScalingPolicies.size(); i++) { + CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i); + final int steps = cpuScalingPolicyPower.stepPower.length; for (int step = 0; step < steps; step++) { - mCpuClusters[cluster].powerBrackets[step] = bracket++; + cpuScalingPolicyPower.powerBrackets[step] = bracket++; } } } else { @@ -553,27 +638,70 @@ public class PowerProfile { final double logBracket = (Math.log(maxPower) - minLogPower) / defaultCpuPowerBracketNumber; - for (int cluster = 0; cluster < mCpuClusters.length; cluster++) { - final int steps = getNumSpeedStepsInCpuCluster(cluster); + for (int i = mCpuScalingPolicies.size() - 1; i >= 0; i--) { + int policy = mCpuScalingPolicies.keyAt(i); + CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i); + final int steps = cpuScalingPolicyPower.stepPower.length; for (int step = 0; step < steps; step++) { - final double power = getAveragePowerForCpuCore(cluster, step); + final double power = getAveragePowerForCpuScalingStep(policy, step); int bracket = (int) ((Math.log(power) - minLogPower) / logBracket); if (bracket >= defaultCpuPowerBracketNumber) { bracket = defaultCpuPowerBracketNumber - 1; } - mCpuClusters[cluster].powerBrackets[step] = bracket; + cpuScalingPolicyPower.powerBrackets[step] = bracket; } } } } } + private static class CpuScalingPolicyPower { + public final double policyPower; + public final double[] stepPower; + public int[] powerBrackets; + + private CpuScalingPolicyPower(double policyPower, double[] stepPower) { + this.policyPower = policyPower; + this.stepPower = stepPower; + } + } + + /** + * Returns the average additional power in (mA) when the CPU scaling policy <code>policy</code> + * is used. + * + * @param policy Policy ID as per <code>ls /sys/devices/system/cpu/cpufreq</code>. Typically, + * policy ID corresponds to the index of the first related CPU, e.g. for "policy6" + * <code>/sys/devices/system/cpu/cpufreq/policy6/related_cpus</code> will + * contain CPU IDs like <code>6, 7</code> + */ + public double getAveragePowerForCpuScalingPolicy(int policy) { + CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.get(policy); + if (cpuScalingPolicyPower != null) { + return cpuScalingPolicyPower.policyPower; + } + return 0; + } + + /** + * Returns the average additional power in (mA) when the CPU scaling policy <code>policy</code> + * is used at the <code>step</code> frequency step (this is not the frequency itself, but the + * integer index of the frequency step). + */ + public double getAveragePowerForCpuScalingStep(int policy, int step) { + CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.get(policy); + if (cpuScalingPolicyPower != null + && step >= 0 && step < cpuScalingPolicyPower.stepPower.length) { + return cpuScalingPolicyPower.stepPower[step]; + } + return 0; + } + private static class CpuClusterKey { public final String freqKey; public final String clusterPowerKey; public final String corePowerKey; public final int numCpus; - public int[] powerBrackets; private CpuClusterKey(String freqKey, String clusterPowerKey, String corePowerKey, int numCpus) { @@ -584,11 +712,19 @@ public class PowerProfile { } } + /** + * @deprecated Use CpuScalingPolicy instead + */ @UnsupportedAppUsage + @Deprecated public int getNumCpuClusters() { return mCpuClusters.length; } + /** + * @deprecated Use CpuScalingPolicy instead + */ + @Deprecated public int getNumCoresInCpuCluster(int cluster) { if (cluster < 0 || cluster >= mCpuClusters.length) { return 0; // index out of bound @@ -596,7 +732,11 @@ public class PowerProfile { return mCpuClusters[cluster].numCpus; } + /** + * @deprecated Use CpuScalingPolicy instead + */ @UnsupportedAppUsage + @Deprecated public int getNumSpeedStepsInCpuCluster(int cluster) { if (cluster < 0 || cluster >= mCpuClusters.length) { return 0; // index out of bound @@ -607,6 +747,10 @@ public class PowerProfile { return 1; // Only one speed } + /** + * @deprecated Use getAveragePowerForCpuScalingPolicy + */ + @Deprecated public double getAveragePowerForCpuCluster(int cluster) { if (cluster >= 0 && cluster < mCpuClusters.length) { return getAveragePower(mCpuClusters[cluster].clusterPowerKey); @@ -614,6 +758,10 @@ public class PowerProfile { return 0; } + /** + * @deprecated Use getAveragePowerForCpuScalingStep + */ + @Deprecated public double getAveragePowerForCpuCore(int cluster, int step) { if (cluster >= 0 && cluster < mCpuClusters.length) { return getAveragePower(mCpuClusters[cluster].corePowerKey, step); @@ -631,26 +779,28 @@ public class PowerProfile { /** * Description of a CPU power bracket: which cluster/frequency combinations are included. */ - public String getCpuPowerBracketDescription(int powerBracket) { + public String getCpuPowerBracketDescription(CpuScalingPolicies cpuScalingPolicies, + int powerBracket) { StringBuilder sb = new StringBuilder(); - for (int cluster = 0; cluster < mCpuClusters.length; cluster++) { - int[] brackets = mCpuClusters[cluster].powerBrackets; + for (int i = 0; i < mCpuScalingPolicies.size(); i++) { + int policy = mCpuScalingPolicies.keyAt(i); + CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.valueAt(i); + int[] brackets = cpuScalingPolicyPower.powerBrackets; + int[] freqs = cpuScalingPolicies.getFrequencies(policy); for (int step = 0; step < brackets.length; step++) { if (brackets[step] == powerBracket) { if (sb.length() != 0) { sb.append(", "); } - if (mCpuClusters.length > 1) { - sb.append(cluster).append('/'); + if (mCpuScalingPolicies.size() > 1) { + sb.append(policy).append('/'); } - Double[] freqs = sPowerArrayMap.get(mCpuClusters[cluster].freqKey); - if (freqs != null && step < freqs.length) { - // Frequency in MHz - sb.append(freqs[step].intValue() / 1000); + if (step < freqs.length) { + sb.append(freqs[step] / 1000); } sb.append('('); sb.append(String.format(Locale.US, "%.1f", - getAveragePowerForCpuCore(cluster, step))); + getAveragePowerForCpuScalingStep(policy, step))); sb.append(')'); } } @@ -659,19 +809,18 @@ public class PowerProfile { } /** - * Returns the CPU power bracket corresponding to the specified cluster and frequency step + * Returns the CPU power bracket corresponding to the specified scaling policy and frequency + * step */ - public int getPowerBracketForCpuCore(int cluster, int step) { - if (cluster >= 0 - && cluster < mCpuClusters.length - && step >= 0 - && step < mCpuClusters[cluster].powerBrackets.length) { - return mCpuClusters[cluster].powerBrackets[step]; + public int getCpuPowerBracketForScalingStep(int policy, int step) { + CpuScalingPolicyPower cpuScalingPolicyPower = mCpuScalingPolicies.get(policy); + if (cpuScalingPolicyPower != null + && step >= 0 && step < cpuScalingPolicyPower.powerBrackets.length) { + return cpuScalingPolicyPower.powerBrackets[step]; } return 0; } - private int mNumDisplays; private void initDisplays() { diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index af1fdd79169a..bb868018bc95 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -232,7 +232,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private boolean mLastHasRightStableInset = false; private boolean mLastHasLeftStableInset = false; private int mLastWindowFlags = 0; - private boolean mLastShouldAlwaysConsumeSystemBars = false; + private @InsetsType int mLastForceConsumingTypes = 0; private @InsetsType int mLastSuppressScrimTypes = 0; private int mRootScrollY = 0; @@ -1111,19 +1111,19 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind : controller.getSystemBarsAppearance(); if (insets != null) { - mLastShouldAlwaysConsumeSystemBars = insets.shouldAlwaysConsumeSystemBars(); + mLastForceConsumingTypes = insets.getForceConsumingTypes(); - final boolean clearsCompatInsets = - clearsCompatInsets(attrs.type, attrs.flags, - getResources().getConfiguration().windowConfiguration - .getWindowingMode()) - && !mLastShouldAlwaysConsumeSystemBars; + @InsetsType int compatInsetsTypes = + WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout(); + if (clearsCompatInsets(attrs.type, attrs.flags, + getResources().getConfiguration().windowConfiguration.getWindowingMode())) { + compatInsetsTypes &= mLastForceConsumingTypes; + } final Insets stableBarInsets = insets.getInsetsIgnoringVisibility( WindowInsets.Type.systemBars()); - final Insets systemInsets = clearsCompatInsets + final Insets systemInsets = compatInsetsTypes == 0 ? Insets.NONE - : Insets.min(insets.getInsets(WindowInsets.Type.systemBars() - | WindowInsets.Type.displayCutout()), stableBarInsets); + : Insets.min(insets.getInsets(compatInsetsTypes), stableBarInsets); mLastTopInset = systemInsets.top; mLastBottomInset = systemInsets.bottom; mLastRightInset = systemInsets.right; @@ -1208,7 +1208,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0 && decorFitsSystemWindows && !hideNavigation) - || (mLastShouldAlwaysConsumeSystemBars && hideNavigation); + || ((mLastForceConsumingTypes & WindowInsets.Type.navigationBars()) != 0 + && hideNavigation); boolean consumingNavBar = ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 @@ -1224,13 +1225,15 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind boolean fullscreen = (sysUiVisibility & SYSTEM_UI_FLAG_FULLSCREEN) != 0 || (attrs.flags & FLAG_FULLSCREEN) != 0 || (requestedVisibleTypes & WindowInsets.Type.statusBars()) == 0; - boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0 - && decorFitsSystemWindows - && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0 - && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0 - && mForceWindowDrawsBarBackgrounds - && mLastTopInset != 0 - || (mLastShouldAlwaysConsumeSystemBars && fullscreen); + boolean consumingStatusBar = + ((sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0 + && decorFitsSystemWindows + && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0 + && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0 + && mForceWindowDrawsBarBackgrounds + && mLastTopInset != 0) + || ((mLastForceConsumingTypes & WindowInsets.Type.statusBars()) != 0 + && fullscreen); int consumedTop = consumingStatusBar ? mLastTopInset : 0; int consumedRight = consumingNavBar ? mLastRightInset : 0; @@ -1434,9 +1437,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind private void updateColorViewInt(final ColorViewState state, int color, int dividerColor, int size, boolean verticalBar, boolean seascape, int sideMargin, boolean animate, boolean force, @InsetsType int requestedVisibleTypes) { + final @InsetsType int type = state.attributes.insetsType; state.present = state.attributes.isPresent( - (requestedVisibleTypes & state.attributes.insetsType) != 0 - || mLastShouldAlwaysConsumeSystemBars, + (requestedVisibleTypes & type) != 0 || (mLastForceConsumingTypes & type) != 0, mWindow.getAttributes().flags, force); boolean show = state.attributes.isVisible(state.present, color, mWindow.getAttributes().flags, force); diff --git a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java index 8b9a9913183d..4f827cda6afa 100644 --- a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java +++ b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java @@ -16,7 +16,6 @@ package com.android.internal.statusbar; -import android.inputmethodservice.InputMethodService; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -32,9 +31,7 @@ public final class RegisterStatusBarResult implements Parcelable { public final int mDisabledFlags1; // switch[0] public final int mAppearance; // switch[1] public final AppearanceRegion[] mAppearanceRegions; // switch[2] - @InputMethodService.ImeWindowVisibility public final int mImeWindowVis; // switch[3] - @InputMethodService.BackDispositionMode public final int mImeBackDisposition; // switch[4] public final boolean mShowImeSwitcher; // switch[5] public final int mDisabledFlags2; // switch[6] @@ -47,12 +44,10 @@ public final class RegisterStatusBarResult implements Parcelable { public final LetterboxDetails[] mLetterboxDetails; public RegisterStatusBarResult(ArrayMap<String, StatusBarIcon> icons, int disabledFlags1, - int appearance, AppearanceRegion[] appearanceRegions, - @InputMethodService.ImeWindowVisibility int imeWindowVis, - @InputMethodService.BackDispositionMode int imeBackDisposition, boolean showImeSwitcher, - int disabledFlags2, IBinder imeToken, boolean navbarColorManagedByIme, int behavior, - int requestedVisibleTypes, String packageName, int transientBarTypes, - LetterboxDetails[] letterboxDetails) { + int appearance, AppearanceRegion[] appearanceRegions, int imeWindowVis, + int imeBackDisposition, boolean showImeSwitcher, int disabledFlags2, IBinder imeToken, + boolean navbarColorManagedByIme, int behavior, int requestedVisibleTypes, + String packageName, int transientBarTypes, LetterboxDetails[] letterboxDetails) { mIcons = new ArrayMap<>(icons); mDisabledFlags1 = disabledFlags1; mAppearance = appearance; diff --git a/core/jni/Android.bp b/core/jni/Android.bp index 8024a6315e04..c19265a83441 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -379,7 +379,6 @@ cc_library_shared { "libbinary_parse", "libdng_sdk", "libft2", - "libhostgraphics", "libhwui", "libimage_type_recognition", "libjpeg", diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp index 2a670e865ced..1c597424f221 100644 --- a/core/jni/android_hardware_Camera.cpp +++ b/core/jni/android_hardware_Camera.cpp @@ -17,22 +17,21 @@ //#define LOG_NDEBUG 0 #define LOG_TAG "Camera-JNI" -#include <utils/Log.h> - -#include "jni.h" -#include <nativehelper/JNIHelp.h> -#include "core_jni_helpers.h" #include <android_runtime/android_graphics_SurfaceTexture.h> #include <android_runtime/android_view_Surface.h> - +#include <binder/IMemory.h> +#include <camera/Camera.h> +#include <camera/StringUtils.h> #include <cutils/properties.h> -#include <utils/Vector.h> -#include <utils/Errors.h> - #include <gui/GLConsumer.h> #include <gui/Surface.h> -#include <camera/Camera.h> -#include <binder/IMemory.h> +#include <nativehelper/JNIHelp.h> +#include <utils/Errors.h> +#include <utils/Log.h> +#include <utils/Vector.h> + +#include "core_jni_helpers.h" +#include "jni.h" using namespace android; @@ -562,7 +561,7 @@ static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobj const char16_t *rawClientName = reinterpret_cast<const char16_t*>( env->GetStringChars(clientPackageName, NULL)); jsize rawClientNameLen = env->GetStringLength(clientPackageName); - String16 clientName(rawClientName, rawClientNameLen); + std::string clientName = toStdString(rawClientName, rawClientNameLen); env->ReleaseStringChars(clientPackageName, reinterpret_cast<const jchar*>(rawClientName)); diff --git a/core/jni/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp index d94b9828808b..8fc30d1c248d 100644 --- a/core/jni/android_os_GraphicsEnvironment.cpp +++ b/core/jni/android_os_GraphicsEnvironment.cpp @@ -16,11 +16,12 @@ #define LOG_TAG "GraphicsEnvironment" -#include <vector> - #include <graphicsenv/GraphicsEnv.h> #include <nativehelper/ScopedUtfChars.h> #include <nativeloader/native_loader.h> + +#include <vector> + #include "core_jni_helpers.h" namespace { @@ -49,11 +50,10 @@ void setGpuStats_native(JNIEnv* env, jobject clazz, jstring driverPackageName, appPackageNameChars.c_str(), vulkanVersion); } -void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring packageName, - jstring devOptIn, jobjectArray featuresObj) { +void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useNativeDriver, + jstring packageName, jobjectArray featuresObj) { ScopedUtfChars pathChars(env, path); ScopedUtfChars packageNameChars(env, packageName); - ScopedUtfChars devOptInChars(env, devOptIn); std::vector<std::string> features; if (featuresObj != nullptr) { @@ -73,8 +73,8 @@ void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jstring packa } } - android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), packageNameChars.c_str(), - devOptInChars.c_str(), features); + android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useNativeDriver, + packageNameChars.c_str(), features); } void setLayerPaths_native(JNIEnv* env, jobject clazz, jobject classLoader, jstring layerPaths) { @@ -118,8 +118,7 @@ const JNINativeMethod g_methods[] = { reinterpret_cast<void*>(setGpuStats_native)}, {"setInjectLayersPrSetDumpable", "()Z", reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)}, - {"setAngleInfo", - "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)V", + {"nativeSetAngleInfo", "(Ljava/lang/String;ZLjava/lang/String;[Ljava/lang/String;)V", reinterpret_cast<void*>(setAngleInfo_native)}, {"setLayerPaths", "(Ljava/lang/ClassLoader;Ljava/lang/String;)V", reinterpret_cast<void*>(setLayerPaths_native)}, diff --git a/core/res/res/drawable-watch/global_actions_item_grey_background_shape.xml b/core/res/res/drawable-watch/global_actions_item_grey_background_shape.xml index f2df319c888d..3ac9ffba3cbf 100644 --- a/core/res/res/drawable-watch/global_actions_item_grey_background_shape.xml +++ b/core/res/res/drawable-watch/global_actions_item_grey_background_shape.xml @@ -18,5 +18,5 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <corners android:radius="26dp"/> - <solid android:color="@color/wear_material_grey_900"/> + <solid android:color="?attr/colorSurface"/> </shape>
\ No newline at end of file diff --git a/core/res/res/drawable-watch/global_actions_item_red_background_shape.xml b/core/res/res/drawable-watch/global_actions_item_red_background_shape.xml index 4f23700a9014..b85e01dd2e06 100644 --- a/core/res/res/drawable-watch/global_actions_item_red_background_shape.xml +++ b/core/res/res/drawable-watch/global_actions_item_red_background_shape.xml @@ -18,5 +18,5 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <corners android:radius="26dp"/> - <solid android:color="@color/wear_material_red_mid"/> + <solid android:color="?attr/colorError"/> </shape>
\ No newline at end of file diff --git a/core/res/res/layout-watch/global_actions_item.xml b/core/res/res/layout-watch/global_actions_item.xml index f964a4a53d4f..021c9abb81f6 100644 --- a/core/res/res/layout-watch/global_actions_item.xml +++ b/core/res/res/layout-watch/global_actions_item.xml @@ -36,7 +36,7 @@ android:textSize="15sp" android:letterSpacing="0.013" android:fadingEdgeLength="12dp" - android:textColor="@android:color/white" + android:textColor="?attr/textColorPrimary" android:layout_weight="1" android:fontFamily="google-sans-text-medium" android:layout_width="wrap_content" diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 55122ce25ea1..d80cfa340dcb 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3030,15 +3030,15 @@ representation this attribute can be used for providing such. --> <attr name="contentDescription" format="string" localization="suggested" /> - <!-- Sets the id of a view before which this one is visited in accessibility traversal. - A screen-reader must visit the content of this view before the content of the one - it precedes. + <!-- Sets the id of a view that screen readers are requested to visit after this view. + Requests that a screen-reader visits the content of this view before the content of the + one it precedes. This does nothing if either view is not important for accessibility. {@see android.view.View#setAccessibilityTraversalBefore(int)} --> <attr name="accessibilityTraversalBefore" format="integer" /> - <!-- Sets the id of a view after which this one is visited in accessibility traversal. - A screen-reader must visit the content of the other view before the content of - this one. + <!-- Sets the id of a view that screen readers are requested to visit before this view. + Requests that a screen-reader visits the content of the other view before the content + of this one. This does nothing if either view is not important for accessibility. {@see android.view.View#setAccessibilityTraversalAfter(int)} --> <attr name="accessibilityTraversalAfter" format="integer" /> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 7f66a8342939..d828f33ca514 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3798,7 +3798,7 @@ keyboard is connected --> <string name="show_ime">Keep it on screen while physical keyboard is active</string> <!-- Title of the physical keyboard category in the input method selector [CHAR LIMIT=30] --> - <string name="hardware">Show virtual keyboard</string> + <string name="hardware">Use on-screen keyboard</string> <!-- Title of the notification to prompt the user to configure physical keyboard settings. [CHAR LIMIT=NOTIF_TITLE] --> <string name="select_keyboard_layout_notification_title">Configure <xliff:g id="device_name" example="Foobar USB Keyboard">%s</xliff:g></string> diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml index f28da1faf770..3b099e8ccafc 100644 --- a/core/res/res/xml/sms_short_codes.xml +++ b/core/res/res/xml/sms_short_codes.xml @@ -40,7 +40,7 @@ <shortcode country="al" pattern="\\d{5}" premium="15191|55[56]00" /> <!-- Argentina: 5 digits, known short codes listed --> - <shortcode country="ar" pattern="\\d{5}" free="11711|28291|44077" /> + <shortcode country="ar" pattern="\\d{5}" free="11711|28291|44077|78887" /> <!-- Armenia: 3-4 digits, emergency numbers 10[123] --> <shortcode country="am" pattern="\\d{3,4}" premium="11[2456]1|3024" free="10[123]" /> @@ -162,7 +162,7 @@ <shortcode country="jp" pattern="\\d{1,5}" free="8083" /> <!-- Kenya: 5 digits, known premium codes listed --> - <shortcode country="ke" pattern="\\d{5}" free="21725|21562|40520|23342" /> + <shortcode country="ke" pattern="\\d{5}" free="21725|21562|40520|23342|40023" /> <!-- Kyrgyzstan: 4 digits, known premium codes listed --> <shortcode country="kg" pattern="\\d{4}" premium="415[2367]|444[69]" /> @@ -208,7 +208,7 @@ <shortcode country="nz" pattern="\\d{3,4}" premium="3903|8995|4679" free="1737|176|2141|3067|3068|3110|4006|4053|4061|4062|4202|4300|4334|4412|4575|5626|8006|8681" /> <!-- Peru: 4-5 digits (not confirmed), known premium codes listed --> - <shortcode country="pe" pattern="\\d{4,5}" free="9963|40777" /> + <shortcode country="pe" pattern="\\d{4,5}" free="9963|40778" /> <!-- Philippines --> <shortcode country="ph" pattern="\\d{1,5}" free="2147|5495|5496" /> diff --git a/core/tests/coretests/res/xml/power_profile_test.xml b/core/tests/coretests/res/xml/power_profile_test.xml index 22571142a350..322ae05bc63e 100644 --- a/core/tests/coretests/res/xml/power_profile_test.xml +++ b/core/tests/coretests/res/xml/power_profile_test.xml @@ -19,12 +19,6 @@ <!-- This is the battery capacity in mAh --> <item name="battery.capacity">3000</item> - <!-- Number of cores each CPU cluster contains --> - <array name="cpu.clusters.cores"> - <value>4</value> <!-- Cluster 0 has 4 cores (cpu0, cpu1, cpu2, cpu3) --> - <value>4</value> <!-- Cluster 1 has 4 cores (cpu4, cpu5, cpu5, cpu7) --> - </array> - <!-- Power consumption when CPU is suspended --> <item name="cpu.suspend">5</item> <!-- Additional power consumption when CPU is in a kernel idle loop --> @@ -32,37 +26,21 @@ <!-- Additional power consumption by CPU excluding cluster and core when running --> <item name="cpu.active">2.55</item> - <!-- Additional power consumption by CPU cluster0 itself when running excluding cores in it --> - <item name="cpu.cluster_power.cluster0">2.11</item> - <!-- Additional power consumption by CPU cluster1 itself when running excluding cores in it --> - <item name="cpu.cluster_power.cluster1">2.22</item> - - <!-- Different CPU speeds as reported in - /sys/devices/system/cpu/cpu0/cpufreq/stats/scaling_available_frequencies --> - <array name="cpu.core_speeds.cluster0"> - <value>300000</value> <!-- 300 MHz CPU speed --> - <value>1000000</value> <!-- 1000 MHz CPU speed --> - <value>2000000</value> <!-- 2000 MHz CPU speed --> - </array> - <!-- Different CPU speeds as reported in - /sys/devices/system/cpu/cpu4/cpufreq/stats/scaling_available_frequencies --> - <array name="cpu.core_speeds.cluster1"> - <value>300000</value> <!-- 300 MHz CPU speed --> - <value>1000000</value> <!-- 1000 MHz CPU speed --> - <value>2500000</value> <!-- 2500 MHz CPU speed --> - <value>3000000</value> <!-- 3000 MHz CPU speed --> - </array> + <!-- Additional power consumption of CPU policy0 itself when running on related cores --> + <item name="cpu.scaling_policy_power.policy0">2.11</item> + <!-- Additional power consumption of CPU policy4 itself when running on related cores --> + <item name="cpu.scaling_policy_power.policy3">2.22</item> - <!-- Additional power used by a CPU from cluster 0 when running at different - speeds. Currently this measurement also includes cluster cost. --> - <array name="cpu.core_power.cluster0"> + <!-- Additional power used by a CPU related to policy3 when running at different + speeds. --> + <array name="cpu.scaling_step_power.policy0"> <value>10</value> <!-- 300 MHz CPU speed --> <value>20</value> <!-- 1000 MHz CPU speed --> <value>30</value> <!-- 1900 MHz CPU speed --> </array> - <!-- Additional power used by a CPU from cluster 1 when running at different - speeds. Currently this measurement also includes cluster cost. --> - <array name="cpu.core_power.cluster1"> + <!-- Additional power used by a CPU related to policy3 when running at different + speeds. --> + <array name="cpu.scaling_step_power.policy3"> <value>25</value> <!-- 300 MHz CPU speed --> <value>35</value> <!-- 1000 MHz CPU speed --> <value>50</value> <!-- 2500 MHz CPU speed --> diff --git a/core/tests/coretests/res/xml/power_profile_test_cpu_legacy.xml b/core/tests/coretests/res/xml/power_profile_test_cpu_legacy.xml new file mode 100644 index 000000000000..bd7d712b50a4 --- /dev/null +++ b/core/tests/coretests/res/xml/power_profile_test_cpu_legacy.xml @@ -0,0 +1,116 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<device name="Android"> + <!-- All values are in mAh except as noted. + This file is for PowerProfileTest.java. Changes must be synced between these two. Since + power_profile.xml may be overridden by actual device's power_profile.xml at compile time, + this test config ensures we have something constant to test against. Values below are + sample values, not meant to reflect any real device. + --> + + <!-- This is the battery capacity in mAh --> + <item name="battery.capacity">3000</item> + + <!-- Number of cores each CPU cluster contains --> + <array name="cpu.clusters.cores"> + <value>4</value> <!-- Cluster 0 has 4 cores (cpu0, cpu1, cpu2, cpu3) --> + <value>4</value> <!-- Cluster 1 has 4 cores (cpu4, cpu5, cpu5, cpu7) --> + </array> + + <!-- Power consumption when CPU is suspended --> + <item name="cpu.suspend">5</item> + <!-- Additional power consumption when CPU is in a kernel idle loop --> + <item name="cpu.idle">1.11</item> + <!-- Additional power consumption by CPU excluding cluster and core when running --> + <item name="cpu.active">2.55</item> + + <!-- Additional power consumption by CPU cluster0 itself when running excluding cores in it --> + <item name="cpu.cluster_power.cluster0">2.11</item> + <!-- Additional power consumption by CPU cluster1 itself when running excluding cores in it --> + <item name="cpu.cluster_power.cluster1">2.22</item> + + <!-- Different CPU speeds as reported in + /sys/devices/system/cpu/cpu0/cpufreq/stats/scaling_available_frequencies --> + <array name="cpu.core_speeds.cluster0"> + <value>300000</value> <!-- 300 MHz CPU speed --> + <value>1000000</value> <!-- 1000 MHz CPU speed --> + <value>2000000</value> <!-- 2000 MHz CPU speed --> + </array> + <!-- Different CPU speeds as reported in + /sys/devices/system/cpu/cpu4/cpufreq/stats/scaling_available_frequencies --> + <array name="cpu.core_speeds.cluster1"> + <value>300000</value> <!-- 300 MHz CPU speed --> + <value>1000000</value> <!-- 1000 MHz CPU speed --> + <value>2500000</value> <!-- 2500 MHz CPU speed --> + <value>3000000</value> <!-- 3000 MHz CPU speed --> + </array> + + <!-- Additional power used by a CPU from cluster 0 when running at different + speeds. Currently this measurement also includes cluster cost. --> + <array name="cpu.core_power.cluster0"> + <value>10</value> <!-- 300 MHz CPU speed --> + <value>20</value> <!-- 1000 MHz CPU speed --> + <value>30</value> <!-- 1900 MHz CPU speed --> + </array> + <!-- Additional power used by a CPU from cluster 1 when running at different + speeds. Currently this measurement also includes cluster cost. --> + <array name="cpu.core_power.cluster1"> + <value>25</value> <!-- 300 MHz CPU speed --> + <value>35</value> <!-- 1000 MHz CPU speed --> + <value>50</value> <!-- 2500 MHz CPU speed --> + <value>60</value> <!-- 3000 MHz CPU speed --> + </array> + + <!-- Power used by display unit in ambient display mode, including back lighting--> + <item name="ambient.on">0.5</item> + <!-- Additional power used when screen is turned on at minimum brightness --> + <item name="screen.on">100</item> + <!-- Additional power used when screen is at maximum brightness, compared to + screen at minimum brightness --> + <item name="screen.full">800</item> + + <!-- Average power used by the camera flash module when on --> + <item name="camera.flashlight">500</item> + <!-- Average power use by the camera subsystem for a typical camera + application. Intended as a rough estimate for an application running a + preview and capturing approximately 10 full-resolution pictures per + minute. --> + <item name="camera.avg">600</item> + + <!-- Additional power used by the audio hardware, probably due to DSP --> + <item name="audio">100.0</item> + + <!-- Additional power used by the video hardware, probably due to DSP --> + <item name="video">150.0</item> <!-- ~50mA --> + + <!-- Additional power used when GPS is acquiring a signal --> + <item name="gps.on">10</item> + + <!-- Additional power used when cellular radio is transmitting/receiving --> + <item name="radio.active">60</item> + <!-- Additional power used when cellular radio is paging the tower --> + <item name="radio.scanning">3</item> + <!-- Additional power used when the cellular radio is on. Multi-value entry, + one per signal strength (no signal, weak, moderate, strong) --> + <array name="radio.on"> <!-- Strength 0 to BINS-1 --> + <value>6</value> <!-- none --> + <value>5</value> <!-- poor --> + <value>4</value> <!-- moderate --> + <value>3</value> <!-- good --> + <value>3</value> <!-- great --> + </array> +</device> diff --git a/core/tests/coretests/res/xml/power_profile_test_power_brackets.xml b/core/tests/coretests/res/xml/power_profile_test_power_brackets.xml index c1293880de0a..a46c6083009c 100644 --- a/core/tests/coretests/res/xml/power_profile_test_power_brackets.xml +++ b/core/tests/coretests/res/xml/power_profile_test_power_brackets.xml @@ -17,34 +17,20 @@ <device name="Android"> - <array name="cpu.clusters.cores"> - <value>1</value> - <value>2</value> - </array> - - <array name="cpu.core_speeds.cluster0"> - <value>300000</value> - </array> - - <array name="cpu.core_speeds.cluster1"> - <value>300000</value> - <value>1000000</value> - </array> - - <array name="cpu.core_power.cluster0"> + <array name="cpu.scaling_step_power.policy0"> <value>10</value> </array> - <array name="cpu.core_power.cluster1"> + <array name="cpu.scaling_step_power.policy4"> <value>25</value> <value>35</value> </array> - <array name="cpu.power_brackets.cluster0"> + <array name="cpu.power_brackets.policy0"> <value>1</value> </array> - <array name="cpu.power_brackets.cluster1"> + <array name="cpu.power_brackets.policy4"> <value>1</value> <value>0</value> </array> diff --git a/core/tests/coretests/src/android/app/OWNERS b/core/tests/coretests/src/android/app/OWNERS index 8d9461d8035d..64f5e6c68a95 100644 --- a/core/tests/coretests/src/android/app/OWNERS +++ b/core/tests/coretests/src/android/app/OWNERS @@ -7,3 +7,6 @@ per-file *StatusBar* = file:/packages/SystemUI/OWNERS # A11Y and related per-file *UiAutomation* = file:/services/accessibility/OWNERS + +# KeyguardManagerTest +per-file KeyguardManagerTest.java = file:/services/core/java/com/android/server/locksettings/OWNERS diff --git a/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java b/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java index fb3a099b0ac8..c0fdf9f2d527 100644 --- a/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java +++ b/core/tests/coretests/src/android/os/vibrator/persistence/VibrationEffectXmlSerializationTest.java @@ -20,6 +20,7 @@ import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK; import static android.os.VibrationEffect.Composition.PRIMITIVE_LOW_TICK; import static android.os.VibrationEffect.Composition.PRIMITIVE_SPIN; import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK; +import static android.os.vibrator.persistence.VibrationXmlParser.isSupportedMimeType; import static com.google.common.truth.Truth.assertThat; @@ -50,6 +51,20 @@ import java.util.Map; public class VibrationEffectXmlSerializationTest { @Test + public void isSupportedMimeType_onlySupportsVibrationXmlMimeType() { + // Single MIME type supported + assertThat(isSupportedMimeType( + VibrationXmlParser.APPLICATION_VIBRATION_XML_MIME_TYPE)).isTrue(); + assertThat(isSupportedMimeType("application/vnd.android.haptics.vibration+xml")).isTrue(); + // without xml suffix not supported + assertThat(isSupportedMimeType("application/vnd.android.haptics.vibration")).isFalse(); + // different top-level not supported + assertThat(isSupportedMimeType("haptics/vnd.android.haptics.vibration+xml")).isFalse(); + // different type not supported + assertThat(isSupportedMimeType("application/vnd.android.vibration+xml")).isFalse(); + } + + @Test public void testPrimitives_allSucceed() throws IOException { VibrationEffect effect = VibrationEffect.startComposition() .addPrimitive(PRIMITIVE_CLICK) diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java index 925da4968517..0ebf03fab966 100644 --- a/core/tests/coretests/src/android/text/StaticLayoutTest.java +++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java @@ -24,7 +24,6 @@ import static org.junit.Assert.assertTrue; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.FontMetricsInt; -import android.graphics.text.LineBreakConfig; import android.os.LocaleList; import android.platform.test.annotations.Presubmit; import android.text.Layout.Alignment; @@ -926,24 +925,4 @@ public class StaticLayoutTest { assertEquals(0, layout.getHeight(true)); assertEquals(2, layout.getLineCount()); } - - @Test - public void testBuilder_autoPhraseBreaking() { - { - // setAutoPhraseBreaking true - LineBreakConfig lineBreakConfig = new LineBreakConfig.Builder() - .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_STYLE_NONE) - .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE) - .setAutoPhraseBreaking(true) - .build(); - final String text = "これが正解。"; - // Obtain. - StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, - text.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH); - builder.setLineBreakConfig(lineBreakConfig); - builder.build(); - assertEquals(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE, - builder.getLineBreakWordStyle()); - } - } } diff --git a/core/tests/coretests/src/android/util/LongSparseArrayTest.java b/core/tests/coretests/src/android/util/LongSparseArrayTest.java index bf3f0f50f4bf..247fe37b5e1f 100644 --- a/core/tests/coretests/src/android/util/LongSparseArrayTest.java +++ b/core/tests/coretests/src/android/util/LongSparseArrayTest.java @@ -51,4 +51,73 @@ public class LongSparseArrayTest { .isFalse(); } } + + @Test + public void firstIndexOnOrAfter() { + final LongSparseArray<Object> longSparseArray = new LongSparseArray<>(); + + // Values don't matter for this test. + longSparseArray.put(51, new Object()); + longSparseArray.put(10, new Object()); + longSparseArray.put(59, new Object()); + + assertThat(longSparseArray.size()).isEqualTo(3); + + // Testing any number arbitrarily smaller than 10. + assertThat(longSparseArray.firstIndexOnOrAfter(-141213)).isEqualTo(0); + for (long time = -43; time <= 10; time++) { + assertThat(longSparseArray.firstIndexOnOrAfter(time)).isEqualTo(0); + } + + for (long time = 11; time <= 51; time++) { + assertThat(longSparseArray.firstIndexOnOrAfter(time)).isEqualTo(1); + } + + for (long time = 52; time <= 59; time++) { + assertThat(longSparseArray.firstIndexOnOrAfter(time)).isEqualTo(2); + } + + for (long time = 60; time <= 102; time++) { + assertThat(longSparseArray.firstIndexOnOrAfter(time)).isEqualTo(3); + } + // Testing any number arbitrarily larger than 59. + assertThat(longSparseArray.firstIndexOnOrAfter(15332)).isEqualTo(3); + } + + @Test + public void lastIndexOnOrBefore() { + final LongSparseArray<Object> longSparseArray = new LongSparseArray<>(); + + // Values don't matter for this test. + longSparseArray.put(21, new Object()); + longSparseArray.put(4, new Object()); + longSparseArray.put(91, new Object()); + longSparseArray.put(39, new Object()); + + assertThat(longSparseArray.size()).isEqualTo(4); + + // Testing any number arbitrarily smaller than 4. + assertThat(longSparseArray.lastIndexOnOrBefore(-1478133)).isEqualTo(-1); + for (long time = -42; time < 4; time++) { + assertThat(longSparseArray.lastIndexOnOrBefore(time)).isEqualTo(-1); + } + + for (long time = 4; time < 21; time++) { + assertThat(longSparseArray.lastIndexOnOrBefore(time)).isEqualTo(0); + } + + for (long time = 21; time < 39; time++) { + assertThat(longSparseArray.lastIndexOnOrBefore(time)).isEqualTo(1); + } + + for (long time = 39; time < 91; time++) { + assertThat(longSparseArray.lastIndexOnOrBefore(time)).isEqualTo(2); + } + + for (long time = 91; time < 109; time++) { + assertThat(longSparseArray.lastIndexOnOrBefore(time)).isEqualTo(3); + } + // Testing any number arbitrarily larger than 91. + assertThat(longSparseArray.lastIndexOnOrBefore(1980732)).isEqualTo(3); + } } diff --git a/core/tests/coretests/src/android/util/TimeSparseArrayTest.java b/core/tests/coretests/src/android/util/TimeSparseArrayTest.java deleted file mode 100644 index c8e2364c48cd..000000000000 --- a/core/tests/coretests/src/android/util/TimeSparseArrayTest.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES 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.util; - -import static com.google.common.truth.Truth.assertThat; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Tests for {@link TimeSparseArray}. - * This class only tests subclass specific functionality. Tests for the super class - * {@link LongSparseArray} should be covered under {@link LongSparseArrayTest}. - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class TimeSparseArrayTest { - - @Test - public void closestIndexOnOrAfter() { - final TimeSparseArray<Object> timeSparseArray = new TimeSparseArray<>(); - - // Values don't matter for this test. - timeSparseArray.put(51, new Object()); - timeSparseArray.put(10, new Object()); - timeSparseArray.put(59, new Object()); - - assertThat(timeSparseArray.size()).isEqualTo(3); - - // Testing any number arbitrarily smaller than 10. - assertThat(timeSparseArray.closestIndexOnOrAfter(-141213)).isEqualTo(0); - for (long time = -43; time <= 10; time++) { - assertThat(timeSparseArray.closestIndexOnOrAfter(time)).isEqualTo(0); - } - - for (long time = 11; time <= 51; time++) { - assertThat(timeSparseArray.closestIndexOnOrAfter(time)).isEqualTo(1); - } - - for (long time = 52; time <= 59; time++) { - assertThat(timeSparseArray.closestIndexOnOrAfter(time)).isEqualTo(2); - } - - for (long time = 60; time <= 102; time++) { - assertThat(timeSparseArray.closestIndexOnOrAfter(time)).isEqualTo(3); - } - // Testing any number arbitrarily larger than 59. - assertThat(timeSparseArray.closestIndexOnOrAfter(15332)).isEqualTo(3); - } - - @Test - public void closestIndexOnOrBefore() { - final TimeSparseArray<Object> timeSparseArray = new TimeSparseArray<>(); - - // Values don't matter for this test. - timeSparseArray.put(21, new Object()); - timeSparseArray.put(4, new Object()); - timeSparseArray.put(91, new Object()); - timeSparseArray.put(39, new Object()); - - assertThat(timeSparseArray.size()).isEqualTo(4); - - // Testing any number arbitrarily smaller than 4. - assertThat(timeSparseArray.closestIndexOnOrBefore(-1478133)).isEqualTo(-1); - for (long time = -42; time < 4; time++) { - assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(-1); - } - - for (long time = 4; time < 21; time++) { - assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(0); - } - - for (long time = 21; time < 39; time++) { - assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(1); - } - - for (long time = 39; time < 91; time++) { - assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(2); - } - - for (long time = 91; time < 109; time++) { - assertThat(timeSparseArray.closestIndexOnOrBefore(time)).isEqualTo(3); - } - // Testing any number arbitrarily larger than 91. - assertThat(timeSparseArray.closestIndexOnOrBefore(1980732)).isEqualTo(3); - } -} diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java index b4ba23c92a22..69abf5f3204f 100644 --- a/core/tests/coretests/src/android/view/WindowInsetsTest.java +++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java @@ -40,14 +40,14 @@ public class WindowInsetsTest { @Test public void systemWindowInsets_afterConsuming_isConsumed() { assertTrue(new WindowInsets(WindowInsets.createCompatTypeMap(new Rect(1, 2, 3, 4)), null, - null, false, false, 0, null, null, null, null, + null, false, 0, 0, null, null, null, null, WindowInsets.Type.systemBars(), false) .consumeSystemWindowInsets().isConsumed()); } @Test public void multiNullConstructor_isConsumed() { - assertTrue(new WindowInsets(null, null, null, false, false, 0, null, null, null, null, + assertTrue(new WindowInsets(null, null, null, false, 0, 0, null, null, null, null, WindowInsets.Type.systemBars(), false).isConsumed()); } @@ -63,7 +63,7 @@ public class WindowInsetsTest { boolean[] visible = new boolean[SIZE]; WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0)); WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0)); - WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, false, + WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, 0, 0, null, null, null, DisplayShape.NONE, systemBars(), true /* compatIgnoreVisibility */); assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets()); diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java index 6d635af20645..3d4918b1bd42 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java @@ -58,7 +58,7 @@ public class AccessibilityNodeInfoTest { // The number of flags held in boolean properties. Their values should also be double-checked // in the methods above. - private static final int NUM_BOOLEAN_PROPERTIES = 26; + private static final int NUM_BOOLEAN_PROPERTIES = 27; @Test public void testStandardActions_serializationFlagIsValid() { diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java index 756888f3d91f..610b8aedc983 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java @@ -225,8 +225,16 @@ public class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon } @Override - public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) {} + public void attachAccessibilityOverlayToDisplay( + int interactionId, + int displayId, + SurfaceControl sc, + IAccessibilityInteractionConnectionCallback callback) {} @Override - public void attachAccessibilityOverlayToWindow(int accessibilityWindowId, SurfaceControl sc) {} + public void attachAccessibilityOverlayToWindow( + int interactionId, + int accessibilityWindowId, + SurfaceControl sc, + IAccessibilityInteractionConnectionCallback callback) {} } diff --git a/core/tests/coretests/src/com/android/internal/content/OWNERS b/core/tests/coretests/src/com/android/internal/content/OWNERS new file mode 100644 index 000000000000..dd9ede53239c --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/content/OWNERS @@ -0,0 +1,4 @@ + +per-file PackageMonitorTest.java = file:/core/java/android/content/pm/OWNERS + +per-file Overlay* = file:/core/java/android/content/res/OWNERS diff --git a/core/tests/coretests/src/com/android/internal/content/PackageMonitorTest.java b/core/tests/coretests/src/com/android/internal/content/PackageMonitorTest.java new file mode 100644 index 000000000000..5290478dfbd3 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/content/PackageMonitorTest.java @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.content; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Handler; +import android.os.UserHandle; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * A unit test for PackageMonitor implementation. + */ +@RunWith(AndroidJUnit4.class) +public class PackageMonitorTest { + + private static final String FAKE_PACKAGE_NAME = "com.android.internal.content.fakeapp"; + private static final int FAKE_PACKAGE_UID = 123; + private static final int FAKE_USER_ID = 0; + + @Mock + Context mMockContext; + @Mock + Handler mMockHandler; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testPackageMonitorMultipleRegisterThrowsException() throws Exception { + PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor()); + + spyPackageMonitor.register(mMockContext, UserHandle.ALL, false /* externalStorage */, + mMockHandler); + assertThat(spyPackageMonitor.getRegisteredHandler()).isEqualTo(mMockHandler); + verify(mMockContext, times(2)).registerReceiverAsUser(any(), eq(UserHandle.ALL), any(), + eq(null), eq(mMockHandler)); + + assertThrows(IllegalStateException.class, + () -> spyPackageMonitor.register(mMockContext, UserHandle.ALL, + false /* externalStorage */, mMockHandler)); + } + + @Test + public void testPackageMonitorRegisterMultipleUnRegisterThrowsException() throws Exception { + PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor()); + + spyPackageMonitor.register(mMockContext, UserHandle.ALL, false /* externalStorage */, + mMockHandler); + spyPackageMonitor.unregister(); + + assertThrows(IllegalStateException.class, spyPackageMonitor::unregister); + } + + @Test + public void testPackageMonitorNotRegisterUnRegisterThrowsException() throws Exception { + PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor()); + + assertThrows(IllegalStateException.class, spyPackageMonitor::unregister); + } + + @Test + public void testPackageMonitorDoHandlePackageEventUidRemoved() throws Exception { + PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor()); + + Intent intent = new Intent(Intent.ACTION_UID_REMOVED); + intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID); + intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID); + spyPackageMonitor.doHandlePackageEvent(intent); + + verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); + verify(spyPackageMonitor, times(1)).onUidRemoved(eq(FAKE_PACKAGE_UID)); + verify(spyPackageMonitor, times(1)).onFinishPackageChanges(); + } + + @Test + public void testPackageMonitorDoHandlePackageEventPackageSuspended() throws Exception { + PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor()); + + Intent intent = new Intent(Intent.ACTION_PACKAGES_SUSPENDED); + intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID); + String [] packageList = new String[]{FAKE_PACKAGE_NAME}; + intent.putExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST, packageList); + spyPackageMonitor.doHandlePackageEvent(intent); + + verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); + verify(spyPackageMonitor, times(1)).onPackagesSuspended(eq(packageList)); + verify(spyPackageMonitor, times(1)).onSomePackagesChanged(); + verify(spyPackageMonitor, times(1)).onFinishPackageChanges(); + } + + @Test + public void testPackageMonitorDoHandlePackageEventPackageUnSuspended() throws Exception { + PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor()); + + Intent intent = new Intent(Intent.ACTION_PACKAGES_UNSUSPENDED); + intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID); + String [] packageList = new String[]{FAKE_PACKAGE_NAME}; + intent.putExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST, packageList); + spyPackageMonitor.doHandlePackageEvent(intent); + + verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); + verify(spyPackageMonitor, times(1)).onPackagesUnsuspended(eq(packageList)); + verify(spyPackageMonitor, times(1)).onSomePackagesChanged(); + verify(spyPackageMonitor, times(1)).onFinishPackageChanges(); + } + + @Test + public void testPackageMonitorDoHandlePackageEventUserStop() throws Exception { + PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor()); + + Intent intent = new Intent(Intent.ACTION_USER_STOPPED); + intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID); + spyPackageMonitor.doHandlePackageEvent(intent); + + verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); + verify(spyPackageMonitor, times(1)).onHandleUserStop(eq(intent), eq(FAKE_USER_ID)); + verify(spyPackageMonitor, times(1)).onFinishPackageChanges(); + } + + @Test + public void testPackageMonitorDoHandlePackageEventExternalApplicationAvailable() + throws Exception { + PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor()); + + Intent intent = new Intent(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); + intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID); + String [] packageList = new String[]{FAKE_PACKAGE_NAME}; + intent.putExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST, packageList); + intent.putExtra(Intent.EXTRA_REPLACING, true); + spyPackageMonitor.doHandlePackageEvent(intent); + + verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); + verify(spyPackageMonitor, times(1)).onPackagesAvailable(eq(packageList)); + verify(spyPackageMonitor, times(1)).onPackageAppeared(eq(FAKE_PACKAGE_NAME), + eq(PackageMonitor.PACKAGE_UPDATING)); + verify(spyPackageMonitor, times(1)).onSomePackagesChanged(); + verify(spyPackageMonitor, times(1)).onFinishPackageChanges(); + } + + @Test + public void testPackageMonitorDoHandlePackageEventExternalApplicationUnavailable() + throws Exception { + PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor()); + + Intent intent = new Intent(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); + intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID); + String [] packageList = new String[]{FAKE_PACKAGE_NAME}; + intent.putExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST, packageList); + intent.putExtra(Intent.EXTRA_REPLACING, true); + spyPackageMonitor.doHandlePackageEvent(intent); + + verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); + verify(spyPackageMonitor, times(1)).onPackagesUnavailable(eq(packageList)); + verify(spyPackageMonitor, times(1)).onPackageDisappeared(eq(FAKE_PACKAGE_NAME), + eq(PackageMonitor.PACKAGE_UPDATING)); + verify(spyPackageMonitor, times(1)).onSomePackagesChanged(); + verify(spyPackageMonitor, times(1)).onFinishPackageChanges(); + } + + @Test + public void testPackageMonitorDoHandlePackageEventPackageRestarted() throws Exception { + PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor()); + + Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED); + intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID); + intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID); + intent.setData(Uri.fromParts("package", FAKE_PACKAGE_NAME, null)); + spyPackageMonitor.doHandlePackageEvent(intent); + + verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); + verify(spyPackageMonitor, times(1)).onHandleForceStop(eq(intent), + eq(new String[]{FAKE_PACKAGE_NAME}), eq(FAKE_PACKAGE_UID), eq(true)); + verify(spyPackageMonitor, times(1)).onFinishPackageChanges(); + } + + @Test + public void testPackageMonitorDoHandlePackageEventPackageQueryRestarted() throws Exception { + PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor()); + + Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART); + intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID); + String [] packageList = new String[]{FAKE_PACKAGE_NAME}; + intent.putExtra(Intent.EXTRA_PACKAGES, packageList); + intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID); + spyPackageMonitor.doHandlePackageEvent(intent); + + verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); + verify(spyPackageMonitor, times(1)).onHandleForceStop(eq(intent), + eq(packageList), eq(FAKE_PACKAGE_UID), eq(false)); + verify(spyPackageMonitor, times(1)).onFinishPackageChanges(); + } + + @Test + public void testPackageMonitorDoHandlePackageEventPackageDataClear() throws Exception { + PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor()); + + Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED); + intent.setData(Uri.fromParts("package", FAKE_PACKAGE_NAME, null)); + intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID); + intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID); + spyPackageMonitor.doHandlePackageEvent(intent); + + verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); + verify(spyPackageMonitor, times(1)) + .onPackageDataCleared(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID)); + verify(spyPackageMonitor, times(1)).onFinishPackageChanges(); + } + + @Test + public void testPackageMonitorDoHandlePackageEventPackageChanged() throws Exception { + PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor()); + + Intent intent = new Intent(Intent.ACTION_PACKAGE_CHANGED); + intent.setData(Uri.fromParts("package", FAKE_PACKAGE_NAME, null)); + intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID); + intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID); + String [] packageList = new String[]{FAKE_PACKAGE_NAME}; + intent.putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, packageList); + spyPackageMonitor.doHandlePackageEvent(intent); + + verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); + verify(spyPackageMonitor, times(1)) + .onPackageChanged(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID), eq(packageList)); + verify(spyPackageMonitor, times(1)).onPackageModified(eq(FAKE_PACKAGE_NAME)); + verify(spyPackageMonitor, times(1)).onSomePackagesChanged(); + verify(spyPackageMonitor, times(1)).onFinishPackageChanges(); + } + + @Test + public void testPackageMonitorDoHandlePackageEventPackageRemovedReplacing() throws Exception { + PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor()); + + Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED); + intent.setData(Uri.fromParts("package", FAKE_PACKAGE_NAME, null)); + intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID); + intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID); + intent.putExtra(Intent.EXTRA_REPLACING, true); + intent.putExtra(Intent.EXTRA_REMOVED_FOR_ALL_USERS, true); + spyPackageMonitor.doHandlePackageEvent(intent); + + verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); + verify(spyPackageMonitor, times(1)) + .onPackageUpdateStarted(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID)); + verify(spyPackageMonitor, times(1)) + .onPackageDisappeared(eq(FAKE_PACKAGE_NAME), eq(PackageMonitor.PACKAGE_UPDATING)); + verify(spyPackageMonitor, times(1)).onFinishPackageChanges(); + } + + @Test + public void testPackageMonitorDoHandlePackageEventPackageRemovedNotReplacing() + throws Exception { + PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor()); + + Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED); + intent.setData(Uri.fromParts("package", FAKE_PACKAGE_NAME, null)); + intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID); + intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID); + intent.putExtra(Intent.EXTRA_REPLACING, false); + intent.putExtra(Intent.EXTRA_REMOVED_FOR_ALL_USERS, true); + spyPackageMonitor.doHandlePackageEvent(intent); + + verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); + verify(spyPackageMonitor, times(1)) + .onPackageRemoved(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID)); + verify(spyPackageMonitor, times(1)) + .onPackageRemovedAllUsers(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID)); + verify(spyPackageMonitor, times(1)).onPackageDisappeared(eq(FAKE_PACKAGE_NAME), + eq(PackageMonitor.PACKAGE_PERMANENT_CHANGE)); + verify(spyPackageMonitor, times(1)).onSomePackagesChanged(); + verify(spyPackageMonitor, times(1)).onFinishPackageChanges(); + } + + @Test + public void testPackageMonitorDoHandlePackageEventPackageAddReplacing() throws Exception { + PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor()); + + Intent intent = new Intent(Intent.ACTION_PACKAGE_ADDED); + intent.setData(Uri.fromParts("package", FAKE_PACKAGE_NAME, null)); + intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID); + intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID); + intent.putExtra(Intent.EXTRA_REPLACING, true); + spyPackageMonitor.doHandlePackageEvent(intent); + + verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); + verify(spyPackageMonitor, times(1)) + .onPackageUpdateFinished(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID)); + verify(spyPackageMonitor, times(1)).onPackageModified(eq(FAKE_PACKAGE_NAME)); + verify(spyPackageMonitor, times(1)) + .onPackageAppeared(eq(FAKE_PACKAGE_NAME), eq(PackageMonitor.PACKAGE_UPDATING)); + verify(spyPackageMonitor, times(1)).onSomePackagesChanged(); + verify(spyPackageMonitor, times(1)).onFinishPackageChanges(); + } + + @Test + public void testPackageMonitorDoHandlePackageEventPackageAddNotReplacing() throws Exception { + PackageMonitor spyPackageMonitor = spy(new TestPackageMonitor()); + + Intent intent = new Intent(Intent.ACTION_PACKAGE_ADDED); + intent.setData(Uri.fromParts("package", FAKE_PACKAGE_NAME, null)); + intent.putExtra(Intent.EXTRA_USER_HANDLE, FAKE_USER_ID); + intent.putExtra(Intent.EXTRA_UID, FAKE_PACKAGE_UID); + intent.putExtra(Intent.EXTRA_REPLACING, false); + spyPackageMonitor.doHandlePackageEvent(intent); + + verify(spyPackageMonitor, times(1)).onBeginPackageChanges(); + verify(spyPackageMonitor, times(1)) + .onPackageAdded(eq(FAKE_PACKAGE_NAME), eq(FAKE_PACKAGE_UID)); + verify(spyPackageMonitor, times(1)).onPackageAppeared(eq(FAKE_PACKAGE_NAME), + eq(PackageMonitor.PACKAGE_PERMANENT_CHANGE)); + verify(spyPackageMonitor, times(1)).onSomePackagesChanged(); + verify(spyPackageMonitor, times(1)).onFinishPackageChanges(); + } + + public static class TestPackageMonitor extends PackageMonitor { + } +} diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/OWNERS b/core/tests/coretests/src/com/android/internal/inputmethod/OWNERS index 5deb2ce8f24b..cbd94ba6b467 100644 --- a/core/tests/coretests/src/com/android/internal/inputmethod/OWNERS +++ b/core/tests/coretests/src/com/android/internal/inputmethod/OWNERS @@ -1 +1,2 @@ +# Bug component: 34867 include /core/java/android/view/inputmethod/OWNERS diff --git a/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java b/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java new file mode 100644 index 000000000000..7f054d136639 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/CpuScalingPolicyReaderTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.os; + +import static androidx.test.InstrumentationRegistry.getContext; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.os.FileUtils; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.File; +import java.io.IOException; + +@RunWith(AndroidJUnit4.class) +public class CpuScalingPolicyReaderTest { + private CpuScalingPolicyReader mCpuScalingPolicyReader; + + @Before + public void setup() throws IOException { + File testDir = getContext().getDir("test", Context.MODE_PRIVATE); + FileUtils.deleteContents(testDir); + + File policy0 = new File(testDir, "policy0"); + FileUtils.createDir(policy0); + FileUtils.stringToFile(new File(policy0, "related_cpus"), "0 2 7"); + FileUtils.stringToFile(new File(policy0, "scaling_available_frequencies"), "1234 9876"); + + File policy5 = new File(testDir, "policy5"); + FileUtils.createDir(policy5); + FileUtils.stringToFile(new File(policy5, "related_cpus"), "3 6\n"); + FileUtils.stringToFile(new File(policy5, "scaling_available_frequencies"), "1234 5678\n"); + FileUtils.stringToFile(new File(policy5, "scaling_boost_frequencies"), "9998 9999\n"); + + File policy7 = new File(testDir, "policy7"); + FileUtils.createDir(policy7); + FileUtils.stringToFile(new File(policy7, "related_cpus"), "8\n"); + FileUtils.stringToFile(new File(policy7, "cpuinfo_cur_freq"), "1000000"); + + File policy9 = new File(testDir, "policy9"); + FileUtils.createDir(policy9); + FileUtils.stringToFile(new File(policy9, "related_cpus"), "42"); + + File policy999 = new File(testDir, "policy999"); + FileUtils.createDir(policy999); + + mCpuScalingPolicyReader = new CpuScalingPolicyReader(testDir.getPath()); + } + + @Test + public void readFromSysFs() { + CpuScalingPolicies info = mCpuScalingPolicyReader.read(); + assertThat(info.getPolicies()).isEqualTo(new int[]{0, 5, 7, 9}); + assertThat(info.getRelatedCpus(0)).isEqualTo(new int[]{0, 2, 7}); + assertThat(info.getFrequencies(0)).isEqualTo(new int[]{1234, 9876}); + assertThat(info.getRelatedCpus(5)).isEqualTo(new int[]{3, 6}); + assertThat(info.getFrequencies(5)).isEqualTo(new int[]{1234, 5678, 9998, 9999}); + assertThat(info.getRelatedCpus(7)).isEqualTo(new int[]{8}); + assertThat(info.getFrequencies(7)).isEqualTo(new int[]{1000000}); + assertThat(info.getRelatedCpus(9)).isEqualTo(new int[]{42}); + assertThat(info.getFrequencies(9)).isEqualTo(new int[]{0}); // Unknown + assertThat(info.getScalingStepCount()).isEqualTo(8); + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java index c60a6d61ffa1..783f264d6772 100644 --- a/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java +++ b/core/tests/coretests/src/com/android/internal/os/KernelCpuUidFreqTimeReaderTest.java @@ -18,7 +18,6 @@ package com.android.internal.os; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; @@ -100,63 +99,6 @@ public class KernelCpuUidFreqTimeReaderTest { } @Test - public void testReadFreqs_perClusterTimesNotAvailable() throws Exception { - final long[][] freqs = { - {1, 12, 123, 1234}, - {1, 12, 123, 23, 123, 1234, 12345, 123456}, - {1, 12, 123, 23, 123, 1234, 12345, 123456, 12, 123, 12345}, - {1, 12, 123, 23, 2345, 234567} - }; - final int[] numClusters = {2, 2, 3, 1}; - final int[][] numFreqs = {{3, 6}, {4, 5}, {3, 5, 4}, {3}}; - for (int i = 0; i < freqs.length; ++i) { - mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(), - new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false); - setCpuClusterFreqs(numClusters[i], numFreqs[i]); - setFreqs(freqs[i]); - long[] actualFreqs = mReader.readFreqs(mPowerProfile); - assertArrayEquals(freqs[i], actualFreqs); - final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s", - Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i])); - assertFalse(errMsg, mReader.perClusterTimesAvailable()); - - // Verify that a second call won't re-read the freqs - clearFreqsAndData(); - actualFreqs = mReader.readFreqs(mPowerProfile); - assertArrayEquals(freqs[i], actualFreqs); - assertFalse(errMsg, mReader.perClusterTimesAvailable()); - } - } - - @Test - public void testReadFreqs_perClusterTimesAvailable() throws Exception { - final long[][] freqs = { - {1, 12, 123, 1234}, - {1, 12, 123, 23, 123, 1234, 12345, 123456}, - {1, 12, 123, 23, 123, 1234, 12345, 123456, 12, 123, 12345, 1234567} - }; - final int[] numClusters = {1, 2, 3}; - final int[][] numFreqs = {{4}, {3, 5}, {3, 5, 4}}; - for (int i = 0; i < freqs.length; ++i) { - mReader = new KernelCpuUidFreqTimeReader(mTestFile.getAbsolutePath(), - new KernelCpuProcStringReader(mTestFile.getAbsolutePath()), mBpfMapReader, false); - setCpuClusterFreqs(numClusters[i], numFreqs[i]); - setFreqs(freqs[i]); - long[] actualFreqs = mReader.readFreqs(mPowerProfile); - assertArrayEquals(freqs[i], actualFreqs); - final String errMsg = String.format("Freqs=%s, nClusters=%d, nFreqs=%s", - Arrays.toString(freqs[i]), numClusters[i], Arrays.toString(numFreqs[i])); - assertTrue(errMsg, mReader.perClusterTimesAvailable()); - - // Verify that a second call won't re-read the freqs - clearFreqsAndData(); - actualFreqs = mReader.readFreqs(mPowerProfile); - assertArrayEquals(freqs[i], actualFreqs); - assertTrue(errMsg, mReader.perClusterTimesAvailable()); - } - } - - @Test public void testReadDelta() throws Exception { final long[] freqs = {110, 123, 145, 167, 289, 997}; final long[][] times = increaseTime(new long[mUids.length][freqs.length]); @@ -170,8 +112,6 @@ public class KernelCpuUidFreqTimeReaderTest { // Verify that readDelta also reads the frequencies if not already available. clearFreqsAndData(); - long[] actualFreqs = mReader.readFreqs(mPowerProfile); - assertArrayEquals(freqs, actualFreqs); // Verify that a second call will only return deltas. mCallback.clear(); @@ -222,8 +162,6 @@ public class KernelCpuUidFreqTimeReaderTest { // Verify that readDelta also reads the frequencies if not already available. clearFreqsAndData(); - long[] actualFreqs = mReader.readFreqs(mPowerProfile); - assertArrayEquals(freqs, actualFreqs); // Verify that a second call should still return absolute values mCallback.clear(); diff --git a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java index 6a3d379363a8..8fa63760d231 100644 --- a/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java +++ b/core/tests/coretests/src/com/android/internal/os/PowerProfileTest.java @@ -30,6 +30,7 @@ import android.annotation.XmlRes; import android.content.Context; import android.content.res.Resources; import android.content.res.XmlResourceParser; +import android.util.SparseArray; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -71,18 +72,13 @@ public class PowerProfileTest { public void testPowerProfile() { mProfile.forceInitForTesting(mContext, R.xml.power_profile_test); - assertEquals(2, mProfile.getNumCpuClusters()); - assertEquals(4, mProfile.getNumCoresInCpuCluster(0)); - assertEquals(4, mProfile.getNumCoresInCpuCluster(1)); assertEquals(5.0, mProfile.getAveragePower(PowerProfile.POWER_CPU_SUSPEND)); assertEquals(1.11, mProfile.getAveragePower(PowerProfile.POWER_CPU_IDLE)); assertEquals(2.55, mProfile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE)); - assertEquals(2.11, mProfile.getAveragePowerForCpuCluster(0)); - assertEquals(2.22, mProfile.getAveragePowerForCpuCluster(1)); - assertEquals(3, mProfile.getNumSpeedStepsInCpuCluster(0)); - assertEquals(30.0, mProfile.getAveragePowerForCpuCore(0, 2)); - assertEquals(4, mProfile.getNumSpeedStepsInCpuCluster(1)); - assertEquals(60.0, mProfile.getAveragePowerForCpuCore(1, 3)); + assertEquals(2.11, mProfile.getAveragePowerForCpuScalingPolicy(0)); + assertEquals(2.22, mProfile.getAveragePowerForCpuScalingPolicy(3)); + assertEquals(30.0, mProfile.getAveragePowerForCpuScalingStep(0, 2)); + assertEquals(60.0, mProfile.getAveragePowerForCpuScalingStep(3, 3)); assertEquals(3000.0, mProfile.getBatteryCapacity()); assertEquals(0.5, mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 0)); @@ -130,6 +126,23 @@ public class PowerProfileTest { | ModemPowerProfile.MODEM_DRAIN_TYPE_TX | ModemPowerProfile.MODEM_TX_LEVEL_4)); } + @Test + public void testPowerProfile_legacyCpuConfig() { + // This power profile has per-cluster data, rather than per-policy + mProfile.forceInitForTesting(mContext, R.xml.power_profile_test_cpu_legacy); + + assertEquals(2.11, mProfile.getAveragePowerForCpuScalingPolicy(0)); + assertEquals(2.22, mProfile.getAveragePowerForCpuScalingPolicy(4)); + assertEquals(30.0, mProfile.getAveragePowerForCpuScalingStep(0, 2)); + assertEquals(60.0, mProfile.getAveragePowerForCpuScalingStep(4, 3)); + assertEquals(3000.0, mProfile.getBatteryCapacity()); + assertEquals(0.5, + mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_AMBIENT, 0)); + assertEquals(100.0, + mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_ON, 0)); + assertEquals(800.0, + mProfile.getAveragePowerForOrdinal(POWER_GROUP_DISPLAY_SCREEN_FULL, 0)); + } @Test public void testModemPowerProfile_defaultRat() throws Exception { @@ -524,15 +537,10 @@ public class PowerProfileTest { return null; } - private void assertEquals(int expected, int actual) { - Assert.assertEquals(expected, actual); - } - private void assertEquals(double expected, double actual) { Assert.assertEquals(expected, actual, 0.1); } - @Test public void powerBrackets_specifiedInPowerProfile() { mProfile.forceInitForTesting(mContext, R.xml.power_profile_test_power_brackets); @@ -541,31 +549,40 @@ public class PowerProfileTest { int cpuPowerBracketCount = mProfile.getCpuPowerBracketCount(); assertThat(cpuPowerBracketCount).isEqualTo(2); assertThat(new int[]{ - mProfile.getPowerBracketForCpuCore(0, 0), - mProfile.getPowerBracketForCpuCore(1, 0), - mProfile.getPowerBracketForCpuCore(1, 1), + mProfile.getCpuPowerBracketForScalingStep(0, 0), + mProfile.getCpuPowerBracketForScalingStep(4, 0), + mProfile.getCpuPowerBracketForScalingStep(4, 1), }).isEqualTo(new int[]{1, 1, 0}); } @Test public void powerBrackets_automatic() { mProfile.forceInitForTesting(mContext, R.xml.power_profile_test); + CpuScalingPolicies scalingPolicies = new CpuScalingPolicies( + new SparseArray<>() {{ + put(0, new int[]{0, 1, 2}); + put(3, new int[]{3, 4}); + }}, + new SparseArray<>() {{ + put(0, new int[]{300000, 1000000, 2000000}); + put(3, new int[]{300000, 1000000, 2500000, 3000000}); + }}); assertThat(mProfile.getCpuPowerBracketCount()).isEqualTo(3); - assertThat(mProfile.getCpuPowerBracketDescription(0)) + assertThat(mProfile.getCpuPowerBracketDescription(scalingPolicies, 0)) .isEqualTo("0/300(10.0)"); - assertThat(mProfile.getCpuPowerBracketDescription(1)) - .isEqualTo("0/1000(20.0), 0/2000(30.0), 1/300(25.0)"); - assertThat(mProfile.getCpuPowerBracketDescription(2)) - .isEqualTo("1/1000(35.0), 1/2500(50.0), 1/3000(60.0)"); + assertThat(mProfile.getCpuPowerBracketDescription(scalingPolicies, 1)) + .isEqualTo("0/1000(20.0), 0/2000(30.0), 3/300(25.0)"); + assertThat(mProfile.getCpuPowerBracketDescription(scalingPolicies, 2)) + .isEqualTo("3/1000(35.0), 3/2500(50.0), 3/3000(60.0)"); assertThat(new int[]{ - mProfile.getPowerBracketForCpuCore(0, 0), - mProfile.getPowerBracketForCpuCore(0, 1), - mProfile.getPowerBracketForCpuCore(0, 2), - mProfile.getPowerBracketForCpuCore(1, 0), - mProfile.getPowerBracketForCpuCore(1, 1), - mProfile.getPowerBracketForCpuCore(1, 2), - mProfile.getPowerBracketForCpuCore(1, 3), + mProfile.getCpuPowerBracketForScalingStep(0, 0), + mProfile.getCpuPowerBracketForScalingStep(0, 1), + mProfile.getCpuPowerBracketForScalingStep(0, 2), + mProfile.getCpuPowerBracketForScalingStep(3, 0), + mProfile.getCpuPowerBracketForScalingStep(3, 1), + mProfile.getCpuPowerBracketForScalingStep(3, 2), + mProfile.getCpuPowerBracketForScalingStep(3, 3), }).isEqualTo(new int[]{0, 1, 1, 1, 2, 2, 2}); } @@ -576,13 +593,13 @@ public class PowerProfileTest { assertThat(mProfile.getCpuPowerBracketCount()).isEqualTo(7); assertThat(new int[]{ - mProfile.getPowerBracketForCpuCore(0, 0), - mProfile.getPowerBracketForCpuCore(0, 1), - mProfile.getPowerBracketForCpuCore(0, 2), - mProfile.getPowerBracketForCpuCore(1, 0), - mProfile.getPowerBracketForCpuCore(1, 1), - mProfile.getPowerBracketForCpuCore(1, 2), - mProfile.getPowerBracketForCpuCore(1, 3), + mProfile.getCpuPowerBracketForScalingStep(0, 0), + mProfile.getCpuPowerBracketForScalingStep(0, 1), + mProfile.getCpuPowerBracketForScalingStep(0, 2), + mProfile.getCpuPowerBracketForScalingStep(3, 0), + mProfile.getCpuPowerBracketForScalingStep(3, 1), + mProfile.getCpuPowerBracketForScalingStep(3, 2), + mProfile.getCpuPowerBracketForScalingStep(3, 3), }).isEqualTo(new int[]{0, 1, 2, 3, 4, 5, 6}); } } diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java index a1a4265cd0a5..84dd2740e8b7 100644 --- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java +++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java @@ -169,7 +169,7 @@ public class ActionBarOverlayLayoutTest { private WindowInsets insetsWith(Insets content, DisplayCutout cutout) { return new WindowInsets(WindowInsets.createCompatTypeMap(content.toRect()), null, null, - false, false, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false); + false, 0, 0, cutout, null, null, null, WindowInsets.Type.systemBars(), false); } private ViewGroup createViewGroupWithId(int id) { diff --git a/data/keyboards/Vendor_0957_Product_0001.kl b/data/keyboards/Vendor_0957_Product_0001.kl index 5d7fd85fc178..1da3ba6efc2b 100644 --- a/data/keyboards/Vendor_0957_Product_0001.kl +++ b/data/keyboards/Vendor_0957_Product_0001.kl @@ -45,7 +45,7 @@ key 11 0 # custom keys key usage 0x000c01BB TV_INPUT -key usage 0x000c0186 MACRO_1 +key usage 0x000c0186 MACRO_1 WAKE key usage 0x000c0185 TV_TELETEXT key usage 0x000c0061 CAPTIONS diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index b9d3756ac6d2..a4c655c8ce55 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -782,10 +782,13 @@ public final class Bitmap implements Parcelable { @Nullable public static Bitmap wrapHardwareBuffer(@NonNull HardwareBuffer hardwareBuffer, @Nullable ColorSpace colorSpace) { - if ((hardwareBuffer.getUsage() & HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE) == 0) { + final long usage = hardwareBuffer.getUsage(); + if ((usage & HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE) == 0) { throw new IllegalArgumentException("usage flags must contain USAGE_GPU_SAMPLED_IMAGE."); } - int format = hardwareBuffer.getFormat(); + if ((usage & HardwareBuffer.USAGE_PROTECTED_CONTENT) != 0) { + throw new IllegalArgumentException("Bitmap is not compatible with protected buffers"); + } if (colorSpace == null) { colorSpace = ColorSpace.get(ColorSpace.Named.SRGB); } diff --git a/graphics/java/android/graphics/fonts/FontFamily.java b/graphics/java/android/graphics/fonts/FontFamily.java index bf79b1bedd8e..7cca7f19da7d 100644 --- a/graphics/java/android/graphics/fonts/FontFamily.java +++ b/graphics/java/android/graphics/fonts/FontFamily.java @@ -30,6 +30,7 @@ import dalvik.annotation.optimization.FastNative; import libcore.util.NativeAllocationRegistry; import java.util.ArrayList; +import java.util.Set; /** * A font family class can be used for creating Typeface. @@ -58,6 +59,7 @@ import java.util.ArrayList; * */ public final class FontFamily { + private static final String TAG = "FontFamily"; /** @@ -73,6 +75,7 @@ public final class FontFamily { // initial capacity. private final SparseIntArray mStyles = new SparseIntArray(4); + /** * Constructs a builder. * @@ -110,23 +113,63 @@ public final class FontFamily { } /** + * Build a variable font family that automatically adjust the `wght` and `ital` axes value + * for the requested weight/italic style values. + * + * To build a variable font family, added fonts must meet one of following conditions. + * + * If two font files are added, both font files must support `wght` axis and one font must + * support {@link FontStyle#FONT_SLANT_UPRIGHT} and another font must support + * {@link FontStyle#FONT_SLANT_ITALIC}. If the requested weight value is lower than minimum + * value of the supported `wght` axis, the minimum supported `wght` value is used. If the + * requested weight value is larger than maximum value of the supported `wght` axis, the + * maximum supported `wght` value is used. The weight values of the fonts are ignored. + * + * If one font file is added, that font must support the `wght` axis. If that font support + * `ital` axis, that `ital` value is set to 1 when the italic style is requested. If that + * font doesn't support `ital` axis, synthetic italic may be used. If the requested + * weight value is lower than minimum value of the supported `wght` axis, the minimum + * supported `wght` value is used. If the requested weight value is larger than maximum + * value of the supported `wght`axis, the maximum supported `wght` value is used. The weight + * value of the font is ignored. + * + * If none of the above conditions are met, this function return {@code null}. + * + * @return A variable font family. null if a variable font cannot be built from the given + * fonts. + */ + public @Nullable FontFamily buildVariableFamily() { + int variableFamilyType = analyzeAndResolveVariableType(mFonts); + if (variableFamilyType == VARIABLE_FONT_FAMILY_TYPE_UNKNOWN) { + return null; + } + return build("", FontConfig.FontFamily.VARIANT_DEFAULT, + true /* isCustomFallback */, + false /* isDefaultFallback */, + variableFamilyType); + } + + /** * Build the font family * @return a font family */ public @NonNull FontFamily build() { - return build("", FontConfig.FontFamily.VARIANT_DEFAULT, true /* isCustomFallback */, - false /* isDefaultFallback */); + return build("", FontConfig.FontFamily.VARIANT_DEFAULT, + true /* isCustomFallback */, + false /* isDefaultFallback */, + VARIABLE_FONT_FAMILY_TYPE_NONE); } /** @hide */ public @NonNull FontFamily build(@NonNull String langTags, int variant, - boolean isCustomFallback, boolean isDefaultFallback) { + boolean isCustomFallback, boolean isDefaultFallback, int variableFamilyType) { + final long builderPtr = nInitBuilder(); for (int i = 0; i < mFonts.size(); ++i) { nAddFont(builderPtr, mFonts.get(i).getNativePtr()); } final long ptr = nBuild(builderPtr, langTags, variant, isCustomFallback, - isDefaultFallback); + isDefaultFallback, variableFamilyType); final FontFamily family = new FontFamily(ptr); sFamilyRegistory.registerNativeAllocation(family, ptr); return family; @@ -136,11 +179,94 @@ public final class FontFamily { return font.getStyle().getWeight() | (font.getStyle().getSlant() << 16); } + /** + * @see #buildVariableFamily() + * @hide + */ + public static final int VARIABLE_FONT_FAMILY_TYPE_UNKNOWN = -1; + + /** + * @see #buildVariableFamily() + * @hide + */ + public static final int VARIABLE_FONT_FAMILY_TYPE_NONE = 0; + /** + * @see #buildVariableFamily() + * @hide + */ + public static final int VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY = 1; + /** + * @see #buildVariableFamily() + * @hide + */ + public static final int VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL = 2; + /** + * @see #buildVariableFamily() + * @hide + */ + public static final int VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT = 3; + + /** + * The registered italic axis used for adjusting requested style. + * https://learn.microsoft.com/en-us/typography/opentype/spec/dvaraxistag_ital + */ + private static final int TAG_ital = 0x6974616C; // i(0x69), t(0x74), a(0x61), l(0x6c) + + /** + * The registered weight axis used for adjusting requested style. + * https://learn.microsoft.com/en-us/typography/opentype/spec/dvaraxistag_wght + */ + private static final int TAG_wght = 0x77676874; // w(0x77), g(0x67), h(0x68), t(0x74) + + private static int analyzeAndResolveVariableType(ArrayList<Font> fonts) { + if (fonts.size() > 2) { + return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN; + } + + if (fonts.size() == 1) { + Font font = fonts.get(0); + Set<Integer> supportedAxes = + FontFileUtil.getSupportedAxes(font.getBuffer(), font.getTtcIndex()); + if (supportedAxes.contains(TAG_wght)) { + if (supportedAxes.contains(TAG_ital)) { + return VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ITAL; + } else { + return VARIABLE_FONT_FAMILY_TYPE_SINGLE_FONT_WGHT_ONLY; + } + } else { + return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN; + } + } else { + for (int i = 0; i < fonts.size(); ++i) { + Font font = fonts.get(i); + Set<Integer> supportedAxes = + FontFileUtil.getSupportedAxes(font.getBuffer(), font.getTtcIndex()); + if (!supportedAxes.contains(TAG_wght)) { + return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN; + } + } + boolean italic1 = fonts.get(0).getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC; + boolean italic2 = fonts.get(1).getStyle().getSlant() == FontStyle.FONT_SLANT_ITALIC; + + if (italic1 == italic2) { + return VARIABLE_FONT_FAMILY_TYPE_UNKNOWN; + } else { + if (italic1) { + // Swap fonts to make the first font upright, second font italic. + Font firstFont = fonts.get(0); + fonts.set(0, fonts.get(1)); + fonts.set(1, firstFont); + } + return VARIABLE_FONT_FAMILY_TYPE_TWO_FONTS_WGHT; + } + } + } + private static native long nInitBuilder(); @CriticalNative private static native void nAddFont(long builderPtr, long fontPtr); private static native long nBuild(long builderPtr, String langTags, int variant, - boolean isCustomFallback, boolean isDefaultFallback); + boolean isCustomFallback, boolean isDefaultFallback, int variableFamilyType); @CriticalNative private static native long nGetReleaseNativeFamily(); } diff --git a/graphics/java/android/graphics/fonts/FontFileUtil.java b/graphics/java/android/graphics/fonts/FontFileUtil.java index 917eef2ffede..ff38282255f2 100644 --- a/graphics/java/android/graphics/fonts/FontFileUtil.java +++ b/graphics/java/android/graphics/fonts/FontFileUtil.java @@ -19,11 +19,14 @@ package android.graphics.fonts; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.util.ArraySet; import dalvik.annotation.optimization.FastNative; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.Collections; +import java.util.Set; /** * Provides a utility for font file operations. @@ -62,6 +65,7 @@ public class FontFileUtil { private static final int SFNT_VERSION_OTTO = 0x4F54544F; private static final int TTC_TAG = 0x74746366; private static final int OS2_TABLE_TAG = 0x4F532F32; + private static final int FVAR_TABLE_TAG = 0x66766172; private static final int ANALYZE_ERROR = 0xFFFFFFFF; @@ -200,6 +204,73 @@ public class FontFileUtil { } } + private static int getUInt16(ByteBuffer buffer, int offset) { + return ((int) buffer.getShort(offset)) & 0xFFFF; + } + + /** + * Returns supported axes of font + * + * @param buffer A buffer of the entire font file. + * @param index A font index in case of font collection. Must be 0 otherwise. + * @return set of supported axes tag. Returns empty set on error. + */ + public static Set<Integer> getSupportedAxes(@NonNull ByteBuffer buffer, int index) { + ByteOrder originalOrder = buffer.order(); + buffer.order(ByteOrder.BIG_ENDIAN); + try { + int fontFileOffset = 0; + int magicNumber = buffer.getInt(0); + if (magicNumber == TTC_TAG) { + // TTC file. + if (index >= buffer.getInt(8 /* offset to number of fonts in TTC */)) { + return Collections.EMPTY_SET; + } + fontFileOffset = buffer.getInt( + 12 /* offset to array of offsets of font files */ + 4 * index); + } + int sfntVersion = buffer.getInt(fontFileOffset); + + if (sfntVersion != SFNT_VERSION_1 && sfntVersion != SFNT_VERSION_OTTO) { + return Collections.EMPTY_SET; + } + + int numTables = buffer.getShort(fontFileOffset + 4 /* offset to number of tables */); + int fvarTableOffset = -1; + for (int i = 0; i < numTables; ++i) { + int tableOffset = fontFileOffset + 12 /* size of offset table */ + + i * 16 /* size of table record */; + if (buffer.getInt(tableOffset) == FVAR_TABLE_TAG) { + fvarTableOffset = buffer.getInt(tableOffset + 8 /* offset to the table */); + break; + } + } + + if (fvarTableOffset == -1) { + // Couldn't find OS/2 table. use regular style + return Collections.EMPTY_SET; + } + + if (buffer.getShort(fvarTableOffset) != 1 + || buffer.getShort(fvarTableOffset + 2) != 0) { + return Collections.EMPTY_SET; + } + + int axesArrayOffset = getUInt16(buffer, fvarTableOffset + 4); + int axisCount = getUInt16(buffer, fvarTableOffset + 8); + int axisSize = getUInt16(buffer, fvarTableOffset + 10); + + ArraySet<Integer> axes = new ArraySet<>(); + for (int i = 0; i < axisCount; ++i) { + axes.add(buffer.getInt(fvarTableOffset + axesArrayOffset + axisSize * i)); + } + + return axes; + } finally { + buffer.order(originalOrder); + } + } + @FastNative private static native long nGetFontRevision(@NonNull ByteBuffer buffer, @IntRange(from = 0) int index); diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java index 8fe28ae731b8..3fea65f42d4f 100644 --- a/graphics/java/android/graphics/fonts/SystemFonts.java +++ b/graphics/java/android/graphics/fonts/SystemFonts.java @@ -194,7 +194,7 @@ public final class SystemFonts { } } return b == null ? null : b.build(languageTags, variant, false /* isCustomFallback */, - isDefaultFallback); + isDefaultFallback, FontFamily.Builder.VARIABLE_FONT_FAMILY_TYPE_NONE); } private static void appendNamedFamilyList(@NonNull FontConfig.NamedFamilyList namedFamilyList, diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java index d0327159b04d..7b204f244bd8 100644 --- a/graphics/java/android/graphics/text/LineBreakConfig.java +++ b/graphics/java/android/graphics/text/LineBreakConfig.java @@ -18,6 +18,7 @@ package android.graphics.text; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -33,6 +34,32 @@ import java.util.Objects; public final class LineBreakConfig { /** + * No line break style is specified. + * + * This is a special value of line break style indicating no style value is specified. + * When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig} with + * {@link Builder#merge(LineBreakConfig)} function, the line break style of overridden config + * will be kept if the line break style of overriding config is + * {@link #LINE_BREAK_STYLE_UNSPECIFIED}. + * + * <pre> + * val override = LineBreakConfig.Builder() + * .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) + * .build(); // UNSPECIFIED if no setLineBreakStyle is called. + * val config = LineBreakConfig.Builder() + * .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT) + * .merge(override) + * .build() + * // Here, config has LINE_BREAK_STYLE_STRICT for line break config and + * // LINE_BREAK_WORD_STYLE_PHRASE for line break word style. + * </pre> + * + * This value is resolved to {@link #LINE_BREAK_STYLE_NONE} if this value is used for text + * layout/rendering. + */ + public static final int LINE_BREAK_STYLE_UNSPECIFIED = -1; + + /** * No line-break rules are used for line breaking. */ public static final int LINE_BREAK_STYLE_NONE = 0; @@ -56,12 +83,38 @@ public final class LineBreakConfig { /** @hide */ @IntDef(prefix = { "LINE_BREAK_STYLE_" }, value = { LINE_BREAK_STYLE_NONE, LINE_BREAK_STYLE_LOOSE, LINE_BREAK_STYLE_NORMAL, - LINE_BREAK_STYLE_STRICT + LINE_BREAK_STYLE_STRICT, LINE_BREAK_STYLE_UNSPECIFIED }) @Retention(RetentionPolicy.SOURCE) public @interface LineBreakStyle {} /** + * No line break word style is specified. + * + * This is a special value of line break word style indicating no style value is specified. + * When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig} with + * {@link Builder#merge(LineBreakConfig)} function, the line break word style of overridden + * config will be kept if the line break word style of overriding config is + * {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}. + * + * <pre> + * val override = LineBreakConfig.Builder() + * .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT) + * .build(); // UNSPECIFIED if no setLineBreakWordStyle is called. + * val config = LineBreakConfig.Builder() + * .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) + * .merge(override) + * .build() + * // Here, config has LINE_BREAK_STYLE_STRICT for line break config and + * // LINE_BREAK_WORD_STYLE_PHRASE for line break word style. + * </pre> + * + * This value is resolved to {@link #LINE_BREAK_WORD_STYLE_NONE} if this value is used for + * text layout/rendering. + */ + public static final int LINE_BREAK_WORD_STYLE_UNSPECIFIED = -1; + + /** * No line-break word style is used for line breaking. */ public static final int LINE_BREAK_WORD_STYLE_NONE = 0; @@ -78,7 +131,7 @@ public final class LineBreakConfig { /** @hide */ @IntDef(prefix = { "LINE_BREAK_WORD_STYLE_" }, value = { - LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE + LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE, LINE_BREAK_WORD_STYLE_UNSPECIFIED }) @Retention(RetentionPolicy.SOURCE) public @interface LineBreakWordStyle {} @@ -88,26 +141,80 @@ public final class LineBreakConfig { */ public static final class Builder { // The line break style for the LineBreakConfig. - private @LineBreakStyle int mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE; + private @LineBreakStyle int mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_UNSPECIFIED; // The line break word style for the LineBreakConfig. private @LineBreakWordStyle int mLineBreakWordStyle = - LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; - - // Whether or not enabling phrase breaking automatically. - // TODO(b/226012260): Remove this and add LINE_BREAK_WORD_STYLE_PHRASE_AUTO after - // the experiment. - private boolean mAutoPhraseBreaking = false; + LineBreakConfig.LINE_BREAK_WORD_STYLE_UNSPECIFIED; /** * Builder constructor. */ public Builder() { + reset(null); + } + + /** + * Merges line break config with other config + * + * Update the internal configurations with passed {@code config}. If the config values of + * passed {@code config} are unspecified, the original config values are kept. For example, + * the following code passes {@code config} that has {@link #LINE_BREAK_STYLE_UNSPECIFIED}. + * This code generates {@link LineBreakConfig} that has line break config + * {@link #LINE_BREAK_STYLE_STRICT}. + * + * <pre> + * val override = LineBreakConfig.Builder() + * .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) + * .build(); // UNSPECIFIED if no setLineBreakStyle is called. + * val config = LineBreakConfig.Builder() + * .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT) + * .merge(override) + * .build() + * // Here, config has LINE_BREAK_STYLE_STRICT of line break config and + * // LINE_BREAK_WORD_STYLE_PHRASE of line break word style. + * </pre> + * + * @see #LINE_BREAK_STYLE_UNSPECIFIED + * @see #LINE_BREAK_WORD_STYLE_UNSPECIFIED + * + * @param config an override line break config + * @return This {@code Builder}. + */ + public @NonNull Builder merge(@NonNull LineBreakConfig config) { + if (config.mLineBreakStyle != LINE_BREAK_STYLE_UNSPECIFIED) { + mLineBreakStyle = config.mLineBreakStyle; + } + if (config.mLineBreakWordStyle != LINE_BREAK_WORD_STYLE_UNSPECIFIED) { + mLineBreakWordStyle = config.mLineBreakWordStyle; + } + return this; + } + + /** + * Resets this builder to the given config state. + * + * @return This {@code Builder}. + * @hide + */ + public @NonNull Builder reset(@Nullable LineBreakConfig config) { + if (config == null) { + mLineBreakStyle = LINE_BREAK_STYLE_UNSPECIFIED; + mLineBreakWordStyle = LINE_BREAK_WORD_STYLE_UNSPECIFIED; + } else { + mLineBreakStyle = config.mLineBreakStyle; + mLineBreakWordStyle = config.mLineBreakWordStyle; + } + return this; } /** * Sets the line-break style. * + * Note: different from {@link #merge(LineBreakConfig)} if this function is called with + * {@link #LINE_BREAK_STYLE_UNSPECIFIED}, the line break style is reset to + * {@link #LINE_BREAK_STYLE_UNSPECIFIED}. + * * @param lineBreakStyle The new line-break style. * @return This {@code Builder}. */ @@ -119,6 +226,10 @@ public final class LineBreakConfig { /** * Sets the line-break word style. * + * Note: different from {@link #merge(LineBreakConfig)} method, if this function is called + * with {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}, the line break style is reset to + * {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}. + * * @param lineBreakWordStyle The new line-break word style. * @return This {@code Builder}. */ @@ -128,22 +239,15 @@ public final class LineBreakConfig { } /** - * Enables or disables the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE}. - * - * @hide - */ - public @NonNull Builder setAutoPhraseBreaking(boolean autoPhraseBreaking) { - mAutoPhraseBreaking = autoPhraseBreaking; - return this; - } - - /** * Builds a {@link LineBreakConfig} instance. * + * This method can be called multiple times for generating multiple {@link LineBreakConfig} + * instances. + * * @return The {@code LineBreakConfig} instance. */ public @NonNull LineBreakConfig build() { - return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, mAutoPhraseBreaking); + return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle); } } @@ -164,23 +268,6 @@ public final class LineBreakConfig { .build(); } - /** - * Create the LineBreakConfig instance. - * - * @param lineBreakStyle the line break style for text wrapping. - * @param lineBreakWordStyle the line break word style for text wrapping. - * @return the {@link LineBreakConfig} instance. * - * @hide - */ - public static @NonNull LineBreakConfig getLineBreakConfig(@LineBreakStyle int lineBreakStyle, - @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) { - LineBreakConfig.Builder builder = new LineBreakConfig.Builder(); - return builder.setLineBreakStyle(lineBreakStyle) - .setLineBreakWordStyle(lineBreakWordStyle) - .setAutoPhraseBreaking(autoPhraseBreaking) - .build(); - } - /** @hide */ public static final LineBreakConfig NONE = new Builder().setLineBreakStyle(LINE_BREAK_STYLE_NONE) @@ -188,7 +275,6 @@ public final class LineBreakConfig { private final @LineBreakStyle int mLineBreakStyle; private final @LineBreakWordStyle int mLineBreakWordStyle; - private final boolean mAutoPhraseBreaking; /** * Constructor with line-break parameters. @@ -197,10 +283,9 @@ public final class LineBreakConfig { * {@code LineBreakConfig} instance. */ private LineBreakConfig(@LineBreakStyle int lineBreakStyle, - @LineBreakWordStyle int lineBreakWordStyle, boolean autoPhraseBreaking) { + @LineBreakWordStyle int lineBreakWordStyle) { mLineBreakStyle = lineBreakStyle; mLineBreakWordStyle = lineBreakWordStyle; - mAutoPhraseBreaking = autoPhraseBreaking; } /** @@ -213,6 +298,22 @@ public final class LineBreakConfig { } /** + * Gets the resolved line break style. + * + * This method never returns {@link #LINE_BREAK_STYLE_UNSPECIFIED}. + * + * @return The line break style. + * @hide + */ + public static @LineBreakStyle int getResolvedLineBreakStyle(@Nullable LineBreakConfig config) { + if (config == null) { + return LINE_BREAK_STYLE_NONE; + } + return config.mLineBreakStyle == LINE_BREAK_STYLE_UNSPECIFIED + ? LINE_BREAK_STYLE_NONE : config.mLineBreakStyle; + } + + /** * Gets the current line-break word style. * * @return The line-break word style to be used for text wrapping. @@ -222,14 +323,50 @@ public final class LineBreakConfig { } /** - * Used to identify if the automation of {@link LINE_BREAK_WORD_STYLE_PHRASE} is enabled. + * Gets the resolved line break style. * - * @return The result that records whether or not the automation of - * {@link LINE_BREAK_WORD_STYLE_PHRASE} is enabled. + * This method never returns {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}. + * + * @return The line break word style. * @hide */ - public boolean getAutoPhraseBreaking() { - return mAutoPhraseBreaking; + public static @LineBreakWordStyle int getResolvedLineBreakWordStyle( + @Nullable LineBreakConfig config) { + if (config == null) { + return LINE_BREAK_WORD_STYLE_NONE; + } + return config.mLineBreakWordStyle == LINE_BREAK_WORD_STYLE_UNSPECIFIED + ? LINE_BREAK_WORD_STYLE_NONE : config.mLineBreakWordStyle; + } + + /** + * Generates a new {@link LineBreakConfig} instance merged with given {@code config}. + * + * If values of passing {@code config} are unspecified, the original values are kept. For + * example, the following code shows how line break config is merged. + * + * <pre> + * val override = LineBreakConfig.Builder() + * .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE) + * .build(); // UNSPECIFIED if no setLineBreakStyle is called. + * val config = LineBreakConfig.Builder() + * .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT) + * .build(); + * + * val newConfig = config.merge(override) + * // newConfig has LINE_BREAK_STYLE_STRICT of line break style and + * LINE_BREAK_WORD_STYLE_PHRASE of line break word style. + * </pre> + * + * @param config an overriding config. + * @return newly created instance that is current style merged with passed config. + */ + public @NonNull LineBreakConfig merge(@NonNull LineBreakConfig config) { + return new LineBreakConfig( + config.mLineBreakStyle == LINE_BREAK_STYLE_UNSPECIFIED + ? mLineBreakStyle : config.mLineBreakStyle, + config.mLineBreakWordStyle == LINE_BREAK_WORD_STYLE_UNSPECIFIED + ? mLineBreakWordStyle : config.mLineBreakWordStyle); } @Override @@ -239,12 +376,19 @@ public final class LineBreakConfig { if (!(o instanceof LineBreakConfig)) return false; LineBreakConfig that = (LineBreakConfig) o; return (mLineBreakStyle == that.mLineBreakStyle) - && (mLineBreakWordStyle == that.mLineBreakWordStyle) - && (mAutoPhraseBreaking == that.mAutoPhraseBreaking); + && (mLineBreakWordStyle == that.mLineBreakWordStyle); } @Override public int hashCode() { return Objects.hash(mLineBreakStyle, mLineBreakWordStyle); } + + @Override + public String toString() { + return "LineBreakConfig{" + + "mLineBreakStyle=" + mLineBreakStyle + + ", mLineBreakWordStyle=" + mLineBreakWordStyle + + '}'; + } } diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java index 3f7f0880d160..fd08c8b13303 100644 --- a/graphics/java/android/graphics/text/MeasuredText.java +++ b/graphics/java/android/graphics/text/MeasuredText.java @@ -296,10 +296,8 @@ public class MeasuredText { Preconditions.checkArgument(length > 0, "length can not be negative"); final int end = mCurrentOffset + length; Preconditions.checkArgument(end <= mText.length, "Style exceeds the text length"); - int lbStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakStyle() : - LineBreakConfig.LINE_BREAK_STYLE_NONE; - int lbWordStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakWordStyle() : - LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE; + int lbStyle = LineBreakConfig.getResolvedLineBreakStyle(lineBreakConfig); + int lbWordStyle = LineBreakConfig.getResolvedLineBreakWordStyle(lineBreakConfig); nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle, mCurrentOffset, end, isRtl); mCurrentOffset = end; diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java index 8d20e9cee7d7..49e9d0cf8372 100644 --- a/graphics/java/android/graphics/text/PositionedGlyphs.java +++ b/graphics/java/android/graphics/text/PositionedGlyphs.java @@ -165,6 +165,68 @@ public final class PositionedGlyphs { } /** + * Returns true if the fake bold option used for drawing, otherwise false. + * + * @param index the glyph index + * @return true if the fake bold option is on, otherwise off. + */ + public boolean getFakeBold(@IntRange(from = 0) int index) { + Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); + return nGetFakeBold(mLayoutPtr, index); + } + + /** + * Returns true if the fake italic option used for drawing, otherwise false. + * + * @param index the glyph index + * @return true if the fake italic option is on, otherwise off. + */ + public boolean getFakeItalic(@IntRange(from = 0) int index) { + Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); + return nGetFakeItalic(mLayoutPtr, index); + } + + /** + * A special value returned by {@link #getWeightOverride(int)} and + * {@link #getItalicOverride(int)} that indicates no font variation setting is overridden. + */ + public static final float NO_OVERRIDE = Float.MIN_VALUE; + + /** + * Returns overridden weight value if the font is variable font and `wght` value is overridden + * for drawing. Otherwise returns {@link #NO_OVERRIDE}. + * + * @param index the glyph index + * @return overridden weight value or {@link #NO_OVERRIDE}. + */ + public float getWeightOverride(@IntRange(from = 0) int index) { + Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); + float value = nGetWeightOverride(mLayoutPtr, index); + if (value == -1) { + return NO_OVERRIDE; + } else { + return value; + } + } + + /** + * Returns overridden italic value if the font is variable font and `ital` value is overridden + * for drawing. Otherwise returns {@link #NO_OVERRIDE}. + * + * @param index the glyph index + * @return overridden weight value or {@link #NO_OVERRIDE}. + */ + public float getItalicOverride(@IntRange(from = 0) int index) { + Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); + float value = nGetItalicOverride(mLayoutPtr, index); + if (value == -1) { + return NO_OVERRIDE; + } else { + return value; + } + } + + /** * Create single style layout from native result. * * @hide @@ -210,6 +272,14 @@ public final class PositionedGlyphs { private static native long nGetFont(long minikinLayout, int i); @CriticalNative private static native long nReleaseFunc(); + @CriticalNative + private static native boolean nGetFakeBold(long minikinLayout, int i); + @CriticalNative + private static native boolean nGetFakeItalic(long minikinLayout, int i); + @CriticalNative + private static native float nGetWeightOverride(long minikinLayout, int i); + @CriticalNative + private static native float nGetItalicOverride(long minikinLayout, int i); @Override public boolean equals(Object o) { diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index fe5432fcd9d8..fc5f7d6f399e 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -1351,7 +1351,9 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * the key, it is also irreversibly invalidated once a new biometric is enrolled or once\ * no more biometrics are enrolled, unless {@link * #setInvalidatedByBiometricEnrollment(boolean)} is used to allow validity after - * enrollment. Attempts to initialize cryptographic operations using such keys will throw + * enrollment, or {@code KeyProperties.AUTH_DEVICE_CREDENTIAL} is specified as part of + * the parameters to {@link #setUserAuthenticationParameters}. + * Attempts to initialize cryptographic operations using such keys will throw * {@link KeyPermanentlyInvalidatedException}.</li> * </ul> * diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 24aaa9b75ebe..73eb62ae47e9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -339,19 +339,36 @@ public class PipTransition extends PipTransitionController { } // This means an expand happened before enter-pip finished and we are now "merging" a // no-op transition that happens to match our exit-pip. + // Or that the keyguard is up and preventing the transition from applying, in which case we + // want to manually reset pip. (b/283783868) boolean cancelled = false; if (mPipAnimationController.getCurrentAnimator() != null) { mPipAnimationController.getCurrentAnimator().cancel(); + mPipAnimationController.resetAnimatorState(); cancelled = true; } + // Unset exitTransition AFTER cancel so that finishResize knows we are merging. mExitTransition = null; - if (!cancelled || aborted) return; + if (!cancelled) return; final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo(); if (taskInfo != null) { - startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(), - mPipBoundsState.getBounds(), mPipBoundsState.getBounds(), - new Rect(mExitDestinationBounds), Surface.ROTATION_0, null /* startT */); + if (aborted) { + // keyguard case - the transition got aborted, so we want to reset state and + // windowing mode before reapplying the resize transaction + sendOnPipTransitionFinished(TRANSITION_DIRECTION_LEAVE_PIP); + mPipOrganizer.onExitPipFinished(taskInfo); + + WindowContainerTransaction wct = new WindowContainerTransaction(); + mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP); + wct.setBounds(taskInfo.token, null); + mPipOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_LEAVE_PIP, false); + } else { + // merge case + startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(), + mPipBoundsState.getBounds(), mPipBoundsState.getBounds(), + new Rect(mExitDestinationBounds), Surface.ROTATION_0, null /* startT */); + } } mExitDestinationBounds.setEmpty(); mCurrentPipTaskToken = null; @@ -567,7 +584,16 @@ public class PipTransition extends PipTransitionController { mPipBoundsState.getDisplayBounds()); mFinishCallback = (wct, wctCB) -> { mPipOrganizer.onExitPipFinished(taskInfo); - if (!Transitions.SHELL_TRANSITIONS_ROTATION && toFullscreen) { + + // TODO(b/286346098): remove the OPEN app flicker completely + // not checking if we go to fullscreen helps avoid getting pip into an inconsistent + // state after the flicker occurs. This is a temp solution until flicker is removed. + if (!Transitions.SHELL_TRANSITIONS_ROTATION) { + // will help to debug the case when we are not exiting to fullscreen + if (!toFullscreen) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: startExitAnimation() not exiting to fullscreen", TAG); + } wct = wct != null ? wct : new WindowContainerTransaction(); wct.setBounds(pipTaskToken, null); mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP); @@ -831,7 +857,7 @@ public class PipTransition extends PipTransitionController { } final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); - final Rect currentBounds = taskInfo.configuration.windowConfiguration.getBounds(); + final Rect currentBounds = pipChange.getStartAbsBounds(); int rotationDelta = deltaRotation(startRotation, endRotation); Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( taskInfo.pictureInPictureParams, currentBounds, destinationBounds); @@ -1050,7 +1076,7 @@ public class PipTransition extends PipTransitionController { // When the PIP window is visible and being a part of the transition, such as display // rotation, we need to update its bounds and rounded corner. final SurfaceControl leash = pipChange.getLeash(); - final Rect destBounds = mPipBoundsState.getBounds(); + final Rect destBounds = mPipOrganizer.getCurrentOrAnimatingBounds(); final boolean isInPip = mPipTransitionState.isInPip(); mSurfaceTransactionHelper .crop(startTransaction, leash, destBounds) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java index 4a06d84ce90d..8c2879e0d816 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAccessibilityInteractionConnection.java @@ -383,6 +383,9 @@ public class PipAccessibilityInteractionConnection { } @Override - public void attachAccessibilityOverlayToWindow(SurfaceControl sc) {} + public void attachAccessibilityOverlayToWindow( + SurfaceControl sc, + int interactionId, + IAccessibilityInteractionConnectionCallback callback) {} } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 843e5af67af9..837f11803ab2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -567,7 +567,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { && taskInfo.configuration.windowConfiguration.isAlwaysOnTop()) { // Tasks that are always on top (e.g. bubbles), will handle their own transition // as they are on top of everything else. So cancel the merge here. - cancel("task #" + taskInfo.taskId + " is always_on_top"); + cancel(false /* toHome */, false /* withScreenshots */, + "task #" + taskInfo.taskId + " is always_on_top"); return; } final boolean isRootTask = taskInfo != null diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index acc1c5eb74b6..bf70d48e5801 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -2853,18 +2853,24 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + final ArrayMap<Integer, SurfaceControl> dismissingTasks = new ArrayMap<>(); + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + if (taskInfo == null) continue; + if (getStageOfTask(taskInfo) != null + || getSplitItemPosition(change.getLastParent()) != SPLIT_POSITION_UNDEFINED) { + dismissingTasks.put(taskInfo.taskId, change.getLeash()); + } + } + + if (shouldBreakPairedTaskInRecents(dismissReason)) { // Notify recents if we are exiting in a way that breaks the pair, and disable further // updates to splits in the recents until we enter split again mRecentTasks.ifPresent(recentTasks -> { - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); - if (taskInfo != null && (getStageOfTask(taskInfo) != null - || getSplitItemPosition(change.getLastParent()) - != SPLIT_POSITION_UNDEFINED)) { - recentTasks.removeSplitPair(taskInfo.taskId); - } + for (int i = dismissingTasks.keySet().size() - 1; i >= 0; --i) { + recentTasks.removeSplitPair(dismissingTasks.keyAt(i)); } }); } @@ -2882,6 +2888,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, t.hide(toStage == STAGE_TYPE_MAIN ? mSideStage.mRootLeash : mMainStage.mRootLeash); t.setPosition(toStage == STAGE_TYPE_MAIN ? mMainStage.mRootLeash : mSideStage.mRootLeash, 0, 0); + } else { + for (int i = dismissingTasks.keySet().size() - 1; i >= 0; --i) { + finishT.hide(dismissingTasks.valueAt(i)); + } } if (toStage == STAGE_TYPE_UNDEFINED) { @@ -2891,7 +2901,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } // Hide divider and dim layer on transition finished. - setDividerVisibility(false, finishT); + setDividerVisibility(false, t); finishT.hide(mMainStage.mDimLayer); finishT.hide(mSideStage.mDimLayer); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java index c964df1452e0..c2f15f6cba75 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java @@ -16,6 +16,7 @@ package com.android.wm.shell.startingsurface; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.graphics.Color.WHITE; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; @@ -77,6 +78,13 @@ public class TaskSnapshotWindow { @NonNull Runnable clearWindowHandler) { final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo; final int taskId = runningTaskInfo.taskId; + + // if we're in PIP we don't want to create the snapshot + if (runningTaskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, + "did not create taskSnapshot due to being in PIP"); + return null; + } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW, "create taskSnapshot surface for task: %d", taskId); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index a28ce55e8c94..d9edde16a863 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -25,6 +25,7 @@ import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; import static com.android.wm.shell.util.TransitionUtil.isOpeningType; @@ -500,6 +501,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, } } + mPipHandler.setEnterAnimationType(ANIM_TYPE_ALPHA); mPipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction, finishCB); // Dispatch the rest of the transition normally. This will most-likely be taken by diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index d8a88770072d..75659960bc32 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -27,6 +27,7 @@ import static android.view.WindowManager.TRANSIT_SLEEP; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.fixScale; +import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import static android.window.TransitionInfo.FLAG_IS_OCCLUDED; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP; @@ -728,11 +729,15 @@ public class Transitions implements RemoteCallable<Transitions>, final int changeSize = info.getChanges().size(); boolean taskChange = false; boolean transferStartingWindow = false; + int noAnimationBehindStartingWindow = 0; boolean allOccluded = changeSize > 0; for (int i = changeSize - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); taskChange |= change.getTaskInfo() != null; transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT); + if (change.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION)) { + noAnimationBehindStartingWindow++; + } if (!change.hasFlags(FLAG_IS_OCCLUDED)) { allOccluded = false; } @@ -740,9 +745,11 @@ public class Transitions implements RemoteCallable<Transitions>, // There does not need animation when: // A. Transfer starting window. Apply transfer starting window directly if there is no other // task change. Since this is an activity->activity situation, we can detect it by selecting - // transitions with only 2 changes where neither are tasks and one is a starting-window - // recipient. - if (!taskChange && transferStartingWindow && changeSize == 2 + // transitions with only 2 changes where + // 1. neither are tasks, and + // 2. one is a starting-window recipient, or all change is behind starting window. + if (!taskChange && (transferStartingWindow || noAnimationBehindStartingWindow == changeSize) + && changeSize == 2 // B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all // changes are underneath another change. || ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt index 27eaa40ee49b..fd56a6e49d3e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/SplitScreenUtils.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.flicker.splitscreen +package com.android.wm.shell.flicker import android.app.Instrumentation import android.graphics.Point @@ -40,8 +40,6 @@ import com.android.server.wm.flicker.helpers.NotificationAppHelper import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions import com.android.server.wm.flicker.testapp.ActivityOptions.SplitScreen.Primary -import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME -import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME import org.junit.Assert.assertNotNull internal object SplitScreenUtils { @@ -114,13 +112,12 @@ internal object SplitScreenUtils { } fun enterSplitViaIntent( - wmHelper: WindowManagerStateHelper, - primaryApp: StandardAppHelper, - secondaryApp: StandardAppHelper + wmHelper: WindowManagerStateHelper, + primaryApp: StandardAppHelper, + secondaryApp: StandardAppHelper ) { val stringExtras = mapOf(Primary.EXTRA_LAUNCH_ADJACENT to "true") - primaryApp.launchViaIntent(wmHelper, null, null, - stringExtras) + primaryApp.launchViaIntent(wmHelper, null, null, stringExtras) waitForSplitComplete(wmHelper, primaryApp, secondaryApp) } @@ -326,14 +323,14 @@ internal object SplitScreenUtils { dividerBar.drag( Point( if (dragToRight) { - displayBounds.width * 4 / 5 + displayBounds.right } else { - displayBounds.width * 1 / 5 + displayBounds.left }, if (dragToBottom) { - displayBounds.height * 4 / 5 + displayBounds.bottom } else { - displayBounds.height * 1 / 5 + displayBounds.top } ) ) diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt new file mode 100644 index 000000000000..6fe88cacbbc7 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/appcompat/QuickSwitchLauncherToLetterboxAppTest.kt @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.wm.shell.flicker.appcompat + +import android.platform.test.annotations.Postsubmit +import android.platform.test.annotations.RequiresDevice +import android.tools.common.flicker.assertions.FlickerTest +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.datatypes.Rect +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test quick switching to letterboxed app from launcher + * + * To run this test: `atest WMShellFlickerTestsOther:QuickSwitchLauncherToLetterboxAppTest` + * + * Actions: + * ``` + * Launch a letterboxed app + * Navigate home to show launcher + * Swipe right from the bottom of the screen to quick switch back to the app + * ``` + */ + +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +open class QuickSwitchLauncherToLetterboxAppTest(flicker: LegacyFlickerTest) : + BaseAppCompat(flicker) { + + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotationCheckEnabled(false) + + tapl.setExpectedRotation(flicker.scenario.startRotation.value) + + letterboxApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper + .StateSyncBuilder() + .withHomeActivityVisible() + .withWindowSurfaceDisappeared(letterboxApp) + .waitForAndVerify() + + startDisplayBounds = + wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found") + } + transitions { + tapl.workspace.quickSwitchToPreviousApp() + wmHelper + .StateSyncBuilder() + .withFullScreenApp(letterboxApp) + .withNavOrTaskBarVisible() + .withStatusBarVisible() + .waitForAndVerify() + } + teardown { letterboxApp.exit(wmHelper) } + } + + /** + * Checks that [letterboxApp] is the top window at the end of the transition once we have fully + * quick switched from the launcher back to the [letterboxApp]. + */ + @Postsubmit + @Test + fun endsWithAppBeingOnTop() { + flicker.assertWmEnd { this.isAppWindowOnTop(letterboxApp) } + } + + /** Checks that the transition starts with the home activity being tagged as visible. */ + @Postsubmit + @Test + fun startsWithHomeActivityFlaggedVisible() { + flicker.assertWmStart { this.isHomeActivityVisible() } + } + + /** + * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] windows + * filling/covering exactly display size + */ + @Postsubmit + @Test + fun startsWithLauncherWindowsCoverFullScreen() { + flicker.assertWmStart { + this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] layers + * filling/covering exactly the display size. + */ + @Postsubmit + @Test + fun startsWithLauncherLayersCoverFullScreen() { + flicker.assertLayersStart { + this.visibleRegion(ComponentNameMatcher.LAUNCHER).coversExactly(startDisplayBounds) + } + } + + /** + * Checks that the transition starts with the [ComponentNameMatcher.LAUNCHER] being the top + * window. + */ + @Postsubmit + @Test + fun startsWithLauncherBeingOnTop() { + flicker.assertWmStart { this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) } + } + + /** + * Checks that the transition ends with the home activity being flagged as not visible. By this + * point we should have quick switched away from the launcher back to the [letterboxApp]. + */ + @Postsubmit + @Test + fun endsWithHomeActivityFlaggedInvisible() { + flicker.assertWmEnd { this.isHomeActivityInvisible() } + } + + /** + * Checks that [letterboxApp]'s window starts off invisible and becomes visible at some point + * before the end of the transition and then stays visible until the end of the transition. + */ + @Postsubmit + @Test + fun appWindowBecomesAndStaysVisible() { + flicker.assertWm { + this.isAppWindowInvisible(letterboxApp) + .then() + .isAppWindowVisible(letterboxApp) } + } + + /** + * Checks that [letterboxApp]'s layer starts off invisible and becomes visible at some point + * before the end of the transition and then stays visible until the end of the transition. + */ + @Postsubmit + @Test + fun appLayerBecomesAndStaysVisible() { + flicker.assertLayers { this.isInvisible(letterboxApp).then().isVisible(letterboxApp) } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] window starts off visible and becomes + * invisible at some point before the end of the transition and then stays invisible until the + * end of the transition. + */ + @Postsubmit + @Test + fun launcherWindowBecomesAndStaysInvisible() { + flicker.assertWm { + this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) + .then() + .isAppWindowNotOnTop(ComponentNameMatcher.LAUNCHER) + } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] layer starts off visible and becomes + * invisible at some point before the end of the transition and then stays invisible until the + * end of the transition. + */ + @Postsubmit + @Test + fun launcherLayerBecomesAndStaysInvisible() { + flicker.assertLayers { + this.isVisible(ComponentNameMatcher.LAUNCHER) + .then() + .isInvisible(ComponentNameMatcher.LAUNCHER) + } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] window is visible at least until the app + * window is visible. Ensures that at any point, either the launcher or [letterboxApp] windows + * are at least partially visible. + */ + @Postsubmit + @Test + fun appWindowIsVisibleOnceLauncherWindowIsInvisible() { + flicker.assertWm { + this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER) + .then() + .isAppWindowVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isAppWindowVisible(letterboxApp) + } + } + + /** + * Checks that the [ComponentNameMatcher.LAUNCHER] layer is visible at least until the app layer + * is visible. Ensures that at any point, either the launcher or [letterboxApp] layers are at + * least partially visible. + */ + @Postsubmit + @Test + fun appLayerIsVisibleOnceLauncherLayerIsInvisible() { + flicker.assertLayers { + this.isVisible(ComponentNameMatcher.LAUNCHER) + .then() + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isVisible(letterboxApp) + } + } + + /** + * Checks that the [ComponentNameMatcher.LETTERBOX] layer is visible as soon as the + * [letterboxApp] layer is visible at the end of the transition once we have fully quick + * switched from the launcher back to the [letterboxApp]. + */ + @Postsubmit + @Test + fun appAndLetterboxLayersBothVisibleOnceLauncherIsInvisible() { + flicker.assertLayers { + this.isVisible(ComponentNameMatcher.LAUNCHER) + .then() + .isVisible(ComponentNameMatcher.SNAPSHOT, isOptional = true) + .then() + .isVisible(letterboxApp) + .isVisible(ComponentNameMatcher.LETTERBOX) } + } + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd() + + /** {@inheritDoc} */ + @Postsubmit + @Test + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + super.visibleLayersShownMoreThanOneConsecutiveEntry() + } + + companion object { + /** {@inheritDoc} */ + private var startDisplayBounds = Rect.EMPTY + + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<FlickerTest> { + return LegacyFlickerTestFactory.nonRotationTests( + supportedNavigationModes = listOf(NavBar.MODE_GESTURAL), + supportedRotations = listOf(Rotation.ROTATION_90) + ) + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt index 36bbafb4c05e..8a85374d0712 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipFromSplitScreenOnGoToHomeTest.kt @@ -16,12 +16,9 @@ package com.android.wm.shell.flicker.pip -import android.app.Instrumentation -import android.os.SystemClock import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.common.Rotation -import android.tools.device.apphelpers.StandardAppHelper import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest @@ -29,13 +26,9 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import android.tools.device.helpers.WindowUtils import android.tools.device.traces.parsers.toFlickerComponent import androidx.test.filters.RequiresDevice -import androidx.test.uiautomator.By -import androidx.test.uiautomator.BySelector -import androidx.test.uiautomator.UiObject2 -import androidx.test.uiautomator.Until import com.android.server.wm.flicker.helpers.SimpleAppHelper import com.android.server.wm.flicker.testapp.ActivityOptions -import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.Assume import org.junit.FixMethodOrder import org.junit.Test @@ -73,13 +66,11 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) : AutoEnterPipOnGoToHomeTest(flicker) { private val portraitDisplayBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0) /** Second app used to enter split screen mode */ - protected val secondAppForSplitScreen = getSplitScreenApp(instrumentation) - fun getSplitScreenApp(instrumentation: Instrumentation): StandardAppHelper = - SimpleAppHelper( - instrumentation, - ActivityOptions.SplitScreen.Primary.LABEL, - ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent() - ) + private val secondAppForSplitScreen = SimpleAppHelper( + instrumentation, + ActivityOptions.SplitScreen.Primary.LABEL, + ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent() + ) /** Defines the transition used to run the test */ override val transition: FlickerBuilder.() -> Unit @@ -88,14 +79,7 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) : secondAppForSplitScreen.launchViaIntent(wmHelper) pipApp.launchViaIntent(wmHelper) tapl.goHome() - enterSplitScreen() - // wait until split screen is established - wmHelper - .StateSyncBuilder() - .withWindowSurfaceAppeared(pipApp) - .withWindowSurfaceAppeared(secondAppForSplitScreen) - .withSplitDividerVisible() - .waitForAndVerify() + SplitScreenUtils.enterSplit(wmHelper, tapl, device, pipApp, secondAppForSplitScreen) pipApp.enableAutoEnterForPipActivity() } teardown { @@ -107,46 +91,6 @@ class AutoEnterPipFromSplitScreenOnGoToHomeTest(flicker: LegacyFlickerTest) : transitions { tapl.goHome() } } - // TODO(b/285400227) merge the code in a common utility - this is copied from SplitScreenUtils - private val TIMEOUT_MS = 3_000L - private val overviewSnapshotSelector: BySelector - get() = By.res(LAUNCHER_UI_PACKAGE_NAME, "snapshot") - private fun enterSplitScreen() { - // Note: The initial split position in landscape is different between tablet and phone. - // In landscape, tablet will let the first app split to right side, and phone will - // split to left side. - if (tapl.isTablet) { - // TAPL's currentTask on tablet is sometimes not what we expected if the overview - // contains more than 3 task views. We need to use uiautomator directly to find the - // second task to split. - tapl.workspace.switchToOverview().overviewActions.clickSplit() - val snapshots = - tapl.device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS) - if (snapshots == null || snapshots.size < 1) { - error("Fail to find a overview snapshot to split.") - } - - // Find the second task in the upper right corner in split select mode by sorting - // 'left' in descending order and 'top' in ascending order. - snapshots.sortWith { t1: UiObject2, t2: UiObject2 -> - t2.getVisibleBounds().left - t1.getVisibleBounds().left - } - snapshots.sortWith { t1: UiObject2, t2: UiObject2 -> - t1.getVisibleBounds().top - t2.getVisibleBounds().top - } - snapshots[0].click() - } else { - tapl.workspace - .switchToOverview() - .currentTask - .tapMenu() - .tapSplitMenuItem() - .currentTask - .open() - } - SystemClock.sleep(TIMEOUT_MS) - } - @Presubmit @Test override fun pipOverlayLayerAppearThenDisappear() { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt new file mode 100644 index 000000000000..76ad6b9bc49c --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/CopyContentInSplit.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class CopyContentInSplit +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + private val textEditApp = SplitScreenUtils.getIme(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp) + } + + @Test + open fun copyContentInSplit() { + SplitScreenUtils.copyContentInSplit(instrumentation, device, primaryApp, textEditApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt new file mode 100644 index 000000000000..25182b40a300 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByDivider.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class DismissSplitScreenByDivider +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + } + + @Test + open fun dismissSplitScreenByDivider() { + if (tapl.isTablet) { + SplitScreenUtils.dragDividerToDismissSplit( + device, + wmHelper, + dragToRight = false, + dragToBottom = true + ) + } else { + SplitScreenUtils.dragDividerToDismissSplit( + device, + wmHelper, + dragToRight = true, + dragToBottom = true + ) + } + wmHelper.StateSyncBuilder().withFullScreenApp(secondaryApp).waitForAndVerify() + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt new file mode 100644 index 000000000000..000b628b5ff6 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DismissSplitScreenByGoHome.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class DismissSplitScreenByGoHome +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + } + + @Test + open fun dismissSplitScreenByGoHome() { + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt new file mode 100644 index 000000000000..dd9ff3c7f64f --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/DragDividerToResize.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class DragDividerToResize +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + } + + @Test + open fun dragDividerToResize() { + SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt new file mode 100644 index 000000000000..4bbb9aa07911 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromAllApps.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenByDragFromAllApps +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + tapl.goHome() + primaryApp.launchViaIntent(wmHelper) + } + + @Test + open fun enterSplitScreenByDragFromAllApps() { + tapl.launchedAppState.taskbar + .openAllApps() + .getAppIcon(secondaryApp.appName) + .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt new file mode 100644 index 000000000000..a2b75267b662 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromNotification.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenByDragFromNotification +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + private val sendNotificationApp = SplitScreenUtils.getSendNotification(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + // Send a notification + sendNotificationApp.launchViaIntent(wmHelper) + sendNotificationApp.postNotification(wmHelper) + tapl.goHome() + primaryApp.launchViaIntent(wmHelper) + } + + @Test + open fun enterSplitScreenByDragFromNotification() { + SplitScreenUtils.dragFromNotificationToSplit(instrumentation, device, wmHelper) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, sendNotificationApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + sendNotificationApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt new file mode 100644 index 000000000000..1ccd8133c234 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromShortcut.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenByDragFromShortcut +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + Assume.assumeTrue(tapl.isTablet) + + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + tapl.goHome() + SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName) + primaryApp.launchViaIntent(wmHelper) + } + + @Test + open fun enterSplitScreenByDragFromShortcut() { + tapl.launchedAppState.taskbar + .getAppIcon(secondaryApp.appName) + .openDeepShortcutMenu() + .getMenuItem("Split Screen Secondary Activity") + .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + + // TODO: Do we want this check in here? Add to the other tests? + // flicker.splitScreenEntered( + // primaryApp, + // secondaryApp, + // fromOtherApp = false, + // appExistAtStart = false + // ) + } + + @After + fun teardwon() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt new file mode 100644 index 000000000000..664786b9e523 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenByDragFromTaskbar.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenByDragFromTaskbar +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + tapl.goHome() + SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName) + primaryApp.launchViaIntent(wmHelper) + } + + @Test + open fun enterSplitScreenByDragFromTaskbar() { + tapl.launchedAppState.taskbar + .getAppIcon(secondaryApp.appName) + .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt new file mode 100644 index 000000000000..88fd0841b174 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/EnterSplitScreenFromOverview.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class EnterSplitScreenFromOverview +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + primaryApp.launchViaIntent(wmHelper) + secondaryApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withHomeActivityVisible() + .waitForAndVerify() + } + + @Test + open fun enterSplitScreenFromOverview() { + SplitScreenUtils.splitFromOverview(tapl, device) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt new file mode 100644 index 000000000000..83a18e8d0b49 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SplitScreenUtils.kt @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.graphics.Point +import android.os.SystemClock +import android.platform.test.rule.NavigationModeRule +import android.platform.test.rule.PressHomeRule +import android.platform.test.rule.UnlockScreenRule +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.common.traces.component.IComponentMatcher +import android.tools.common.traces.component.IComponentNameMatcher +import android.tools.device.apphelpers.MessagingAppHelper +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.device.flicker.rules.ChangeDisplayOrientationRule +import android.tools.device.flicker.rules.LaunchAppRule +import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule +import android.tools.device.traces.parsers.WindowManagerStateHelper +import android.tools.device.traces.parsers.toFlickerComponent +import android.view.InputDevice +import android.view.MotionEvent +import android.view.ViewConfiguration +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.By +import androidx.test.uiautomator.BySelector +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.ImeAppHelper +import com.android.server.wm.flicker.helpers.NonResizeableAppHelper +import com.android.server.wm.flicker.helpers.NotificationAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME +import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME +import org.junit.Assert.assertNotNull +import org.junit.rules.RuleChain + +object SplitScreenUtils { + private const val TIMEOUT_MS = 3_000L + private const val DRAG_DURATION_MS = 1_000L + private const val NOTIFICATION_SCROLLER = "notification_stack_scroller" + private const val DIVIDER_BAR = "docked_divider_handle" + private const val OVERVIEW_SNAPSHOT = "snapshot" + private const val GESTURE_STEP_MS = 16L + private val LONG_PRESS_TIME_MS = ViewConfiguration.getLongPressTimeout() * 2L + private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#") + + private val notificationScrollerSelector: BySelector + get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER) + private val notificationContentSelector: BySelector + get() = By.text("Flicker Test Notification") + private val dividerBarSelector: BySelector + get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR) + private val overviewSnapshotSelector: BySelector + get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT) + + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + + fun testSetupRule(navigationMode: () -> NavBar, rotation: () -> Rotation): RuleChain { + return RuleChain.outerRule(UnlockScreenRule()) + .around( + NavigationModeRule( + navigationMode().value, + /* changeNavigationModeAfterTest */ false + ) + ) + .around( + LaunchAppRule(MessagingAppHelper(instrumentation), clearCacheAfterParsing = false) + ) + .around(RemoveAllTasksButHomeRule()) + .around( + ChangeDisplayOrientationRule( + rotation(), + resetOrientationAfterTest = false, + clearCacheAfterParsing = false + ) + ) + .around(PressHomeRule()) + } + + fun getPrimary(instrumentation: Instrumentation): StandardAppHelper = + SimpleAppHelper( + instrumentation, + ActivityOptions.SplitScreen.Primary.LABEL, + ActivityOptions.SplitScreen.Primary.COMPONENT.toFlickerComponent() + ) + + fun getSecondary(instrumentation: Instrumentation): StandardAppHelper = + SimpleAppHelper( + instrumentation, + ActivityOptions.SplitScreen.Secondary.LABEL, + ActivityOptions.SplitScreen.Secondary.COMPONENT.toFlickerComponent() + ) + + fun getNonResizeable(instrumentation: Instrumentation): NonResizeableAppHelper = + NonResizeableAppHelper(instrumentation) + + fun getSendNotification(instrumentation: Instrumentation): NotificationAppHelper = + NotificationAppHelper(instrumentation) + + fun getIme(instrumentation: Instrumentation): ImeAppHelper = ImeAppHelper(instrumentation) + + fun waitForSplitComplete( + wmHelper: WindowManagerStateHelper, + primaryApp: IComponentMatcher, + secondaryApp: IComponentMatcher, + ) { + wmHelper + .StateSyncBuilder() + .withWindowSurfaceAppeared(primaryApp) + .withWindowSurfaceAppeared(secondaryApp) + .withSplitDividerVisible() + .waitForAndVerify() + } + + fun enterSplit( + wmHelper: WindowManagerStateHelper, + tapl: LauncherInstrumentation, + device: UiDevice, + primaryApp: StandardAppHelper, + secondaryApp: StandardAppHelper + ) { + primaryApp.launchViaIntent(wmHelper) + secondaryApp.launchViaIntent(wmHelper) + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + splitFromOverview(tapl, device) + waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) { + // Note: The initial split position in landscape is different between tablet and phone. + // In landscape, tablet will let the first app split to right side, and phone will + // split to left side. + if (tapl.isTablet) { + // TAPL's currentTask on tablet is sometimes not what we expected if the overview + // contains more than 3 task views. We need to use uiautomator directly to find the + // second task to split. + tapl.workspace.switchToOverview().overviewActions.clickSplit() + val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS) + if (snapshots == null || snapshots.size < 1) { + error("Fail to find a overview snapshot to split.") + } + + // Find the second task in the upper right corner in split select mode by sorting + // 'left' in descending order and 'top' in ascending order. + snapshots.sortWith { t1: UiObject2, t2: UiObject2 -> + t2.getVisibleBounds().left - t1.getVisibleBounds().left + } + snapshots.sortWith { t1: UiObject2, t2: UiObject2 -> + t1.getVisibleBounds().top - t2.getVisibleBounds().top + } + snapshots[0].click() + } else { + tapl.workspace + .switchToOverview() + .currentTask + .tapMenu() + .tapSplitMenuItem() + .currentTask + .open() + } + SystemClock.sleep(TIMEOUT_MS) + } + + fun enterSplitViaIntent( + wmHelper: WindowManagerStateHelper, + primaryApp: StandardAppHelper, + secondaryApp: StandardAppHelper + ) { + val stringExtras = + mapOf(ActivityOptions.SplitScreen.Primary.EXTRA_LAUNCH_ADJACENT to "true") + primaryApp.launchViaIntent(wmHelper, null, null, stringExtras) + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + fun dragFromNotificationToSplit( + instrumentation: Instrumentation, + device: UiDevice, + wmHelper: WindowManagerStateHelper + ) { + val displayBounds = + wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace + ?: error("Display not found") + + // Pull down the notifications + device.swipe( + displayBounds.centerX(), + 5, + displayBounds.centerX(), + displayBounds.bottom, + 50 /* steps */ + ) + SystemClock.sleep(TIMEOUT_MS) + + // Find the target notification + val notificationScroller = + device.wait(Until.findObject(notificationScrollerSelector), TIMEOUT_MS) + ?: error("Unable to find view $notificationScrollerSelector") + var notificationContent = notificationScroller.findObject(notificationContentSelector) + + while (notificationContent == null) { + device.swipe( + displayBounds.centerX(), + displayBounds.centerY(), + displayBounds.centerX(), + displayBounds.centerY() - 150, + 20 /* steps */ + ) + notificationContent = notificationScroller.findObject(notificationContentSelector) + } + + // Drag to split + val dragStart = notificationContent.visibleCenter + val dragMiddle = Point(dragStart.x + 50, dragStart.y) + val dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4) + val downTime = SystemClock.uptimeMillis() + + touch(instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime, TIMEOUT_MS, dragStart) + // It needs a horizontal movement to trigger the drag + touchMove( + instrumentation, + downTime, + SystemClock.uptimeMillis(), + DRAG_DURATION_MS, + dragStart, + dragMiddle + ) + touchMove( + instrumentation, + downTime, + SystemClock.uptimeMillis(), + DRAG_DURATION_MS, + dragMiddle, + dragEnd + ) + // Wait for a while to start splitting + SystemClock.sleep(TIMEOUT_MS) + touch( + instrumentation, + MotionEvent.ACTION_UP, + downTime, + SystemClock.uptimeMillis(), + GESTURE_STEP_MS, + dragEnd + ) + SystemClock.sleep(TIMEOUT_MS) + } + + fun touch( + instrumentation: Instrumentation, + action: Int, + downTime: Long, + eventTime: Long, + duration: Long, + point: Point + ) { + val motionEvent = + MotionEvent.obtain(downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0) + motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN + instrumentation.uiAutomation.injectInputEvent(motionEvent, true) + motionEvent.recycle() + SystemClock.sleep(duration) + } + + fun touchMove( + instrumentation: Instrumentation, + downTime: Long, + eventTime: Long, + duration: Long, + from: Point, + to: Point + ) { + val steps: Long = duration / GESTURE_STEP_MS + var currentTime = eventTime + var currentX = from.x.toFloat() + var currentY = from.y.toFloat() + val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat() + val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat() + + for (i in 1..steps) { + val motionMove = + MotionEvent.obtain( + downTime, + currentTime, + MotionEvent.ACTION_MOVE, + currentX, + currentY, + 0 + ) + motionMove.source = InputDevice.SOURCE_TOUCHSCREEN + instrumentation.uiAutomation.injectInputEvent(motionMove, true) + motionMove.recycle() + + currentTime += GESTURE_STEP_MS + if (i == steps - 1) { + currentX = to.x.toFloat() + currentY = to.y.toFloat() + } else { + currentX += stepX + currentY += stepY + } + SystemClock.sleep(GESTURE_STEP_MS) + } + } + + fun createShortcutOnHotseatIfNotExist(tapl: LauncherInstrumentation, appName: String) { + tapl.workspace.deleteAppIcon(tapl.workspace.getHotseatAppIcon(0)) + val allApps = tapl.workspace.switchToAllApps() + allApps.freeze() + try { + allApps.getAppIcon(appName).dragToHotseat(0) + } finally { + allApps.unfreeze() + } + } + + fun dragDividerToResizeAndWait(device: UiDevice, wmHelper: WindowManagerStateHelper) { + val displayBounds = + wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace + ?: error("Display not found") + val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) + dividerBar.drag(Point(displayBounds.width * 1 / 3, displayBounds.height * 2 / 3), 200) + + wmHelper + .StateSyncBuilder() + .withWindowSurfaceDisappeared(SPLIT_DECOR_MANAGER) + .waitForAndVerify() + } + + fun dragDividerToDismissSplit( + device: UiDevice, + wmHelper: WindowManagerStateHelper, + dragToRight: Boolean, + dragToBottom: Boolean + ) { + val displayBounds = + wmHelper.currentState.layerState.displays.firstOrNull { !it.isVirtual }?.layerStackSpace + ?: error("Display not found") + val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) + dividerBar.drag( + Point( + if (dragToRight) { + displayBounds.width * 4 / 5 + } else { + displayBounds.width * 1 / 5 + }, + if (dragToBottom) { + displayBounds.height * 4 / 5 + } else { + displayBounds.height * 1 / 5 + } + ) + ) + } + + fun doubleTapDividerToSwitch(device: UiDevice) { + val dividerBar = device.wait(Until.findObject(dividerBarSelector), TIMEOUT_MS) + val interval = + (ViewConfiguration.getDoubleTapTimeout() + ViewConfiguration.getDoubleTapMinTime()) / 2 + dividerBar.click() + SystemClock.sleep(interval.toLong()) + dividerBar.click() + } + + fun copyContentInSplit( + instrumentation: Instrumentation, + device: UiDevice, + sourceApp: IComponentNameMatcher, + destinationApp: IComponentNameMatcher, + ) { + // Copy text from sourceApp + val textView = + device.wait( + Until.findObject(By.res(sourceApp.packageName, "SplitScreenTest")), + TIMEOUT_MS + ) + assertNotNull("Unable to find the TextView", textView) + textView.click(LONG_PRESS_TIME_MS) + + val copyBtn = device.wait(Until.findObject(By.text("Copy")), TIMEOUT_MS) + assertNotNull("Unable to find the copy button", copyBtn) + copyBtn.click() + + // Paste text to destinationApp + val editText = + device.wait( + Until.findObject(By.res(destinationApp.packageName, "plain_text_input")), + TIMEOUT_MS + ) + assertNotNull("Unable to find the EditText", editText) + editText.click(LONG_PRESS_TIME_MS) + + val pasteBtn = device.wait(Until.findObject(By.text("Paste")), TIMEOUT_MS) + assertNotNull("Unable to find the paste button", pasteBtn) + pasteBtn.click() + + // Verify text + if (!textView.text.contentEquals(editText.text)) { + error("Fail to copy content in split") + } + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt new file mode 100644 index 000000000000..e5501f4c6cd2 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchAppByDoubleTapDivider.kt @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.graphics.Point +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.helpers.WindowUtils +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchAppByDoubleTapDivider +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + tapl.workspace.switchToOverview().dismissAllTasks() + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + } + + @Test + open fun switchAppByDoubleTapDivider() { + SplitScreenUtils.doubleTapDividerToSwitch(device) + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() + + waitForLayersToSwitch(wmHelper) + waitForWindowsToSwitch(wmHelper) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } + + private fun waitForWindowsToSwitch(wmHelper: WindowManagerStateHelper) { + wmHelper + .StateSyncBuilder() + .add("appWindowsSwitched") { + val primaryAppWindow = + it.wmState.visibleWindows.firstOrNull { window -> + primaryApp.windowMatchesAnyOf(window) + } + ?: return@add false + val secondaryAppWindow = + it.wmState.visibleWindows.firstOrNull { window -> + secondaryApp.windowMatchesAnyOf(window) + } + ?: return@add false + + if (isLandscape(rotation)) { + return@add if (isTablet()) { + secondaryAppWindow.frame.right <= primaryAppWindow.frame.left + } else { + primaryAppWindow.frame.right <= secondaryAppWindow.frame.left + } + } else { + return@add if (isTablet()) { + primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top + } else { + primaryAppWindow.frame.bottom <= secondaryAppWindow.frame.top + } + } + } + .waitForAndVerify() + } + + private fun waitForLayersToSwitch(wmHelper: WindowManagerStateHelper) { + wmHelper + .StateSyncBuilder() + .add("appLayersSwitched") { + val primaryAppLayer = + it.layerState.visibleLayers.firstOrNull { window -> + primaryApp.layerMatchesAnyOf(window) + } + ?: return@add false + val secondaryAppLayer = + it.layerState.visibleLayers.firstOrNull { window -> + secondaryApp.layerMatchesAnyOf(window) + } + ?: return@add false + + val primaryVisibleRegion = primaryAppLayer.visibleRegion?.bounds ?: return@add false + val secondaryVisibleRegion = + secondaryAppLayer.visibleRegion?.bounds ?: return@add false + + if (isLandscape(rotation)) { + return@add if (isTablet()) { + secondaryVisibleRegion.right <= primaryVisibleRegion.left + } else { + primaryVisibleRegion.right <= secondaryVisibleRegion.left + } + } else { + return@add if (isTablet()) { + primaryVisibleRegion.bottom <= secondaryVisibleRegion.top + } else { + primaryVisibleRegion.bottom <= secondaryVisibleRegion.top + } + } + } + .waitForAndVerify() + } + + private fun isLandscape(rotation: Rotation): Boolean { + val displayBounds = WindowUtils.getDisplayBounds(rotation) + return displayBounds.width > displayBounds.height + } + + private fun isTablet(): Boolean { + val sizeDp: Point = device.displaySizeDp + val LARGE_SCREEN_DP_THRESHOLD = 600 + return sizeDp.x >= LARGE_SCREEN_DP_THRESHOLD && sizeDp.y >= LARGE_SCREEN_DP_THRESHOLD + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt new file mode 100644 index 000000000000..b3f1e87380e4 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromAnotherApp.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchBackToSplitFromAnotherApp +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + private val thirdApp = SplitScreenUtils.getNonResizeable(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + + thirdApp.launchViaIntent(wmHelper) + wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify() + } + + @Test + open fun switchBackToSplitFromAnotherApp() { + tapl.launchedAppState.quickSwitchToPreviousApp() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt new file mode 100644 index 000000000000..d1121162c267 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromHome.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchBackToSplitFromHome +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + + @Test + open fun switchBackToSplitFromHome() { + tapl.workspace.quickSwitchToPreviousApp() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt new file mode 100644 index 000000000000..9ab924ca46f6 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBackToSplitFromRecent.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchBackToSplitFromRecent +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + + tapl.goHome() + wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify() + } + + @Test + open fun switchBackToSplitFromRecent() { + tapl.workspace.switchToOverview().currentTask.open() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt new file mode 100644 index 000000000000..b694dfa7f384 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/SwitchBetweenSplitPairs.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class SwitchBetweenSplitPairs +@JvmOverloads +constructor(val rotation: Rotation = Rotation.ROTATION_0) { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + private val thirdApp = SplitScreenUtils.getIme(instrumentation) + private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation) + + @Rule + @JvmField + val testSetupRule = SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { rotation }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(rotation.value) + + SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp) + SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp) + SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp) + } + + @Test + open fun switchBetweenSplitPairs() { + tapl.launchedAppState.quickSwitchToPreviousApp() + SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp) + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + thirdApp.exit(wmHelper) + fourthApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt new file mode 100644 index 000000000000..f78b7881735a --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/scenarios/UnlockKeyguardToSplitScreen.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.wm.shell.flicker.service.splitscreen.scenarios + +import android.app.Instrumentation +import android.tools.common.NavBar +import android.tools.common.Rotation +import android.tools.device.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import org.junit.After +import org.junit.Before +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test + +@Ignore("Base Test Class") +abstract class UnlockKeyguardToSplitScreen { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + private val primaryApp = SplitScreenUtils.getPrimary(instrumentation) + private val secondaryApp = SplitScreenUtils.getSecondary(instrumentation) + + @Rule + @JvmField + val testSetupRule = + SplitScreenUtils.testSetupRule({ NavBar.MODE_GESTURAL }, { Rotation.ROTATION_0 }) + + @Before + fun setup() { + tapl.setEnableRotation(true) + tapl.setExpectedRotation(Rotation.ROTATION_0.value) + + SplitScreenUtils.enterSplitViaIntent(wmHelper, primaryApp, secondaryApp) + } + + @Test + open fun unlockKeyguardToSplitScreen() { + device.sleep() + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() + device.wakeUp() + device.pressMenu() + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() + } + + @After + fun teardown() { + primaryApp.exit(wmHelper) + secondaryApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt index 580b153421a4..d3434a5b18e5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt @@ -21,6 +21,7 @@ import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest import com.android.server.wm.flicker.helpers.setRotation import com.android.wm.shell.flicker.BaseBenchmarkTest +import com.android.wm.shell.flicker.SplitScreenUtils abstract class SplitScreenBase(flicker: LegacyFlickerTest) : BaseBenchmarkTest(flicker) { protected val context: Context = instrumentation.context diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt index d1ca9eac198d..9c68aa488d65 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/CopyContentInSplitBenchmark.kt @@ -25,7 +25,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt index 73acb1f0cc47..21ac7839c2f4 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByDividerBenchmark.kt @@ -25,7 +25,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenDismissed import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt index 86ffd2af6748..931bff6f97e5 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DismissSplitScreenByGoHomeBenchmark.kt @@ -25,7 +25,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenDismissed import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt index dfde3b669813..7fa2c0bba2be 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/DragDividerToResizeBenchmark.kt @@ -24,7 +24,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt index d13e4134a961..952051f62a92 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromAllAppsBenchmark.kt @@ -26,7 +26,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt index 1d4166922b13..1de1c0c5e91e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromNotificationBenchmark.kt @@ -26,7 +26,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt index b4bafa79cd48..929c7eab3105 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromShortcutBenchmark.kt @@ -26,7 +26,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt index da44ecdb9304..9f829c9dc46e 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenByDragFromTaskbarBenchmark.kt @@ -26,7 +26,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.Assume import org.junit.Before import org.junit.FixMethodOrder diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt index af06d6da2518..1d5518f319d8 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/EnterSplitScreenFromOverviewBenchmark.kt @@ -25,7 +25,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt index 23156b5d1628..a7fb93e9b645 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchAppByDoubleTapDividerBenchmark.kt @@ -28,7 +28,7 @@ import android.tools.device.helpers.WindowUtils import android.tools.device.traces.parsers.WindowManagerStateHelper import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt index 2d810d3e2631..8358aff00213 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromAnotherAppBenchmark.kt @@ -26,7 +26,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt index f6df1e42d1b7..b63c7659c894 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromHomeBenchmark.kt @@ -26,7 +26,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt index ba46bdcdad21..ce5a409b2756 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBackToSplitFromRecentBenchmark.kt @@ -26,7 +26,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitScreenEntered import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt index 0d871e500688..9821bfac7a74 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/SwitchBetweenSplitPairsBenchmark.kt @@ -24,7 +24,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.FixMethodOrder import org.junit.Test import org.junit.runner.RunWith @@ -64,8 +64,7 @@ open class SwitchBetweenSplitPairsBenchmark(override val flicker: LegacyFlickerT thisTransition(this) } - @PlatinumTest(focusArea = "sysui") - @Presubmit @Test open fun cujCompleted() {} + @PlatinumTest(focusArea = "sysui") @Presubmit @Test open fun cujCompleted() {} companion object { @Parameterized.Parameters(name = "{0}") diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt index 7952b7125a34..4fc4627093db 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/benchmark/UnlockKeyguardToSplitScreenBenchmark.kt @@ -23,7 +23,7 @@ import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory import androidx.test.filters.RequiresDevice import com.android.wm.shell.flicker.splitscreen.SplitScreenBase -import com.android.wm.shell.flicker.splitscreen.SplitScreenUtils +import com.android.wm.shell.flicker.SplitScreenUtils import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp index 69774cc6e133..af1668fbd7dd 100644 --- a/libs/hwui/jni/FontFamily.cpp +++ b/libs/hwui/jni/FontFamily.cpp @@ -89,7 +89,8 @@ static jlong FontFamily_create(CRITICAL_JNI_PARAMS_COMMA jlong builderPtr) { } std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create( builder->langId, builder->variant, std::move(builder->fonts), - true /* isCustomFallback */, false /* isDefaultFallback */); + true /* isCustomFallback */, false /* isDefaultFallback */, + minikin::VariationFamilyType::None); if (family->getCoverage().length() == 0) { return 0; } diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp index ee158ee4e316..1e392b14728d 100644 --- a/libs/hwui/jni/fonts/FontFamily.cpp +++ b/libs/hwui/jni/fonts/FontFamily.cpp @@ -60,7 +60,7 @@ static void FontFamily_Builder_addFont(CRITICAL_JNI_PARAMS_COMMA jlong builderPt // Regular JNI static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr, jstring langTags, jint variant, jboolean isCustomFallback, - jboolean isDefaultFallback) { + jboolean isDefaultFallback, jint variationFamilyType) { std::unique_ptr<NativeFamilyBuilder> builder(toBuilder(builderPtr)); uint32_t localeId; if (langTags == nullptr) { @@ -71,7 +71,8 @@ static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderP } std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create( localeId, static_cast<minikin::FamilyVariant>(variant), std::move(builder->fonts), - isCustomFallback, isDefaultFallback); + isCustomFallback, isDefaultFallback, + static_cast<minikin::VariationFamilyType>(variationFamilyType)); if (family->getCoverage().length() == 0) { // No coverage means minikin rejected given font for some reasons. jniThrowException(env, "java/lang/IllegalArgumentException", @@ -121,7 +122,7 @@ static jlong FontFamily_getFont(CRITICAL_JNI_PARAMS_COMMA jlong familyPtr, jint static const JNINativeMethod gFontFamilyBuilderMethods[] = { {"nInitBuilder", "()J", (void*)FontFamily_Builder_initBuilder}, {"nAddFont", "(JJ)V", (void*)FontFamily_Builder_addFont}, - {"nBuild", "(JLjava/lang/String;IZZ)J", (void*)FontFamily_Builder_build}, + {"nBuild", "(JLjava/lang/String;IZZI)J", (void*)FontFamily_Builder_build}, {"nGetReleaseNativeFamily", "()J", (void*)FontFamily_Builder_GetReleaseFunc}, }; diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp index d69a47c5b085..8c377b9f1829 100644 --- a/libs/hwui/jni/text/TextShaper.cpp +++ b/libs/hwui/jni/text/TextShaper.cpp @@ -148,6 +148,30 @@ static jfloat TextShaper_Result_getY(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i } // CriticalNative +static jboolean TextShaper_Result_getFakeBold(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->layout.getFakery(i).isFakeBold(); +} + +// CriticalNative +static jboolean TextShaper_Result_getFakeItalic(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->layout.getFakery(i).isFakeItalic(); +} + +// CriticalNative +static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->layout.getFakery(i).wghtAdjustment(); +} + +// CriticalNative +static jfloat TextShaper_Result_getItalicOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->layout.getFakery(i).italAdjustment(); +} + +// CriticalNative static jlong TextShaper_Result_getFont(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) { const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); std::shared_ptr<minikin::Font> fontRef = layout->layout.getFontRef(i); @@ -185,15 +209,19 @@ static const JNINativeMethod gMethods[] = { }; static const JNINativeMethod gResultMethods[] = { - { "nGetGlyphCount", "(J)I", (void*)TextShaper_Result_getGlyphCount }, - { "nGetTotalAdvance", "(J)F", (void*)TextShaper_Result_getTotalAdvance }, - { "nGetAscent", "(J)F", (void*)TextShaper_Result_getAscent }, - { "nGetDescent", "(J)F", (void*)TextShaper_Result_getDescent }, - { "nGetGlyphId", "(JI)I", (void*)TextShaper_Result_getGlyphId }, - { "nGetX", "(JI)F", (void*)TextShaper_Result_getX }, - { "nGetY", "(JI)F", (void*)TextShaper_Result_getY }, - { "nGetFont", "(JI)J", (void*)TextShaper_Result_getFont }, - { "nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc }, + {"nGetGlyphCount", "(J)I", (void*)TextShaper_Result_getGlyphCount}, + {"nGetTotalAdvance", "(J)F", (void*)TextShaper_Result_getTotalAdvance}, + {"nGetAscent", "(J)F", (void*)TextShaper_Result_getAscent}, + {"nGetDescent", "(J)F", (void*)TextShaper_Result_getDescent}, + {"nGetGlyphId", "(JI)I", (void*)TextShaper_Result_getGlyphId}, + {"nGetX", "(JI)F", (void*)TextShaper_Result_getX}, + {"nGetY", "(JI)F", (void*)TextShaper_Result_getY}, + {"nGetFont", "(JI)J", (void*)TextShaper_Result_getFont}, + {"nGetFakeBold", "(JI)Z", (void*)TextShaper_Result_getFakeBold}, + {"nGetFakeItalic", "(JI)Z", (void*)TextShaper_Result_getFakeItalic}, + {"nGetWeightOverride", "(JI)F", (void*)TextShaper_Result_getWeightOverride}, + {"nGetItalicOverride", "(JI)F", (void*)TextShaper_Result_getItalicOverride}, + {"nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc}, }; int register_android_graphics_text_TextShaper(JNIEnv* env) { diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp index 00919dc3f22a..f71e7289bd37 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.cpp +++ b/libs/hwui/pipeline/skia/ShaderCache.cpp @@ -79,7 +79,7 @@ bool ShaderCache::validateCache(const void* identity, ssize_t size) { void ShaderCache::initShaderDiskCache(const void* identity, ssize_t size) { ATRACE_NAME("initShaderDiskCache"); - std::lock_guard<std::mutex> lock(mMutex); + std::lock_guard lock(mMutex); // Emulators can switch between different renders either as part of config // or snapshot migration. Also, program binaries may not work well on some @@ -92,7 +92,7 @@ void ShaderCache::initShaderDiskCache(const void* identity, ssize_t size) { } void ShaderCache::setFilename(const char* filename) { - std::lock_guard<std::mutex> lock(mMutex); + std::lock_guard lock(mMutex); mFilename = filename; } @@ -104,7 +104,7 @@ BlobCache* ShaderCache::getBlobCacheLocked() { sk_sp<SkData> ShaderCache::load(const SkData& key) { ATRACE_NAME("ShaderCache::load"); size_t keySize = key.size(); - std::lock_guard<std::mutex> lock(mMutex); + std::lock_guard lock(mMutex); if (!mInitialized) { return nullptr; } @@ -181,13 +181,18 @@ void ShaderCache::saveToDiskLocked() { auto key = sIDKey; set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size()); } + // The most straightforward way to make ownership shared + mMutex.unlock(); + mMutex.lock_shared(); mBlobCache->writeToFile(); + mMutex.unlock_shared(); + mMutex.lock(); } } void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /*description*/) { ATRACE_NAME("ShaderCache::store"); - std::lock_guard<std::mutex> lock(mMutex); + std::lock_guard lock(mMutex); mNumShadersCachedInRam++; ATRACE_FORMAT("HWUI RAM cache: %d shaders", mNumShadersCachedInRam); @@ -229,7 +234,7 @@ void ShaderCache::store(const SkData& key, const SkData& data, const SkString& / mSavePending = true; std::thread deferredSaveThread([this]() { usleep(mDeferredSaveDelayMs * 1000); // milliseconds to microseconds - std::lock_guard<std::mutex> lock(mMutex); + std::lock_guard lock(mMutex); // Store file on disk if there a new shader or Vulkan pipeline cache size changed. if (mCacheDirty || mNewPipelineCacheSize != mOldPipelineCacheSize) { saveToDiskLocked(); @@ -245,11 +250,12 @@ void ShaderCache::store(const SkData& key, const SkData& data, const SkString& / void ShaderCache::onVkFrameFlushed(GrDirectContext* context) { { - std::lock_guard<std::mutex> lock(mMutex); - + mMutex.lock_shared(); if (!mInitialized || !mTryToStorePipelineCache) { + mMutex.unlock_shared(); return; } + mMutex.unlock_shared(); } mInStoreVkPipelineInProgress = true; context->storeVkPipelineCacheData(); diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h index 0492d70652df..c63a65ac1ed8 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.h +++ b/libs/hwui/pipeline/skia/ShaderCache.h @@ -19,8 +19,10 @@ #include <GrContextOptions.h> #include <SkRefCnt.h> #include <cutils/compiler.h> +#include <ftl/shared_mutex.h> +#include <utils/Mutex.h> + #include <memory> -#include <mutex> #include <string> #include <vector> @@ -100,20 +102,20 @@ private: * this will do so, loading the serialized cache contents from disk if * possible. */ - BlobCache* getBlobCacheLocked(); + BlobCache* getBlobCacheLocked() REQUIRES(mMutex); /** * "validateCache" updates the cache to match the given identity. If the * cache currently has the wrong identity, all entries in the cache are cleared. */ - bool validateCache(const void* identity, ssize_t size); + bool validateCache(const void* identity, ssize_t size) REQUIRES(mMutex); /** - * "saveToDiskLocked" attemps to save the current contents of the cache to + * "saveToDiskLocked" attempts to save the current contents of the cache to * disk. If the identity hash exists, we will insert the identity hash into * the cache for next validation. */ - void saveToDiskLocked(); + void saveToDiskLocked() REQUIRES(mMutex); /** * "mInitialized" indicates whether the ShaderCache is in the initialized @@ -123,7 +125,7 @@ private: * the load and store methods will return without performing any cache * operations. */ - bool mInitialized = false; + bool mInitialized GUARDED_BY(mMutex) = false; /** * "mBlobCache" is the cache in which the key/value blob pairs are stored. It @@ -132,7 +134,7 @@ private: * The blob cache contains the Android build number. We treat version mismatches as an empty * cache (logic implemented in BlobCache::unflatten). */ - std::unique_ptr<FileBlobCache> mBlobCache; + std::unique_ptr<FileBlobCache> mBlobCache GUARDED_BY(mMutex); /** * "mFilename" is the name of the file for storing cache contents in between @@ -141,7 +143,7 @@ private: * empty string indicates that the cache should not be saved to or restored * from disk. */ - std::string mFilename; + std::string mFilename GUARDED_BY(mMutex); /** * "mIDHash" is the current identity hash for the cache validation. It is @@ -150,7 +152,7 @@ private: * indicates that cache validation is not performed, and the hash should * not be stored on disk. */ - std::vector<uint8_t> mIDHash; + std::vector<uint8_t> mIDHash GUARDED_BY(mMutex); /** * "mSavePending" indicates whether or not a deferred save operation is @@ -160,7 +162,7 @@ private: * contents to disk, unless mDeferredSaveDelayMs is 0 in which case saving * is disabled. */ - bool mSavePending = false; + bool mSavePending GUARDED_BY(mMutex) = false; /** * "mObservedBlobValueSize" is the maximum value size observed by the cache reading function. @@ -175,16 +177,16 @@ private: unsigned int mDeferredSaveDelayMs = 4 * 1000; /** - * "mMutex" is the mutex used to prevent concurrent access to the member + * "mMutex" is the shared mutex used to prevent concurrent access to the member * variables. It must be locked whenever the member variables are accessed. */ - mutable std::mutex mMutex; + mutable ftl::SharedMutex mMutex; /** * If set to "true", the next call to onVkFrameFlushed, will invoke * GrCanvas::storeVkPipelineCacheData. This does not guarantee that data will be stored on disk. */ - bool mTryToStorePipelineCache = true; + bool mTryToStorePipelineCache GUARDED_BY(mMutex) = true; /** * This flag is used by "ShaderCache::store" to distinguish between shader data and @@ -196,16 +198,16 @@ private: * "mNewPipelineCacheSize" has the size of the new Vulkan pipeline cache data. It is used * to prevent unnecessary disk writes, if the pipeline cache size has not changed. */ - size_t mNewPipelineCacheSize = -1; + size_t mNewPipelineCacheSize GUARDED_BY(mMutex) = -1; /** * "mOldPipelineCacheSize" has the size of the Vulkan pipeline cache data stored on disk. */ - size_t mOldPipelineCacheSize = -1; + size_t mOldPipelineCacheSize GUARDED_BY(mMutex) = -1; /** * "mCacheDirty" is true when there is new shader cache data, which is not saved to disk. */ - bool mCacheDirty = false; + bool mCacheDirty GUARDED_BY(mMutex) = false; /** * "sCache" is the singleton ShaderCache object. @@ -222,7 +224,7 @@ private: * interesting to keep track of how many shaders are stored in RAM. This * class provides a convenient entry point for that. */ - int mNumShadersCachedInRam = 0; + int mNumShadersCachedInRam GUARDED_BY(mMutex) = 0; friend class ShaderCacheTestUtils; // used for unit testing }; diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp index 7bcd45c6b643..9aa2e1db4461 100644 --- a/libs/hwui/tests/unit/ShaderCacheTests.cpp +++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp @@ -49,7 +49,7 @@ public: */ static void reinitializeAllFields(ShaderCache& cache) { ShaderCache newCache = ShaderCache(); - std::lock_guard<std::mutex> lock(cache.mMutex); + std::lock_guard lock(cache.mMutex), newLock(newCache.mMutex); // By order of declaration cache.mInitialized = newCache.mInitialized; cache.mBlobCache.reset(nullptr); @@ -72,7 +72,7 @@ public: * manually, as seen in the "terminate" testing helper function. */ static void setSaveDelayMs(ShaderCache& cache, unsigned int saveDelayMs) { - std::lock_guard<std::mutex> lock(cache.mMutex); + std::lock_guard lock(cache.mMutex); cache.mDeferredSaveDelayMs = saveDelayMs; } @@ -81,7 +81,7 @@ public: * Next call to "initShaderDiskCache" will load again the in-memory cache from disk. */ static void terminate(ShaderCache& cache, bool saveContent) { - std::lock_guard<std::mutex> lock(cache.mMutex); + std::lock_guard lock(cache.mMutex); if (saveContent) { cache.saveToDiskLocked(); } @@ -93,6 +93,7 @@ public: */ template <typename T> static bool validateCache(ShaderCache& cache, std::vector<T> hash) { + std::lock_guard lock(cache.mMutex); return cache.validateCache(hash.data(), hash.size() * sizeof(T)); } @@ -108,7 +109,7 @@ public: */ static void waitForPendingSave(ShaderCache& cache, const int timeoutMs = 50) { { - std::lock_guard<std::mutex> lock(cache.mMutex); + std::lock_guard lock(cache.mMutex); ASSERT_TRUE(cache.mSavePending); } bool saving = true; @@ -123,7 +124,7 @@ public: usleep(delayMicroseconds); elapsedMilliseconds += (float)delayMicroseconds / 1000; - std::lock_guard<std::mutex> lock(cache.mMutex); + std::lock_guard lock(cache.mMutex); saving = cache.mSavePending; } } diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index 3123ee6dd4d7..3f013de8dad6 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1342,6 +1342,11 @@ public class AudioSystem } /** @hide */ + public static boolean isRemoteSubmixDevice(int deviceType) { + return deviceType == DEVICE_IN_REMOTE_SUBMIX || deviceType == DEVICE_OUT_REMOTE_SUBMIX; + } + + /** @hide */ public static final String LEGACY_REMOTE_SUBMIX_ADDRESS = "0"; // device states, must match AudioSystem::device_connection_state diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java index ed68d1eb5724..d0270d3d3246 100644 --- a/media/java/android/media/audiopolicy/AudioMix.java +++ b/media/java/android/media/audiopolicy/AudioMix.java @@ -16,6 +16,9 @@ package android.media.audiopolicy; +import static android.media.AudioSystem.getDeviceName; +import static android.media.AudioSystem.isRemoteSubmixDevice; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; @@ -445,36 +448,36 @@ public class AudioMix { // CHANNEL_IN_FRONT_BACK is hidden, should not appear. } } - if ((mDeviceSystemType != AudioSystem.DEVICE_NONE) - && (mDeviceSystemType != AudioSystem.DEVICE_OUT_REMOTE_SUBMIX) - && (mDeviceSystemType != AudioSystem.DEVICE_IN_REMOTE_SUBMIX)) { - if ((mRouteFlags & ROUTE_FLAG_RENDER) == 0) { - throw new IllegalArgumentException( - "Can't have audio device without flag ROUTE_FLAG_RENDER"); - } - if (mRule.getTargetMixType() != AudioMix.MIX_TYPE_PLAYERS) { - throw new IllegalArgumentException("Unsupported device on non-playback mix"); + + if ((mRouteFlags & ROUTE_FLAG_LOOP_BACK) == ROUTE_FLAG_LOOP_BACK) { + if (mDeviceSystemType == AudioSystem.DEVICE_NONE) { + // If there was no device type explicitly set, configure it based on mix type. + mDeviceSystemType = getLoopbackDeviceSystemTypeForAudioMixingRule(mRule); + } else if (!isRemoteSubmixDevice(mDeviceSystemType)) { + // Loopback mode only supports remote submix devices. + throw new IllegalArgumentException("Device " + getDeviceName(mDeviceSystemType) + + "is not supported for loopback mix."); } - } else if (mDeviceSystemType == AudioSystem.DEVICE_OUT_REMOTE_SUBMIX) { - if (mRule.getTargetMixType() != AudioMix.MIX_TYPE_PLAYERS) { + } + + if ((mRouteFlags & ROUTE_FLAG_RENDER) == ROUTE_FLAG_RENDER) { + if (mDeviceSystemType == AudioSystem.DEVICE_NONE) { throw new IllegalArgumentException( - "DEVICE_OUT_REMOTE_SUBMIX device is not supported on non-playback mix"); + "Can't have flag ROUTE_FLAG_RENDER without an audio device"); } - } else { - if ((mRouteFlags & ROUTE_FLAG_SUPPORTED) == ROUTE_FLAG_RENDER) { + + if (AudioSystem.DEVICE_IN_ALL_SET.contains(mDeviceSystemType)) { throw new IllegalArgumentException( - "Can't have flag ROUTE_FLAG_RENDER without an audio device"); + "Input device is not supported with ROUTE_FLAG_RENDER"); } - if ((mRouteFlags & ROUTE_FLAG_LOOP_BACK) == ROUTE_FLAG_LOOP_BACK) { - if (mRule.getTargetMixType() == MIX_TYPE_PLAYERS) { - mDeviceSystemType = AudioSystem.DEVICE_OUT_REMOTE_SUBMIX; - } else if (mRule.getTargetMixType() == MIX_TYPE_RECORDERS) { - mDeviceSystemType = AudioSystem.DEVICE_IN_REMOTE_SUBMIX; - } else { - throw new IllegalArgumentException("Unknown mixing rule type"); - } + + if (mRule.getTargetMixType() == MIX_TYPE_RECORDERS) { + throw new IllegalArgumentException( + "ROUTE_FLAG_RENDER/ROUTE_FLAG_LOOP_BACK_RENDER is not supported for " + + "non-playback mix rule"); } } + if (mRule.allowPrivilegedMediaPlaybackCapture()) { String error = AudioMix.canBeUsedForPrivilegedMediaCapture(mFormat); if (error != null) { @@ -484,5 +487,18 @@ public class AudioMix { return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags, mDeviceSystemType, mDeviceAddress); } + + private int getLoopbackDeviceSystemTypeForAudioMixingRule(AudioMixingRule rule) { + switch (mRule.getTargetMixType()) { + case MIX_TYPE_PLAYERS: + return AudioSystem.DEVICE_OUT_REMOTE_SUBMIX; + case MIX_TYPE_RECORDERS: + return AudioSystem.DEVICE_IN_REMOTE_SUBMIX; + default: + throw new IllegalArgumentException( + "Unknown mixing rule type - 0x" + Integer.toHexString( + rule.getTargetMixType())); + } + } } } diff --git a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl index 2231ce14eea6..e46d34e81483 100644 --- a/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl +++ b/media/java/android/media/projection/IMediaProjectionWatcherCallback.aidl @@ -17,9 +17,22 @@ package android.media.projection; import android.media.projection.MediaProjectionInfo; +import android.view.ContentRecordingSession; /** {@hide} */ oneway interface IMediaProjectionWatcherCallback { void onStart(in MediaProjectionInfo info); void onStop(in MediaProjectionInfo info); + /** + * Called when the {@link ContentRecordingSession} was set for the current media + * projection. + * + * @param info always present and contains information about the media projection host. + * @param session the recording session for the current media projection. Can be + * {@code null} when the recording will stop. + */ + void onRecordingSessionSet( + in MediaProjectionInfo info, + in @nullable ContentRecordingSession session + ); } diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java index 5703c429c32b..5a68c53b8f68 100644 --- a/media/java/android/media/projection/MediaProjectionManager.java +++ b/media/java/android/media/projection/MediaProjectionManager.java @@ -29,6 +29,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.util.ArrayMap; import android.util.Log; +import android.view.ContentRecordingSession; import android.view.Surface; import java.util.Map; @@ -300,7 +301,22 @@ public final class MediaProjectionManager { /** @hide */ public static abstract class Callback { public abstract void onStart(MediaProjectionInfo info); + public abstract void onStop(MediaProjectionInfo info); + + /** + * Called when the {@link ContentRecordingSession} was set for the current media + * projection. + * + * @param info always present and contains information about the media projection host. + * @param session the recording session for the current media projection. Can be + * {@code null} when the recording will stop. + */ + public void onRecordingSessionSet( + @NonNull MediaProjectionInfo info, + @Nullable ContentRecordingSession session + ) { + } } /** @hide */ @@ -335,5 +351,13 @@ public final class MediaProjectionManager { } }); } + + @Override + public void onRecordingSessionSet( + @NonNull final MediaProjectionInfo info, + @Nullable final ContentRecordingSession session + ) { + mHandler.post(() -> mCallback.onRecordingSessionSet(info, session)); + } } } diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java index af33149867bd..f664fdc949de 100644 --- a/media/java/android/media/session/MediaSession.java +++ b/media/java/android/media/session/MediaSession.java @@ -267,25 +267,33 @@ public final class MediaSession { } /** - * Set a pending intent for your media button receiver to allow restarting - * playback after the session has been stopped. If your app is started in - * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via - * the pending intent. - * <p> - * The pending intent is recommended to be explicit to follow the security recommendation of - * {@link PendingIntent#getActivity}. + * Set a pending intent for your media button receiver to allow restarting playback after the + * session has been stopped. * - * @param mbr The {@link PendingIntent} to send the media button event to. - * @see PendingIntent#getActivity + * <p>If your app is started in this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be + * sent via the pending intent. + * + * <p>The provided {@link PendingIntent} must not target an activity. On apps targeting Android + * V and above, passing an activity pending intent to this method causes an {@link + * IllegalArgumentException}. On apps targeting Android U and below, passing an activity pending + * intent causes the call to be ignored. Refer to this <a + * href="https://developer.android.com/guide/components/activities/background-starts">guide</a> + * for more information. * + * <p>The pending intent is recommended to be explicit to follow the security recommendation of + * {@link PendingIntent#getService}. + * + * @param mbr The {@link PendingIntent} to send the media button event to. * @deprecated Use {@link #setMediaButtonBroadcastReceiver(ComponentName)} instead. + * @throws IllegalArgumentException if the pending intent targets an activity on apps targeting + * Android V and above. */ @Deprecated public void setMediaButtonReceiver(@Nullable PendingIntent mbr) { try { mBinder.setMediaButtonReceiver(mbr); } catch (RemoteException e) { - Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e); + e.rethrowFromSystemServer(); } } diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java index 5a569456b4a7..d81843d1323a 100644 --- a/media/java/android/media/tv/tuner/Tuner.java +++ b/media/java/android/media/tv/tuner/Tuner.java @@ -1565,6 +1565,10 @@ public class Tuner implements AutoCloseable { mFrontendCiCamLock.lock(); mFrontendLock.lock(); try { + if (mFrontendHandle == null) { + Log.d(TAG, "Operation cannot be done without frontend"); + return RESULT_INVALID_STATE; + } if (mFeOwnerTuner != null) { Log.d(TAG, "Operation cannot be done by sharee of tuner"); return RESULT_INVALID_STATE; @@ -1632,6 +1636,10 @@ public class Tuner implements AutoCloseable { public int disconnectFrontendToCiCam(int ciCamId) { acquireTRMSLock("disconnectFrontendToCiCam()"); try { + if (mFrontendHandle == null) { + Log.d(TAG, "Operation cannot be done without frontend"); + return RESULT_INVALID_STATE; + } if (mFeOwnerTuner != null) { Log.d(TAG, "Operation cannot be done by sharee of tuner"); return RESULT_INVALID_STATE; diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java index ac8a7f37c527..a26398ac198a 100644 --- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java +++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java @@ -117,7 +117,7 @@ public class AudioMixUnitTests { // --- Equality group 3 final AudioMix recordingAudioMixWithSessionId42AndUid123Render = new AudioMix.Builder(new AudioMixingRule.Builder() - .setTargetMixRole(MIX_ROLE_INJECTOR) + .setTargetMixRole(MIX_ROLE_PLAYERS) .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42) .addMixRule(RULE_MATCH_UID, 123).build()) .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM) @@ -205,7 +205,19 @@ public class AudioMixUnitTests { } @Test - public void buildLoopbackWithDevice_throws() { + public void buildLoopbackForInjectorMix_success() { + final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder() + .setTargetMixRole(MIX_ROLE_INJECTOR) + .addMixRule(RULE_MATCH_UID, 42).build()) + .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) + .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); + + assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat()); + assertEquals(AudioMix.ROUTE_FLAG_LOOP_BACK, audioMix.getRouteFlags()); + } + + @Test + public void buildLoopbackWithIncompatibleDevice_throws() { assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder( new AudioMixingRule.Builder() .setTargetMixRole(MIX_ROLE_PLAYERS) @@ -225,6 +237,28 @@ public class AudioMixUnitTests { .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER).build()); } + @Test + public void buildRenderWithInputDevice_throws() { + assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder( + new AudioMixingRule.Builder() + .setTargetMixRole(MIX_ROLE_PLAYERS) + .addMixRule(RULE_MATCH_UID, 42).build()) + .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) + .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER) + .setDevice(AudioSystem.DEVICE_IN_BUILTIN_MIC, /*address=*/"").build()); + } + + @Test + public void buildRenderWithInjectorMix_throws() { + assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder( + new AudioMixingRule.Builder() + .setTargetMixRole(MIX_ROLE_INJECTOR) + .addMixRule(RULE_MATCH_UID, 42).build()) + .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) + .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER) + .setDevice(AudioSystem.DEVICE_OUT_SPEAKER, /*address=*/"").build()); + } + private static AudioMix writeToAndFromParcel(AudioMix audioMix) { diff --git a/packages/CarrierDefaultApp/assets/slice_purchase_test.html b/packages/CarrierDefaultApp/assets/slice_purchase_test.html index 917276b9ce65..ad18a9d64074 100644 --- a/packages/CarrierDefaultApp/assets/slice_purchase_test.html +++ b/packages/CarrierDefaultApp/assets/slice_purchase_test.html @@ -81,5 +81,7 @@ Dismiss flow </button> <p id="dismiss_flow"></p> + + <h2>Test <a href="http://www.google.com">hyperlink</a></h2> </body> </html> diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java index 2530257d629a..b1009808cccc 100644 --- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java +++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SlicePurchaseActivity.java @@ -29,6 +29,7 @@ import android.util.Log; import android.view.KeyEvent; import android.webkit.CookieManager; import android.webkit.WebView; +import android.webkit.WebViewClient; import com.android.phone.slice.SlicePurchaseController; @@ -113,8 +114,10 @@ public class SlicePurchaseActivity extends Activity { return; } - // Create and configure WebView - setupWebView(); + // Clear any cookies that might be persisted from previous sessions before loading WebView + CookieManager.getInstance().removeAllCookies(value -> { + setupWebView(); + }); } protected void onPurchaseSuccessful() { @@ -176,12 +179,7 @@ public class SlicePurchaseActivity extends Activity { private void setupWebView() { // Create WebView mWebView = new WebView(this); - - // Clear any cookies and state that might be saved from previous sessions - CookieManager.getInstance().removeAllCookies(null); - CookieManager.getInstance().flush(); - mWebView.clearCache(true); - mWebView.clearHistory(); + mWebView.setWebViewClient(new WebViewClient()); // Enable JavaScript for the carrier purchase website to send results back to // the slice purchase application. diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java index 1231bb3fa416..f2f019d96023 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java @@ -269,9 +269,16 @@ public class InstallStart extends Activity { } private boolean isCallerSessionOwner(int originatingUid, int sessionId) { + if (originatingUid == Process.ROOT_UID) { + return true; + } PackageInstaller packageInstaller = getPackageManager().getPackageInstaller(); - int installerUid = packageInstaller.getSessionInfo(sessionId).getInstallerUid(); - return (originatingUid == Process.ROOT_UID) || (originatingUid == installerUid); + PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId); + if (sessionInfo == null) { + return false; + } + int installerUid = sessionInfo.getInstallerUid(); + return originatingUid == installerUid; } private void checkDevicePolicyRestriction() { diff --git a/packages/SettingsLib/res/layout/dialog_with_icon.xml b/packages/SettingsLib/res/layout/dialog_with_icon.xml index 2f30508ca504..3586dcb2909c 100644 --- a/packages/SettingsLib/res/layout/dialog_with_icon.xml +++ b/packages/SettingsLib/res/layout/dialog_with_icon.xml @@ -33,13 +33,13 @@ android:importantForAccessibility="no"/> <TextView android:id="@+id/dialog_with_icon_title" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" style="@style/DialogWithIconTitle"/> <TextView android:id="@+id/dialog_with_icon_message" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" style="@style/TextAppearanceSmall"/> diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java index 00ccea1f93f1..ff960f3184db 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java @@ -409,7 +409,7 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils { */ public static EnforcedAdmin checkIfUsbDataSignalingIsDisabled(Context context, int userId) { DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); - if (dpm == null || dpm.isUsbDataSignalingEnabledForUser(userId)) { + if (dpm == null || dpm.isUsbDataSignalingEnabled()) { return null; } else { EnforcedAdmin admin = getProfileOrDeviceOwner(context, getUserHandleOf(userId)); diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java index b5e4fa38d244..af06d7304160 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java @@ -243,7 +243,9 @@ public class RestrictedSwitchPreference extends SwitchPreference { return mHelper != null ? mHelper.packageName : null; } - public void updateState(@NonNull String packageName, int uid, boolean isEnabled) { + /** Updates enabled state based on associated package. */ + public void updateState( + @NonNull String packageName, int uid, boolean isEnableAllowed, boolean isEnabled) { mHelper.updatePackageDetails(packageName, uid); if (mAppOpsManager == null) { mAppOpsManager = getContext().getSystemService(AppOpsManager.class); @@ -254,7 +256,9 @@ public class RestrictedSwitchPreference extends SwitchPreference { final boolean ecmEnabled = getContext().getResources().getBoolean( com.android.internal.R.bool.config_enhancedConfirmationModeEnabled); final boolean appOpsAllowed = !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED; - if (isEnabled) { + if (!isEnableAllowed && !isEnabled) { + setEnabled(false); + } else if (isEnabled) { setEnabled(true); } else if (appOpsAllowed && isDisabledByAppOps()) { setEnabled(true); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 2e6bb535a8f0..f522fd13c9f8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -583,7 +583,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> */ public void setName(String name) { // Prevent getName() to be set to null if setName(null) is called - if (name == null || TextUtils.equals(name, getName())) { + if (TextUtils.isEmpty(name) || TextUtils.equals(name, getName())) { return; } mDevice.setAlias(name); diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java index cd6609ec463e..963bd9daa975 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaDevice.java @@ -15,6 +15,8 @@ */ package com.android.settingslib.media; +import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; + import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.content.Context; @@ -22,6 +24,7 @@ import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; +import android.media.RouteListingPreference; import com.android.settingslib.R; import com.android.settingslib.bluetooth.BluetoothUtils; @@ -39,7 +42,13 @@ public class BluetoothMediaDevice extends MediaDevice { BluetoothMediaDevice(Context context, CachedBluetoothDevice device, MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) { - super(context, routerManager, info, packageName, null); + this(context, device, routerManager, info, packageName, null); + } + + BluetoothMediaDevice(Context context, CachedBluetoothDevice device, + MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName, + RouteListingPreference.Item item) { + super(context, routerManager, info, packageName, item); mCachedDevice = device; mAudioManager = context.getSystemService(AudioManager.class); initDeviceRecord(); @@ -58,6 +67,12 @@ public class BluetoothMediaDevice extends MediaDevice { } @Override + public int getSelectionBehavior() { + // We don't allow apps to override the selection behavior of system routes. + return SELECTION_BEHAVIOR_TRANSFER; + } + + @Override public Drawable getIcon() { return BluetoothUtils.isAdvancedUntetheredDevice(mCachedDevice.getDevice()) ? mContext.getDrawable(R.drawable.ic_earbuds_advanced) diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index a1beed8569d9..3e864f905da3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -50,7 +50,6 @@ import android.bluetooth.BluetoothDevice; import android.content.ComponentName; import android.content.Context; import android.media.MediaRoute2Info; -import android.media.MediaRouter2Manager; import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; import android.os.Build; @@ -71,6 +70,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; /** InfoMediaManager provide interface to get InfoMediaDevice list. */ @RequiresApi(Build.VERSION_CODES.R) @@ -174,11 +174,34 @@ public abstract class InfoMediaManager extends MediaManager { MediaRoute2Info route, RouteListingPreference.Item routeListingPreferenceItem); @NonNull - protected abstract PhoneMediaDevice createPhoneMediaDevice(MediaRoute2Info route); + protected abstract PhoneMediaDevice createPhoneMediaDevice(MediaRoute2Info route, + RouteListingPreference.Item routeListingPreferenceItem); @NonNull protected abstract BluetoothMediaDevice createBluetoothMediaDevice( - MediaRoute2Info route, CachedBluetoothDevice cachedDevice); + MediaRoute2Info route, CachedBluetoothDevice cachedDevice, + RouteListingPreference.Item routeListingPreferenceItem); + + protected final void rebuildDeviceList() { + mMediaDevices.clear(); + mCurrentConnectedDevice = null; + if (TextUtils.isEmpty(mPackageName)) { + buildAllRoutes(); + } else { + buildAvailableRoutes(); + } + } + + protected final void notifyCurrentConnectedDeviceChanged() { + final String id = mCurrentConnectedDevice != null ? mCurrentConnectedDevice.getId() : null; + dispatchConnectedDeviceChanged(id); + } + + @RequiresApi(34) + protected final void notifyRouteListingPreferenceUpdated( + RouteListingPreference routeListingPreference) { + Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference, mPreferenceItemMap); + } /** * Get current device that played media. @@ -474,14 +497,8 @@ public abstract class InfoMediaManager extends MediaManager { || sessionInfo.getVolumeHandling() != MediaRoute2Info.PLAYBACK_VOLUME_FIXED; } - private synchronized void refreshDevices() { - mMediaDevices.clear(); - mCurrentConnectedDevice = null; - if (TextUtils.isEmpty(mPackageName)) { - buildAllRoutes(); - } else { - buildAvailableRoutes(); - } + protected final synchronized void refreshDevices() { + rebuildDeviceList(); dispatchDeviceListAdded(); } @@ -578,7 +595,8 @@ public abstract class InfoMediaManager extends MediaManager { case TYPE_HDMI: case TYPE_WIRED_HEADSET: case TYPE_WIRED_HEADPHONES: - mediaDevice = createPhoneMediaDevice(route); + mediaDevice = createPhoneMediaDevice(route, + mPreferenceItemMap.getOrDefault(route.getId(), null)); break; case TYPE_HEARING_AID: case TYPE_BLUETOOTH_A2DP: @@ -588,7 +606,8 @@ public abstract class InfoMediaManager extends MediaManager { final CachedBluetoothDevice cachedDevice = mBluetoothManager.getCachedDeviceManager().findDevice(device); if (cachedDevice != null) { - mediaDevice = createBluetoothMediaDevice(route, cachedDevice); + mediaDevice = createBluetoothMediaDevice(route, cachedDevice, + mPreferenceItemMap.getOrDefault(route.getId(), null)); } break; case TYPE_REMOTE_AUDIO_VIDEO_RECEIVER: @@ -612,75 +631,6 @@ public abstract class InfoMediaManager extends MediaManager { } } - class RouterManagerCallback implements MediaRouter2Manager.Callback { - - @Override - public void onRoutesUpdated() { - refreshDevices(); - } - - @Override - public void onPreferredFeaturesChanged(String packageName, List<String> preferredFeatures) { - if (TextUtils.equals(mPackageName, packageName)) { - refreshDevices(); - } - } - - @Override - public void onTransferred(RoutingSessionInfo oldSession, RoutingSessionInfo newSession) { - if (DEBUG) { - Log.d(TAG, "onTransferred() oldSession : " + oldSession.getName() - + ", newSession : " + newSession.getName()); - } - mMediaDevices.clear(); - mCurrentConnectedDevice = null; - if (TextUtils.isEmpty(mPackageName)) { - buildAllRoutes(); - } else { - buildAvailableRoutes(); - } - - final String id = mCurrentConnectedDevice != null - ? mCurrentConnectedDevice.getId() - : null; - dispatchConnectedDeviceChanged(id); - } - - /** - * Ignore callback here since we'll also receive{@link - * MediaRouter2Manager.Callback#onRequestFailed onRequestFailed} with reason code. - */ - @Override - public void onTransferFailed(RoutingSessionInfo session, MediaRoute2Info route) { - } - - @Override - public void onRequestFailed(int reason) { - dispatchOnRequestFailed(reason); - } - - @Override - public void onSessionUpdated(RoutingSessionInfo sessionInfo) { - refreshDevices(); - } - - @Override - public void onSessionReleased(@NonNull RoutingSessionInfo session) { - refreshDevices(); - } - - @Override - public void onRouteListingPreferenceUpdated( - String packageName, - RouteListingPreference routeListingPreference) { - if (TextUtils.equals(mPackageName, packageName)) { - Api34Impl.onRouteListingPreferenceUpdated( - routeListingPreference, mPreferenceItemMap); - refreshDevices(); - } - } - } - @RequiresApi(34) static class Api34Impl { @DoNotInline @@ -719,20 +669,19 @@ public abstract class InfoMediaManager extends MediaManager { List<MediaRoute2Info> selectedRouteInfos, List<MediaRoute2Info> infolist, List<RouteListingPreference.Item> preferenceRouteListing) { final List<MediaRoute2Info> sortedInfoList = new ArrayList<>(selectedRouteInfos); + infolist.removeAll(selectedRouteInfos); + sortedInfoList.addAll(infolist.stream().filter( + MediaRoute2Info::isSystemRoute).collect(Collectors.toList())); for (RouteListingPreference.Item item : preferenceRouteListing) { for (MediaRoute2Info info : infolist) { if (item.getRouteId().equals(info.getId()) - && !selectedRouteInfos.contains(info)) { + && !selectedRouteInfos.contains(info) + && !info.isSystemRoute()) { sortedInfoList.add(info); break; } } } - if (sortedInfoList.size() != infolist.size()) { - infolist.removeAll(sortedInfoList); - sortedInfoList.addAll(infolist.stream().filter( - MediaRoute2Info::isSystemRoute).toList()); - } return sortedInfoList; } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java index 4f081369f5bf..c86a943eff18 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java @@ -24,6 +24,8 @@ import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; import android.media.RouteListingPreference; import android.media.RoutingSessionInfo; +import android.text.TextUtils; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.settingslib.bluetooth.CachedBluetoothDevice; @@ -39,10 +41,14 @@ import java.util.concurrent.Executors; */ public class ManagerInfoMediaManager extends InfoMediaManager { + private static final String TAG = "ManagerInfoMediaManager"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + @VisibleForTesting /* package */ final RouterManagerCallback mMediaRouterCallback = new RouterManagerCallback(); @VisibleForTesting /* package */ MediaRouter2Manager mRouterManager; + boolean mIsScanning = false; private final Executor mExecutor = Executors.newSingleThreadExecutor(); @@ -58,14 +64,20 @@ public class ManagerInfoMediaManager extends InfoMediaManager { @Override protected void startScanOnRouter() { - mRouterManager.registerCallback(mExecutor, mMediaRouterCallback); - mRouterManager.registerScanRequest(); + if (!mIsScanning) { + mRouterManager.registerCallback(mExecutor, mMediaRouterCallback); + mRouterManager.registerScanRequest(); + mIsScanning = true; + } } @Override public void stopScan() { - mRouterManager.unregisterCallback(mMediaRouterCallback); - mRouterManager.unregisterScanRequest(); + if (mIsScanning) { + mRouterManager.unregisterCallback(mMediaRouterCallback); + mRouterManager.unregisterScanRequest(); + mIsScanning = false; + } } @Override @@ -173,15 +185,81 @@ public class ManagerInfoMediaManager extends InfoMediaManager { @Override @NonNull - protected PhoneMediaDevice createPhoneMediaDevice(MediaRoute2Info route) { - return new PhoneMediaDevice(mContext, mRouterManager, route, mPackageName); + protected PhoneMediaDevice createPhoneMediaDevice(MediaRoute2Info route, + RouteListingPreference.Item routeListingPreferenceItem) { + return new PhoneMediaDevice(mContext, mRouterManager, route, mPackageName, + routeListingPreferenceItem); } @Override @NonNull protected BluetoothMediaDevice createBluetoothMediaDevice( - MediaRoute2Info route, CachedBluetoothDevice cachedDevice) { + MediaRoute2Info route, CachedBluetoothDevice cachedDevice, + RouteListingPreference.Item routeListingPreferenceItem) { return new BluetoothMediaDevice( - mContext, cachedDevice, mRouterManager, route, mPackageName); + mContext, cachedDevice, mRouterManager, route, mPackageName, + routeListingPreferenceItem); + } + + @VisibleForTesting + /* package */ final class RouterManagerCallback implements MediaRouter2Manager.Callback { + + @Override + public void onRoutesUpdated() { + refreshDevices(); + } + + @Override + public void onPreferredFeaturesChanged(String packageName, List<String> preferredFeatures) { + if (TextUtils.equals(mPackageName, packageName)) { + refreshDevices(); + } + } + + @Override + public void onTransferred(RoutingSessionInfo oldSession, RoutingSessionInfo newSession) { + if (DEBUG) { + Log.d( + TAG, + "onTransferred() oldSession : " + + oldSession.getName() + + ", newSession : " + + newSession.getName()); + } + rebuildDeviceList(); + notifyCurrentConnectedDeviceChanged(); + } + + /** + * Ignore callback here since we'll also receive{@link + * MediaRouter2Manager.Callback#onRequestFailed onRequestFailed} with reason code. + */ + @Override + public void onTransferFailed(RoutingSessionInfo session, MediaRoute2Info route) {} + + @Override + public void onRequestFailed(int reason) { + dispatchOnRequestFailed(reason); + } + + @Override + public void onSessionUpdated(RoutingSessionInfo sessionInfo) { + refreshDevices(); + } + + @Override + public void onSessionReleased(@NonNull RoutingSessionInfo session) { + refreshDevices(); + } + + @Override + public void onRouteListingPreferenceUpdated( + String packageName, RouteListingPreference routeListingPreference) { + if (!TextUtils.equals(mPackageName, packageName)) { + return; + } + notifyRouteListingPreferenceUpdated(routeListingPreference); + refreshDevices(); + } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java index 34519c993d27..accd88c2bfe3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java @@ -24,10 +24,13 @@ import static android.media.MediaRoute2Info.TYPE_USB_HEADSET; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; +import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; + import android.content.Context; import android.graphics.drawable.Drawable; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; +import android.media.RouteListingPreference; import androidx.annotation.VisibleForTesting; @@ -51,7 +54,12 @@ public class PhoneMediaDevice extends MediaDevice { PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info, String packageName) { - super(context, routerManager, info, packageName, null); + this(context, routerManager, info, packageName, null); + } + + PhoneMediaDevice(Context context, MediaRouter2Manager routerManager, MediaRoute2Info info, + String packageName, RouteListingPreference.Item item) { + super(context, routerManager, info, packageName, item); mDeviceIconUtil = new DeviceIconUtil(); initDeviceRecord(); } @@ -86,6 +94,12 @@ public class PhoneMediaDevice extends MediaDevice { } @Override + public int getSelectionBehavior() { + // We don't allow apps to override the selection behavior of system routes. + return SELECTION_BEHAVIOR_TRANSFER; + } + + @Override public String getSummary() { return mSummary; } diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java index c40388fee710..361a24612b3a 100644 --- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java +++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java @@ -16,27 +16,23 @@ package com.android.settingslib.mobile.dataservice; -import static androidx.room.ForeignKey.CASCADE; - import android.text.TextUtils; -import java.util.Objects; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.room.ColumnInfo; import androidx.room.Entity; -import androidx.room.ForeignKey; -import androidx.room.Index; import androidx.room.PrimaryKey; +import java.util.Objects; + @Entity(tableName = DataServiceUtils.SubscriptionInfoData.TABLE_NAME) public class SubscriptionInfoEntity { public SubscriptionInfoEntity(@NonNull String subId, int simSlotIndex, int carrierId, String displayName, String carrierName, int dataRoaming, String mcc, String mnc, String countryIso, boolean isEmbedded, int cardId, int portIndex, boolean isOpportunistic, @Nullable String groupUUID, int subscriptionType, - String uniqueName, boolean isSubscriptionVisible, String formattedPhoneNumber, + String uniqueName, boolean isSubscriptionVisible, @Nullable String formattedPhoneNumber, boolean isFirstRemovableSubscription, boolean isDefaultSubscriptionSelection, boolean isValidSubscription, boolean isUsableSubscription, boolean isActiveSubscriptionId, boolean isAvailableSubscription, @@ -123,6 +119,7 @@ public class SubscriptionInfoEntity { public boolean isSubscriptionVisible; @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_FORMATTED_PHONE_NUMBER) + @Nullable public String formattedPhoneNumber; @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_FIRST_REMOVABLE_SUBSCRIPTION) @@ -165,33 +162,32 @@ public class SubscriptionInfoEntity { @Override public int hashCode() { - int result = 17; - result = 31 * result + subId.hashCode(); - result = 31 * result + simSlotIndex; - result = 31 * result + carrierId; - result = 31 * result + displayName.hashCode(); - result = 31 * result + carrierName.hashCode(); - result = 31 * result + dataRoaming; - result = 31 * result + mcc.hashCode(); - result = 31 * result + mnc.hashCode(); - result = 31 * result + countryIso.hashCode(); - result = 31 * result + Boolean.hashCode(isEmbedded); - result = 31 * result + cardId; - result = 31 * result + portIndex; - result = 31 * result + Boolean.hashCode(isOpportunistic); - result = 31 * result + groupUUID.hashCode(); - result = 31 * result + subscriptionType; - result = 31 * result + uniqueName.hashCode(); - result = 31 * result + Boolean.hashCode(isSubscriptionVisible); - result = 31 * result + formattedPhoneNumber.hashCode(); - result = 31 * result + Boolean.hashCode(isFirstRemovableSubscription); - result = 31 * result + Boolean.hashCode(isDefaultSubscriptionSelection); - result = 31 * result + Boolean.hashCode(isValidSubscription); - result = 31 * result + Boolean.hashCode(isUsableSubscription); - result = 31 * result + Boolean.hashCode(isActiveSubscriptionId); - result = 31 * result + Boolean.hashCode(isAvailableSubscription); - result = 31 * result + Boolean.hashCode(isActiveDataSubscriptionId); - return result; + return Objects.hash( + subId, + simSlotIndex, + carrierId, + displayName, + carrierName, + dataRoaming, + mcc, + mnc, + countryIso, + isEmbedded, + cardId, + portIndex, + isOpportunistic, + groupUUID, + subscriptionType, + uniqueName, + isSubscriptionVisible, + formattedPhoneNumber, + isFirstRemovableSubscription, + isDefaultSubscriptionSelection, + isValidSubscription, + isUsableSubscription, + isActiveSubscriptionId, + isAvailableSubscription, + isActiveDataSubscriptionId); } @Override diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index 6444f3bd4341..4b61ff1177bd 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -1015,6 +1015,13 @@ public class CachedBluetoothDeviceTest { } @Test + public void setName_setDeviceNameIsEmpty() { + mCachedDevice.setName(""); + + verify(mDevice, never()).setAlias(any()); + } + + @Test public void getProfileConnectionState_nullProfile_returnDisconnected() { assertThat(mCachedDevice.getProfileConnectionState(null)).isEqualTo( BluetoothProfile.STATE_DISCONNECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java index 5f53a92c131e..35223c28613f 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java @@ -140,8 +140,8 @@ public class EnableAdbPreferenceControllerTest { public void updateState_settingsOn_shouldCheck() { when(mUserManager.isAdminUser()).thenReturn(true); when(mDevicePolicyManager.getProfileOwner()).thenReturn(TEST_COMPONENT_NAME); - when(mDevicePolicyManager.isUsbDataSignalingEnabledForUser( - UserHandle.myUserId())).thenReturn(true); + when(mDevicePolicyManager.isUsbDataSignalingEnabled( + )).thenReturn(true); Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.ADB_ENABLED, 1); mPreference.setChecked(false); @@ -156,8 +156,8 @@ public class EnableAdbPreferenceControllerTest { public void updateState_settingsOff_shouldUncheck() { when(mUserManager.isAdminUser()).thenReturn(true); when(mDevicePolicyManager.getProfileOwner()).thenReturn(TEST_COMPONENT_NAME); - when(mDevicePolicyManager.isUsbDataSignalingEnabledForUser( - UserHandle.myUserId())).thenReturn(true); + when(mDevicePolicyManager.isUsbDataSignalingEnabled( + )).thenReturn(true); Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.ADB_ENABLED, 0); mPreference.setChecked(true); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index ce1744dd415e..ee68fc2fa00d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -115,6 +115,23 @@ public class InfoMediaManagerTest { } @Test + public void stopScan_notStartFirst_notCallsUnregister() { + mInfoMediaManager.mRouterManager = mRouterManager; + mInfoMediaManager.stopScan(); + + verify(mRouterManager, never()).unregisterScanRequest(); + } + + @Test + public void stopScan_startFirst_callsUnregister() { + mInfoMediaManager.mRouterManager = mRouterManager; + mInfoMediaManager.startScan(); + mInfoMediaManager.stopScan(); + + verify(mRouterManager).unregisterScanRequest(); + } + + @Test public void onRouteAdded_getAvailableRoutes_shouldAddMediaDevice() { final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>(); final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class); @@ -328,11 +345,12 @@ public class InfoMediaManagerTest { routeListingPreference); mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated(); - assertThat(mInfoMediaManager.mMediaDevices).hasSize(3); + assertThat(mInfoMediaManager.mMediaDevices).hasSize(4); assertThat(mInfoMediaManager.mMediaDevices.get(0).getId()).isEqualTo(TEST_ID); - assertThat(mInfoMediaManager.mMediaDevices.get(1).getId()).isEqualTo(TEST_ID_4); - assertThat(mInfoMediaManager.mMediaDevices.get(1).isSuggestedDevice()).isTrue(); - assertThat(mInfoMediaManager.mMediaDevices.get(2).getId()).isEqualTo(TEST_ID_3); + assertThat(mInfoMediaManager.mMediaDevices.get(1).getId()).isEqualTo(TEST_ID_1); + assertThat(mInfoMediaManager.mMediaDevices.get(2).getId()).isEqualTo(TEST_ID_4); + assertThat(mInfoMediaManager.mMediaDevices.get(2).isSuggestedDevice()).isTrue(); + assertThat(mInfoMediaManager.mMediaDevices.get(3).getId()).isEqualTo(TEST_ID_3); } @Test @@ -406,8 +424,13 @@ public class InfoMediaManagerTest { when(availableInfo3.getClientPackageName()).thenReturn(packageName); availableRoutes.add(availableInfo3); - when(mRouterManager.getAvailableRoutes(packageName)).thenReturn( - availableRoutes); + final MediaRoute2Info availableInfo4 = mock(MediaRoute2Info.class); + when(availableInfo4.getId()).thenReturn(TEST_ID_1); + when(availableInfo4.isSystemRoute()).thenReturn(true); + when(availableInfo4.getClientPackageName()).thenReturn(packageName); + availableRoutes.add(availableInfo4); + + when(mRouterManager.getAvailableRoutes(packageName)).thenReturn(availableRoutes); return availableRoutes; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java index c058a61a3e9e..f22e090fe7df 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/MediaDeviceTest.java @@ -19,6 +19,9 @@ import static android.media.MediaRoute2Info.TYPE_BLUETOOTH_A2DP; import static android.media.MediaRoute2Info.TYPE_BUILTIN_SPEAKER; import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; +import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_GO_TO_APP; + +import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; import static com.google.common.truth.Truth.assertThat; @@ -32,6 +35,7 @@ import android.content.Context; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; import android.media.NearbyDevice; +import android.media.RouteListingPreference; import android.os.Parcel; import com.android.settingslib.bluetooth.A2dpProfile; @@ -110,6 +114,8 @@ public class MediaDeviceTest { @Mock private MediaRouter2Manager mMediaRouter2Manager; + private RouteListingPreference.Item mItem; + private BluetoothMediaDevice mBluetoothMediaDevice1; private BluetoothMediaDevice mBluetoothMediaDevice2; private BluetoothMediaDevice mBluetoothMediaDevice3; @@ -497,4 +503,21 @@ public class MediaDeviceTest { assertThat(mBluetoothMediaDevice1.getFeatures().size()).isEqualTo(0); } + + @Test + public void getSelectionBehavior_setItemWithSelectionBehaviorOnSystemRoute_returnTransfer() { + mItem = new RouteListingPreference.Item.Builder(DEVICE_ADDRESS_1) + .setSelectionBehavior(SELECTION_BEHAVIOR_GO_TO_APP) + .build(); + mBluetoothMediaDevice1 = new BluetoothMediaDevice(mContext, mCachedDevice1, + mMediaRouter2Manager, null /* MediaRoute2Info */, TEST_PACKAGE_NAME, mItem); + mPhoneMediaDevice = + new PhoneMediaDevice(mContext, mMediaRouter2Manager, mPhoneRouteInfo, + TEST_PACKAGE_NAME, mItem); + + assertThat(mBluetoothMediaDevice1.getSelectionBehavior()).isEqualTo( + SELECTION_BEHAVIOR_TRANSFER); + assertThat(mPhoneMediaDevice.getSelectionBehavior()).isEqualTo( + SELECTION_BEHAVIOR_TRANSFER); + } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 9c70a7073293..1784e4840151 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -1261,11 +1261,13 @@ public class SettingsProvider extends ContentProvider { Setting settingLocked = mSettingsRegistry.getSettingLocked( SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM, Global.DEVICE_CONFIG_SYNC_DISABLED); - if (settingLocked == null) { - return SYNC_DISABLED_MODE_NONE; + String settingValue = settingLocked == null ? null : settingLocked.getValue(); + if (settingValue == null) { + // Disable sync by default in test harness mode. + return ActivityManager.isRunningInUserTestHarness() + ? SYNC_DISABLED_MODE_PERSISTENT : SYNC_DISABLED_MODE_NONE; } - String settingValue = settingLocked.getValue(); - boolean isSyncDisabledPersistent = settingValue != null && !"0".equals(settingValue); + boolean isSyncDisabledPersistent = !"0".equals(settingValue); return isSyncDisabledPersistent ? SYNC_DISABLED_MODE_PERSISTENT : SYNC_DISABLED_MODE_NONE; } finally { diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 140b1e02fc14..016dc2144d3f 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -346,6 +346,8 @@ public class SettingsBackupTest { Settings.Global.MIN_DURATION_BETWEEN_RECOVERY_STEPS_IN_MS, Settings.Global.MOBILE_DATA, // Candidate for backup? Settings.Global.MOBILE_DATA_ALWAYS_ON, + Settings.Global.DSRM_DURATION_MILLIS, + Settings.Global.DSRM_ENABLED_ACTIONS, Settings.Global.MODE_RINGER, Settings.Global.MULTI_SIM_DATA_CALL_SUBSCRIPTION, Settings.Global.MULTI_SIM_SMS_PROMPT, diff --git a/packages/SoundPicker/Android.bp b/packages/SoundPicker/Android.bp index c8999fbcd271..1ac9bbbd9f18 100644 --- a/packages/SoundPicker/Android.bp +++ b/packages/SoundPicker/Android.bp @@ -19,6 +19,10 @@ android_library { "androidx.appcompat_appcompat", "hilt_android", "guava", + "androidx.recyclerview_recyclerview", + "androidx-constraintlayout_constraintlayout", + "androidx.viewpager2_viewpager2", + "com.google.android.material_material", ], } diff --git a/packages/SoundPicker/AndroidManifest.xml b/packages/SoundPicker/AndroidManifest.xml index 1f99e75ebc88..934b003c605c 100644 --- a/packages/SoundPicker/AndroidManifest.xml +++ b/packages/SoundPicker/AndroidManifest.xml @@ -34,6 +34,9 @@ <intent-filter> <action android:name="android.intent.action.RINGTONE_PICKER" /> <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.RINGTONE_PICKER_SOUND" /> + <category android:name="android.intent.category.RINGTONE_PICKER_VIBRATION" /> + <category android:name="android.intent.category.RINGTONE_PICKER_RINGTONE" /> </intent-filter> </activity> </application> diff --git a/packages/SoundPicker/res/layout/activity_ringtone_picker.xml b/packages/SoundPicker/res/layout/activity_ringtone_picker.xml index 4eecf89bb481..6fc60801ad3a 100644 --- a/packages/SoundPicker/res/layout/activity_ringtone_picker.xml +++ b/packages/SoundPicker/res/layout/activity_ringtone_picker.xml @@ -1,4 +1,5 @@ -<?xml version="1.0" encoding="utf-8"?><!-- +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2023 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,6 +16,6 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" />
\ No newline at end of file + android:layout_height="match_parent"/>
\ No newline at end of file diff --git a/packages/SoundPicker/res/layout/add_new_sound_item.xml b/packages/SoundPicker/res/layout/add_new_sound_item.xml index 14421c9a50dc..024b97ef23be 100644 --- a/packages/SoundPicker/res/layout/add_new_sound_item.xml +++ b/packages/SoundPicker/res/layout/add_new_sound_item.xml @@ -16,12 +16,14 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:gravity="center_vertical" - android:background="?android:attr/selectableItemBackground"> + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:background="?android:attr/selectableItemBackground" + android:focusable="true" + android:clickable="true"> -<ImageView + <ImageView android:layout_width="24dp" android:layout_height="24dp" android:layout_alignParentRight="true" @@ -29,9 +31,9 @@ android:scaleType="centerCrop" android:layout_marginRight="24dp" android:layout_marginLeft="24dp" - android:src="@drawable/ic_add" /> + android:src="@drawable/ic_add"/> -<TextView xmlns:android="http://schemas.android.com/apk/res/android" + <TextView android:id="@+id/add_new_sound_text" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -43,5 +45,5 @@ android:gravity="center_vertical" android:paddingEnd="?android:attr/dialogPreferredPadding" android:drawablePadding="20dp" - android:ellipsize="marquee" /> + android:ellipsize="marquee"/> </LinearLayout>
\ No newline at end of file diff --git a/packages/SoundPicker/res/layout/fragment_sound_picker.xml b/packages/SoundPicker/res/layout/fragment_sound_picker.xml new file mode 100644 index 000000000000..787f92ec06d6 --- /dev/null +++ b/packages/SoundPicker/res/layout/fragment_sound_picker.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<androidx.recyclerview.widget.RecyclerView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/recycler_view" + android:layout_width="match_parent" + android:layout_height="match_parent" +/>
\ No newline at end of file diff --git a/packages/SoundPicker/res/layout/fragment_tabbed_dialog.xml b/packages/SoundPicker/res/layout/fragment_tabbed_dialog.xml new file mode 100644 index 000000000000..7efd91191b79 --- /dev/null +++ b/packages/SoundPicker/res/layout/fragment_tabbed_dialog.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT 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:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <com.google.android.material.tabs.TabLayout + android:id="@+id/tabLayout" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + <androidx.viewpager2.widget.ViewPager2 + android:id="@+id/masterViewPager" + android:paddingTop="12dp" + android:paddingBottom="12dp" + android:layout_width="match_parent" + android:layout_height="match_parent"/> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SoundPicker/res/layout/fragment_vibration_picker.xml b/packages/SoundPicker/res/layout/fragment_vibration_picker.xml new file mode 100644 index 000000000000..34d95aa2e81b --- /dev/null +++ b/packages/SoundPicker/res/layout/fragment_vibration_picker.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<androidx.constraintlayout.widget.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:text="@string/empty_list" + android:textColor="?android:attr/colorAccent" + android:textAppearance="?android:attr/textAppearanceMedium" + android:gravity="center" + /> + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SoundPicker/res/layout/radio_with_work_badge.xml b/packages/SoundPicker/res/layout/radio_with_work_badge.xml index c8ca231f27a4..36ac93ed630b 100644 --- a/packages/SoundPicker/res/layout/radio_with_work_badge.xml +++ b/packages/SoundPicker/res/layout/radio_with_work_badge.xml @@ -14,12 +14,14 @@ limitations under the License. --> -<com.android.soundpicker.CheckedListItem xmlns:android="http://schemas.android.com/apk/res/android" +<com.android.soundpicker.CheckedListItem + xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:background="?android:attr/selectableItemBackground" - > + android:focusable="true" + android:clickable="true"> <CheckedTextView android:id="@+id/checked_text_view" @@ -35,7 +37,7 @@ android:drawablePadding="20dp" android:ellipsize="marquee" android:layout_toLeftOf="@+id/work_icon" - android:maxLines="3" /> + android:maxLines="3"/> <ImageView android:id="@id/work_icon" @@ -44,5 +46,5 @@ android:layout_alignParentRight="true" android:layout_centerVertical="true" android:scaleType="centerCrop" - android:layout_marginRight="20dp" /> + android:layout_marginRight="20dp"/> </com.android.soundpicker.CheckedListItem> diff --git a/packages/SoundPicker/res/values/strings.xml b/packages/SoundPicker/res/values/strings.xml index 04a2c2bb83c3..ab7b95a09028 100644 --- a/packages/SoundPicker/res/values/strings.xml +++ b/packages/SoundPicker/res/values/strings.xml @@ -40,4 +40,8 @@ <!-- Text for the name of the app. [CHAR LIMIT=12] --> <string name="app_label">Sounds</string> + + <string name="empty_list">The list is empty</string> + <string name="sound_page_title">Sound</string> + <string name="vibration_page_title">Vibration</string> </resources> diff --git a/packages/SoundPicker/src/com/android/soundpicker/LocalizedCursor.java b/packages/SoundPicker/src/com/android/soundpicker/LocalizedCursor.java new file mode 100644 index 000000000000..83d04a345f8b --- /dev/null +++ b/packages/SoundPicker/src/com/android/soundpicker/LocalizedCursor.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.soundpicker; + +import android.content.res.Resources; +import android.database.Cursor; +import android.database.CursorWrapper; +import android.util.Log; +import android.util.TypedValue; + +import androidx.annotation.Nullable; + +import java.util.Locale; +import java.util.regex.Pattern; + +/** + * A cursor wrapper class mainly used to guarantee getting a ringtone title + */ +final class LocalizedCursor extends CursorWrapper { + + private static final String TAG = "LocalizedCursor"; + private static final String SOUND_NAME_RES_PREFIX = "sound_name_"; + + private final int mTitleIndex; + private final Resources mResources; + private final Pattern mSanitizePattern; + private final String mNamePrefix; + + LocalizedCursor(Cursor cursor, Resources resources, String columnLabel) { + super(cursor); + mTitleIndex = mCursor.getColumnIndex(columnLabel); + mResources = resources; + mSanitizePattern = Pattern.compile("[^a-zA-Z0-9]"); + if (mTitleIndex == -1) { + Log.e(TAG, "No index for column " + columnLabel); + mNamePrefix = null; + } else { + mNamePrefix = buildNamePrefix(mResources); + } + } + + /** + * Builds the prefix for the name of the resource to look up. + * The format is: "ResourcePackageName::ResourceTypeName/" (the type name is expected to be + * "string" but let's not hardcode it). + * Here we use an existing resource "notification_sound_default" which is always expected to be + * found. + * + * @param resources Application's resources + * @return the built name prefix, or null if failed to build. + */ + @Nullable + private static String buildNamePrefix(Resources resources) { + try { + return String.format("%s:%s/%s", + resources.getResourcePackageName(R.string.notification_sound_default), + resources.getResourceTypeName(R.string.notification_sound_default), + SOUND_NAME_RES_PREFIX); + } catch (Resources.NotFoundException e) { + Log.e(TAG, "Failed to build the prefix for the name of the resource.", e); + } + + return null; + } + + /** + * Process resource name to generate a valid resource name. + * + * @return a non-null String + */ + private String sanitize(String input) { + if (input == null) { + return ""; + } + return mSanitizePattern.matcher(input).replaceAll("_").toLowerCase(Locale.ROOT); + } + + @Override + public String getString(int columnIndex) { + final String defaultName = mCursor.getString(columnIndex); + if ((columnIndex != mTitleIndex) || (mNamePrefix == null)) { + return defaultName; + } + TypedValue value = new TypedValue(); + try { + // the name currently in the database is used to derive a name to match + // against resource names in this package + mResources.getValue(mNamePrefix + sanitize(defaultName), value, + /* resolveRefs= */ false); + } catch (Resources.NotFoundException e) { + Log.d(TAG, "Failed to get localized string. Using default string instead.", e); + return defaultName; + } + if ((value != null) && (value.type == TypedValue.TYPE_STRING)) { + Log.d(TAG, String.format("Replacing name %s with %s", + defaultName, value.string.toString())); + return value.string.toString(); + } else { + Log.e(TAG, "Invalid value when looking up localized name, using " + defaultName); + return defaultName; + } + } +} diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtoneAdapter.java b/packages/SoundPicker/src/com/android/soundpicker/RingtoneAdapter.java new file mode 100644 index 000000000000..1f33aa2ce4d9 --- /dev/null +++ b/packages/SoundPicker/src/com/android/soundpicker/RingtoneAdapter.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.soundpicker; + +import static com.android.internal.widget.RecyclerView.NO_ID; + +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.media.RingtoneManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CheckedTextView; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.StringRes; +import androidx.recyclerview.widget.RecyclerView; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * The adapter presents a list of ringtones which may include fixed item in the list and an action + * button at the end. + * + * The adapter handles three different types of items: + * <ul> + * <li>FIXED: Fixed items are items added to the top of the list. These items can not be modified + * and their position will never change. + * <li>DYNAMIC: Dynamic items are items from the ringtone manager. These items can be modified + * and their position can change. + * <li>FOOTER: A footer item is an added button to the end of the list. This item can be clicked + * but not selected and its position will never change. + * </ul> + */ +final class RingtoneAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { + + private static final int VIEW_TYPE_FIXED_ITEM = 0; + private static final int VIEW_TYPE_DYNAMIC_ITEM = 1; + private static final int VIEW_TYPE_ADD_RINGTONE_ITEM = 2; + private final Cursor mCursor; + private final List<Integer> mFixedItemTitles; + private final WorkRingtoneProvider mWorkRingtoneProvider; + private final RingtoneSelectionListener mRingtoneSelectionListener; + private final int mRowIDColumn; + private int mSelectedItem = -1; + @StringRes private Integer mAddRingtoneItemTitle; + + /** Listener for ringtone selections. */ + interface RingtoneSelectionListener { + void onRingtoneSelected(int position); + void onAddRingtoneSelected(); + } + /** Provides a mean to detect work ringtones. */ + interface WorkRingtoneProvider { + boolean isWorkRingtone(int position); + + Drawable getWorkIconDrawable(); + } + + RingtoneAdapter(Cursor cursor, RingtoneSelectionListener ringtoneSelectionListener, + WorkRingtoneProvider workRingtoneProvider) { + mCursor = cursor; + mRingtoneSelectionListener = ringtoneSelectionListener; + mWorkRingtoneProvider = workRingtoneProvider; + mFixedItemTitles = new ArrayList<>(); + mRowIDColumn = mCursor != null ? mCursor.getColumnIndex("_id") : -1; + setHasStableIds(true); + } + + void setSelectedItem(int position) { + notifyItemChanged(mSelectedItem); + mSelectedItem = position; + notifyItemChanged(mSelectedItem); + } + + void addTitleForFixedItem(@StringRes int textResId) { + mFixedItemTitles.add(textResId); + notifyItemInserted(mFixedItemTitles.size() - 1); + } + + void addTitleForAddRingtoneItem(@StringRes int textResId) { + mAddRingtoneItemTitle = textResId; + notifyItemInserted(getItemCount() - 1); + } + + @NotNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + + if (viewType == VIEW_TYPE_FIXED_ITEM) { + View fixedItemView = inflater.inflate( + com.android.internal.R.layout.select_dialog_singlechoice_material, parent, + false); + + return new FixedItemViewHolder(fixedItemView, mRingtoneSelectionListener); + } + + if (viewType == VIEW_TYPE_ADD_RINGTONE_ITEM) { + View addRingtoneItemView = inflater.inflate(R.layout.add_new_sound_item, parent, false); + + return new AddRingtoneItemViewHolder(addRingtoneItemView, mRingtoneSelectionListener); + } + + View view = inflater.inflate(R.layout.radio_with_work_badge, parent, false); + + return new DynamicItemViewHolder(view, mRingtoneSelectionListener); + } + + @Override + public void onBindViewHolder(@NotNull RecyclerView.ViewHolder holder, int position) { + if (holder instanceof FixedItemViewHolder) { + FixedItemViewHolder viewHolder = (FixedItemViewHolder) holder; + + viewHolder.onBind(mFixedItemTitles.get(position), + /* isChecked= */ position == mSelectedItem); + return; + } + if (holder instanceof AddRingtoneItemViewHolder) { + AddRingtoneItemViewHolder viewHolder = (AddRingtoneItemViewHolder) holder; + + viewHolder.onBind(mAddRingtoneItemTitle); + return; + } + + if (!(holder instanceof DynamicItemViewHolder)) { + throw new IllegalArgumentException("holder type is not supported"); + } + + DynamicItemViewHolder viewHolder = (DynamicItemViewHolder) holder; + int pos = position - mFixedItemTitles.size(); + if (!mCursor.moveToPosition(pos)) { + throw new IllegalStateException("Could not move cursor to position: " + pos); + } + + Drawable workIcon = (mWorkRingtoneProvider != null) + && mWorkRingtoneProvider.isWorkRingtone(position) + ? mWorkRingtoneProvider.getWorkIconDrawable() : null; + + viewHolder.onBind(mCursor.getString(RingtoneManager.TITLE_COLUMN_INDEX), + /* isChecked= */ position == mSelectedItem, workIcon); + } + + @Override + public int getItemViewType(int position) { + if (!mFixedItemTitles.isEmpty() && position < mFixedItemTitles.size()) { + return VIEW_TYPE_FIXED_ITEM; + } + if (mAddRingtoneItemTitle != null && position == getItemCount() - 1) { + return VIEW_TYPE_ADD_RINGTONE_ITEM; + } + + return VIEW_TYPE_DYNAMIC_ITEM; + } + + @Override + public int getItemCount() { + int itemCount = mFixedItemTitles.size() + mCursor.getCount(); + + if (mAddRingtoneItemTitle != null) { + itemCount++; + } + + return itemCount; + } + + @Override + public long getItemId(int position) { + int itemViewType = getItemViewType(position); + if (itemViewType == VIEW_TYPE_FIXED_ITEM) { + // Since the item is a fixed item, then we can use the position as a stable ID + // since the order of the fixed items should never change. + return position; + } + if (itemViewType == VIEW_TYPE_DYNAMIC_ITEM && mCursor != null + && mCursor.moveToPosition(position - mFixedItemTitles.size()) + && mRowIDColumn != -1) { + return mCursor.getLong(mRowIDColumn) + mFixedItemTitles.size(); + } + + // The position is either invalid or the item is the add ringtone item view, so no stable + // ID is returned. Add ringtone item view cannot be selected and only include an action + // buttons. + return NO_ID; + } + + private static class DynamicItemViewHolder extends RecyclerView.ViewHolder { + private final CheckedTextView mTitleTextView; + private final ImageView mWorkIcon; + + DynamicItemViewHolder(View itemView, RingtoneSelectionListener listener) { + super(itemView); + + mTitleTextView = itemView.findViewById(R.id.checked_text_view); + mWorkIcon = itemView.findViewById(R.id.work_icon); + itemView.setOnClickListener(v -> listener.onRingtoneSelected(this.getLayoutPosition())); + } + + void onBind(String title, boolean isChecked, Drawable workIcon) { + Objects.requireNonNull(mTitleTextView); + Objects.requireNonNull(mWorkIcon); + + mTitleTextView.setText(title); + mTitleTextView.setChecked(isChecked); + + if (workIcon == null) { + mWorkIcon.setVisibility(View.GONE); + } else { + mWorkIcon.setImageDrawable(workIcon); + mWorkIcon.setVisibility(View.VISIBLE); + } + } + } + + private static class FixedItemViewHolder extends RecyclerView.ViewHolder { + private final CheckedTextView mTitleTextView; + + FixedItemViewHolder(View itemView, RingtoneSelectionListener listener) { + super(itemView); + + mTitleTextView = (CheckedTextView) itemView; + itemView.setOnClickListener(v -> listener.onRingtoneSelected(this.getLayoutPosition())); + } + + void onBind(@StringRes int title, boolean isChecked) { + Objects.requireNonNull(mTitleTextView); + + mTitleTextView.setText(title); + mTitleTextView.setChecked(isChecked); + } + } + + private static class AddRingtoneItemViewHolder extends RecyclerView.ViewHolder { + private final TextView mTitleTextView; + + AddRingtoneItemViewHolder(View itemView, RingtoneSelectionListener listener) { + super(itemView); + + mTitleTextView = itemView.findViewById(R.id.add_new_sound_text); + itemView.setOnClickListener(v -> listener.onAddRingtoneSelected()); + } + + void onBind(@StringRes int title) { + Objects.requireNonNull(mTitleTextView); + + mTitleTextView.setText(title); + } + } +} diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java index f591aa54a50e..4d7cf1cba161 100644 --- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java +++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerActivity.java @@ -16,46 +16,20 @@ package com.android.soundpicker; -import android.content.ContentProvider; -import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; -import android.content.res.Resources; -import android.content.res.Resources.NotFoundException; -import android.database.Cursor; -import android.database.CursorWrapper; import android.media.RingtoneManager; import android.net.Uri; import android.os.Bundle; -import android.os.Environment; -import android.os.Handler; import android.os.UserHandle; -import android.os.UserManager; -import android.provider.MediaStore; import android.util.Log; -import android.util.TypedValue; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.CursorAdapter; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.Toast; -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.ViewModelProvider; -import com.google.common.util.concurrent.FutureCallback; - import dagger.hilt.android.AndroidEntryPoint; -import java.util.regex.Pattern; - /** * The {@link RingtonePickerActivity} allows the user to choose one from all of the * available ringtones. The chosen ringtone's URI will be persisted as a string. @@ -63,106 +37,17 @@ import java.util.regex.Pattern; * @see RingtoneManager#ACTION_RINGTONE_PICKER */ @AndroidEntryPoint(AppCompatActivity.class) -public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity implements - AdapterView.OnItemSelectedListener, Runnable, DialogInterface.OnClickListener, - DialogInterface.OnDismissListener { - - private static final int POS_UNKNOWN = -1; +public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity { private static final String TAG = "RingtonePickerActivity"; - - private static final int DELAY_MS_SELECTION_PLAYED = 300; - - private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE; - - private static final String SAVE_CLICKED_POS = "clicked_pos"; - - private static final String SOUND_NAME_RES_PREFIX = "sound_name_"; - - private static final int ADD_FILE_REQUEST_CODE = 300; + // TODO: Use the extra key from RingtoneManager once it's added. + private static final String EXTRA_RINGTONE_PICKER_CATEGORY = "EXTRA_RINGTONE_PICKER_CATEGORY"; + private static final boolean RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED = false; private RingtonePickerViewModel mRingtonePickerViewModel; - private int mType; - - private Cursor mCursor; - private Handler mHandler; - private BadgedRingtoneAdapter mAdapter; - - /** Whether this list has the 'Silent' item. */ - private boolean mHasSilentItem; - - /** The Uri to place a checkmark next to. */ - private Uri mExistingUri; - - /** Whether this list has the 'Default' item. */ - private boolean mHasDefaultItem; - - /** The Uri to play when the 'Default' item is clicked. */ - private Uri mUriForDefaultItem; - - /** Id of the user to which the ringtone picker should list the ringtones */ - private int mPickerUserId; - - /** - * Stable ID for the ringtone that is currently checked (may be -1 if no ringtone is checked). - */ - private long mCheckedItemId = -1; - private int mAttributesFlags; - private boolean mShowOkCancelButtons; - - private AlertDialog mAlertDialog; - - private int mCheckedItem = POS_UNKNOWN; - - private final DialogInterface.OnClickListener mRingtoneClickListener = - new DialogInterface.OnClickListener() { - - /* - * On item clicked - */ - public void onClick(DialogInterface dialog, int which) { - if (which == mCursor.getCount() + mRingtonePickerViewModel.getFixedItemCount()) { - // The "Add new ringtone" item was clicked. Start a file picker intent to select - // only audio files (MIME type "audio/*") - final Intent chooseFile = getMediaFilePickerIntent(); - startActivityForResult(chooseFile, ADD_FILE_REQUEST_CODE); - return; - } - - // Save the position of most recently clicked item - setCheckedItem(which); - - // In the buttonless (watch-only) version, preemptively set our result since we won't - // have another chance to do so before the activity closes. - if (!mShowOkCancelButtons) { - setSuccessResultWithRingtone( - mRingtonePickerViewModel.getCurrentlySelectedRingtoneUri(getCheckedItem(), - mUriForDefaultItem)); - } - - // Play clip - playRingtone(which, 0); - } - - }; - private final FutureCallback<Uri> mAddCustomRingtoneCallback = new FutureCallback<>() { - @Override - public void onSuccess(Uri ringtoneUri) { - requeryForAdapter(); - } - - @Override - public void onFailure(Throwable throwable) { - Log.e(TAG, "Failed to add custom ringtone.", throwable); - // Ringtone was not added, display error Toast - Toast.makeText(RingtonePickerActivity.this.getApplicationContext(), - R.string.unable_to_add_ringtone, Toast.LENGTH_SHORT).show(); - } - }; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -170,300 +55,75 @@ public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity im mRingtonePickerViewModel = new ViewModelProvider(this).get(RingtonePickerViewModel.class); - mHandler = new Handler(); - Intent intent = getIntent(); - mPickerUserId = UserHandle.myUserId(); + /** + * Id of the user to which the ringtone picker should list the ringtones + */ + int pickerUserId = UserHandle.myUserId(); // Get the types of ringtones to show - mType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, + int ringtoneType = intent.getIntExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtonePickerViewModel.RINGTONE_TYPE_UNKNOWN); - mRingtonePickerViewModel.initRingtoneManager(mType); - setupCursor(); - /* - * Get whether to show the 'Default' item, and the URI to play when the - * default is clicked - */ - mHasDefaultItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); - mUriForDefaultItem = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI); - if (mUriForDefaultItem == null) { - mUriForDefaultItem = RingtonePickerViewModel.getDefaultItemUriByType(mType); + // Get whether to show the 'Default' item, and the URI to play when the default is clicked + boolean hasDefaultItem = + intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); + // The Uri to play when the 'Default' item is clicked. + Uri uriForDefaultItem = + intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI); + if (uriForDefaultItem == null) { + uriForDefaultItem = RingtonePickerViewModel.getDefaultItemUriByType(ringtoneType); } - // Get whether to show the 'Silent' item - mHasSilentItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); + // Get whether this list has the 'Silent' item. + boolean hasSilentItem = + intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); // AudioAttributes flags mAttributesFlags |= intent.getIntExtra( RingtoneManager.EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS, 0 /*defaultValue == no flags*/); - mShowOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons); - - // The volume keys will control the stream that we are choosing a ringtone for - setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType()); + boolean showOkCancelButtons = getResources().getBoolean(R.bool.config_showOkCancelButtons); // Get the URI whose list item should have a checkmark - mExistingUri = intent + Uri existingUri = intent .getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI); - // Create the list of ringtones and hold on to it so we can update later. - mAdapter = new BadgedRingtoneAdapter(this, mCursor, - /* isManagedProfile = */ UserManager.get(this).isManagedProfile(mPickerUserId)); - - AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this, - android.R.style.ThemeOverlay_Material_Dialog); - alertDialogBuilder - .setSingleChoiceItems(mAdapter, getCheckedItem(), mRingtoneClickListener) - .setOnItemSelectedListener(this) - .setOnDismissListener(this); - if (mShowOkCancelButtons) { - alertDialogBuilder - .setPositiveButton(getString(com.android.internal.R.string.ok), this) - .setNegativeButton(getString(com.android.internal.R.string.cancel), this); + String title = intent.getStringExtra(RingtoneManager.EXTRA_RINGTONE_TITLE); + if (title == null) { + title = getString(RingtonePickerViewModel.getTitleByType(ringtoneType)); } + String ringtonePickerCategory = intent.getStringExtra(EXTRA_RINGTONE_PICKER_CATEGORY); + RingtonePickerViewModel.PickerType pickerType = mapCategoryToPickerType( + ringtonePickerCategory); - String title = intent.getStringExtra(RingtoneManager.EXTRA_RINGTONE_TITLE); - alertDialogBuilder.setTitle( - title != null ? title : getString(RingtonePickerViewModel.getTitleByType(mType))); + mRingtonePickerViewModel.init(new RingtonePickerViewModel.PickerConfig(title, pickerUserId, + ringtoneType, hasDefaultItem, uriForDefaultItem, hasSilentItem, + mAttributesFlags, existingUri, showOkCancelButtons, pickerType)); - mAlertDialog = alertDialogBuilder.show(); - ListView listView = mAlertDialog.getListView(); - if (listView != null) { - // List view needs to gain focus in order for RSB to work. - if (!listView.requestFocus()) { - Log.e(TAG, "Unable to gain focus! RSB may not work properly."); - } - prepareListView(listView); - } - if (savedInstanceState != null) { - setCheckedItem(savedInstanceState.getInt(SAVE_CLICKED_POS, POS_UNKNOWN)); - } - } - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putInt(SAVE_CLICKED_POS, getCheckedItem()); - } + // The volume keys will control the stream that we are choosing a ringtone for + setVolumeControlStream(mRingtonePickerViewModel.getRingtoneStreamType()); - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); + if (savedInstanceState == null) { + TabbedDialogFragment dialogFragment = new TabbedDialogFragment(); - if (requestCode == ADD_FILE_REQUEST_CODE && resultCode == RESULT_OK) { - mRingtonePickerViewModel.addRingtoneAsync(data.getData(), - mType, - mAddCustomRingtoneCallback, - // Causes the callback to be executed on the main thread. - ContextCompat.getMainExecutor(this.getApplicationContext())); + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + Fragment prev = getSupportFragmentManager().findFragmentByTag(TabbedDialogFragment.TAG); + if (prev != null) { + ft.remove(prev); + } + ft.addToBackStack(null); + dialogFragment.show(ft, TabbedDialogFragment.TAG); } } - @Override - public void onDismiss(DialogInterface dialog) { - if (!isChangingConfigurations()) { - finish(); - } - } @Override public void onDestroy() { mRingtonePickerViewModel.cancelPendingAsyncTasks(); - if (mAlertDialog != null && mAlertDialog.isShowing()) { - mAlertDialog.dismiss(); - } - if (mHandler != null) { - mHandler.removeCallbacksAndMessages(null); - } - if (mCursor != null) { - mCursor.close(); - mCursor = null; - } super.onDestroy(); } - private void prepareListView(@NonNull ListView listView) { - // Reset the static item count, as this method can be called multiple times - mRingtonePickerViewModel.resetFixedItemCount(); - - if (mHasDefaultItem) { - int defaultItemPos = addDefaultRingtoneItem(listView); - - if (getCheckedItem() == POS_UNKNOWN && RingtoneManager.isDefault(mExistingUri)) { - setCheckedItem(defaultItemPos); - } - } - - if (mHasSilentItem) { - int silentItemPos = addSilentItem(listView); - - // The 'Silent' item should use a null Uri - if (getCheckedItem() == POS_UNKNOWN && mExistingUri == null) { - setCheckedItem(silentItemPos); - } - } - - if (getCheckedItem() == POS_UNKNOWN) { - setCheckedItem( - getListPosition(mRingtonePickerViewModel.getRingtonePosition(mExistingUri))); - } - - // In the buttonless (watch-only) version, preemptively set our result since we won't - // have another chance to do so before the activity closes. - if (!mShowOkCancelButtons) { - setSuccessResultWithRingtone( - mRingtonePickerViewModel.getCurrentlySelectedRingtoneUri(getCheckedItem(), - mUriForDefaultItem)); - } - // If external storage is available, add a button to install sounds from storage. - if (resolvesMediaFilePicker() - && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { - addNewSoundItem(listView); - } - - // Enable context menu in ringtone items - registerForContextMenu(listView); - } - - /** - * Re-query RingtoneManager for the most recent set of installed ringtones. May move the - * selected item position to match the new position of the chosen sound. - * - * This should only need to happen after adding or removing a ringtone. - */ - private void requeryForAdapter() { - // Refresh and set a new cursor, closing the old one. - mRingtonePickerViewModel.initRingtoneManager(mType); - setupCursor(); - mAdapter.changeCursor(mCursor); - - // Update checked item location. - int checkedPosition = POS_UNKNOWN; - for (int i = 0; i < mAdapter.getCount(); i++) { - if (mAdapter.getItemId(i) == mCheckedItemId) { - checkedPosition = getListPosition(i); - break; - } - } - if (mHasSilentItem && checkedPosition == POS_UNKNOWN) { - checkedPosition = mRingtonePickerViewModel.getSilentItemPosition(); - } - setCheckedItem(checkedPosition); - } - - /** - * Adds a static item to the top of the list. A static item is one that is not from the - * RingtoneManager. - * - * @param listView The ListView to add to. - * @param textResId The resource ID of the text for the item. - * @return The position of the inserted item. - */ - private int addStaticItem(@NonNull ListView listView, int textResId) { - TextView textView = (TextView) getLayoutInflater().inflate( - com.android.internal.R.layout.select_dialog_singlechoice_material, listView, false); - textView.setText(textResId); - listView.addHeaderView(textView); - mRingtonePickerViewModel.incrementFixedItemCount(); - return listView.getHeaderViewsCount() - 1; - } - - private int addDefaultRingtoneItem(@NonNull ListView listView) { - int defaultRingtoneItemPos = addStaticItem(listView, - RingtonePickerViewModel.getDefaultRingtoneItemTextByType(mType)); - mRingtonePickerViewModel.setDefaultItemPosition(defaultRingtoneItemPos); - return defaultRingtoneItemPos; - } - - private int addSilentItem(@NonNull ListView listView) { - int silentItemPos = addStaticItem(listView, com.android.internal.R.string.ringtone_silent); - mRingtonePickerViewModel.setSilentItemPosition(silentItemPos); - return silentItemPos; - } - - private void addNewSoundItem(@NonNull ListView listView) { - View view = getLayoutInflater().inflate(R.layout.add_new_sound_item, listView, - false /* attachToRoot */); - TextView text = (TextView)view.findViewById(R.id.add_new_sound_text); - - text.setText(RingtonePickerViewModel.getAddNewItemTextByType(mType)); - - listView.addFooterView(view); - } - - private void setupCursor() { - mCursor = new LocalizedCursor( - mRingtonePickerViewModel.getRingtoneCursor(), getResources(), COLUMN_LABEL); - } - - private int getCheckedItem() { - return mCheckedItem; - } - - private void setCheckedItem(int pos) { - mCheckedItem = pos; - ListView listView = mAlertDialog.getListView(); - if (listView != null) { - listView.setItemChecked(pos, true); - listView.smoothScrollToPosition(pos); - } - mCheckedItemId = mAdapter.getItemId( - mRingtonePickerViewModel.itemPositionToRingtonePosition(pos)); - } - - /* - * On click of Ok/Cancel buttons - */ - public void onClick(DialogInterface dialog, int which) { - boolean positiveResult = which == DialogInterface.BUTTON_POSITIVE; - - if (positiveResult) { - setSuccessResultWithRingtone( - mRingtonePickerViewModel.getCurrentlySelectedRingtoneUri(getCheckedItem(), - mUriForDefaultItem)); - } else { - setResult(RESULT_CANCELED); - } - - finish(); - } - - /* - * On item selected via keys - */ - public void onItemSelected(AdapterView parent, View view, int position, long id) { - // footer view - if (position >= mCursor.getCount() + mRingtonePickerViewModel.getFixedItemCount()) { - return; - } - - playRingtone(position, DELAY_MS_SELECTION_PLAYED); - - // In the buttonless (watch-only) version, preemptively set our result since we won't - // have another chance to do so before the activity closes. - if (!mShowOkCancelButtons) { - setSuccessResultWithRingtone( - mRingtonePickerViewModel.getCurrentlySelectedRingtoneUri(getCheckedItem(), - mUriForDefaultItem)); - } - } - - public void onNothingSelected(AdapterView parent) { - } - - private void playRingtone(int position, int delayMs) { - mHandler.removeCallbacks(this); - mRingtonePickerViewModel.setSampleItemPosition(position); - mHandler.postDelayed(this, delayMs); - } - - public void run() { - mRingtonePickerViewModel.playRingtone( - mRingtonePickerViewModel.itemPositionToRingtonePosition( - mRingtonePickerViewModel.getSampleItemPosition()), mUriForDefaultItem, - mAttributesFlags); - } - @Override protected void onStop() { super.onStop(); @@ -476,155 +136,29 @@ public final class RingtonePickerActivity extends Hilt_RingtonePickerActivity im mRingtonePickerViewModel.onPause(isChangingConfigurations()); } - private void setSuccessResultWithRingtone(Uri ringtoneUri) { - setResult(RESULT_OK, - new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, ringtoneUri)); - } - - private int getListPosition(int ringtoneManagerPos) { - - // If the manager position is -1 (for not found), return that - if (ringtoneManagerPos < 0) return ringtoneManagerPos; - - return ringtoneManagerPos + mRingtonePickerViewModel.getFixedItemCount(); - } - - private Intent getMediaFilePickerIntent() { - final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT); - chooseFile.setType("audio/*"); - chooseFile.putExtra(Intent.EXTRA_MIME_TYPES, - new String[] { "audio/*", "application/ogg" }); - return chooseFile; - } - - private boolean resolvesMediaFilePicker() { - return getMediaFilePickerIntent().resolveActivity(getPackageManager()) != null; - } - - private static class LocalizedCursor extends CursorWrapper { - - final int mTitleIndex; - final Resources mResources; - String mNamePrefix; - final Pattern mSanitizePattern; - - LocalizedCursor(Cursor cursor, Resources resources, String columnLabel) { - super(cursor); - mTitleIndex = mCursor.getColumnIndex(columnLabel); - mResources = resources; - mSanitizePattern = Pattern.compile("[^a-zA-Z0-9]"); - if (mTitleIndex == -1) { - Log.e(TAG, "No index for column " + columnLabel); - mNamePrefix = null; - } else { - try { - // Build the prefix for the name of the resource to look up - // format is: "ResourcePackageName::ResourceTypeName/" - // (the type name is expected to be "string" but let's not hardcode it). - // Here we use an existing resource "notification_sound_default" which is - // always expected to be found. - mNamePrefix = String.format("%s:%s/%s", - mResources.getResourcePackageName(R.string.notification_sound_default), - mResources.getResourceTypeName(R.string.notification_sound_default), - SOUND_NAME_RES_PREFIX); - } catch (NotFoundException e) { - mNamePrefix = null; - } - } - } - - /** - * Process resource name to generate a valid resource name. - * @param input - * @return a non-null String - */ - private String sanitize(String input) { - if (input == null) { - return ""; - } - return mSanitizePattern.matcher(input).replaceAll("_").toLowerCase(); - } - - @Override - public String getString(int columnIndex) { - final String defaultName = mCursor.getString(columnIndex); - if ((columnIndex != mTitleIndex) || (mNamePrefix == null)) { - return defaultName; - } - TypedValue value = new TypedValue(); - try { - // the name currently in the database is used to derive a name to match - // against resource names in this package - mResources.getValue(mNamePrefix + sanitize(defaultName), value, false); - } catch (NotFoundException e) { - // no localized string, use the default string - return defaultName; - } - if ((value != null) && (value.type == TypedValue.TYPE_STRING)) { - Log.d(TAG, String.format("Replacing name %s with %s", - defaultName, value.string.toString())); - return value.string.toString(); - } else { - Log.e(TAG, "Invalid value when looking up localized name, using " + defaultName); - return defaultName; - } - } - } - - private class BadgedRingtoneAdapter extends CursorAdapter { - private final boolean mIsManagedProfile; - - public BadgedRingtoneAdapter(Context context, Cursor cursor, boolean isManagedProfile) { - super(context, cursor); - mIsManagedProfile = isManagedProfile; - } - - @Override - public long getItemId(int position) { - if (position < 0) { - return position; - } - return super.getItemId(position); - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - LayoutInflater inflater = LayoutInflater.from(context); - return inflater.inflate(R.layout.radio_with_work_badge, parent, false); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - // Set text as the title of the ringtone - ((TextView) view.findViewById(R.id.checked_text_view)) - .setText(cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX)); - - boolean isWorkRingtone = false; - if (mIsManagedProfile) { - /* - * Display the work icon if the ringtone belongs to a work profile. We can tell that - * a ringtone belongs to a work profile if the picker user is a managed profile, the - * ringtone Uri is in external storage, and either the uri has no user id or has the - * id of the picker user - */ - Uri currentUri = mRingtonePickerViewModel.getRingtoneUri(cursor.getPosition()); - int uriUserId = ContentProvider.getUserIdFromUri(currentUri, mPickerUserId); - Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri); - - if (uriUserId == mPickerUserId && uriWithoutUserId.toString() - .startsWith(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString())) { - isWorkRingtone = true; - } - } - - ImageView workIcon = (ImageView) view.findViewById(R.id.work_icon); - if(isWorkRingtone) { - workIcon.setImageDrawable(getPackageManager().getUserBadgeForDensityNoBackground( - UserHandle.of(mPickerUserId), -1 /* density */)); - workIcon.setVisibility(View.VISIBLE); - } else { - workIcon.setVisibility(View.GONE); - } + /** + * Maps the ringtone picker category to the appropriate PickerType. + * If the category is null or the feature is still not released, then it defaults to sound + * picker. + * + * @param category the ringtone picker category. + * @return the corresponding picker type. + */ + private static RingtonePickerViewModel.PickerType mapCategoryToPickerType(String category) { + if (category == null || !RINGTONE_PICKER_CATEGORY_FEATURE_ENABLED) { + return RingtonePickerViewModel.PickerType.SOUND_PICKER; + } + + switch (category) { + case "android.intent.category.RINGTONE_PICKER_RINGTONE": + return RingtonePickerViewModel.PickerType.RINGTONE_PICKER; + case "android.intent.category.RINGTONE_PICKER_SOUND": + return RingtonePickerViewModel.PickerType.SOUND_PICKER; + case "android.intent.category.RINGTONE_PICKER_VIBRATION": + return RingtonePickerViewModel.PickerType.VIBRATION_PICKER; + default: + Log.w(TAG, "Unrecognized category: " + category + ". Defaulting to sound picker."); + return RingtonePickerViewModel.PickerType.SOUND_PICKER; } } } diff --git a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java index f045dc2f864c..914f16ab41df 100644 --- a/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java +++ b/packages/SoundPicker/src/com/android/soundpicker/RingtonePickerViewModel.java @@ -27,6 +27,7 @@ import android.media.RingtoneManager; import android.net.Uri; import android.provider.Settings; +import androidx.annotation.NonNull; import androidx.lifecycle.ViewModel; import com.android.internal.annotations.VisibleForTesting; @@ -50,6 +51,7 @@ import javax.inject.Inject; public final class RingtonePickerViewModel extends ViewModel { static final int RINGTONE_TYPE_UNKNOWN = -1; + /** * Keep the currently playing ringtone around when changing orientation, so that it * can be stopped later, after the activity is recreated. @@ -72,16 +74,87 @@ public final class RingtonePickerViewModel extends ViewModel { private int mSampleItemPosition = ITEM_POSITION_UNKNOWN; /** The position in the list of the 'Default' item. */ private int mDefaultItemPosition = ITEM_POSITION_UNKNOWN; - /** The number of static items in the list. */ + /** The number of fixed items in the list. */ private int mFixedItemCount; private ListenableFuture<Uri> mAddCustomRingtoneFuture; private RingtoneManager mRingtoneManager; /** + * Stable ID for the ringtone that is currently selected (may be -1 if no ringtone is selected). + */ + private long mSelectedItemId = -1; + private int mSelectedItemPosition = ITEM_POSITION_UNKNOWN; + + /** * The ringtone that's currently playing. */ private Ringtone mCurrentRingtone; + private PickerConfig mPickerConfig; + + public enum PickerType { + RINGTONE_PICKER, + SOUND_PICKER, + VIBRATION_PICKER + } + + /** + * Holds immutable info on the picker that should be displayed. + */ + static final class PickerConfig { + public final String title; + /** + * Id of the user to which the ringtone picker should list the ringtones. + */ + public final int userId; + /** + * Ringtone type. + */ + public final int ringtoneType; + /** + * Whether this list has the 'Default' item. + */ + public final boolean hasDefaultItem; + /** + * The Uri to play when the 'Default' item is clicked. + */ + public final Uri uriForDefaultItem; + /** + * Whether this list has the 'Silent' item. + */ + public final boolean hasSilentItem; + /** + * AudioAttributes flags. + */ + public final int audioAttributesFlags; + /** + * The Uri to place a checkmark next to. + */ + public final Uri existingUri; + /** + * In the buttonless (watch-only) version we don't show the OK/Cancel buttons. + */ + public final boolean showOkCancelButtons; + + public final PickerType mPickerType; + + PickerConfig(String title, int userId, int ringtoneType, + boolean hasDefaultItem, Uri uriForDefaultItem, boolean hasSilentItem, + int audioAttributesFlags, Uri existingUri, boolean showOkCancelButtons, + PickerType pickerType) { + this.title = title; + this.userId = userId; + this.ringtoneType = ringtoneType; + this.hasDefaultItem = hasDefaultItem; + this.uriForDefaultItem = uriForDefaultItem; + this.hasSilentItem = hasSilentItem; + this.audioAttributesFlags = audioAttributesFlags; + this.existingUri = existingUri; + this.showOkCancelButtons = showOkCancelButtons; + this.mPickerType = pickerType; + } + } + @Inject RingtonePickerViewModel(RingtoneManagerFactory ringtoneManagerFactory, RingtoneFactory ringtoneFactory, @@ -91,6 +164,13 @@ public final class RingtonePickerViewModel extends ViewModel { mListeningExecutorService = listeningExecutorServiceFactory.createSingleThreadExecutor(); } + @NonNull + PickerConfig getPickerConfig() { + return requireNonNull(mPickerConfig, + "PickerConfig was never set. Did you forget to call " + + "RingtonePickerViewModel#init?"); + } + @StringRes static int getTitleByType(int ringtoneType) { switch (ringtoneType) { @@ -138,10 +218,11 @@ public final class RingtonePickerViewModel extends ViewModel { } } - void initRingtoneManager(int type) { + void init(@NonNull PickerConfig pickerConfig) { mRingtoneManager = mRingtoneManagerFactory.create(); - if (type != RINGTONE_TYPE_UNKNOWN) { - mRingtoneManager.setType(type); + mPickerConfig = pickerConfig; + if (pickerConfig.ringtoneType != RINGTONE_TYPE_UNKNOWN) { + mRingtoneManager.setType(pickerConfig.ringtoneType); } } @@ -166,7 +247,7 @@ public final class RingtonePickerViewModel extends ViewModel { */ void cancelPendingAsyncTasks() { if (mAddCustomRingtoneFuture != null && !mAddCustomRingtoneFuture.isDone()) { - mAddCustomRingtoneFuture.cancel(/*mayInterruptIfRunning=*/true); + mAddCustomRingtoneFuture.cancel(/* mayInterruptIfRunning= */ true); } } @@ -180,36 +261,48 @@ public final class RingtonePickerViewModel extends ViewModel { return mRingtoneManager.getCursor(); } - Uri getRingtoneUri(int ringtonePosition) { + Uri getRingtoneUri(int position) { requireNonNull(mRingtoneManager, RINGTONE_MANAGER_NULL_MESSAGE); - return mRingtoneManager.getRingtoneUri(ringtonePosition); + return mRingtoneManager.getRingtoneUri(mapListPositionToRingtonePosition(position)); } int getRingtonePosition(Uri uri) { requireNonNull(mRingtoneManager, RINGTONE_MANAGER_NULL_MESSAGE); - return mRingtoneManager.getRingtonePosition(uri); + return mapRingtonePositionToListPosition(mRingtoneManager.getRingtonePosition(uri)); } /** - * Returns the position of the item in the list before header views were added. + * Maps the item position in the list, to its equivalent position in the RingtoneManager. * - * @param itemPosition the position of item in the list with any added headers. - * @return position of the item in the list ignoring headers. + * @param itemPosition the position of item in the list. + * @return position of the item in the RingtoneManager. */ - int itemPositionToRingtonePosition(int itemPosition) { + private int mapListPositionToRingtonePosition(int itemPosition) { + // If the manager position is -1 (for not found), then return that. + if (itemPosition < 0) return itemPosition; + return itemPosition - mFixedItemCount; } - int getFixedItemCount() { - return mFixedItemCount; + /** + * Maps the item position in the RingtoneManager, to its equivalent position in the list. + * + * @param itemPosition the position of the item in the RingtoneManager. + * @return position of the item in the list. + */ + private int mapRingtonePositionToListPosition(int itemPosition) { + // If the manager position is -1 (for not found), then return that. + if (itemPosition < 0) return itemPosition; + + return itemPosition + mFixedItemCount; } void resetFixedItemCount() { mFixedItemCount = 0; } - void incrementFixedItemCount() { - mFixedItemCount++; + int incrementAndGetFixedItemCount() { + return mFixedItemCount++; } void setDefaultItemPosition(int defaultItemPosition) { @@ -232,6 +325,22 @@ public final class RingtonePickerViewModel extends ViewModel { mSampleItemPosition = sampleItemPosition; } + public int getSelectedItemPosition() { + return mSelectedItemPosition; + } + + public void setSelectedItemPosition(int selectedItemPosition) { + mSelectedItemPosition = selectedItemPosition; + } + + public void setSelectedItemId(long selectedItemId) { + mSelectedItemId = selectedItemId; + } + + public long getSelectedItemId() { + return mSelectedItemId; + } + void onPause(boolean isChangingConfigurations) { if (!isChangingConfigurations) { stopAnyPlayingRingtone(); @@ -247,19 +356,19 @@ public final class RingtonePickerViewModel extends ViewModel { } @Nullable - Uri getCurrentlySelectedRingtoneUri(int checkedItem, Uri defaultUri) { - if (checkedItem == ITEM_POSITION_UNKNOWN) { - // When the getCheckItem is POS_UNKNOWN, it is not the case we expected. + Uri getCurrentlySelectedRingtoneUri() { + if (mSelectedItemPosition == ITEM_POSITION_UNKNOWN) { + // When the selected item is POS_UNKNOWN, it is not the case we expected. // We return null for this case. return null; - } else if (checkedItem == mDefaultItemPosition) { + } else if (mSelectedItemPosition == mDefaultItemPosition) { // Use the default Uri that they originally gave us. - return defaultUri; - } else if (checkedItem == mSilentItemPosition) { + return mPickerConfig.uriForDefaultItem; + } else if (mSelectedItemPosition == mSilentItemPosition) { // Use a null Uri for the 'Silent' item. return null; } else { - return getRingtoneUri(itemPositionToRingtonePosition(checkedItem)); + return getRingtoneUri(mSelectedItemPosition); } } @@ -280,7 +389,8 @@ public final class RingtonePickerViewModel extends ViewModel { mCurrentRingtone.setStreamType(mRingtoneManager.inferStreamType()); } } else { - mCurrentRingtone = mRingtoneManager.getRingtone(position); + mCurrentRingtone = mRingtoneManager.getRingtone( + mapListPositionToRingtonePosition(position)); } if (mCurrentRingtone != null) { diff --git a/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java b/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java new file mode 100644 index 000000000000..e07d1095bed2 --- /dev/null +++ b/packages/SoundPicker/src/com/android/soundpicker/SoundPickerFragment.java @@ -0,0 +1,332 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.soundpicker; + +import android.app.Activity; +import android.content.ContentProvider; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.MediaStore; +import android.util.Log; +import android.view.View; +import android.widget.Toast; + +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.common.util.concurrent.FutureCallback; + +import dagger.hilt.android.AndroidEntryPoint; + +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * A fragment that will display a picker used to select sound or silent. It also includes the + * ability to add custom sounds. + */ +@AndroidEntryPoint(Fragment.class) +public class SoundPickerFragment extends Hilt_SoundPickerFragment { + + private static final String TAG = "SoundPickerFragment"; + private static final String COLUMN_LABEL = MediaStore.Audio.Media.TITLE; + private static final int POS_UNKNOWN = -1; + + private RingtonePickerViewModel.PickerConfig mPickerConfig; + private boolean mIsManagedProfile; + private RingtonePickerViewModel mRingtonePickerViewModel; + private RingtoneAdapter mRingtoneAdapter; + private RecyclerView mSoundRecyclerView; + + private final RingtoneAdapter.WorkRingtoneProvider mWorkRingtoneProvider = + new RingtoneAdapter.WorkRingtoneProvider() { + private Drawable mWorkIconDrawable; + @Override + public boolean isWorkRingtone(int position) { + if (mIsManagedProfile) { + /* + * Display the w ork icon if the ringtone belongs to a work profile. We + * can tell that a ringtone belongs to a work profile if the picker user + * is a managed profile, the ringtone Uri is in external storage, and + * either the uri has no user id or has the id of the picker user + */ + Uri currentUri = mRingtonePickerViewModel.getRingtoneUri(position); + int uriUserId = ContentProvider.getUserIdFromUri(currentUri, + mPickerConfig.userId); + Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(currentUri); + + return uriUserId == mPickerConfig.userId + && uriWithoutUserId.toString().startsWith( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.toString()); + } + + return false; + } + + @Override + public Drawable getWorkIconDrawable() { + if (mWorkIconDrawable == null) { + mWorkIconDrawable = requireActivity().getPackageManager() + .getUserBadgeForDensityNoBackground( + UserHandle.of(mPickerConfig.userId), /* density= */ -1); + } + + return mWorkIconDrawable; + } + }; + + private final FutureCallback<Uri> mAddCustomRingtoneCallback = new FutureCallback<>() { + @Override + public void onSuccess(Uri ringtoneUri) { + requeryForAdapter(); + } + + @Override + public void onFailure(Throwable throwable) { + Log.e(TAG, "Failed to add custom ringtone.", throwable); + // Ringtone was not added, display error Toast + Toast.makeText(requireActivity().getApplicationContext(), + R.string.unable_to_add_ringtone, Toast.LENGTH_SHORT).show(); + } + }; + + ActivityResultLauncher<Intent> mActivityResultLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + new ActivityResultCallback<ActivityResult>() { + @Override + public void onActivityResult(ActivityResult result) { + if (result.getResultCode() == Activity.RESULT_OK) { + // There are no request codes + Intent data = result.getData(); + mRingtonePickerViewModel.addRingtoneAsync(data.getData(), + mPickerConfig.ringtoneType, + mAddCustomRingtoneCallback, + // Causes the callback to be executed on the main thread. + ContextCompat.getMainExecutor( + requireActivity().getApplicationContext())); + } + } + }); + + private final RingtoneAdapter.RingtoneSelectionListener mRingtoneSelectionListener = + new RingtoneAdapter.RingtoneSelectionListener() { + @Override + public void onRingtoneSelected(int position) { + SoundPickerFragment.this.setSelectedItem(position); + + // In the buttonless (watch-only) version, preemptively set our result since + // we won't have another chance to do so before the activity closes. + if (!mPickerConfig.showOkCancelButtons) { + setSuccessResultWithRingtone( + mRingtonePickerViewModel.getCurrentlySelectedRingtoneUri()); + } + + // Play clip + playRingtone(position); + } + + @Override + public void onAddRingtoneSelected() { + // The "Add new ringtone" item was clicked. Start a file picker intent to + // select only audio files (MIME type "audio/*") + final Intent chooseFile = getMediaFilePickerIntent(); + mActivityResultLauncher.launch(chooseFile); + } + }; + + public SoundPickerFragment() { + super(R.layout.fragment_sound_picker); + } + + @Override + public void onViewCreated(@NotNull View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get( + RingtonePickerViewModel.class); + mSoundRecyclerView = view.findViewById(R.id.recycler_view); + Objects.requireNonNull(mSoundRecyclerView); + + mPickerConfig = mRingtonePickerViewModel.getPickerConfig(); + mIsManagedProfile = UserManager.get(requireActivity()).isManagedProfile( + mPickerConfig.userId); + + mRingtoneAdapter = createRingtoneAdapter(); + mSoundRecyclerView.setHasFixedSize(true); + mSoundRecyclerView.setAdapter(mRingtoneAdapter); + mSoundRecyclerView.setLayoutManager(new LinearLayoutManager(requireActivity())); + setSelectedItem(mRingtonePickerViewModel.getSelectedItemPosition()); + prepareRecyclerView(mSoundRecyclerView); + } + + private void prepareRecyclerView(@NonNull RecyclerView recyclerView) { + // Reset the static item count, as this method can be called multiple times + mRingtonePickerViewModel.resetFixedItemCount(); + + if (mPickerConfig.hasDefaultItem) { + int defaultItemPos = addDefaultRingtoneItem(); + + if (getSelectedItem() == POS_UNKNOWN + && RingtoneManager.isDefault(mPickerConfig.existingUri)) { + setSelectedItem(defaultItemPos); + } + } + + if (mPickerConfig.hasSilentItem) { + int silentItemPos = addSilentItem(); + + // The 'Silent' item should use a null Uri + if (getSelectedItem() == POS_UNKNOWN && mPickerConfig.existingUri == null) { + setSelectedItem(silentItemPos); + } + } + + if (getSelectedItem() == POS_UNKNOWN) { + setSelectedItem( + mRingtonePickerViewModel.getRingtonePosition(mPickerConfig.existingUri)); + } + + // In the buttonless (watch-only) version, preemptively set our result since we won't + // have another chance to do so before the activity closes. + if (!mPickerConfig.showOkCancelButtons) { + setSuccessResultWithRingtone( + mRingtonePickerViewModel.getCurrentlySelectedRingtoneUri()); + } + // If external storage is available, add a button to install sounds from storage. + if (resolvesMediaFilePicker() + && Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + addNewSoundItem(); + } + + // Enable context menu in ringtone items + registerForContextMenu(recyclerView); + } + + /** + * Re-query RingtoneManager for the most recent set of installed ringtones. May move the + * selected item position to match the new position of the chosen sound. + * <p> + * This should only need to happen after adding or removing a ringtone. + */ + private void requeryForAdapter() { + // Refresh and set a new cursor, closing the old one. + mRingtonePickerViewModel.init(mPickerConfig); + mRingtoneAdapter = createRingtoneAdapter(); + mSoundRecyclerView.setAdapter(mRingtoneAdapter); + prepareRecyclerView(mSoundRecyclerView); + + // Update selected item location. + int selectedPosition = POS_UNKNOWN; + for (int i = 0; i < mRingtoneAdapter.getItemCount(); i++) { + if (mRingtoneAdapter.getItemId(i) == mRingtonePickerViewModel.getSelectedItemId()) { + selectedPosition = i; + break; + } + } + if (mPickerConfig.hasSilentItem && selectedPosition == POS_UNKNOWN) { + selectedPosition = mRingtonePickerViewModel.getSilentItemPosition(); + } + setSelectedItem(selectedPosition); + } + + private void playRingtone(int position) { + mRingtonePickerViewModel.setSampleItemPosition(position); + mRingtonePickerViewModel.playRingtone(mRingtonePickerViewModel.getSampleItemPosition(), + mPickerConfig.uriForDefaultItem, mPickerConfig.audioAttributesFlags); + } + + private boolean resolvesMediaFilePicker() { + return getMediaFilePickerIntent().resolveActivity(requireActivity().getPackageManager()) + != null; + } + + private Intent getMediaFilePickerIntent() { + final Intent chooseFile = new Intent(Intent.ACTION_GET_CONTENT); + chooseFile.setType("audio/*"); + chooseFile.putExtra(Intent.EXTRA_MIME_TYPES, + new String[]{"audio/*", "application/ogg"}); + return chooseFile; + } + + private void setSuccessResultWithRingtone(Uri ringtoneUri) { + requireActivity().setResult(Activity.RESULT_OK, + new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, ringtoneUri)); + } + + private int getSelectedItem() { + return mRingtonePickerViewModel.getSelectedItemPosition(); + } + + private void setSelectedItem(int pos) { + Objects.requireNonNull(mRingtoneAdapter); + mRingtonePickerViewModel.setSelectedItemPosition(pos); + mRingtoneAdapter.setSelectedItem(pos); + mRingtonePickerViewModel.setSelectedItemId(mRingtoneAdapter.getItemId(pos)); + mSoundRecyclerView.scrollToPosition(pos); + } + + /** + * Adds a fixed item to the fixed items list . A fixed item is one that is not from + * the RingtoneManager. + * + * @param textResId The resource ID of the text for the item. + * @return The position of the inserted item. + */ + private int addFixedItem(int textResId) { + mRingtoneAdapter.addTitleForFixedItem(textResId); + return mRingtonePickerViewModel.incrementAndGetFixedItemCount(); + } + + private int addDefaultRingtoneItem() { + int defaultRingtoneItemPos = addFixedItem( + RingtonePickerViewModel.getDefaultRingtoneItemTextByType( + mPickerConfig.ringtoneType)); + mRingtonePickerViewModel.setDefaultItemPosition(defaultRingtoneItemPos); + return defaultRingtoneItemPos; + } + + private int addSilentItem() { + int silentItemPos = addFixedItem(com.android.internal.R.string.ringtone_silent); + mRingtonePickerViewModel.setSilentItemPosition(silentItemPos); + return silentItemPos; + } + + private void addNewSoundItem() { + mRingtoneAdapter.addTitleForAddRingtoneItem( + RingtonePickerViewModel.getAddNewItemTextByType(mPickerConfig.ringtoneType)); + } + + private RingtoneAdapter createRingtoneAdapter() { + LocalizedCursor cursor = new LocalizedCursor( + mRingtonePickerViewModel.getRingtoneCursor(), getResources(), COLUMN_LABEL); + return new RingtoneAdapter(cursor, mRingtoneSelectionListener, mWorkRingtoneProvider); + } +} diff --git a/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java b/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java new file mode 100644 index 000000000000..63140d21516e --- /dev/null +++ b/packages/SoundPicker/src/com/android/soundpicker/TabbedDialogFragment.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.soundpicker; + +import static android.app.Activity.RESULT_CANCELED; + +import android.app.Activity; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.viewpager2.widget.ViewPager2; + +import com.google.android.material.tabs.TabLayout; +import com.google.android.material.tabs.TabLayoutMediator; + +import dagger.hilt.android.AndroidEntryPoint; + +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +/** + * A dialog fragment with a sound and/or vibration tab based on the picker type. + * <ul> + * <li> Ringtone Pickers will display both sound and vibration tabs. + * <li> Sound Pickers will only display the sound tab. + * <li> Vibration Pickers will only display the vibration tab. + * </ul> + */ +@AndroidEntryPoint(DialogFragment.class) +public class TabbedDialogFragment extends Hilt_TabbedDialogFragment { + + static final String TAG = "TabbedDialogFragment"; + + private RingtonePickerViewModel mRingtonePickerViewModel; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mRingtonePickerViewModel = new ViewModelProvider(requireActivity()).get( + RingtonePickerViewModel.class); + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(requireActivity(), + android.R.style.ThemeOverlay_Material_Dialog) + .setTitle(mRingtonePickerViewModel.getPickerConfig().title); + // Do not show OK/Cancel buttons in the buttonless (watch-only) version. + if (mRingtonePickerViewModel.getPickerConfig().showOkCancelButtons) { + dialogBuilder + .setPositiveButton(getString(com.android.internal.R.string.ok), + (dialog, whichButton) -> { + setSuccessResultWithRingtone( + mRingtonePickerViewModel.getCurrentlySelectedRingtoneUri()); + requireActivity().finish(); + }) + .setNegativeButton(getString(com.android.internal.R.string.cancel), + (dialog, whichButton) -> { + requireActivity().setResult(RESULT_CANCELED); + requireActivity().finish(); + }); + } + + View view = buildTabbedView(requireActivity().getLayoutInflater()); + dialogBuilder.setView(view); + + return dialogBuilder.create(); + } + + @Override + public void onCancel(@NonNull @NotNull DialogInterface dialog) { + super.onCancel(dialog); + if (!requireActivity().isChangingConfigurations()) { + requireActivity().finish(); + } + } + + @Override + public void onDismiss(@NonNull @NotNull DialogInterface dialog) { + super.onDismiss(dialog); + if (!requireActivity().isChangingConfigurations()) { + requireActivity().finish(); + } + } + + private void setSuccessResultWithRingtone(Uri ringtoneUri) { + requireActivity().setResult(Activity.RESULT_OK, + new Intent().putExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI, ringtoneUri)); + } + + /** + * Inflates the tabbed layout view and adds the required fragments. If there's only one + * fragment to display, then the tab area is hidden. + * @param inflater The LayoutInflater that is used to inflate the tabbed view. + * @return The tabbed view. + */ + private View buildTabbedView(@NonNull LayoutInflater inflater) { + View view = inflater.inflate(R.layout.fragment_tabbed_dialog, null, false); + TabLayout tabLayout = view.findViewById(R.id.tabLayout); + ViewPager2 viewPager = view.findViewById(R.id.masterViewPager); + Objects.requireNonNull(tabLayout); + Objects.requireNonNull(viewPager); + + ViewPagerAdapter adapter = new ViewPagerAdapter(requireActivity()); + addFragments(adapter); + + if (adapter.getItemCount() == 1) { + // Hide the tab area since there's only one fragment to display. + tabLayout.setVisibility(View.GONE); + } + + viewPager.setAdapter(adapter); + new TabLayoutMediator(tabLayout, viewPager, + (tab, position) -> tab.setText(adapter.getTitle(position))).attach(); + + return view; + } + + /** + * Adds the appropriate fragments to the adapter based on the PickerType. + * + * @param adapter The adapter to add the fragments to. + */ + private void addFragments(ViewPagerAdapter adapter) { + switch (mRingtonePickerViewModel.getPickerConfig().mPickerType) { + case RINGTONE_PICKER: + adapter.addFragment(getString(R.string.sound_page_title), + new SoundPickerFragment()); + adapter.addFragment(getString(R.string.vibration_page_title), + new VibrationPickerFragment()); + break; + case SOUND_PICKER: + adapter.addFragment(getString(R.string.sound_page_title), + new SoundPickerFragment()); + break; + case VIBRATION_PICKER: + adapter.addFragment(getString(R.string.vibration_page_title), + new VibrationPickerFragment()); + break; + default: + adapter.addFragment(getString(R.string.sound_page_title), + new SoundPickerFragment()); + break; + } + } +} diff --git a/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java b/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java new file mode 100644 index 000000000000..356b9aee2c16 --- /dev/null +++ b/packages/SoundPicker/src/com/android/soundpicker/VibrationPickerFragment.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.soundpicker; + +import androidx.fragment.app.Fragment; + +/** + * A fragment that will display a picker used to select vibration. + */ +public class VibrationPickerFragment extends Fragment { + + public VibrationPickerFragment() { + super(R.layout.fragment_vibration_picker); + } +} diff --git a/packages/SoundPicker/src/com/android/soundpicker/ViewPagerAdapter.java b/packages/SoundPicker/src/com/android/soundpicker/ViewPagerAdapter.java new file mode 100644 index 000000000000..179068e9f20f --- /dev/null +++ b/packages/SoundPicker/src/com/android/soundpicker/ViewPagerAdapter.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.soundpicker; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.adapter.FragmentStateAdapter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * An adapter used to populate pages inside a ViewPager. + */ +public class ViewPagerAdapter extends FragmentStateAdapter { + + private final List<Fragment> mFragments = new ArrayList<>(); + private final List<String> mTitles = new ArrayList<>(); + + public ViewPagerAdapter(@NonNull FragmentActivity fragmentActivity) { + super(fragmentActivity); + } + + /** + * Adds a fragment and page title to the adapter. + * @param title the title of the page in the ViewPager. + * @param fragment the fragment that will be inflated on this page. + */ + public void addFragment(String title, Fragment fragment) { + mTitles.add(title); + mFragments.add(fragment); + } + + /** + * Returns the title of the requested page. + * @param position the position of the page in the Viewpager. + * @return The title of the requested page. + */ + public String getTitle(int position) { + return mTitles.get(position); + } + + @NonNull + @Override + public Fragment createFragment(int position) { + return Objects.requireNonNull(mFragments.get(position), + "Could not find a fragment using position: " + position); + } + + @Override + public int getItemCount() { + return mFragments.size(); + } +} diff --git a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java b/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java index 9ef3aa3b245f..659aae8188dd 100644 --- a/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java +++ b/packages/SoundPicker/tests/src/com/android/soundpicker/RingtonePickerViewModelTest.java @@ -112,7 +112,7 @@ public class RingtonePickerViewModelTest { @Test public void testInitRingtoneManager_whenTypeIsUnknown_createManagerButDoNotSetType() { - mViewModel.initRingtoneManager(RINGTONE_TYPE_UNKNOWN); + mViewModel.init(createPickerConfig(RINGTONE_TYPE_UNKNOWN)); verify(mMockRingtoneManagerFactory).create(); verify(mMockRingtoneManager, never()).setType(anyInt()); @@ -120,7 +120,7 @@ public class RingtonePickerViewModelTest { @Test public void testInitRingtoneManager_whenTypeIsNotUnknown_createManagerAndSetType() { - mViewModel.initRingtoneManager(RingtoneManager.TYPE_NOTIFICATION); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_NOTIFICATION)); verify(mMockRingtoneManagerFactory).create(); verify(mMockRingtoneManager).setType(RingtoneManager.TYPE_NOTIFICATION); @@ -129,14 +129,14 @@ public class RingtonePickerViewModelTest { @Test public void testGetStreamType_returnsTheCorrectStreamType() { when(mMockRingtoneManager.inferStreamType()).thenReturn(AudioManager.STREAM_ALARM); - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); assertEquals(mViewModel.getRingtoneStreamType(), AudioManager.STREAM_ALARM); } @Test public void testGetRingtoneCursor_returnsTheCorrectRingtoneCursor() { when(mMockRingtoneManager.getCursor()).thenReturn(mMockCursor); - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); assertEquals(mViewModel.getRingtoneCursor(), mMockCursor); } @@ -144,14 +144,14 @@ public class RingtonePickerViewModelTest { public void testGetRingtoneUri_returnsTheCorrectRingtoneUri() { Uri expectedUri = DEFAULT_URI; when(mMockRingtoneManager.getRingtoneUri(anyInt())).thenReturn(expectedUri); - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); Uri actualUri = mViewModel.getRingtoneUri(DEFAULT_RINGTONE_POSITION); assertEquals(actualUri, expectedUri); } @Test public void testOnPause_withChangingConfigurationTrue_doNotStopPlayingRingtone() { - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); mViewModel.playRingtone(RINGTONE_POSITION, DEFAULT_URI, AudioAttributes.FLAG_AUDIBILITY_ENFORCED); verifyRingtonePlayCalledAndMockPlayingState(mMockRingtone); @@ -161,7 +161,7 @@ public class RingtonePickerViewModelTest { @Test public void testOnPause_withChangingConfigurationFalse_stopPlayingRingtone() { - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION); mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI, AudioAttributes.FLAG_AUDIBILITY_ENFORCED); @@ -172,7 +172,7 @@ public class RingtonePickerViewModelTest { @Test public void testOnViewModelRecreated_previousRingtoneCanStillBeStopped() { - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); mViewModel.setSampleItemPosition(RINGTONE_POSITION); Ringtone mockRingtone1 = createMockRingtone(); Ringtone mockRingtone2 = createMockRingtone(); @@ -186,7 +186,7 @@ public class RingtonePickerViewModelTest { verify(mockRingtone1, never()).stop(); mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory, mMockListeningExecutorServiceFactory); - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); mViewModel.setSampleItemPosition(RINGTONE_POSITION); mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI, AudioAttributes.FLAG_AUDIBILITY_ENFORCED); @@ -197,7 +197,7 @@ public class RingtonePickerViewModelTest { @Test public void testOnStop_withChangingConfigurationTrueAndDefaultRingtonePlaying_saveRingtone() { - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION); mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI, AudioAttributes.FLAG_AUDIBILITY_ENFORCED); @@ -208,7 +208,7 @@ public class RingtonePickerViewModelTest { @Test public void testOnStop_withChangingConfigurationTrueAndCurrentRingtonePlaying_saveRingtone() { - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); mViewModel.setSampleItemPosition(RINGTONE_POSITION); mViewModel.playRingtone(RINGTONE_POSITION, DEFAULT_URI, AudioAttributes.FLAG_AUDIBILITY_ENFORCED); @@ -226,7 +226,7 @@ public class RingtonePickerViewModelTest { @Test public void testOnStop_withChangingConfigurationFalse_stopPlayingRingtone() { - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION); mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI, AudioAttributes.FLAG_AUDIBILITY_ENFORCED); @@ -237,21 +237,24 @@ public class RingtonePickerViewModelTest { @Test public void testGetCurrentlySelectedRingtoneUri_checkedItemIsUnknown_returnsNull() { - Uri uri = mViewModel.getCurrentlySelectedRingtoneUri(POS_UNKNOWN, DEFAULT_URI); + mViewModel.setSelectedItemPosition(POS_UNKNOWN); + Uri uri = mViewModel.getCurrentlySelectedRingtoneUri(); assertNull(uri); } @Test public void testGetCurrentlySelectedRingtoneUri_checkedItemIsDefaultPos_returnsDefaultUri() { Uri expectedUri = DEFAULT_URI; - Uri actualUri = mViewModel.getCurrentlySelectedRingtoneUri(DEFAULT_RINGTONE_POSITION, - expectedUri); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); + mViewModel.setSelectedItemPosition(DEFAULT_RINGTONE_POSITION); + Uri actualUri = mViewModel.getCurrentlySelectedRingtoneUri(); assertEquals(actualUri, expectedUri); } @Test public void testGetCurrentlySelectedRingtoneUri_checkedItemIsSilentPos_returnsNull() { - Uri uri = mViewModel.getCurrentlySelectedRingtoneUri(SILENT_RINGTONE_POSITION, DEFAULT_URI); + mViewModel.setSelectedItemPosition(SILENT_RINGTONE_POSITION); + Uri uri = mViewModel.getCurrentlySelectedRingtoneUri(); assertNull(uri); } @@ -266,7 +269,7 @@ public class RingtonePickerViewModelTest { RingtoneManager.TYPE_NOTIFICATION)).thenReturn(DEFAULT_URI); mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory, mMockListeningExecutorServiceFactory); - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); mViewModel.addRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION, mockCallback, mMainThreadExecutor); verify(mockCallback, never()).onFailure(any()); @@ -290,7 +293,7 @@ public class RingtonePickerViewModelTest { RingtoneManager.TYPE_NOTIFICATION)).thenReturn(DEFAULT_URI); mViewModel = new RingtonePickerViewModel(mMockRingtoneManagerFactory, mMockRingtoneFactory, mMockListeningExecutorServiceFactory); - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); mViewModel.addRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION, mockCallback1, mMainThreadExecutor); verify(mockCallback1, never()).onFailure(any()); @@ -312,7 +315,7 @@ public class RingtonePickerViewModelTest { when(mMockRingtoneManager.addCustomExternalRingtone(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION)).thenReturn(expectedUri); - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); mViewModel.addRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION, mockCallback, mMainThreadExecutor); @@ -330,7 +333,7 @@ public class RingtonePickerViewModelTest { when(mMockRingtoneManager.addCustomExternalRingtone(any(), anyInt())).thenThrow( IOException.class); - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); mViewModel.addRingtoneAsync(DEFAULT_URI, RingtoneManager.TYPE_NOTIFICATION, mockCallback, mMainThreadExecutor); @@ -342,8 +345,9 @@ public class RingtonePickerViewModelTest { public void testGetCurrentlySelectedRingtoneUri_checkedItemRingtonePos_returnsTheCorrectUri() { Uri expectedUri = DEFAULT_URI; when(mMockRingtoneManager.getRingtoneUri(RINGTONE_POSITION)).thenReturn(expectedUri); - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); - Uri actualUri = mViewModel.getCurrentlySelectedRingtoneUri(RINGTONE_POSITION, DEFAULT_URI); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); + mViewModel.setSelectedItemPosition(RINGTONE_POSITION); + Uri actualUri = mViewModel.getCurrentlySelectedRingtoneUri(); verify(mMockRingtoneManager).getRingtoneUri(RINGTONE_POSITION); assertEquals(actualUri, expectedUri); @@ -353,7 +357,7 @@ public class RingtonePickerViewModelTest { public void testPlayRingtone_stopsPreviouslyRunningRingtone() { // Start playing the first ringtone mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION); - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI, AudioAttributes.FLAG_AUDIBILITY_ENFORCED); verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone); @@ -369,7 +373,7 @@ public class RingtonePickerViewModelTest { @Test public void testPlayRingtone_samplePosEqualToSilentPos_onlyStopPlayingRingtone() { mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION); - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); mViewModel.playRingtone(DEFAULT_RINGTONE_POSITION, DEFAULT_URI, AudioAttributes.FLAG_AUDIBILITY_ENFORCED); verifyRingtonePlayCalledAndMockPlayingState(mMockDefaultRingtone); @@ -388,7 +392,7 @@ public class RingtonePickerViewModelTest { @Test public void testPlayRingtone_samplePosEqualToDefaultPos_playDefaultRingtone() { mViewModel.setSampleItemPosition(DEFAULT_RINGTONE_POSITION); - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); when(mMockRingtoneManager.inferStreamType()).thenReturn(AudioManager.STREAM_ALARM); @@ -403,7 +407,7 @@ public class RingtonePickerViewModelTest { @Test public void testPlayRingtone_samplePosNotEqualToDefaultPos_playRingtone() { mViewModel.setSampleItemPosition(RINGTONE_POSITION); - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); mViewModel.playRingtone(RINGTONE_POSITION, DEFAULT_URI, AudioAttributes.FLAG_AUDIBILITY_ENFORCED); @@ -417,7 +421,7 @@ public class RingtonePickerViewModelTest { @Test public void testPlayRingtone_withNoAttributeFlags_doNotUpdateRingtoneAttributesFlags() { mViewModel.setSampleItemPosition(RINGTONE_POSITION); - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); mViewModel.playRingtone(RINGTONE_POSITION, DEFAULT_URI, NO_ATTRIBUTES_FLAGS); @@ -430,7 +434,7 @@ public class RingtonePickerViewModelTest { public void testGetRingtonePosition_returnsTheCorrectRingtonePosition() { int expectedPosition = 1; when(mMockRingtoneManager.getRingtonePosition(any())).thenReturn(expectedPosition); - mViewModel.initRingtoneManager(RingtoneManager.TYPE_RINGTONE); + mViewModel.init(createPickerConfig(RingtoneManager.TYPE_RINGTONE)); int actualPosition = mViewModel.getRingtonePosition(DEFAULT_URI); assertEquals(actualPosition, expectedPosition); @@ -553,4 +557,13 @@ public class RingtonePickerViewModelTest { .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .build(); } + + private RingtonePickerViewModel.PickerConfig createPickerConfig(int ringtoneType) { + return new RingtonePickerViewModel.PickerConfig("Phone ringtone", /* userId= */ 1, + ringtoneType, /* hasDefaultItem= */ true, + /* uriForDefaultItem= */ DEFAULT_URI, /* hasSilentItem= */ true, + /* audioAttributesFlags= */0, /* existingUri= */ Uri.parse(""), + /* showOkCancelButtons= */ true, + RingtonePickerViewModel.PickerType.RINGTONE_PICKER); + } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt index 75bf2813a321..13acde206247 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt @@ -121,8 +121,8 @@ fun FooterActions( } } - val backgroundColor = colorAttr(R.attr.underSurfaceColor) - val contentColor = LocalAndroidColorScheme.current.deprecated.textColorPrimary + val backgroundColor = colorAttr(R.attr.underSurface) + val contentColor = LocalAndroidColorScheme.current.onSurface val backgroundTopRadius = dimensionResource(R.dimen.qs_corner_radius) val backgroundModifier = remember( @@ -268,7 +268,7 @@ private fun NumberButton( val interactionSource = remember { MutableInteractionSource() } Expandable( - color = colorAttr(R.attr.offStateColor), + color = colorAttr(R.attr.shadeInactive), shape = CircleShape, onClick = onClick, interactionSource = interactionSource, @@ -287,7 +287,7 @@ private fun NumberButton( number.toString(), modifier = Modifier.align(Alignment.Center), style = MaterialTheme.typography.bodyLarge, - color = LocalAndroidColorScheme.current.deprecated.textColorPrimary, + color = colorAttr(R.attr.onShadeInactiveVariant), // TODO(b/242040009): This should only use a standard text style instead and // should not override the text size. fontSize = 18.sp, @@ -305,7 +305,7 @@ private fun NumberButton( @Composable private fun NewChangesDot(modifier: Modifier = Modifier) { val contentDescription = stringResource(R.string.fgs_dot_content_description) - val color = LocalAndroidColorScheme.current.deprecated.colorAccentTertiary + val color = LocalAndroidColorScheme.current.tertiary Canvas(modifier.size(12.dp).semantics { this.contentDescription = contentDescription }) { drawCircle(color) @@ -323,10 +323,9 @@ private fun TextButton( ) { Expandable( shape = CircleShape, - color = colorAttr(R.attr.underSurfaceColor), - contentColor = LocalAndroidColorScheme.current.deprecated.textColorSecondary, - borderStroke = - BorderStroke(1.dp, LocalAndroidColorScheme.current.deprecated.colorBackground), + color = colorAttr(R.attr.underSurface), + contentColor = LocalAndroidColorScheme.current.onSurfaceVariant, + borderStroke = BorderStroke(1.dp, colorAttr(R.attr.onShadeActive)), modifier = modifier.padding(horizontal = 4.dp), onClick = onClick, ) { diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index 648ef03895cd..b9d66432b5f2 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -33,8 +33,8 @@ import com.android.internal.annotations.VisibleForTesting import com.android.systemui.animation.GlyphCallback import com.android.systemui.animation.TextAnimator import com.android.systemui.customization.R -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.core.Logger +import com.android.systemui.log.core.MessageBuffer import java.io.PrintWriter import java.util.Calendar import java.util.Locale @@ -51,7 +51,12 @@ class AnimatableClockView @JvmOverloads constructor( defStyleAttr: Int = 0, defStyleRes: Int = 0 ) : TextView(context, attrs, defStyleAttr, defStyleRes) { - var logBuffer: LogBuffer? = null + var messageBuffer: MessageBuffer? = null + set(value) { + logger = if (value != null) Logger(value, TAG) else null + } + + private var logger: Logger? = null private val time = Calendar.getInstance() @@ -129,7 +134,7 @@ class AnimatableClockView @JvmOverloads constructor( override fun onAttachedToWindow() { super.onAttachedToWindow() - logBuffer?.log(TAG, DEBUG, "onAttachedToWindow") + logger?.d("onAttachedToWindow") refreshFormat() } @@ -145,39 +150,32 @@ class AnimatableClockView @JvmOverloads constructor( time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis() contentDescription = DateFormat.format(descFormat, time) val formattedText = DateFormat.format(format, time) - logBuffer?.log(TAG, DEBUG, - { str1 = formattedText?.toString() }, - { "refreshTime: new formattedText=$str1" } - ) + logger?.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() } // Setting text actually triggers a layout pass (because the text view is set to // wrap_content width and TextView always relayouts for this). Avoid needless // relayout if the text didn't actually change. if (!TextUtils.equals(text, formattedText)) { text = formattedText - logBuffer?.log(TAG, DEBUG, - { str1 = formattedText?.toString() }, - { "refreshTime: done setting new time text to: $str1" } - ) + logger?.d({ "refreshTime: done setting new time text to: $str1" }) { + str1 = formattedText?.toString() + } // Because the TextLayout may mutate under the hood as a result of the new text, we // notify the TextAnimator that it may have changed and request a measure/layout. A // crash will occur on the next invocation of setTextStyle if the layout is mutated // without being notified TextInterpolator being notified. if (layout != null) { textAnimator?.updateLayout(layout) - logBuffer?.log(TAG, DEBUG, "refreshTime: done updating textAnimator layout") + logger?.d("refreshTime: done updating textAnimator layout") } requestLayout() - logBuffer?.log(TAG, DEBUG, "refreshTime: after requestLayout") + logger?.d("refreshTime: after requestLayout") } } fun onTimeZoneChanged(timeZone: TimeZone?) { time.timeZone = timeZone refreshFormat() - logBuffer?.log(TAG, DEBUG, - { str1 = timeZone?.toString() }, - { "onTimeZoneChanged newTimeZone=$str1" } - ) + logger?.d({ "onTimeZoneChanged newTimeZone=$str1" }) { str1 = timeZone?.toString() } } @SuppressLint("DrawAllocation") @@ -191,7 +189,7 @@ class AnimatableClockView @JvmOverloads constructor( } else { animator.updateLayout(layout) } - logBuffer?.log(TAG, DEBUG, "onMeasure") + logger?.d("onMeasure") } override fun onDraw(canvas: Canvas) { @@ -203,12 +201,12 @@ class AnimatableClockView @JvmOverloads constructor( } else { super.onDraw(canvas) } - logBuffer?.log(TAG, DEBUG, "onDraw") + logger?.d("onDraw") } override fun invalidate() { super.invalidate() - logBuffer?.log(TAG, DEBUG, "invalidate") + logger?.d("invalidate") } override fun onTextChanged( @@ -218,10 +216,7 @@ class AnimatableClockView @JvmOverloads constructor( lengthAfter: Int ) { super.onTextChanged(text, start, lengthBefore, lengthAfter) - logBuffer?.log(TAG, DEBUG, - { str1 = text.toString() }, - { "onTextChanged text=$str1" } - ) + logger?.d({ "onTextChanged text=$str1" }) { str1 = text.toString() } } fun setLineSpacingScale(scale: Float) { @@ -235,7 +230,7 @@ class AnimatableClockView @JvmOverloads constructor( } fun animateColorChange() { - logBuffer?.log(TAG, DEBUG, "animateColorChange") + logger?.d("animateColorChange") setTextStyle( weight = lockScreenWeight, textSize = -1f, @@ -257,7 +252,7 @@ class AnimatableClockView @JvmOverloads constructor( } fun animateAppearOnLockscreen() { - logBuffer?.log(TAG, DEBUG, "animateAppearOnLockscreen") + logger?.d("animateAppearOnLockscreen") setTextStyle( weight = dozingWeight, textSize = -1f, @@ -283,7 +278,7 @@ class AnimatableClockView @JvmOverloads constructor( if (isAnimationEnabled && textAnimator == null) { return } - logBuffer?.log(TAG, DEBUG, "animateFoldAppear") + logger?.d("animateFoldAppear") setTextStyle( weight = lockScreenWeightInternal, textSize = -1f, @@ -310,7 +305,7 @@ class AnimatableClockView @JvmOverloads constructor( // Skip charge animation if dozing animation is already playing. return } - logBuffer?.log(TAG, DEBUG, "animateCharge") + logger?.d("animateCharge") val startAnimPhase2 = Runnable { setTextStyle( weight = if (isDozing()) dozingWeight else lockScreenWeight, @@ -334,7 +329,7 @@ class AnimatableClockView @JvmOverloads constructor( } fun animateDoze(isDozing: Boolean, animate: Boolean) { - logBuffer?.log(TAG, DEBUG, "animateDoze") + logger?.d("animateDoze") setTextStyle( weight = if (isDozing) dozingWeight else lockScreenWeight, textSize = -1f, @@ -453,10 +448,7 @@ class AnimatableClockView @JvmOverloads constructor( isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12 else -> DOUBLE_LINE_FORMAT_12_HOUR } - logBuffer?.log(TAG, DEBUG, - { str1 = format?.toString() }, - { "refreshFormat format=$str1" } - ) + logger?.d({ "refreshFormat format=$str1" }) { str1 = format?.toString() } descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12 refreshTime() diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt index 14434655fc92..d65edae1caf0 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt @@ -23,12 +23,13 @@ import android.os.UserHandle import android.provider.Settings import android.util.Log import androidx.annotation.OpenForTesting -import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogMessage import com.android.systemui.log.LogMessageImpl -import com.android.systemui.log.MessageInitializer -import com.android.systemui.log.MessagePrinter +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogMessage +import com.android.systemui.log.core.Logger +import com.android.systemui.log.core.MessageBuffer +import com.android.systemui.log.core.MessageInitializer +import com.android.systemui.log.core.MessagePrinter import com.android.systemui.plugins.ClockController import com.android.systemui.plugins.ClockId import com.android.systemui.plugins.ClockMetadata @@ -75,7 +76,7 @@ private fun <TKey, TVal> ConcurrentHashMap<TKey, TVal>.concurrentGetOrPut( private val TMP_MESSAGE: LogMessage by lazy { LogMessageImpl.Factory.create() } -private inline fun LogBuffer?.tryLog( +private inline fun Logger?.tryLog( tag: String, level: LogLevel, messageInitializer: MessageInitializer, @@ -84,7 +85,7 @@ private inline fun LogBuffer?.tryLog( ) { if (this != null) { // Wrap messagePrinter to convert it from crossinline to noinline - this.log(tag, level, messageInitializer, messagePrinter, ex) + this.log(level, messagePrinter, ex, messageInitializer) } else { messageInitializer(TMP_MESSAGE) val msg = messagePrinter(TMP_MESSAGE) @@ -110,7 +111,7 @@ open class ClockRegistry( val handleAllUsers: Boolean, defaultClockProvider: ClockProvider, val fallbackClockId: ClockId = DEFAULT_CLOCK_ID, - val logBuffer: LogBuffer? = null, + messageBuffer: MessageBuffer? = null, val keepAllLoaded: Boolean, subTag: String, var isTransitClockEnabled: Boolean = false, @@ -124,6 +125,7 @@ open class ClockRegistry( fun onAvailableClocksChanged() {} } + private val logger: Logger? = if (messageBuffer != null) Logger(messageBuffer, TAG) else null private val availableClocks = ConcurrentHashMap<ClockId, ClockInfo>() private val clockChangeListeners = mutableListOf<ClockChangeListener>() private val settingObserver = @@ -150,7 +152,7 @@ open class ClockRegistry( val knownClocks = KNOWN_PLUGINS.get(manager.getPackage()) if (knownClocks == null) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.WARNING, { str1 = manager.getPackage() }, @@ -159,7 +161,7 @@ open class ClockRegistry( return true } - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.INFO, { str1 = manager.getPackage() }, @@ -176,7 +178,7 @@ open class ClockRegistry( } if (manager != info.manager) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.ERROR, { str1 = id }, @@ -216,7 +218,7 @@ open class ClockRegistry( } if (manager != info.manager) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.ERROR, { str1 = id }, @@ -244,7 +246,7 @@ open class ClockRegistry( val id = clock.clockId val info = availableClocks[id] if (info?.manager != manager) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.ERROR, { str1 = id }, @@ -319,7 +321,7 @@ open class ClockRegistry( ClockSettings.deserialize(json) } catch (ex: Exception) { - logBuffer.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to parse clock settings" }, ex) + logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to parse clock settings" }, ex) null } settings = result @@ -348,7 +350,7 @@ open class ClockRegistry( ) } } catch (ex: Exception) { - logBuffer.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to set clock settings" }, ex) + logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to set clock settings" }, ex) } settings = value } @@ -508,9 +510,9 @@ open class ClockRegistry( } private fun onConnected(clockId: ClockId) { - logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Connected $str1" }) + logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Connected $str1" }) if (currentClockId == clockId) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.INFO, { str1 = clockId }, @@ -520,10 +522,10 @@ open class ClockRegistry( } private fun onLoaded(clockId: ClockId) { - logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Loaded $str1" }) + logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Loaded $str1" }) if (currentClockId == clockId) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.INFO, { str1 = clockId }, @@ -534,10 +536,10 @@ open class ClockRegistry( } private fun onUnloaded(clockId: ClockId) { - logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Unloaded $str1" }) + logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Unloaded $str1" }) if (currentClockId == clockId) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.WARNING, { str1 = clockId }, @@ -548,10 +550,10 @@ open class ClockRegistry( } private fun onDisconnected(clockId: ClockId) { - logBuffer.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Disconnected $str1" }) + logger.tryLog(TAG, LogLevel.DEBUG, { str1 = clockId }, { "Disconnected $str1" }) if (currentClockId == clockId) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.WARNING, { str1 = clockId }, @@ -597,22 +599,17 @@ open class ClockRegistry( if (isEnabled && clockId.isNotEmpty()) { val clock = createClock(clockId) if (clock != null) { - logBuffer.tryLog( - TAG, - LogLevel.INFO, - { str1 = clockId }, - { "Rendering clock $str1" } - ) + logger.tryLog(TAG, LogLevel.INFO, { str1 = clockId }, { "Rendering clock $str1" }) return clock } else if (availableClocks.containsKey(clockId)) { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.WARNING, { str1 = clockId }, { "Clock $str1 not loaded; using default" } ) } else { - logBuffer.tryLog( + logger.tryLog( TAG, LogLevel.ERROR, { str1 = clockId }, diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt index e557c8e5902a..e539c955a3c6 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -24,7 +24,7 @@ import android.view.View import android.widget.FrameLayout import androidx.annotation.VisibleForTesting import com.android.systemui.customization.R -import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.MessageBuffer import com.android.systemui.plugins.ClockAnimations import com.android.systemui.plugins.ClockConfig import com.android.systemui.plugins.ClockController @@ -108,10 +108,10 @@ class DefaultClockController( override val config = ClockFaceConfig() - override var logBuffer: LogBuffer? - get() = view.logBuffer + override var messageBuffer: MessageBuffer? + get() = view.messageBuffer set(value) { - view.logBuffer = value + view.messageBuffer = value } override var animations: DefaultClockAnimations = DefaultClockAnimations(view, 0f, 0f) diff --git a/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt b/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt index 6fc525369e8b..a4f4e134733f 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/ConstantStringsLoggerImpl.kt @@ -16,6 +16,7 @@ package com.android.systemui.log +import com.android.systemui.log.core.LogLevel import com.google.errorprone.annotations.CompileTimeConstant class ConstantStringsLoggerImpl(val buffer: LogBuffer, val tag: String) : ConstantStringsLogger { diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt index 2007e7606ab8..e0051f59469d 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt @@ -19,6 +19,11 @@ package com.android.systemui.log import android.os.Trace import android.util.Log import com.android.systemui.common.buffer.RingBuffer +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogMessage +import com.android.systemui.log.core.MessageBuffer +import com.android.systemui.log.core.MessageInitializer +import com.android.systemui.log.core.MessagePrinter import com.google.errorprone.annotations.CompileTimeConstant import java.io.PrintWriter import java.util.concurrent.ArrayBlockingQueue @@ -73,7 +78,7 @@ constructor( private val maxSize: Int, private val logcatEchoTracker: LogcatEchoTracker, private val systrace: Boolean = true, -) { +) : MessageBuffer { private val buffer = RingBuffer(maxSize) { LogMessageImpl.create() } private val echoMessageQueue: BlockingQueue<LogMessage>? = @@ -174,11 +179,11 @@ constructor( * store any relevant data on the message and then call [commit]. */ @Synchronized - fun obtain( + override fun obtain( tag: String, level: LogLevel, messagePrinter: MessagePrinter, - exception: Throwable? = null, + exception: Throwable?, ): LogMessage { if (!mutable) { return FROZEN_MESSAGE @@ -195,7 +200,7 @@ constructor( * have finished filling in its data fields. The message will be echoed to logcat if necessary. */ @Synchronized - fun commit(message: LogMessage) { + override fun commit(message: LogMessage) { if (!mutable) { return } @@ -292,11 +297,5 @@ constructor( } } -/** - * A function that will be called immediately to store relevant data on the log message. The value - * of `this` will be the LogMessage to be initialized. - */ -typealias MessageInitializer = LogMessage.() -> Unit - private const val TAG = "LogBuffer" private val FROZEN_MESSAGE = LogMessageImpl.create() diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt index 5e10f783850f..33cc199e7131 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/LogMessageImpl.kt @@ -16,6 +16,10 @@ package com.android.systemui.log +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogMessage +import com.android.systemui.log.core.MessagePrinter + /** Recyclable implementation of [LogMessage]. */ data class LogMessageImpl( override var level: LogLevel, diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt index 55f3a738e4f1..ae717df50fce 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTracker.kt @@ -16,6 +16,8 @@ package com.android.systemui.log +import com.android.systemui.log.core.LogLevel + /** Keeps track of which [LogBuffer] messages should also appear in logcat. */ interface LogcatEchoTracker { /** Whether [bufferName] should echo messages of [level] or higher to logcat. */ diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt index d0ad28f04670..9ff48cabc6f4 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerDebug.kt @@ -23,6 +23,7 @@ import android.os.Handler import android.os.Looper import android.os.Trace import android.provider.Settings +import com.android.systemui.log.core.LogLevel /** * Version of [LogcatEchoTracker] for debuggable builds diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt index 56966773d1b0..044d97f92b50 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/LogcatEchoTrackerProd.kt @@ -16,6 +16,8 @@ package com.android.systemui.log +import com.android.systemui.log.core.LogLevel + /** Production version of [LogcatEchoTracker] that isn't configurable. */ class LogcatEchoTrackerProd : LogcatEchoTracker { override val logInBackgroundThread = false diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogLevel.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/LogLevel.kt index 7d9647a13149..d30d8e9fe003 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/LogLevel.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/core/LogLevel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.log +package com.android.systemui.log.core import android.util.Log diff --git a/packages/SystemUI/log/src/com/android/systemui/log/LogMessage.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/LogMessage.kt index 8c3988b027fe..3bd6473738c7 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/LogMessage.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/core/LogMessage.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.log +package com.android.systemui.log.core import android.icu.text.SimpleDateFormat import java.io.PrintWriter @@ -67,6 +67,12 @@ interface LogMessage { } /** + * A function that will be called immediately to store relevant data on the log message. The value + * of `this` will be the LogMessage to be initialized. + */ +typealias MessageInitializer = LogMessage.() -> Unit + +/** * A function that will be called if and when the message needs to be dumped to logcat or a bug * report. It should read the data stored by the initializer and convert it to a human-readable * string. The value of `this` will be the LogMessage to be printed. **IMPORTANT:** The printer diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/Logger.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/Logger.kt new file mode 100644 index 000000000000..5729ab270487 --- /dev/null +++ b/packages/SystemUI/log/src/com/android/systemui/log/core/Logger.kt @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.log.core + +import com.google.errorprone.annotations.CompileTimeConstant + +/** Logs messages to the [MessageBuffer] with [tag]. */ +open class Logger(val buffer: MessageBuffer, val tag: String) { + /** + * Logs a message to the buffer. + * + * The actual string of the log message is not constructed until it is needed. To accomplish + * this, logging a message is a two-step process. First, a fresh instance of [LogMessage] is + * obtained and is passed to the [messageInitializer]. The initializer stores any relevant data + * on the message's fields. The message is then inserted into the buffer where it waits until it + * is either pushed out by newer messages or it needs to printed. If and when this latter moment + * occurs, the [messagePrinter] function is called on the message. It reads whatever data the + * initializer stored and converts it to a human-readable log message. + * + * @param level Which level to log the message at, both to the buffer and to logcat if it's + * echoed. In general, a module should split most of its logs into either INFO or DEBUG level. + * INFO level should be reserved for information that other parts of the system might care + * about, leaving the specifics of code's day-to-day operations to DEBUG. + * @param messagePrinter A function that will be called if and when the message needs to be + * dumped to logcat or a bug report. It should read the data stored by the initializer and + * convert it to a human-readable string. The value of `this` will be the [LogMessage] to be + * printed. **IMPORTANT:** The printer should ONLY ever reference fields on the [LogMessage] + * and NEVER any variables in its enclosing scope. Otherwise, the runtime will need to + * allocate a new instance of the printer for each call, thwarting our attempts at avoiding + * any sort of allocation. + * @param exception Provide any exception that need to be logged. This is saved as + * [LogMessage.exception] + * @param messageInitializer A function that will be called immediately to store relevant data + * on the log message. The value of `this` will be the [LogMessage] to be initialized. + */ + @JvmOverloads + inline fun log( + level: LogLevel, + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) { + val message = buffer.obtain(tag, level, messagePrinter, exception) + messageInitializer(message) + buffer.commit(message) + } + + /** + * Logs a compile-time string constant [message] to the log buffer. Use sparingly. + * + * This is for simpler use-cases where [message] is a compile time string constant. For + * use-cases where the log message is built during runtime, use the [log] overloaded method that + * takes in an initializer and a message printer. + * + * Buffers are limited by the number of entries, so logging more frequently will limit the time + * window that the [MessageBuffer] covers in a bug report. Richer logs, on the other hand, make + * a bug report more actionable, so using the [log] with a [MessagePrinter] to add more details + * to every log may do more to improve overall logging than adding more logs with this method. + */ + @JvmOverloads + fun log( + level: LogLevel, + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(level, { str1!! }, exception) { str1 = message } + + /** + * Logs a message to the buffer at [LogLevel.VERBOSE]. + * + * @see log + */ + @JvmOverloads + inline fun v( + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) = log(LogLevel.VERBOSE, messagePrinter, exception, messageInitializer) + + /** + * Logs a compile-time string constant [message] to the log buffer at [LogLevel.VERBOSE]. Use + * sparingly. + * + * @see log + */ + @JvmOverloads + fun v( + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(LogLevel.VERBOSE, message, exception) + + /** + * Logs a message to the buffer at [LogLevel.DEBUG]. + * + * @see log + */ + @JvmOverloads + inline fun d( + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) = log(LogLevel.DEBUG, messagePrinter, exception, messageInitializer) + + /** + * Logs a compile-time string constant [message] to the log buffer at [LogLevel.DEBUG]. Use + * sparingly. + * + * @see log + */ + @JvmOverloads + fun d( + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(LogLevel.DEBUG, message, exception) + + /** + * Logs a message to the buffer at [LogLevel.INFO]. + * + * @see log + */ + @JvmOverloads + inline fun i( + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) = log(LogLevel.INFO, messagePrinter, exception, messageInitializer) + + /** + * Logs a compile-time string constant [message] to the log buffer at [LogLevel.INFO]. Use + * sparingly. + * + * @see log + */ + @JvmOverloads + fun i( + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(LogLevel.INFO, message, exception) + + /** + * Logs a message to the buffer at [LogLevel.WARNING]. + * + * @see log + */ + @JvmOverloads + inline fun w( + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) = log(LogLevel.WARNING, messagePrinter, exception, messageInitializer) + + /** + * Logs a compile-time string constant [message] to the log buffer at [LogLevel.WARNING]. Use + * sparingly. + * + * @see log + */ + @JvmOverloads + fun w( + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(LogLevel.WARNING, message, exception) + + /** + * Logs a message to the buffer at [LogLevel.ERROR]. + * + * @see log + */ + @JvmOverloads + inline fun e( + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) = log(LogLevel.ERROR, messagePrinter, exception, messageInitializer) + + /** + * Logs a compile-time string constant [message] to the log buffer at [LogLevel.ERROR]. Use + * sparingly. + * + * @see log + */ + @JvmOverloads + fun e( + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(LogLevel.ERROR, message, exception) + + /** + * Logs a message to the buffer at [LogLevel.WTF]. + * + * @see log + */ + @JvmOverloads + inline fun wtf( + noinline messagePrinter: MessagePrinter, + exception: Throwable? = null, + messageInitializer: MessageInitializer, + ) = log(LogLevel.WTF, messagePrinter, exception, messageInitializer) + + /** + * Logs a compile-time string constant [message] to the log buffer at [LogLevel.WTF]. Use + * sparingly. + * + * @see log + */ + @JvmOverloads + fun wtf( + @CompileTimeConstant message: String, + exception: Throwable? = null, + ) = log(LogLevel.WTF, message, exception) +} diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/MessageBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/MessageBuffer.kt new file mode 100644 index 000000000000..bb91633c4d87 --- /dev/null +++ b/packages/SystemUI/log/src/com/android/systemui/log/core/MessageBuffer.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.log.core + +/** + * [MessageBuffer] is an interface that represents a buffer of log messages, and provides methods to + * [obtain] a log message and [commit] it to the buffer. + */ +interface MessageBuffer { + /** + * Obtains the next [LogMessage] from the buffer. + * + * After calling [obtain], the caller must store any relevant data on the message and then call + * [commit]. + */ + fun obtain( + tag: String, + level: LogLevel, + messagePrinter: MessagePrinter, + exception: Throwable? = null, + ): LogMessage + + /** + * After acquiring a log message via [obtain], call this method to signal to the buffer that + * data fields have been filled. + */ + fun commit(message: LogMessage) +} diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java index 1811c02d549d..64c0f99f4ba7 100644 --- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java +++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java @@ -128,6 +128,16 @@ public interface BcSmartspaceDataPlugin extends Plugin { void setDozeAmount(float amount); /** + * Set if dozing is true or false + */ + default void setDozing(boolean dozing) {} + + /** + * Set if split shade enabled + */ + default void setSplitShadeEnabled(boolean enabled) {} + + /** * Set the current keyguard bypass enabled status. */ default void setKeyguardBypassEnabled(boolean enabled) {} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt index 537b7a41a898..d962732ba884 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt @@ -18,7 +18,7 @@ import android.graphics.Rect import android.graphics.drawable.Drawable import android.view.View import com.android.internal.annotations.Keep -import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.MessageBuffer import com.android.systemui.plugins.annotations.ProvidesInterface import java.io.PrintWriter import java.util.Locale @@ -95,7 +95,7 @@ interface ClockFaceController { val animations: ClockAnimations /** Some clocks may log debug information */ - var logBuffer: LogBuffer? + var messageBuffer: MessageBuffer? } /** Events that should call when various rendering parameters change */ diff --git a/packages/SystemUI/proguard_common.flags b/packages/SystemUI/proguard_common.flags index 02d55104c288..319481523cdf 100644 --- a/packages/SystemUI/proguard_common.flags +++ b/packages/SystemUI/proguard_common.flags @@ -62,15 +62,12 @@ -keep class ** extends androidx.preference.PreferenceFragment -keep class com.android.systemui.tuner.* -# The plugins, log & common subpackages act as shared libraries that might be referenced in +# The plugins and core log subpackages act as shared libraries that might be referenced in # dynamically-loaded plugin APKs. -keep class com.android.systemui.plugins.** { *; } --keep class com.android.systemui.log.** { - *; -} --keep class com.android.systemui.common.** { +-keep class com.android.systemui.log.core.** { *; } -keep class com.android.systemui.fragments.FragmentService$FragmentCreator { diff --git a/core/res/res/values-watch/colors.xml b/packages/SystemUI/res-keyguard/color/shade_disabled.xml index 6d908be48ff0..241f20385eb4 100644 --- a/core/res/res/values-watch/colors.xml +++ b/packages/SystemUI/res-keyguard/color/shade_disabled.xml @@ -1,12 +1,11 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2021 The Android Open Source Project +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2023 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, @@ -15,8 +14,6 @@ ~ limitations under the License. --> -<resources> - <!-- Wear Material standard colors --> - <color name="wear_material_red_mid">#CC5D58</color> - <color name="wear_material_grey_900">#202124</color> -</resources>
\ No newline at end of file +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:color="@android:color/system_neutral1_500" android:lStar="4" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml index a7ffe9ca256f..c09607d19bdd 100644 --- a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml +++ b/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml @@ -26,7 +26,7 @@ android:layout_height="wrap_content" android:textAppearance="@style/TextAppearance.QS.SecurityFooter" android:layout_gravity="center" - android:textColor="?android:attr/textColorPrimary" + android:textColor="?attr/onShadeInactiveVariant" android:textSize="18sp"/> <ImageView android:id="@+id/new_dot" diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml index 6fe7d39f748a..1c31f1da0681 100644 --- a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml +++ b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml @@ -33,7 +33,7 @@ android:layout_marginEnd="12dp" android:contentDescription="@null" android:src="@drawable/ic_info_outline" - android:tint="?android:attr/textColorSecondary" /> + android:tint="?attr/onSurfaceVariant" /> <TextView android:id="@+id/text" @@ -43,7 +43,7 @@ android:maxLines="1" android:ellipsize="end" android:textAppearance="@style/TextAppearance.QS.SecurityFooter" - android:textColor="?android:attr/textColorSecondary"/> + android:textColor="?attr/onSurfaceVariant"/> <ImageView android:id="@+id/new_dot" @@ -62,5 +62,5 @@ android:contentDescription="@null" android:src="@*android:drawable/ic_chevron_end" android:autoMirrored="true" - android:tint="?android:attr/textColorSecondary" /> + android:tint="?attr/onSurfaceVariant" /> </com.android.systemui.animation.view.LaunchableLinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml b/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml new file mode 100644 index 000000000000..360ef2672e75 --- /dev/null +++ b/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT 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.biometrics.UdfpsKeyguardView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/udfps_animation_view" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <!-- Add fingerprint views here. See udfps_keyguard_view_internal.xml. --> + +</com.android.systemui.biometrics.UdfpsKeyguardView> diff --git a/packages/SystemUI/res/drawable/brightness_mirror_background.xml b/packages/SystemUI/res/drawable/brightness_mirror_background.xml index 209510365fe0..b5c181bd896c 100644 --- a/packages/SystemUI/res/drawable/brightness_mirror_background.xml +++ b/packages/SystemUI/res/drawable/brightness_mirror_background.xml @@ -15,6 +15,6 @@ ~ limitations under the License --> <shape xmlns:android="http://schemas.android.com/apk/res/android"> - <solid android:color="?attr/underSurfaceColor" /> + <solid android:color="?attr/underSurface" /> <corners android:radius="@dimen/rounded_slider_background_rounded_corner" /> </shape> diff --git a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml index 569ee76586c2..95c7778c0e76 100644 --- a/packages/SystemUI/res/drawable/brightness_progress_drawable.xml +++ b/packages/SystemUI/res/drawable/brightness_progress_drawable.xml @@ -24,7 +24,7 @@ <shape> <size android:height="@dimen/rounded_slider_track_width" /> <corners android:radius="@dimen/rounded_slider_track_corner_radius" /> - <solid android:color="?attr/offStateColor" /> + <solid android:color="?attr/shadeInactive" /> </shape> </inset> </item> diff --git a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml index 4d9188c40822..2ea90c717863 100644 --- a/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml +++ b/packages/SystemUI/res/drawable/brightness_progress_full_drawable.xml @@ -22,7 +22,7 @@ android:height="@dimen/rounded_slider_height"> <shape> <size android:height="@dimen/rounded_slider_height" /> - <solid android:color="?priv-android:attr/colorAccentPrimary" /> + <solid android:color="?attr/shadeActive" /> <corners android:radius="@dimen/rounded_slider_corner_radius"/> </shape> </item> @@ -34,7 +34,7 @@ android:right="@dimen/rounded_slider_icon_inset"> <com.android.systemui.util.AlphaTintDrawableWrapper android:drawable="@drawable/ic_brightness" - android:tint="?android:attr/textColorPrimaryInverse" + android:tint="?attr/onShadeActive" /> </item> </layer-list>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/fgs_dot.xml b/packages/SystemUI/res/drawable/fgs_dot.xml index 3669e1d3c374..0881d7c5c2b5 100644 --- a/packages/SystemUI/res/drawable/fgs_dot.xml +++ b/packages/SystemUI/res/drawable/fgs_dot.xml @@ -19,5 +19,5 @@ android:shape="oval" android:width="12dp" android:height="12dp"> - <solid android:color="?androidprv:attr/colorAccentTertiary" /> + <solid android:color="?attr/tertiary" /> </shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/list_item_background.xml b/packages/SystemUI/res/drawable/list_item_background.xml new file mode 100644 index 000000000000..2dbab9cfe984 --- /dev/null +++ b/packages/SystemUI/res/drawable/list_item_background.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_focused="true" android:drawable="@*android:drawable/list_choice_background_material" /> + <item android:drawable="@android:color/transparent" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml b/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml index ea0aafd321e1..e138d094f869 100644 --- a/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml +++ b/packages/SystemUI/res/drawable/qs_customizer_background_primary.xml @@ -15,7 +15,7 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android"> <shape> - <solid android:color="?attr/underSurfaceColor"/> + <solid android:color="?attr/underSurface"/> <corners android:radius="?android:attr/dialogCornerRadius" /> </shape> </inset> diff --git a/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml b/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml index ef950fe67ad2..f1a24aa7af9d 100644 --- a/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml +++ b/packages/SystemUI/res/drawable/qs_customizer_toolbar.xml @@ -15,6 +15,6 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android"> <shape> - <solid android:color="?attr/underSurfaceColor"/> + <solid android:color="?attr/underSurface"/> </shape> </inset> diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml index 14cb1de9fa2d..c4e45bf2c223 100644 --- a/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml +++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled.xml @@ -28,7 +28,7 @@ <item> <shape android:shape="rectangle"> <corners android:radius="?android:attr/buttonCornerRadius"/> - <solid android:color="?androidprv:attr/colorAccentPrimary"/> + <solid android:color="?androidprv:attr/materialColorPrimary"/> <padding android:left="@dimen/dialog_button_horizontal_padding" android:top="@dimen/dialog_button_vertical_padding" android:right="@dimen/dialog_button_horizontal_padding" diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml index 0544b871fa06..1590daa8b7f9 100644 --- a/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml +++ b/packages/SystemUI/res/drawable/qs_dialog_btn_filled_large.xml @@ -26,7 +26,7 @@ <item> <shape android:shape="rectangle"> <corners android:radius="18dp"/> - <solid android:color="?androidprv:attr/colorAccentPrimary"/> + <solid android:color="?androidprv:attr/materialColorPrimaryFixed"/> </shape> </item> </ripple> diff --git a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml index a47299d6f854..b0dc6523e971 100644 --- a/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml +++ b/packages/SystemUI/res/drawable/qs_dialog_btn_outline.xml @@ -29,7 +29,7 @@ <shape android:shape="rectangle"> <corners android:radius="?android:attr/buttonCornerRadius"/> <solid android:color="@android:color/transparent"/> - <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant" + <stroke android:color="?androidprv:attr/materialColorPrimary" android:width="1dp" /> <padding android:left="@dimen/dialog_button_horizontal_padding" diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml index c8c36b0081c0..4a5d4af96497 100644 --- a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml +++ b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml @@ -28,7 +28,7 @@ </item> <item> <shape android:shape="rectangle"> - <solid android:color="?attr/offStateColor"/> + <solid android:color="?attr/shadeInactive"/> <corners android:radius="@dimen/qs_footer_action_corner_radius"/> </shape> </item> diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml index 6a365000a21c..a8c034986425 100644 --- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml +++ b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml @@ -28,7 +28,7 @@ </item> <item> <shape android:shape="rectangle"> - <solid android:color="?android:attr/colorAccent"/> + <solid android:color="?attr/shadeActive"/> <corners android:radius="@dimen/qs_footer_action_corner_radius"/> </shape> </item> diff --git a/packages/SystemUI/res/drawable/qs_footer_actions_background.xml b/packages/SystemUI/res/drawable/qs_footer_actions_background.xml index c9517cd905dc..a7e8762a2593 100644 --- a/packages/SystemUI/res/drawable/qs_footer_actions_background.xml +++ b/packages/SystemUI/res/drawable/qs_footer_actions_background.xml @@ -15,7 +15,7 @@ --> <inset xmlns:android="http://schemas.android.com/apk/res/android"> <shape> - <solid android:color="?attr/underSurfaceColor"/> + <solid android:color="?attr/underSurface"/> <corners android:topLeftRadius="@dimen/qs_corner_radius" android:topRightRadius="@dimen/qs_corner_radius"/> </shape> diff --git a/packages/SystemUI/res/drawable/qs_security_footer_background.xml b/packages/SystemUI/res/drawable/qs_security_footer_background.xml index 381af503d47c..0b0055b1f020 100644 --- a/packages/SystemUI/res/drawable/qs_security_footer_background.xml +++ b/packages/SystemUI/res/drawable/qs_security_footer_background.xml @@ -29,7 +29,7 @@ <item> <shape android:shape="rectangle"> <stroke android:width="1dp" - android:color="?android:attr/colorBackground"/> + android:color="?attr/shadeInactive"/> <corners android:radius="@dimen/qs_security_footer_corner_radius"/> </shape> </item> diff --git a/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml index 88f13b451bbe..ca7df86d8296 100644 --- a/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml +++ b/packages/SystemUI/res/layout/alert_dialog_title_systemui.xml @@ -42,7 +42,7 @@ android:layout_marginBottom="16dp" android:scaleType="fitCenter" android:src="@null" - android:tint="?androidprv:attr/colorAccentPrimaryVariant" + android:tint="?androidprv:attr/materialColorPrimary" /> <TextView diff --git a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml index 3786812db827..fcf9638440c7 100644 --- a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml +++ b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml @@ -17,6 +17,8 @@ <com.android.systemui.statusbar.KeyboardShortcutAppItemLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" + android:background="@drawable/list_item_background" + android:focusable="true" android:layout_width="match_parent" android:layout_height="wrap_content" android:minHeight="48dp" @@ -55,6 +57,5 @@ android:layout_alignParentEnd="true" android:textSize="14sp" android:scrollHorizontally="false" - android:layout_centerVertical="true" - android:focusable="true"/> + android:layout_centerVertical="true"/> </com.android.systemui.statusbar.KeyboardShortcutAppItemLayout> diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml index 8414223b7654..0759990f1677 100644 --- a/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml +++ b/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml @@ -16,10 +16,11 @@ ~ limitations under the License --> <TextView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="match_parent" android:textSize="14sp" android:fontFamily="sans-serif-medium" + android:importantForAccessibility="yes" android:paddingStart="24dp" android:paddingTop="20dp" android:paddingEnd="24dp" diff --git a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml index 7105721aff70..21e0d2c0b8d7 100644 --- a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml +++ b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml @@ -164,7 +164,6 @@ /> <ImageView android:id="@+id/media_output_item_end_click_icon" - android:src="@drawable/media_output_status_edit_session" android:layout_width="24dp" android:layout_height="24dp" android:focusable="false" diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index 745cfc6c1655..b8f4c0f212c3 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -53,6 +53,7 @@ android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="center_vertical" + android:tint="?attr/shadeActive" android:visibility="gone" /> <FrameLayout @@ -70,7 +71,7 @@ android:focusable="true" android:padding="@dimen/qs_footer_icon_padding" android:src="@*android:drawable/ic_mode_edit" - android:tint="?android:attr/textColorPrimary" /> + android:tint="?attr/onSurfaceVariant" /> </FrameLayout> </LinearLayout> diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml index c124aea01afc..974cad32f937 100644 --- a/packages/SystemUI/res/layout/qs_tile_label.xml +++ b/packages/SystemUI/res/layout/qs_tile_label.xml @@ -54,6 +54,6 @@ android:focusable="false" android:importantForAccessibility="no" android:textAppearance="@style/TextAppearance.QS.TileLabel.Secondary" - android:textColor="?android:attr/textColorSecondary"/> + android:textColor="?attr/onShadeInactive"/> </com.android.systemui.qs.tileimpl.IgnorableChildLinearLayout> diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml index bbf3adfb8c67..f6ce70d4d032 100644 --- a/packages/SystemUI/res/layout/screen_record_dialog.xml +++ b/packages/SystemUI/res/layout/screen_record_dialog.xml @@ -15,6 +15,7 @@ limitations under the License. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> @@ -47,7 +48,7 @@ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceLarge" + android:textAppearance="@style/TextAppearance.Dialog.Title" android:fontFamily="@*android:string/config_headlineFontFamily" android:text="@string/screenrecord_permission_dialog_title" android:layout_marginTop="22dp" @@ -56,8 +57,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/screenrecord_permission_dialog_warning_entire_screen" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textColor="?android:textColorSecondary" + android:textAppearance="@style/TextAppearance.Dialog.Body.Message" android:gravity="center" android:layout_marginBottom="20dp"/> @@ -81,6 +81,7 @@ android:minHeight="48dp" android:layout_weight="1" android:popupBackground="@drawable/screenrecord_spinner_background" + android:textColor="?androidprv:attr/materialColorOnSurface" android:dropDownWidth="274dp" android:prompt="@string/screenrecord_audio_label"/> <Switch @@ -117,7 +118,7 @@ android:text="@string/screenrecord_taps_label" android:textAppearance="?android:attr/textAppearanceMedium" android:fontFamily="@*android:string/config_headlineFontFamily" - android:textColor="?android:attr/textColorPrimary" + android:textColor="?androidprv:attr/materialColorOnSurface" android:importantForAccessibility="no"/> <Switch android:layout_width="wrap_content" diff --git a/packages/SystemUI/res/layout/screen_share_dialog.xml b/packages/SystemUI/res/layout/screen_share_dialog.xml index ab522a388735..9af46c5b739c 100644 --- a/packages/SystemUI/res/layout/screen_share_dialog.xml +++ b/packages/SystemUI/res/layout/screen_share_dialog.xml @@ -36,14 +36,13 @@ android:layout_width="@dimen/screenrecord_logo_size" android:layout_height="@dimen/screenrecord_logo_size" android:src="@drawable/ic_media_projection_permission" - android:tint="?androidprv:attr/colorAccentPrimaryVariant" + android:tint="?androidprv:attr/materialColorPrimary" android:importantForAccessibility="no"/> <TextView android:id="@+id/screen_share_dialog_title" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceLarge" - android:fontFamily="@*android:string/config_headlineFontFamily" + android:textAppearance="@style/TextAppearance.Dialog.Title" android:layout_marginTop="@dimen/screenrecord_title_margin_top" android:gravity="center"/> <Spinner @@ -64,8 +63,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/screenrecord_permission_dialog_warning_entire_screen" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textColor="?android:textColorSecondary" + style="@style/TextAppearance.Dialog.Body.Message" android:gravity="start" android:lineHeight="@dimen/screenrecord_warning_line_height"/> diff --git a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml b/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml index 00af7f4e10e3..530d752732c1 100644 --- a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml +++ b/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml @@ -16,8 +16,7 @@ --> <com.android.systemui.biometrics.UdfpsKeyguardViewLegacy xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:id="@+id/udfps_animation_view" + android:id="@+id/udfps_animation_view_legacy" android:layout_width="match_parent" android:layout_height="match_parent"> diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml index 3a1d1a8cbf9d..d693631080af 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -118,8 +118,25 @@ <attr name="wallpaperTextColorSecondary" format="reference|color" /> <attr name="wallpaperTextColorAccent" format="reference|color" /> <attr name="backgroundProtectedStyle" format="reference" /> - <attr name="offStateColor" format="reference|color" /> - <attr name="underSurfaceColor" format="reference|color" /> + + <!-- color attribute tokens for QS --> + <attr name="isQsTheme" format="boolean" /> + <attr name="underSurface" format="reference|color"/> + <attr name="shadeActive" format="reference|color" /> + <attr name="onShadeActive" format="reference|color" /> + <attr name="onShadeActiveVariant" format="reference|color" /> + <attr name="shadeInactive" format="reference|color" /> + <attr name="onShadeInactive" format="reference|color" /> + <attr name="onShadeInactiveVariant" format="reference|color" /> + <attr name="shadeDisabled" format="reference|color" /> + <attr name="surfaceBright" format="reference|color" /> + <attr name="scHigh" format="reference|color" /> + <attr name="tertiary" format="reference|color" /> + <attr name="onSurface" format="reference|color" /> + <attr name="onSurfaceVariant" format="reference|color" /> + <attr name="outline" format="reference|color" /> + <attr name="primary" format="reference|color" /> + <declare-styleable name="SmartReplyView"> <attr name="spacing" format="dimension" /> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index cb5342a0d66b..fd74c7eae361 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -83,7 +83,7 @@ <style name="TextAppearance.QS"> <item name="android:textStyle">normal</item> - <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textColor">?attr/onShadeInactive</item> <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> </style> @@ -93,7 +93,7 @@ <style name="TextAppearance.QS.DetailItemSecondary"> <item name="android:textSize">@dimen/qs_tile_text_size</item> - <item name="android:textColor">?android:attr/colorAccent</item> + <item name="android:textColor">?attr/shadeActive</item> </style> <style name="TextAppearance.QS.Introduction"> @@ -117,11 +117,11 @@ <style name="TextAppearance.QS.DataUsage.Usage"> <item name="android:textSize">@dimen/qs_data_usage_usage_text_size</item> - <item name="android:textColor">?android:attr/colorAccent</item> + <item name="android:textColor">?attr/shadeActive</item> </style> <style name="TextAppearance.QS.DataUsage.Secondary"> - <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textColor">?attr/onShadeInactiveVariant</item> </style> <style name="TextAppearance.QS.TileLabel"> @@ -137,31 +137,31 @@ <style name="TextAppearance.QS.UserSwitcher"> <item name="android:textSize">@dimen/qs_tile_text_size</item> - <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item> </style> <!-- This is hard coded to be sans-serif-condensed to match the icons --> <style name="TextAppearance.QS.Status"> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textColor">?attr/onSurface</item> <item name="android:textSize">14sp</item> <item name="android:letterSpacing">0.01</item> </style> <style name="TextAppearance.QS.SecurityFooter" parent="@style/TextAppearance.QS.Status"> <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item> - <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textColor">?attr/onSurface</item> </style> <style name="TextAppearance.QS.Status.Carriers" /> <style name="TextAppearance.QS.Status.Carriers.NoCarrierText"> - <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textColor">?attr/onSurfaceVariant</item> </style> <style name="TextAppearance.QS.Status.Build"> - <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textColor">?attr/onSurfaceVariant</item> </style> <style name="TextAppearance.DeviceManagementDialog.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle"/> @@ -278,10 +278,10 @@ <style name="DeviceManagementDialogTitle"> <item name="android:gravity">center</item> - <item name="android:textAppearance">@style/TextAppearance.DeviceManagementDialog.Title</item> + <item name="android:textAppearance">@style/TextAppearance.Dialog.Title</item> </style> - <style name="TextAppearance.DeviceManagementDialog.Content" parent="@*android:style/TextAppearance.DeviceDefault.Subhead"/> + <style name="TextAppearance.DeviceManagementDialog.Content" parent="@style/TextAppearance.Dialog.Body.Message"/> <style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI"> <item name="android:layout_width">match_parent</item> @@ -371,14 +371,30 @@ </style> <style name="Theme.SystemUI.QuickSettings" parent="@*android:style/Theme.DeviceDefault"> + <item name="isQsTheme">true</item> <item name="lightIconTheme">@style/QSIconTheme</item> <item name="darkIconTheme">@style/QSIconTheme</item> <item name="android:colorError">@*android:color/error_color_material_dark</item> <item name="android:windowIsFloating">true</item> <item name="android:homeAsUpIndicator">@drawable/ic_arrow_back</item> - <item name="offStateColor">@color/material_dynamic_neutral20</item> - <item name="underSurfaceColor">@color/material_dynamic_neutral0</item> - <item name="android:colorBackground">@color/material_dynamic_neutral10</item> + + <item name="surfaceBright">?androidprv:attr/materialColorSurfaceBright</item> + <item name="android:colorBackground">?attr/surfaceBright</item> + <item name="scHigh">?androidprv:attr/materialColorSurfaceContainerHigh</item> + <item name="primary">?androidprv:attr/materialColorPrimary</item> + <item name="tertiary">?androidprv:attr/materialColorTertiary</item> + <item name="onSurface">?androidprv:attr/materialColorOnSurface</item> + <item name="onSurfaceVariant">?androidprv:attr/materialColorOnSurfaceVariant</item> + <item name="outline">?androidprv:attr/materialColorOutline</item> + + <item name="shadeActive">@color/material_dynamic_primary90</item> + <item name="onShadeActive">@color/material_dynamic_primary10</item> + <item name="onShadeActiveVariant">@color/material_dynamic_primary30</item> + <item name="shadeInactive">@color/material_dynamic_neutral20</item> + <item name="onShadeInactive">@color/material_dynamic_neutral90</item> + <item name="onShadeInactiveVariant">@color/material_dynamic_neutral_variant80</item> + <item name="shadeDisabled">@color/shade_disabled</item> + <item name="underSurface">@color/material_dynamic_neutral0</item> <item name="android:itemTextAppearance">@style/Control.MenuItem</item> </style> @@ -390,8 +406,15 @@ <item name="android:windowBackground">@android:color/transparent</item> </style> - <style name="Theme.SystemUI.QuickSettings.Dialog" parent="@android:style/Theme.DeviceDefault.Dialog"> + <style name="Theme.SystemUI.QuickSettings.Dialog" parent="@style/Theme.SystemUI.Dialog.QuickSettings"> + </style> + + <!-- Parent style overrides style in the dot inheritance --> + <style name="Theme.SystemUI.Dialog.QuickSettings" parent="@style/Theme.SystemUI.QuickSettings"> <item name="android:dialogCornerRadius">@dimen/notification_corner_radius</item> + <item name="android:buttonBarPositiveButtonStyle">@style/Widget.Dialog.Button.QuickSettings</item> + <item name="android:buttonBarNegativeButtonStyle">@style/Widget.Dialog.Button.QuickSettings</item> + <item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.QuickSettings</item> </style> <!-- Overridden by values-television/styles.xml with tv-specific settings --> @@ -406,7 +429,7 @@ <item name="android:buttonBarPositiveButtonStyle">@style/Widget.Dialog.Button</item> <item name="android:buttonBarNegativeButtonStyle">@style/Widget.Dialog.Button.BorderButton</item> <item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.BorderButton</item> - <item name="android:colorBackground">?androidprv:attr/colorSurface</item> + <item name="android:colorBackground">?androidprv:attr/materialColorSurfaceBright</item> <item name="android:alertDialogStyle">@style/ScrollableAlertDialogStyle</item> <item name="android:buttonBarStyle">@style/ButtonBarStyle</item> <item name="android:buttonBarButtonStyle">@style/Widget.Dialog.Button.Large</item> @@ -605,11 +628,11 @@ <item name="android:letterSpacing">0.01</item> <item name="android:lineHeight">20sp</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textColor">?attr/onSurfaceVariant</item> </style> <style name="QSCustomizeToolbar" parent="@*android:style/Widget.DeviceDefault.Toolbar"> - <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textColor">?attr/onSurface</item> <item name="android:elevation">10dp</item> </style> @@ -1055,7 +1078,7 @@ </style> <style name="TextAppearance.Dialog.Title" parent="@android:style/TextAppearance.DeviceDefault.Large"> - <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> <item name="android:textSize">@dimen/dialog_title_text_size</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:lineHeight">32sp</item> @@ -1064,7 +1087,7 @@ </style> <style name="TextAppearance.Dialog.Body" parent="@android:style/TextAppearance.DeviceDefault.Medium"> - <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item> <item name="android:textSize">14sp</item> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:lineHeight">20sp</item> @@ -1092,7 +1115,7 @@ <style name="Widget.Dialog.Button"> <item name="android:buttonCornerRadius">28dp</item> <item name="android:background">@drawable/qs_dialog_btn_filled</item> - <item name="android:textColor">?androidprv:attr/textColorOnAccent</item> + <item name="android:textColor">?androidprv:attr/materialColorOnPrimary</item> <item name="android:textSize">14sp</item> <item name="android:lineHeight">20sp</item> <item name="android:fontFamily">@*android:string/config_bodyFontFamilyMedium</item> @@ -1102,12 +1125,18 @@ <style name="Widget.Dialog.Button.BorderButton"> <item name="android:background">@drawable/qs_dialog_btn_outline</item> - <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> </style> <style name="Widget.Dialog.Button.Large"> <item name="android:background">@drawable/qs_dialog_btn_filled_large</item> <item name="android:minHeight">56dp</item> + <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryFixed</item> + </style> + + <style name="Widget.Dialog.Button.QuickSettings"> + <item name="android:textColor">?attr/primary</item> + <item name="android:background">?android:attr/selectableItemBackground</item> </style> <style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault"> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java index 2e6c485336f3..751a3f8458bd 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java @@ -104,8 +104,7 @@ public class Utilities { * @return updated set of flags from InputMethodService based off {@param oldHints} * Leaves original hints unmodified */ - public static int calculateBackDispositionHints(int oldHints, - @InputMethodService.BackDispositionMode int backDisposition, + public static int calculateBackDispositionHints(int oldHints, int backDisposition, boolean imeShown, boolean showImeSwitcher) { int hints = oldHints; switch (backDisposition) { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 3c447a8eb6d8..ca064efd4f76 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -324,7 +324,7 @@ public class QuickStepContract { * Returns whether the specified sysui state is such that the back gesture should be * disabled. */ - public static boolean isBackGestureDisabled(int sysuiStateFlags) { + public static boolean isBackGestureDisabled(int sysuiStateFlags, boolean forTrackpad) { // Always allow when the bouncer/global actions/voice session is showing (even on top of // the keyguard) if ((sysuiStateFlags & SYSUI_STATE_BOUNCER_SHOWING) != 0 @@ -335,16 +335,23 @@ public class QuickStepContract { if ((sysuiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) { sysuiStateFlags &= ~SYSUI_STATE_NAV_BAR_HIDDEN; } + + return (sysuiStateFlags & getBackGestureDisabledMask(forTrackpad)) != 0; + } + + private static int getBackGestureDisabledMask(boolean forTrackpad) { // Disable when in immersive, or the notifications are interactive - int disableFlags = SYSUI_STATE_NAV_BAR_HIDDEN | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; + int disableFlags = SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; + if (!forTrackpad) { + disableFlags |= SYSUI_STATE_NAV_BAR_HIDDEN; + } // EdgeBackGestureHandler ignores Back gesture when SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED. // To allow Shade to respond to Back, we're bypassing this check (behind a flag). if (!ALLOW_BACK_GESTURE_IN_SHADE) { disableFlags |= SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; } - - return (sysuiStateFlags & disableFlags) != 0; + return disableFlags; } /** diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 84a2c25999a0..4e0e8d0dbcd7 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -42,7 +42,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInterac import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.DEBUG import com.android.systemui.log.dagger.KeyguardLargeClockLog import com.android.systemui.log.dagger.KeyguardSmallClockLog import com.android.systemui.plugins.ClockController @@ -91,9 +91,9 @@ constructor( field = value if (value != null) { smallLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" }) - value.smallClock.logBuffer = smallLogBuffer + value.smallClock.messageBuffer = smallLogBuffer largeLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" }) - value.largeClock.logBuffer = largeLogBuffer + value.largeClock.messageBuffer = largeLogBuffer value.initialize(resources, dozeAmount, 0f) diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 360f755623f7..4793b4f37eb3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -29,7 +29,7 @@ import com.android.app.animation.Interpolators; import com.android.keyguard.dagger.KeyguardStatusViewScope; import com.android.systemui.R; import com.android.systemui.log.LogBuffer; -import com.android.systemui.log.LogLevel; +import com.android.systemui.log.core.LogLevel; import com.android.systemui.plugins.ClockController; import com.android.systemui.shared.clocks.DefaultClockController; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 84fc720f5a3b..21350727908c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -40,7 +40,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.log.LogBuffer; -import com.android.systemui.log.LogLevel; +import com.android.systemui.log.core.LogLevel; import com.android.systemui.log.dagger.KeyguardClockLog; import com.android.systemui.plugins.ClockController; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -352,6 +352,13 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } /** + * Set if the split shade is enabled + */ + public void setSplitShadeEnabled(boolean splitShadeEnabled) { + mSmartspaceController.setSplitShadeEnabled(splitShadeEnabled); + } + + /** * Set which clock should be displayed on the keyguard. The other one will be automatically * hidden. */ diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index 6f596843bf9f..3e16d559742d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -43,15 +43,6 @@ public class KeyguardPinViewController private long mPinLength; private boolean mDisabledAutoConfirmation; - /** - * Responsible for identifying if PIN hinting is to be enabled or not - */ - private boolean mIsPinHinting; - - /** - * Responsible for identifying if auto confirm is enabled or not in Settings - */ - private boolean mIsAutoPinConfirmEnabledInSettings; protected KeyguardPinViewController(KeyguardPINView view, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -72,9 +63,6 @@ public class KeyguardPinViewController mFeatureFlags = featureFlags; mBackspaceKey = view.findViewById(R.id.delete_button); mPinLength = mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser()); - mIsPinHinting = mPinLength == DEFAULT_PIN_LENGTH; - mIsAutoPinConfirmEnabledInSettings = mLockPatternUtils.isAutoPinConfirmEnabled( - KeyguardUpdateMonitor.getCurrentUser()); } @Override @@ -94,7 +82,7 @@ public class KeyguardPinViewController protected void onUserInput() { super.onUserInput(); - if (mIsAutoPinConfirmEnabledInSettings) { + if (isAutoPinConfirmEnabledInSettings()) { updateAutoConfirmationState(); if (mPasswordEntry.getText().length() == mPinLength && mOkButton.getVisibility() == View.INVISIBLE) { @@ -142,7 +130,7 @@ public class KeyguardPinViewController * Updates the visibility of the OK button for auto confirm feature */ private void updateOKButtonVisibility() { - if (mIsPinHinting && !mDisabledAutoConfirmation) { + if (isAutoPinConfirmEnabledInSettings() && !mDisabledAutoConfirmation) { mOkButton.setVisibility(View.INVISIBLE); } else { mOkButton.setVisibility(View.VISIBLE); @@ -154,9 +142,10 @@ public class KeyguardPinViewController * Visibility changes are only for auto confirmation configuration. */ private void updateBackSpaceVisibility() { + boolean isAutoConfirmation = isAutoPinConfirmEnabledInSettings(); mBackspaceKey.setTransparentMode(/* isTransparentMode= */ - mIsAutoPinConfirmEnabledInSettings && !mDisabledAutoConfirmation); - if (mIsAutoPinConfirmEnabledInSettings) { + isAutoConfirmation && !mDisabledAutoConfirmation); + if (isAutoConfirmation) { if (mPasswordEntry.getText().length() > 0 || mDisabledAutoConfirmation) { mBackspaceKey.setVisibility(View.VISIBLE); @@ -166,8 +155,24 @@ public class KeyguardPinViewController } } /** Updates whether to use pin hinting or not. */ - private void updatePinHinting() { - mPasswordEntry.setIsPinHinting(mIsAutoPinConfirmEnabledInSettings && mIsPinHinting + void updatePinHinting() { + mPasswordEntry.setIsPinHinting(isAutoPinConfirmEnabledInSettings() && isPinHinting() && !mDisabledAutoConfirmation); } + + /** + * Responsible for identifying if PIN hinting is to be enabled or not + */ + private boolean isPinHinting() { + return mLockPatternUtils.getPinLength(KeyguardUpdateMonitor.getCurrentUser()) + == DEFAULT_PIN_LENGTH; + } + + /** + * Responsible for identifying if auto confirm is enabled or not in Settings + */ + private boolean isAutoPinConfirmEnabledInSettings() { + //Checks if user has enabled the auto confirm in Settings + return mLockPatternUtils.isAutoPinConfirmEnabled(KeyguardUpdateMonitor.getCurrentUser()); + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 880f242c5938..458ca2b17c6d 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -544,7 +544,6 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard public void setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction) { if (mCancelAction != null) { mCancelAction.run(); - mCancelAction = null; } mDismissAction = action; mCancelAction = cancelAction; @@ -782,9 +781,10 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard case SimPuk: // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId); - if (securityMode == SecurityMode.None || mLockPatternUtils.isLockScreenDisabled( - KeyguardUpdateMonitor.getCurrentUser())) { - finish = true; + boolean isLockscreenDisabled = mLockPatternUtils.isLockScreenDisabled( + KeyguardUpdateMonitor.getCurrentUser()); + if (securityMode == SecurityMode.None || isLockscreenDisabled) { + finish = isLockscreenDisabled; eventSubtype = BOUNCER_DISMISS_SIM; uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM; } else { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 00500d617766..6854c97c3415 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -323,6 +323,13 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } /** + * Set if the split shade is enabled + */ + public void setSplitShadeEnabled(boolean enabled) { + mKeyguardClockSwitchController.setSplitShadeEnabled(enabled); + } + + /** * Updates the alignment of the KeyguardStatusView and animates the transition if requested. */ public void updateAlignment( @@ -350,6 +357,9 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV } mInteractionJankMonitor.begin(mView, CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION); + /* This transition blocks any layout changes while running. For that reason + * special logic with setting visibility was added to {@link BcSmartspaceView#setDozing} + * for split shade to avoid jump of the media object. */ ChangeBounds transition = new ChangeBounds(); if (splitShadeEnabled) { // Excluding media from the transition on split-shade, as it doesn't transition diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 56e90bf73fe1..45744dfcab55 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -24,6 +24,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.ACTION_USER_STOPPED; import static android.content.Intent.ACTION_USER_UNLOCKED; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_NONE; import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_PERMANENT; import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_LOCKOUT_TIMED; @@ -251,6 +253,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final int MSG_REQUIRE_NFC_UNLOCK = 345; private static final int MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED = 346; private static final int MSG_SERVICE_PROVIDERS_UPDATED = 347; + private static final int MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED = 348; /** Biometric authentication state: Not listening. */ private static final int BIOMETRIC_STATE_STOPPED = 0; @@ -300,6 +303,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private static final ComponentName FALLBACK_HOME_COMPONENT = new ComponentName( "com.android.settings", "com.android.settings.FallbackHome"); + private static final List<Integer> ABSENT_SIM_STATE_LIST = Arrays.asList( + TelephonyManager.SIM_STATE_ABSENT, + TelephonyManager.SIM_STATE_UNKNOWN, + TelephonyManager.SIM_STATE_NOT_READY); + private final Context mContext; private final UserTracker mUserTracker; private final KeyguardUpdateMonitorLogger mLogger; @@ -2484,6 +2492,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab case MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED: handleKeyguardDismissAnimationFinished(); break; + case MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED: + notifyAboutEnrollmentChange(msg.arg1); + break; default: super.handleMessage(msg); break; @@ -2584,6 +2595,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) { + mHandler.obtainMessage(MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED, modality, 0) + .sendToTarget(); mainExecutor.execute(() -> updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_TRIGGERED_ENROLLMENTS_CHANGED)); } @@ -3433,6 +3446,25 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return mIsFaceEnrolled; } + private void notifyAboutEnrollmentChange(@BiometricAuthenticator.Modality int modality) { + BiometricSourceType biometricSourceType; + if (modality == TYPE_FINGERPRINT) { + biometricSourceType = FINGERPRINT; + } else if (modality == TYPE_FACE) { + biometricSourceType = FACE; + } else { + return; + } + mLogger.notifyAboutEnrollmentsChanged(biometricSourceType); + Assert.isMainThread(); + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onBiometricEnrollmentStateChanged(biometricSourceType); + } + } + } + private void stopListeningForFingerprint() { mLogger.v("stopListeningForFingerprint()"); if (mFingerprintRunningState == BIOMETRIC_STATE_RUNNING) { @@ -3715,8 +3747,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLogger.logSimState(subId, slotId, state); boolean becameAbsent = false; - if (!SubscriptionManager.isValidSubscriptionId(subId) - && state != TelephonyManager.SIM_STATE_UNKNOWN) { + if (!SubscriptionManager.isValidSubscriptionId(subId)) { mLogger.w("invalid subId in handleSimStateChange()"); /* Only handle No SIM(ABSENT) and Card Error(CARD_IO_ERROR) due to * handleServiceStateChange() handle other case */ @@ -3734,11 +3765,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } else if (state == TelephonyManager.SIM_STATE_CARD_IO_ERROR) { updateTelephonyCapable(true); - } else { - return; } } + becameAbsent |= ABSENT_SIM_STATE_LIST.contains(state); + SimData data = mSimDatas.get(subId); final boolean changed; if (data == null) { @@ -3751,7 +3782,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab data.subId = subId; data.slotId = slotId; } - if ((changed || becameAbsent) || state == TelephonyManager.SIM_STATE_UNKNOWN) { + if ((changed || becameAbsent)) { for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 73940055c89f..7b596328ca13 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -327,4 +327,9 @@ public class KeyguardUpdateMonitorCallback { * Called when the enabled trust agents associated with the specified user. */ public void onEnabledTrustAgentsChanged(int userId) { } + + /** + * On biometric enrollment state changed + */ + public void onBiometricEnrollmentStateChanged(BiometricSourceType biometricSourceType) { } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index 61af7228fe0d..71f78c32317c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -23,7 +23,7 @@ import android.view.View; import com.android.app.animation.Interpolators; import com.android.systemui.log.LogBuffer; -import com.android.systemui.log.LogLevel; +import com.android.systemui.log.core.LogLevel; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.notification.PropertyAnimator; diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index d1fffaa926ea..76b073ef73af 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -32,7 +32,6 @@ import android.widget.ImageView; import androidx.annotation.IntDef; import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; import com.android.internal.graphics.ColorUtils; import com.android.settingslib.Utils; @@ -126,7 +125,6 @@ public class LockIconView extends FrameLayout implements Dumpable { /** * Set the location of the lock icon. */ - @VisibleForTesting public void setCenterLocation(@NonNull Point center, float radius, int drawablePadding) { mLockIconCenter = center; mRadius = radius; diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 239a0cc01c45..e255f5c2ded6 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -60,6 +60,7 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.model.TransitionStep; @@ -387,15 +388,17 @@ public class LockIconViewController extends ViewController<LockIconView> impleme private void updateLockIconLocation() { final float scaleFactor = mAuthController.getScaleFactor(); final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor); - if (mUdfpsSupported) { - mView.setCenterLocation(mAuthController.getUdfpsLocation(), - mAuthController.getUdfpsRadius(), scaledPadding); - } else { - mView.setCenterLocation( - new Point((int) mWidthPixels / 2, - (int) (mHeightPixels - - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))), + if (!mFeatureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) { + if (mUdfpsSupported) { + mView.setCenterLocation(mAuthController.getUdfpsLocation(), + mAuthController.getUdfpsRadius(), scaledPadding); + } else { + mView.setCenterLocation( + new Point((int) mWidthPixels / 2, + (int) (mHeightPixels + - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))), sLockIconRadiusPx * scaleFactor, scaledPadding); + } } } diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt index e7295ef9052c..54738c62646a 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricMessageDeferralLogger.kt @@ -18,7 +18,7 @@ package com.android.keyguard.logging import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.DEBUG import com.android.systemui.log.dagger.BiometricLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt index c00b2c612fa2..cfad614a460d 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/BiometricUnlockLogger.kt @@ -19,9 +19,9 @@ package com.android.keyguard.logging import android.hardware.biometrics.BiometricSourceType import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.log.dagger.BiometricLog import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE diff --git a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt index 19787154bec4..d02b72f37795 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/CarrierTextManagerLogger.kt @@ -19,7 +19,7 @@ package com.android.keyguard.logging import androidx.annotation.IntDef import com.android.keyguard.CarrierTextManager.CarrierTextCallbackInfo import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.CarrierTextManagerLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt index 8b925b1bfb54..bddf3b07dbb5 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt @@ -19,7 +19,7 @@ package com.android.keyguard.logging import com.android.systemui.biometrics.AuthRippleController import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.KeyguardLog import com.android.systemui.statusbar.KeyguardIndicationController import com.google.errorprone.annotations.CompileTimeConstant diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt index 18ba09a63221..59e0cadec9b2 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt @@ -18,6 +18,7 @@ package com.android.keyguard.logging import android.content.Intent import android.hardware.biometrics.BiometricConstants.LockoutMode +import android.hardware.biometrics.BiometricSourceType import android.os.PowerManager import android.os.PowerManager.WakeReason import android.telephony.ServiceState @@ -32,12 +33,12 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.keyguard.TrustGrantFlags import com.android.settingslib.fuelgauge.BatteryStatus import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.VERBOSE -import com.android.systemui.log.LogLevel.WARNING +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.ERROR +import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.VERBOSE +import com.android.systemui.log.core.LogLevel.WARNING import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog import com.google.errorprone.annotations.CompileTimeConstant import javax.inject.Inject @@ -370,16 +371,14 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { fun logServiceProvidersUpdated(intent: Intent) { logBuffer.log( - TAG, - VERBOSE, - { - int1 = intent.getIntExtra(EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID) - str1 = intent.getStringExtra(TelephonyManager.EXTRA_SPN) - str2 = intent.getStringExtra(TelephonyManager.EXTRA_PLMN) - }, - { - "action SERVICE_PROVIDERS_UPDATED subId=$int1 spn=$str1 plmn=$str2" - } + TAG, + VERBOSE, + { + int1 = intent.getIntExtra(EXTRA_SUBSCRIPTION_INDEX, INVALID_SUBSCRIPTION_ID) + str1 = intent.getStringExtra(TelephonyManager.EXTRA_SPN) + str2 = intent.getStringExtra(TelephonyManager.EXTRA_PLMN) + }, + { "action SERVICE_PROVIDERS_UPDATED subId=$int1 spn=$str1 plmn=$str2" } ) } @@ -719,4 +718,13 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) { fun scheduleWatchdog(@CompileTimeConstant watchdogType: String) { logBuffer.log(TAG, DEBUG, "Scheduling biometric watchdog for $watchdogType") } + + fun notifyAboutEnrollmentsChanged(biometricSourceType: BiometricSourceType) { + logBuffer.log( + TAG, + DEBUG, + { str1 = "$biometricSourceType" }, + { "notifying about enrollments changed: $str1" } + ) + } } diff --git a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt index cb764a8f94aa..57b5db15de67 100644 --- a/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt +++ b/packages/SystemUI/src/com/android/keyguard/logging/TrustRepositoryLogger.kt @@ -21,8 +21,8 @@ import com.android.systemui.keyguard.shared.model.ActiveUnlockModel import com.android.systemui.keyguard.shared.model.TrustManagedModel import com.android.systemui.keyguard.shared.model.TrustModel import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogLevel.DEBUG import com.android.systemui.log.dagger.KeyguardUpdateMonitorLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt index 670c1fa45e5c..c46558532372 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt @@ -56,9 +56,14 @@ class ScreenDecorHwcLayer( ) : DisplayCutoutBaseView(context) { val colorMode: Int private val useInvertedAlphaColor: Boolean - private val color: Int + private var color: Int = Color.BLACK + set(value) { + field = value + paint.color = value + } + private val bgColor: Int - private val cornerFilter: ColorFilter + private var cornerFilter: ColorFilter private val cornerBgFilter: ColorFilter private val clearPaint: Paint @JvmField val transparentRect: Rect = Rect() @@ -109,10 +114,16 @@ class ScreenDecorHwcLayer( override fun onAttachedToWindow() { super.onAttachedToWindow() parent.requestTransparentRegion(this) + updateColors() + } + + private fun updateColors() { if (!debug) { viewRootImpl.setDisplayDecoration(true) } + cornerFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) + if (useInvertedAlphaColor) { paint.set(clearPaint) } else { @@ -121,6 +132,21 @@ class ScreenDecorHwcLayer( } } + fun setDebugColor(color: Int) { + if (!debug) { + return + } + + if (this.color == color) { + return + } + + this.color = color + + updateColors() + invalidate() + } + override fun onUpdate() { parent.requestTransparentRegion(this) } @@ -367,7 +393,7 @@ class ScreenDecorHwcLayer( /** * Update the rounded corner drawables. */ - fun updateRoundedCornerDrawable(top: Drawable, bottom: Drawable) { + fun updateRoundedCornerDrawable(top: Drawable?, bottom: Drawable?) { roundedCornerDrawableTop = top roundedCornerDrawableBottom = bottom updateRoundedCornerDrawableBounds() diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 67d4a2e25051..de7a66900355 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -69,9 +69,9 @@ import com.android.internal.util.Preconditions; import com.android.settingslib.Utils; import com.android.systemui.biometrics.AuthController; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.decor.CutoutDecorProviderFactory; import com.android.systemui.decor.DebugRoundedCornerDelegate; +import com.android.systemui.decor.DebugRoundedCornerModel; import com.android.systemui.decor.DecorProvider; import com.android.systemui.decor.DecorProviderFactory; import com.android.systemui.decor.DecorProviderKt; @@ -80,10 +80,12 @@ import com.android.systemui.decor.OverlayWindow; import com.android.systemui.decor.PrivacyDotDecorProviderFactory; import com.android.systemui.decor.RoundedCornerDecorProviderFactory; import com.android.systemui.decor.RoundedCornerResDelegateImpl; +import com.android.systemui.decor.ScreenDecorCommand; import com.android.systemui.log.ScreenDecorationsLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.settings.DisplayTracker; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.statusbar.events.PrivacyDotViewController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.ThreadFactory; @@ -95,7 +97,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.concurrent.Executor; import javax.inject.Inject; @@ -130,7 +131,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { @VisibleForTesting protected boolean mIsRegistered; private final Context mContext; - private final Executor mMainExecutor; + private final CommandRegistry mCommandRegistry; private final SecureSettings mSecureSettings; @VisibleForTesting DisplayTracker.Callback mDisplayListener; @@ -313,8 +314,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable { @Inject public ScreenDecorations(Context context, - @Main Executor mainExecutor, SecureSettings secureSettings, + CommandRegistry commandRegistry, UserTracker userTracker, DisplayTracker displayTracker, PrivacyDotViewController dotViewController, @@ -324,8 +325,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable { ScreenDecorationsLogger logger, AuthController authController) { mContext = context; - mMainExecutor = mainExecutor; mSecureSettings = secureSettings; + mCommandRegistry = commandRegistry; mUserTracker = userTracker; mDisplayTracker = displayTracker; mDotViewController = dotViewController; @@ -350,6 +351,45 @@ public class ScreenDecorations implements CoreStartable, Dumpable { } }; + private final ScreenDecorCommand.Callback mScreenDecorCommandCallback = (cmd, pw) -> { + // If we are exiting debug mode, we can set it (false) and bail, otherwise we will + // ensure that debug mode is set + if (cmd.getDebug() != null && !cmd.getDebug()) { + setDebug(false); + return; + } else { + // setDebug is idempotent + setDebug(true); + } + + if (cmd.getColor() != null) { + mDebugColor = cmd.getColor(); + mExecutor.execute(() -> { + if (mScreenDecorHwcLayer != null) { + mScreenDecorHwcLayer.setDebugColor(cmd.getColor()); + } + updateColorInversionDefault(); + }); + } + + DebugRoundedCornerModel roundedTop = null; + DebugRoundedCornerModel roundedBottom = null; + if (cmd.getRoundedTop() != null) { + roundedTop = cmd.getRoundedTop().toRoundedCornerDebugModel(); + } + if (cmd.getRoundedBottom() != null) { + roundedBottom = cmd.getRoundedBottom().toRoundedCornerDebugModel(); + } + if (roundedTop != null || roundedBottom != null) { + mDebugRoundedCornerDelegate.applyNewDebugCorners(roundedTop, roundedBottom); + mExecutor.execute(() -> { + removeAllOverlays(); + removeHwcOverlay(); + setupDecorations(); + }); + } + }; + @Override public void start() { if (DEBUG_DISABLE_SCREEN_DECORATIONS) { @@ -361,6 +401,8 @@ public class ScreenDecorations implements CoreStartable, Dumpable { mExecutor.execute(this::startOnScreenDecorationsThread); mDotViewController.setUiExecutor(mExecutor); mAuthController.addCallback(mAuthControllerCallback); + mCommandRegistry.registerCommand(ScreenDecorCommand.SCREEN_DECOR_CMD_NAME, + () -> new ScreenDecorCommand(mScreenDecorCommandCallback)); } /** @@ -1228,7 +1270,7 @@ public class ScreenDecorations implements CoreStartable, Dumpable { bottomDrawable = mDebugRoundedCornerDelegate.getBottomRoundedDrawable(); } - if (topDrawable == null || bottomDrawable == null) { + if (topDrawable == null && bottomDrawable == null) { return; } mScreenDecorHwcLayer.updateRoundedCornerDrawable(topDrawable, bottomDrawable); diff --git a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java index 45077d2333b6..6f99a24b7312 100644 --- a/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/SlicePermissionActivity.java @@ -51,7 +51,11 @@ public class SlicePermissionActivity extends Activity implements OnClickListener super.onCreate(savedInstanceState); // Verify intent is valid - mUri = getIntent().getParcelableExtra(SliceProvider.EXTRA_BIND_URI); + try { + mUri = getIntent().getParcelableExtra(SliceProvider.EXTRA_BIND_URI); + } catch (Exception e) { + Log.w(TAG, "Failed to getParcelableExtra", e); + } mCallingPkg = getIntent().getStringExtra(SliceProvider.EXTRA_PKG); if (mUri == null || !SliceProvider.SLICE_TYPE.equals(getContentResolver().getType(mUri)) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 6f0f6331ef50..946ddba6a341 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -37,7 +37,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.WakefulnessLifecycle -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LiftReveal diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index dc9ba8719717..ebff0b05a52d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -85,6 +85,8 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; +import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter; +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -153,6 +155,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull private final SystemUIDialogManager mDialogManager; @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; + @NonNull private final Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels; @NonNull private final VibratorHelper mVibrator; @NonNull private final FeatureFlags mFeatureFlags; @NonNull private final FalsingManager mFalsingManager; @@ -272,7 +275,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { (view, event, fromUdfpsView) -> onTouch(requestId, event, fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags, mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsUtils, - mUdfpsKeyguardAccessibilityDelegate))); + mUdfpsKeyguardAccessibilityDelegate, + mUdfpsKeyguardViewModels))); } @Override @@ -591,6 +595,13 @@ public class UdfpsController implements DozeReceiver, Dumpable { // Pilfer if valid overlap, don't allow following events to reach keyguard shouldPilfer = true; + + // Touch is a valid UDFPS touch. Inform the falsing manager so that the touch + // isn't counted against the falsing algorithm as an accidental touch. + // We do this on the DOWN event instead of CANCEL/UP because the CANCEL/UP events + // get sent too late to this receiver (after the actual cancel/up motions occur), + // and therefore wouldn't end up being used as part of the falsing algo. + mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); break; case UP: @@ -610,7 +621,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { data.getTime(), data.getGestureStart(), mStatusBarStateController.isDozing()); - mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); break; case UNCHANGED: @@ -784,7 +794,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { private boolean shouldTryToDismissKeyguard() { return mOverlay != null && mOverlay.getAnimationViewController() - instanceof UdfpsKeyguardViewControllerLegacy + instanceof UdfpsKeyguardViewControllerAdapter && mKeyguardStateController.canDismissLockScreen() && !mAttemptedToDismissKeyguard; } @@ -829,7 +839,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull InputManager inputManager, @NonNull UdfpsUtils udfpsUtils, @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor, - @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate) { + @NonNull UdfpsKeyguardAccessibilityDelegate udfpsKeyguardAccessibilityDelegate, + @NonNull Provider<UdfpsKeyguardViewModels> udfpsKeyguardViewModelsProvider) { mContext = context; mExecution = execution; mVibrator = vibrator; @@ -895,6 +906,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { return Unit.INSTANCE; }); mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor; + mUdfpsKeyguardViewModels = udfpsKeyguardViewModelsProvider; final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController(); mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index e5421471931f..d6ef94d18e71 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -46,15 +46,19 @@ import android.view.accessibility.AccessibilityManager.TouchExplorationStateChan import androidx.annotation.LayoutRes import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor -import com.android.settingslib.udfps.UdfpsUtils import com.android.settingslib.udfps.UdfpsOverlayParams +import com.android.settingslib.udfps.UdfpsUtils import com.android.systemui.R import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.flags.Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS +import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -64,6 +68,8 @@ import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.settings.SecureSettings +import kotlinx.coroutines.ExperimentalCoroutinesApi +import javax.inject.Provider private const val TAG = "UdfpsControllerOverlay" @@ -75,6 +81,7 @@ const val SETTING_REMOVE_ENROLLMENT_UI = "udfps_overlay_remove_enrollment_ui" * request. This state can persist across configuration changes via the [show] and [hide] * methods. */ +@ExperimentalCoroutinesApi @UiThread class UdfpsControllerOverlay @JvmOverloads constructor( private val context: Context, @@ -105,6 +112,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( private val isDebuggable: Boolean = Build.IS_DEBUGGABLE, private val udfpsUtils: UdfpsUtils, private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate, + private val udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels>, ) { /** The view, when [isShowing], or null. */ var overlayView: UdfpsView? = null @@ -243,27 +251,40 @@ class UdfpsControllerOverlay @JvmOverloads constructor( ) } REASON_AUTH_KEYGUARD -> { - UdfpsKeyguardViewControllerLegacy( - view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) { - updateSensorLocation(sensorBounds) - }, - statusBarStateController, - shadeExpansionStateManager, - statusBarKeyguardViewManager, - keyguardUpdateMonitor, - dumpManager, - transitionController, - configurationController, - keyguardStateController, - unlockedScreenOffAnimationController, - dialogManager, - controller, - activityLaunchAnimator, - featureFlags, - primaryBouncerInteractor, - alternateBouncerInteractor, - udfpsKeyguardAccessibilityDelegate, - ) + if (featureFlags.isEnabled(REFACTOR_UDFPS_KEYGUARD_VIEWS)) { + udfpsKeyguardViewModels.get().setSensorBounds(sensorBounds) + UdfpsKeyguardViewController( + view.addUdfpsView(R.layout.udfps_keyguard_view), + statusBarStateController, + shadeExpansionStateManager, + dialogManager, + dumpManager, + alternateBouncerInteractor, + udfpsKeyguardViewModels.get(), + ) + } else { + UdfpsKeyguardViewControllerLegacy( + view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) { + updateSensorLocation(sensorBounds) + }, + statusBarStateController, + shadeExpansionStateManager, + statusBarKeyguardViewManager, + keyguardUpdateMonitor, + dumpManager, + transitionController, + configurationController, + keyguardStateController, + unlockedScreenOffAnimationController, + dialogManager, + controller, + activityLaunchAnimator, + featureFlags, + primaryBouncerInteractor, + alternateBouncerInteractor, + udfpsKeyguardAccessibilityDelegate, + ) + } } REASON_AUTH_BP -> { // note: empty controller, currently shows no visual affordance @@ -415,7 +436,7 @@ class UdfpsControllerOverlay @JvmOverloads constructor( } private fun shouldRotate(animation: UdfpsAnimationViewController<*>?): Boolean { - if (animation !is UdfpsKeyguardViewControllerLegacy) { + if (animation !is UdfpsKeyguardViewControllerAdapter) { // always rotate view if we're not on the keyguard return true } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt new file mode 100644 index 000000000000..8cc15dadffd2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardView.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.biometrics + +import android.content.Context +import android.util.AttributeSet +import kotlinx.coroutines.ExperimentalCoroutinesApi + +/** View corresponding with udfps_keyguard_view.xml */ +@ExperimentalCoroutinesApi +class UdfpsKeyguardView( + context: Context, + attrs: AttributeSet?, +) : + UdfpsAnimationView( + context, + attrs, + ) { + private val fingerprintDrawablePlaceHolder = UdfpsFpDrawable(context) + private var visible = false + + override fun calculateAlpha(): Int { + return if (mPauseAuth) { + 0 + } else 255 // ViewModels handle animating alpha values + } + + override fun getDrawable(): UdfpsDrawable { + return fingerprintDrawablePlaceHolder + } + + fun useExpandedOverlay(useExpandedOverlay: Boolean) { + mUseExpandedOverlay = useExpandedOverlay + } + + fun isVisible(): Boolean { + return visible + } + + fun setVisible(isVisible: Boolean) { + visible = isVisible + isPauseAuth = !visible + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt index 9bafeeca24a8..15bd73193687 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt @@ -34,6 +34,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeExpansionListener @@ -80,7 +81,8 @@ constructor( shadeExpansionStateManager, systemUIDialogManager, dumpManager, - ) { + ), + UdfpsKeyguardViewControllerAdapter { private val useExpandedOverlay: Boolean = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) private var showingUdfpsBouncer = false diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt index 39199d194cc9..2102a1f72be2 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsLogger.kt @@ -17,10 +17,10 @@ package com.android.systemui.biometrics import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.VERBOSE -import com.android.systemui.log.LogLevel.WARNING +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogLevel.ERROR +import com.android.systemui.log.core.LogLevel.VERBOSE +import com.android.systemui.log.core.LogLevel.WARNING import com.android.systemui.log.dagger.UdfpsLog import com.google.errorprone.annotations.CompileTimeConstant import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt new file mode 100644 index 000000000000..2a9f3eafc776 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.biometrics.ui.controller + +import com.android.systemui.biometrics.UdfpsAnimationViewController +import com.android.systemui.biometrics.UdfpsKeyguardView +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.shade.ShadeExpansionStateManager +import com.android.systemui.statusbar.phone.SystemUIDialogManager +import kotlinx.coroutines.ExperimentalCoroutinesApi + +/** Class that coordinates non-HBM animations during keyguard authentication. */ +@ExperimentalCoroutinesApi +open class UdfpsKeyguardViewController( + val view: UdfpsKeyguardView, + statusBarStateController: StatusBarStateController, + shadeExpansionStateManager: ShadeExpansionStateManager, + systemUIDialogManager: SystemUIDialogManager, + dumpManager: DumpManager, + private val alternateBouncerInteractor: AlternateBouncerInteractor, + udfpsKeyguardViewModels: UdfpsKeyguardViewModels, +) : + UdfpsAnimationViewController<UdfpsKeyguardView>( + view, + statusBarStateController, + shadeExpansionStateManager, + systemUIDialogManager, + dumpManager, + ), + UdfpsKeyguardViewControllerAdapter { + override val tag: String + get() = TAG + + init { + udfpsKeyguardViewModels.bindViews(view) + } + + public override fun onViewAttached() { + super.onViewAttached() + alternateBouncerInteractor.setAlternateBouncerUIAvailable(true) + } + + public override fun onViewDetached() { + super.onViewDetached() + alternateBouncerInteractor.setAlternateBouncerUIAvailable(false) + } + + override fun shouldPauseAuth(): Boolean { + return !view.isVisible() + } + + override fun listenForTouchesOutsideView(): Boolean { + return true + } + + companion object { + private const val TAG = "UdfpsKeyguardViewController" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt index 96af42bfda22..2a457ebd62f7 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BluetoothLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.bluetooth import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.BluetoothLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt index 5b3a982ab5e2..068f32986cf1 100644 --- a/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt @@ -21,10 +21,10 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogMessage +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.core.LogMessage import com.android.systemui.log.dagger.BroadcastDispatcherLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index 76002d3f9693..40db63d609ba 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -49,6 +49,7 @@ import com.android.systemui.settings.dagger.MultiUserUtilsModule import com.android.systemui.shortcut.ShortcutKeyDispatcher import com.android.systemui.statusbar.notification.InstantAppNotifier import com.android.systemui.statusbar.phone.KeyguardLiftController +import com.android.systemui.statusbar.phone.LockscreenWallpaper import com.android.systemui.stylus.StylusUsiPowerStartable import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import com.android.systemui.theme.ThemeOverlayController @@ -301,4 +302,9 @@ abstract class SystemUICoreStartableModule { @IntoMap @ClassKey(KeyguardViewConfigurator::class) abstract fun bindKeyguardViewConfigurator(impl: KeyguardViewConfigurator): CoreStartable + + @Binds + @IntoMap + @ClassKey(LockscreenWallpaper::class) + abstract fun bindLockscreenWallpaper(impl: LockscreenWallpaper): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index f229ffe9d0dc..06769dc3f831 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -53,6 +53,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.FlagsModule; import com.android.systemui.keyboard.KeyboardModule; +import com.android.systemui.keyguard.ui.view.layout.LockscreenLayoutModule; import com.android.systemui.log.dagger.LogModule; import com.android.systemui.log.dagger.MonitorLog; import com.android.systemui.log.table.TableLogBuffer; @@ -177,6 +178,7 @@ import javax.inject.Named; GarbageMonitorModule.class, KeyboardModule.class, LetterboxModule.class, + LockscreenLayoutModule.class, LogModule.class, MediaProjectionModule.class, MotionToolModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt index 4069bc7d73d0..557168731b23 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/DebugRoundedCornerDelegate.kt @@ -77,16 +77,30 @@ class DebugRoundedCornerDelegate : RoundedCornerResDelegate { } fun applyNewDebugCorners( - topCorner: DebugRoundedCornerModel, - bottomCorner: DebugRoundedCornerModel, + topCorner: DebugRoundedCornerModel?, + bottomCorner: DebugRoundedCornerModel?, ) { - hasTop = true - topRoundedDrawable = topCorner.toPathDrawable(paint) - topRoundedSize = topCorner.size() + topCorner?.let { + hasTop = true + topRoundedDrawable = it.toPathDrawable(paint) + topRoundedSize = it.size() + } + ?: { + hasTop = false + topRoundedDrawable = null + topRoundedSize = Size(0, 0) + } - hasBottom = true - bottomRoundedDrawable = bottomCorner.toPathDrawable(paint) - bottomRoundedSize = bottomCorner.size() + bottomCorner?.let { + hasBottom = true + bottomRoundedDrawable = it.toPathDrawable(paint) + bottomRoundedSize = it.size() + } + ?: { + hasBottom = false + bottomRoundedDrawable = null + bottomRoundedSize = Size(0, 0) + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt b/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt new file mode 100644 index 000000000000..fa1d898de850 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/decor/ScreenDecorCommand.kt @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.decor + +import android.graphics.Color +import android.graphics.Path +import android.util.PathParser +import com.android.systemui.statusbar.commandline.ParseableCommand +import com.android.systemui.statusbar.commandline.Type +import com.android.systemui.statusbar.commandline.map +import java.io.PrintWriter + +/** Debug screen-decor command to be handled by the SystemUI command line interface */ +class ScreenDecorCommand( + private val callback: Callback, +) : ParseableCommand(SCREEN_DECOR_CMD_NAME) { + val debug: Boolean? by + param( + longName = "debug", + description = + "Enter or exits debug mode. Effectively makes the corners visible and allows " + + "for overriding the path data for the anti-aliasing corner paths and display " + + "cutout.", + valueParser = Type.Boolean, + ) + + val color: Int? by + param( + longName = "color", + shortName = "c", + description = + "Set a specific color for the debug assets. See Color#parseString() for " + + "accepted inputs.", + valueParser = Type.String.map { it.toColorIntOrNull() } + ) + + val roundedTop: RoundedCornerSubCommand? by subCommand(RoundedCornerSubCommand("rounded-top")) + + val roundedBottom: RoundedCornerSubCommand? by + subCommand(RoundedCornerSubCommand("rounded-bottom")) + + override fun execute(pw: PrintWriter) { + callback.onExecute(this, pw) + } + + override fun toString(): String { + return "ScreenDecorCommand(" + + "debug=$debug, " + + "color=$color, " + + "roundedTop=$roundedTop, " + + "roundedBottom=$roundedBottom)" + } + + /** For use in ScreenDecorations.java, define a Callback */ + interface Callback { + fun onExecute(cmd: ScreenDecorCommand, pw: PrintWriter) + } + + companion object { + const val SCREEN_DECOR_CMD_NAME = "screen-decor" + } +} + +/** + * Defines a subcommand suitable for `rounded-top` and `rounded-bottom`. They both have the same + * API. + */ +class RoundedCornerSubCommand(name: String) : ParseableCommand(name) { + val height by + param( + longName = "height", + description = "The height of a corner, in pixels.", + valueParser = Type.Int, + ) + .required() + + val width by + param( + longName = "width", + description = + "The width of the corner, in pixels. Likely should be equal to the height.", + valueParser = Type.Int, + ) + .required() + + val pathData by + param( + longName = "path-data", + shortName = "d", + description = + "PathParser-compatible path string to be rendered as the corner drawable. " + + "This path should be a closed arc oriented as the top-left corner " + + "of the device", + valueParser = Type.String.map { it.toPathOrNull() } + ) + .required() + + val viewportHeight: Float? by + param( + longName = "viewport-height", + description = + "The height of the viewport for the given path string. " + + "If null, the corner height will be used.", + valueParser = Type.Float, + ) + + val scaleY: Float + get() = viewportHeight?.let { height.toFloat() / it } ?: 1.0f + + val viewportWidth: Float? by + param( + longName = "viewport-width", + description = + "The width of the viewport for the given path string. " + + "If null, the corner width will be used.", + valueParser = Type.Float, + ) + + val scaleX: Float + get() = viewportWidth?.let { width.toFloat() / it } ?: 1.0f + + override fun execute(pw: PrintWriter) { + // Not needed for a subcommand + } + + override fun toString(): String { + return "RoundedCornerSubCommand(" + + "height=$height," + + " width=$width," + + " pathData='$pathData'," + + " viewportHeight=$viewportHeight," + + " viewportWidth=$viewportWidth)" + } + + fun toRoundedCornerDebugModel(): DebugRoundedCornerModel = + DebugRoundedCornerModel( + path = pathData, + width = width, + height = height, + scaleX = scaleX, + scaleY = scaleY, + ) +} + +fun String.toPathOrNull(): Path? = + try { + PathParser.createPathFromPathData(this) + } catch (e: Exception) { + null + } + +fun String.toColorIntOrNull(): Int? = + try { + Color.parseColor(this) + } catch (e: Exception) { + null + } diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt index c962e5155697..bcfeeb9eaadd 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt @@ -23,7 +23,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.DisplayMetricsRepoLog import com.android.systemui.statusbar.policy.ConfigurationController import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt index 536978009f71..75b8e513c14a 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt @@ -20,9 +20,9 @@ import android.view.Display import com.android.systemui.doze.DozeLog.Reason import com.android.systemui.doze.DozeLog.reasonToString import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.ERROR +import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.log.dagger.DozeLog import com.android.systemui.statusbar.policy.DevicePostureController import com.google.errorprone.annotations.CompileTimeConstant diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt index fdb765130157..0e224060a36f 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamLogger.kt @@ -17,7 +17,7 @@ package com.android.systemui.dreams import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.DreamLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 4aa9080cb7f5..68211eede476 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -91,6 +91,15 @@ object Flags { val NOTIFICATION_SHELF_REFACTOR = unreleasedFlag(271161129, "notification_shelf_refactor", teamfood = true) + // TODO(b/288326013): Tracking Bug + @JvmField + val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION = + unreleasedFlag( + 288326013, + "notification_async_hybrid_view_inflation", + teamfood = false + ) + @JvmField val ANIMATED_NOTIFICATION_SHADE_INSETS = releasedFlag(270682168, "animated_notification_shade_insets") @@ -251,11 +260,6 @@ object Flags { @JvmField val MIGRATE_INDICATION_AREA = unreleasedFlag(236, "migrate_indication_area", teamfood = true) - /** Migrate the lock icon view to the new keyguard root view. */ - // TODO(b/286552209): Tracking bug. - @JvmField - val MIGRATE_LOCK_ICON = unreleasedFlag(238, "migrate_lock_icon") - /** Whether to listen for fingerprint authentication over keyguard occluding activities. */ // TODO(b/283260512): Tracking bug. @JvmField @@ -270,6 +274,11 @@ object Flags { @JvmField val TRANSIT_CLOCK = unreleasedFlag(239, "lockscreen_custom_transit_clock") + /** Migrate the lock icon view to the new keyguard root view. */ + // TODO(b/286552209): Tracking bug. + @JvmField + val MIGRATE_LOCK_ICON = unreleasedFlag(240, "migrate_lock_icon") + // 300 - power menu // TODO(b/254512600): Tracking Bug @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite") @@ -308,7 +317,7 @@ object Flags { ) @JvmField - val QS_PIPELINE_NEW_HOST = releasedFlag(504, "qs_pipeline_new_host") + val QS_PIPELINE_NEW_HOST = unreleasedFlag(504, "qs_pipeline_new_host", teamfood = true) // TODO(b/278068252): Tracking Bug @JvmField @@ -669,13 +678,11 @@ object Flags { // 2300 - stylus @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag(2300, "track_stylus_ever_used") @JvmField - val ENABLE_STYLUS_CHARGING_UI = - unreleasedFlag(2301, "enable_stylus_charging_ui", teamfood = true) + val ENABLE_STYLUS_CHARGING_UI = releasedFlag(2301, "enable_stylus_charging_ui") @JvmField val ENABLE_USI_BATTERY_NOTIFICATIONS = - unreleasedFlag(2302, "enable_usi_battery_notifications", teamfood = true) - @JvmField val ENABLE_STYLUS_EDUCATION = - unreleasedFlag(2303, "enable_stylus_education", teamfood = true) + releasedFlag(2302, "enable_usi_battery_notifications") + @JvmField val ENABLE_STYLUS_EDUCATION = releasedFlag(2303, "enable_stylus_education") // 2400 - performance tools and debugging info // TODO(b/238923086): Tracking Bug diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index e8881a482765..f59ad90d86ff 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -26,6 +26,8 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManager +import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManagerCommandListener import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel import com.android.systemui.shade.NotificationShadeWindowView import com.android.systemui.statusbar.KeyguardIndicationController @@ -42,6 +44,8 @@ constructor( private val notificationShadeWindowView: NotificationShadeWindowView, private val featureFlags: FeatureFlags, private val indicationController: KeyguardIndicationController, + private val keyguardLayoutManager: KeyguardLayoutManager, + private val keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener, ) : CoreStartable { private var indicationAreaHandle: DisposableHandle? = null @@ -51,6 +55,8 @@ constructor( notificationShadeWindowView.requireViewById(R.id.notification_panel) as ViewGroup bindIndicationArea(notificationPanel) bindLockIconView(notificationPanel) + keyguardLayoutManager.layoutViews() + keyguardLayoutManagerCommandListener.start() } fun bindIndicationArea(legacyParent: ViewGroup) { @@ -59,7 +65,7 @@ constructor( // At startup, 2 views with the ID `R.id.keyguard_indication_area` will be available. // Disable one of them if (featureFlags.isEnabled(Flags.MIGRATE_INDICATION_AREA)) { - legacyParent.requireViewById<View>(R.id.keyguard_indication_area).let { + legacyParent.findViewById<View>(R.id.keyguard_indication_area)?.let { legacyParent.removeView(it) } } else { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 341c34e38db9..9a32e94180c7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -140,6 +140,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.flags.SystemPropertiesHelper; import com.android.systemui.keyguard.dagger.KeyguardModule; +import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; @@ -545,6 +546,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private CentralSurfaces mCentralSurfaces; + private IRemoteAnimationFinishedCallback mUnoccludeFromDreamFinishedCallback; + private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = new DeviceConfig.OnPropertiesChangedListener() { @Override @@ -582,17 +585,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onUserSwitching(int userId) { if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId)); - // Note that the mLockPatternUtils user has already been updated from setCurrentUser. - // We need to force a reset of the views, since lockNow (called by - // ActivityManagerService) will not reconstruct the keyguard if it is already showing. synchronized (KeyguardViewMediator.this) { resetKeyguardDonePendingLocked(); - if (mLockPatternUtils.isLockScreenDisabled(userId)) { - // If we are switching to a user that has keyguard disabled, dismiss keyguard. - dismiss(null /* callback */, null /* message */); - } else { - resetStateLocked(); - } + dismiss(null /* callback */, null /* message */); adjustStatusBarLocked(); } } @@ -600,16 +595,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onUserSwitchComplete(int userId) { if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId)); - if (userId != UserHandle.USER_SYSTEM) { - UserInfo info = UserManager.get(mContext).getUserInfo(userId); - // Don't try to dismiss if the user has Pin/Pattern/Password set - if (info == null || mLockPatternUtils.isSecure(userId)) { - return; - } else if (info.isGuest() || info.isDemo()) { - // If we just switched to a guest, try to dismiss keyguard. - dismiss(null /* callback */, null /* message */); - } - } + // We are calling dismiss again and with a delay as there are race conditions + // in some scenarios caused by async layout listeners + mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500); } @Override @@ -649,6 +637,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, switch (simState) { case TelephonyManager.SIM_STATE_NOT_READY: case TelephonyManager.SIM_STATE_ABSENT: + case TelephonyManager.SIM_STATE_UNKNOWN: + mPendingPinLock = false; // only force lock screen in case of missing sim if user hasn't // gone through setup wizard synchronized (KeyguardViewMediator.this) { @@ -713,9 +703,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } } break; - case TelephonyManager.SIM_STATE_UNKNOWN: - mPendingPinLock = false; - break; default: if (DEBUG_SIM_STATES) Log.v(TAG, "Unspecific state: " + simState); break; @@ -1177,6 +1164,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, getRemoteSurfaceAlphaApplier().accept(0.0f); mDreamingToLockscreenTransitionViewModel.get() .startTransition(); + mUnoccludeFromDreamFinishedCallback = finishedCallback; return; } @@ -1256,6 +1244,19 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, }; } + private Consumer<TransitionStep> getFinishedCallbackConsumer() { + return (TransitionStep step) -> { + if (mUnoccludeFromDreamFinishedCallback == null) return; + try { + mUnoccludeFromDreamFinishedCallback.onAnimationFinished(); + mUnoccludeFromDreamFinishedCallback = null; + } catch (RemoteException e) { + Log.e(TAG, "Wasn't able to callback", e); + } + mInteractionJankMonitor.end(CUJ_LOCKSCREEN_OCCLUSION); + }; + } + private DeviceConfigProxy mDeviceConfig; private DozeParameters mDozeParameters; @@ -1524,6 +1525,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, collectFlow(viewRootImpl.getView(), mDreamingToLockscreenTransitionViewModel.get().getDreamOverlayAlpha(), getRemoteSurfaceAlphaApplier(), mMainDispatcher); + collectFlow(viewRootImpl.getView(), + mDreamingToLockscreenTransitionViewModel.get().getTransitionEnded(), + getFinishedCallbackConsumer(), mMainDispatcher); } } // Most services aren't available until the system reaches the ready state, so we @@ -2391,58 +2395,72 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) { @Override public void handleMessage(Message msg) { + String message = ""; switch (msg.what) { case SHOW: + message = "SHOW"; handleShow((Bundle) msg.obj); break; case HIDE: + message = "HIDE"; handleHide(); break; case RESET: + message = "RESET"; handleReset(msg.arg1 != 0); break; case VERIFY_UNLOCK: + message = "VERIFY_UNLOCK"; Trace.beginSection("KeyguardViewMediator#handleMessage VERIFY_UNLOCK"); handleVerifyUnlock(); Trace.endSection(); break; case NOTIFY_STARTED_GOING_TO_SLEEP: + message = "NOTIFY_STARTED_GOING_TO_SLEEP"; handleNotifyStartedGoingToSleep(); break; case NOTIFY_FINISHED_GOING_TO_SLEEP: + message = "NOTIFY_FINISHED_GOING_TO_SLEEP"; handleNotifyFinishedGoingToSleep(); break; case NOTIFY_STARTED_WAKING_UP: + message = "NOTIFY_STARTED_WAKING_UP"; Trace.beginSection( "KeyguardViewMediator#handleMessage NOTIFY_STARTED_WAKING_UP"); handleNotifyStartedWakingUp(); Trace.endSection(); break; case KEYGUARD_DONE: + message = "KEYGUARD_DONE"; Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE"); handleKeyguardDone(); Trace.endSection(); break; case KEYGUARD_DONE_DRAWING: + message = "KEYGUARD_DONE_DRAWING"; Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE_DRAWING"); handleKeyguardDoneDrawing(); Trace.endSection(); break; case SET_OCCLUDED: + message = "SET_OCCLUDED"; Trace.beginSection("KeyguardViewMediator#handleMessage SET_OCCLUDED"); handleSetOccluded(msg.arg1 != 0, msg.arg2 != 0); Trace.endSection(); break; case KEYGUARD_TIMEOUT: + message = "KEYGUARD_TIMEOUT"; synchronized (KeyguardViewMediator.this) { doKeyguardLocked((Bundle) msg.obj); } break; case DISMISS: - final DismissMessage message = (DismissMessage) msg.obj; - handleDismiss(message.getCallback(), message.getMessage()); + message = "DISMISS"; + final DismissMessage dismissMsg = (DismissMessage) msg.obj; + handleDismiss(dismissMsg.getCallback(), dismissMsg.getMessage()); break; case START_KEYGUARD_EXIT_ANIM: + message = "START_KEYGUARD_EXIT_ANIM"; Trace.beginSection( "KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM"); synchronized (KeyguardViewMediator.this) { @@ -2460,21 +2478,25 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Trace.endSection(); break; case CANCEL_KEYGUARD_EXIT_ANIM: + message = "CANCEL_KEYGUARD_EXIT_ANIM"; Trace.beginSection( "KeyguardViewMediator#handleMessage CANCEL_KEYGUARD_EXIT_ANIM"); handleCancelKeyguardExitAnimation(); Trace.endSection(); break; case KEYGUARD_DONE_PENDING_TIMEOUT: + message = "KEYGUARD_DONE_PENDING_TIMEOUT"; Trace.beginSection("KeyguardViewMediator#handleMessage" + " KEYGUARD_DONE_PENDING_TIMEOUT"); Log.w(TAG, "Timeout while waiting for activity drawn!"); Trace.endSection(); break; case SYSTEM_READY: + message = "SYSTEM_READY"; handleSystemReady(); break; } + Log.d(TAG, "KeyguardViewMediator queue processing message: " + message); } }; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt index 128057ae6b62..3d8f6fd40e6b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt @@ -254,13 +254,17 @@ constructor( private fun observeFaceDetectGatingChecks() { // Face detection can run only when lockscreen bypass is enabled - // & detection is supported & biometric unlock is not allowed. + // & detection is supported + // & biometric unlock is not allowed + // or user is trusted by trust manager & we want to run face detect to dismiss keyguard listOf( canFaceAuthOrDetectRun(faceDetectLog), logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog), logAndObserve( - biometricSettingsRepository.isNonStrongBiometricAllowed.isFalse(), - "nonStrongBiometricIsNotAllowed", + biometricSettingsRepository.isNonStrongBiometricAllowed + .isFalse() + .or(trustRepository.isCurrentUserTrusted), + "nonStrongBiometricIsNotAllowedOrCurrentUserIsTrusted", faceDetectLog ), // We don't want to run face detect if fingerprint can be used to unlock the device @@ -312,18 +316,19 @@ constructor( tableLogBuffer ), logAndObserve( - keyguardRepository.wakefulness.map { it.isStartingToSleepOrAsleep() }.isFalse(), - "deviceNotSleepingOrNotStartingToSleep", + keyguardRepository.wakefulness.map { it.isStartingToSleep() }.isFalse(), + "deviceNotStartingToSleep", tableLogBuffer ), logAndObserve( - combine( - keyguardInteractor.isSecureCameraActive, - alternateBouncerInteractor.isVisible - ) { a, b -> - !a || b - }, - "secureCameraNotActiveOrAltBouncerIsShowing", + keyguardInteractor.isSecureCameraActive + .isFalse() + .or( + alternateBouncerInteractor.isVisible.or( + keyguardInteractor.primaryBouncerShowing + ) + ), + "secureCameraNotActiveOrAnyBouncerIsShowing", tableLogBuffer ), logAndObserve( @@ -365,6 +370,7 @@ constructor( "nonStrongBiometricIsAllowed", faceAuthLog ), + logAndObserve(isAuthenticated.isFalse(), "faceNotAuthenticated", faceAuthLog), ) .reduce(::and) .distinctUntilChanged() @@ -639,6 +645,10 @@ constructor( private fun and(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) = flow.combine(anotherFlow) { a, b -> a && b } +/** Combine two boolean flows by or-ing both of them */ +private fun Flow<Boolean>.or(anotherFlow: Flow<Boolean>) = + this.combine(anotherFlow) { a, b -> a || b } + /** "Not" the given flow. The return [Flow] will be true when [this] flow is false. */ private fun Flow<Boolean>.isFalse(): Flow<Boolean> { return this.map { !it } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index b7963340228b..ed1bf3ef2753 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -167,9 +167,10 @@ constructor( from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.LOCKSCREEN, animator = - getDefaultAnimatorForTransitionsToState(KeyguardState.LOCKSCREEN).apply { - duration = 0 - } + getDefaultAnimatorForTransitionsToState( + KeyguardState.LOCKSCREEN + ) + .apply { duration = 0 } ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt index a8147d0c2cf6..ff0db34ca06d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt @@ -156,7 +156,12 @@ constructor( override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { return ValueAnimator().apply { - interpolator = Interpolators.LINEAR + interpolator = + when (toState) { + KeyguardState.ALTERNATE_BOUNCER -> Interpolators.FAST_OUT_SLOW_IN + else -> Interpolators.LINEAR + } + duration = when (toState) { KeyguardState.LOCKSCREEN -> TO_LOCKSCREEN_DURATION diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt index 7c5641fcda9c..4f7abd4a2174 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt @@ -19,7 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.keyguard.logging.KeyguardLogger import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.log.LogLevel.VERBOSE +import com.android.systemui.log.core.LogLevel.VERBOSE import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index df7c79ff4264..45bf20d3ec44 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER import com.android.systemui.keyguard.shared.model.KeyguardState.AOD import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.GONE @@ -51,11 +52,35 @@ constructor( /** (any)->GONE transition information */ val anyStateToGoneTransition: Flow<TransitionStep> = - repository.transitions.filter { step -> step.to == KeyguardState.GONE } + repository.transitions.filter { step -> step.to == GONE } /** (any)->AOD transition information */ val anyStateToAodTransition: Flow<TransitionStep> = - repository.transitions.filter { step -> step.to == KeyguardState.AOD } + repository.transitions.filter { step -> step.to == AOD } + + /** DREAMING->(any) transition information. */ + val fromDreamingTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.from == DREAMING } + + /** (any)->Lockscreen transition information */ + val anyStateToLockscreenTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.to == LOCKSCREEN } + + /** (any)->Occluded transition information */ + val anyStateToOccludedTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.to == OCCLUDED } + + /** (any)->PrimaryBouncer transition information */ + val anyStateToPrimaryBouncerTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.to == PRIMARY_BOUNCER } + + /** (any)->Dreaming transition information */ + val anyStateToDreamingTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.to == DREAMING } + + /** (any)->AlternateBouncer transition information */ + val anyStateToAlternateBouncerTransition: Flow<TransitionStep> = + repository.transitions.filter { step -> step.to == ALTERNATE_BOUNCER } /** AOD->LOCKSCREEN transition information. */ val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN) @@ -64,6 +89,9 @@ constructor( val dreamingToLockscreenTransition: Flow<TransitionStep> = repository.transition(DREAMING, LOCKSCREEN) + /** GONE->AOD transition information. */ + val goneToAodTransition: Flow<TransitionStep> = repository.transition(GONE, AOD) + /** GONE->DREAMING transition information. */ val goneToDreamingTransition: Flow<TransitionStep> = repository.transition(GONE, DREAMING) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt index 8b749f0f4bc4..d467225a9d63 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt @@ -16,9 +16,12 @@ package com.android.systemui.keyguard.domain.interactor +import android.content.Context +import android.hardware.biometrics.BiometricFaceConstants import com.android.keyguard.FaceAuthUiEvent import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.CoreStartable +import com.android.systemui.R import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dagger.SysUISingleton @@ -27,6 +30,8 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.shared.model.AuthenticationStatus +import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.log.FaceAuthenticationLogger import com.android.systemui.util.kotlin.pairwise @@ -34,7 +39,9 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -50,6 +57,7 @@ import kotlinx.coroutines.launch class SystemUIKeyguardFaceAuthInteractor @Inject constructor( + private val context: Context, @Application private val applicationScope: CoroutineScope, @Main private val mainDispatcher: CoroutineDispatcher, private val repository: DeviceEntryFaceAuthRepository, @@ -157,17 +165,28 @@ constructor( repository.cancel() } + private val _authenticationStatusOverride = MutableStateFlow<AuthenticationStatus?>(null) /** Provide the status of face authentication */ - override val authenticationStatus = repository.authenticationStatus + override val authenticationStatus = + merge(_authenticationStatusOverride.filterNotNull(), repository.authenticationStatus) /** Provide the status of face detection */ override val detectionStatus = repository.detectionStatus private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) { if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) { - applicationScope.launch { - faceAuthenticationLogger.authRequested(uiEvent) - repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect) + if (repository.isLockedOut.value) { + _authenticationStatusOverride.value = + ErrorAuthenticationStatus( + BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT, + context.resources.getString(R.string.keyguard_face_unlock_unavailable) + ) + } else { + _authenticationStatusOverride.value = null + applicationScope.launch { + faceAuthenticationLogger.authRequested(uiEvent) + repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect) + } } } else { faceAuthenticationLogger.ignoredFaceAuthTrigger( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt index ae6fc9e6e6dc..0dda625a8b41 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt @@ -44,9 +44,9 @@ sealed class TransitionInteractor( abstract fun start() fun startTransitionTo( - toState: KeyguardState, - animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState), - resetIfCancelled: Boolean = false + toState: KeyguardState, + animator: ValueAnimator? = getDefaultAnimatorForTransitionsToState(toState), + resetIfCancelled: Boolean = false ): UUID? { if ( fromState != transitionInteractor.startedKeyguardState.value && diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt new file mode 100644 index 000000000000..bba0e37d8ed0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractor.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.domain.interactor + +import android.animation.FloatEvaluator +import android.animation.IntEvaluator +import com.android.systemui.common.ui.data.repository.ConfigurationRepository +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine + +/** Encapsulates business logic for transitions between UDFPS states on the keyguard. */ +@ExperimentalCoroutinesApi +@SysUISingleton +class UdfpsKeyguardInteractor +@Inject +constructor( + configRepo: ConfigurationRepository, + burnInInteractor: BurnInInteractor, + keyguardInteractor: KeyguardInteractor, +) { + private val intEvaluator = IntEvaluator() + private val floatEvaluator = FloatEvaluator() + + val dozeAmount = keyguardInteractor.dozeAmount + val scaleForResolution = configRepo.scaleForResolution + + /** Burn-in offsets for the UDFPS view to mitigate burn-in on AOD. */ + val burnInOffsets: Flow<BurnInOffsets> = + combine( + keyguardInteractor.dozeAmount, + burnInInteractor.udfpsBurnInXOffset, + burnInInteractor.udfpsBurnInYOffset, + burnInInteractor.udfpsBurnInProgress + ) { dozeAmount, fullyDozingBurnInX, fullyDozingBurnInY, fullyDozingBurnInProgress -> + BurnInOffsets( + intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInX), + intEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInY), + floatEvaluator.evaluate(dozeAmount, 0, fullyDozingBurnInProgress), + ) + } +} + +data class BurnInOffsets( + val burnInXOffset: Int, // current x burn in offset based on the aodTransitionAmount + val burnInYOffset: Int, // current y burn in offset based on the aodTransitionAmount + val burnInProgress: Float, // current progress based on the aodTransitionAmount +) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt index 9e7dec4dc1d0..b354cfd27687 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FaceAuthenticationModels.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.shared.model import android.hardware.face.FaceManager +import android.os.SystemClock.elapsedRealtime /** * Authentication status provided by @@ -38,8 +39,12 @@ data class AcquiredAuthenticationStatus(val acquiredInfo: Int) : AuthenticationS object FailedAuthenticationStatus : AuthenticationStatus() /** Face authentication error message */ -data class ErrorAuthenticationStatus(val msgId: Int, val msg: String? = null) : - AuthenticationStatus() { +data class ErrorAuthenticationStatus( + val msgId: Int, + val msg: String? = null, + // present to break equality check if the same error occurs repeatedly. + val createdAt: Long = elapsedRealtime() +) : AuthenticationStatus() { /** * Method that checks if [msgId] is a lockout error. A lockout error means that face * authentication is locked out. diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt new file mode 100644 index 000000000000..ebf1beb132f0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/adapter/UdfpsKeyguardViewControllerAdapter.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.adapter + +/** + * Temporary adapter class while + * [com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController] is being refactored + * before [com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy] is removed. + * + * TODO (b/278719514): Delete once udfps keyguard view is fully refactored. + */ +interface UdfpsKeyguardViewControllerAdapter diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt new file mode 100644 index 000000000000..728dd3911663 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsAodFingerprintViewBinder.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.binder + +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.airbnb.lottie.LottieAnimationView +import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch + +@ExperimentalCoroutinesApi +object UdfpsAodFingerprintViewBinder { + + /** + * Drives UI for the UDFPS aod fingerprint view. See [UdfpsFingerprintViewBinder] and + * [UdfpsBackgroundViewBinder]. + */ + @JvmStatic + fun bind( + view: LottieAnimationView, + viewModel: UdfpsAodViewModel, + ) { + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.burnInOffsets.collect { burnInOffsets -> + view.progress = burnInOffsets.burnInProgress + view.translationX = burnInOffsets.burnInXOffset.toFloat() + view.translationY = burnInOffsets.burnInYOffset.toFloat() + } + } + + launch { viewModel.alpha.collect { alpha -> view.alpha = alpha } } + + launch { + viewModel.padding.collect { padding -> + view.setPadding(padding, padding, padding, padding) + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt new file mode 100644 index 000000000000..26ef4685d286 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsBackgroundViewBinder.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.binder + +import android.content.res.ColorStateList +import android.widget.ImageView +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch + +@ExperimentalCoroutinesApi +object UdfpsBackgroundViewBinder { + + /** + * Drives UI for the udfps background view. See [UdfpsAodFingerprintViewBinder] and + * [UdfpsFingerprintViewBinder]. + */ + @JvmStatic + fun bind( + view: ImageView, + viewModel: BackgroundViewModel, + ) { + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.transition.collect { + view.alpha = it.alpha + view.scaleX = it.scale + view.scaleY = it.scale + view.imageTintList = ColorStateList.valueOf(it.color) + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt new file mode 100644 index 000000000000..0ab8e52fb6c7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsFingerprintViewBinder.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.binder + +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.airbnb.lottie.LottieAnimationView +import com.airbnb.lottie.LottieProperty +import com.airbnb.lottie.model.KeyPath +import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch + +@ExperimentalCoroutinesApi +object UdfpsFingerprintViewBinder { + private var udfpsIconColor = 0 + + /** + * Drives UI for the UDFPS fingerprint view when it's NOT on aod. See + * [UdfpsAodFingerprintViewBinder] and [UdfpsBackgroundViewBinder]. + */ + @JvmStatic + fun bind( + view: LottieAnimationView, + viewModel: FingerprintViewModel, + ) { + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.transition.collect { + view.alpha = it.alpha + view.scaleX = it.scale + view.scaleY = it.scale + if (udfpsIconColor != (it.color)) { + udfpsIconColor = it.color + view.invalidate() + } + } + } + + launch { + viewModel.burnInOffsets.collect { burnInOffsets -> + view.translationX = burnInOffsets.burnInXOffset.toFloat() + view.translationY = burnInOffsets.burnInYOffset.toFloat() + } + } + + launch { + viewModel.dozeAmount.collect { dozeAmount -> + // Lottie progress represents: aod=0 to lockscreen=1 + view.progress = 1f - dozeAmount + } + } + + launch { + viewModel.padding.collect { padding -> + view.setPadding(padding, padding, padding, padding) + } + } + } + } + + // Add a callback that updates the color to `udfpsIconColor` whenever invalidate is called + view.addValueCallback(KeyPath("**"), LottieProperty.COLOR_FILTER) { + PorterDuffColorFilter(udfpsIconColor, PorterDuff.Mode.SRC_ATOP) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt new file mode 100644 index 000000000000..b568a9af9bb8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.biometrics.ui.binder + +import android.view.View +import com.android.systemui.R +import com.android.systemui.keyguard.ui.binder.UdfpsAodFingerprintViewBinder +import com.android.systemui.keyguard.ui.binder.UdfpsBackgroundViewBinder +import com.android.systemui.keyguard.ui.binder.UdfpsFingerprintViewBinder +import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel +import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel +import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +object UdfpsKeyguardInternalViewBinder { + + @JvmStatic + fun bind( + view: View, + viewModel: UdfpsKeyguardInternalViewModel, + aodViewModel: UdfpsAodViewModel, + fingerprintViewModel: FingerprintViewModel, + backgroundViewModel: BackgroundViewModel, + ) { + view.accessibilityDelegate = viewModel.accessibilityDelegate + + // bind child views + UdfpsAodFingerprintViewBinder.bind(view.findViewById(R.id.udfps_aod_fp), aodViewModel) + UdfpsFingerprintViewBinder.bind( + view.findViewById(R.id.udfps_lockscreen_fp), + fingerprintViewModel + ) + UdfpsBackgroundViewBinder.bind( + view.findViewById(R.id.udfps_keyguard_fp_bg), + backgroundViewModel + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt new file mode 100644 index 000000000000..667abaea0b24 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardViewBinder.kt @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.binder + +import android.graphics.RectF +import android.view.View +import android.widget.FrameLayout +import androidx.asynclayoutinflater.view.AsyncLayoutInflater +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.R +import com.android.systemui.biometrics.UdfpsKeyguardView +import com.android.systemui.biometrics.ui.binder.UdfpsKeyguardInternalViewBinder +import com.android.systemui.keyguard.ui.viewmodel.BackgroundViewModel +import com.android.systemui.keyguard.ui.viewmodel.FingerprintViewModel +import com.android.systemui.keyguard.ui.viewmodel.UdfpsAodViewModel +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardInternalViewModel +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.launch + +@ExperimentalCoroutinesApi +object UdfpsKeyguardViewBinder { + /** + * Drives UI for the keyguard UDFPS view. Inflates child views on a background thread. For view + * binders for its child views, see [UdfpsFingerprintViewBinder], [UdfpsBackgroundViewBinder] & + * [UdfpsAodFingerprintViewBinder]. + */ + @JvmStatic + fun bind( + view: UdfpsKeyguardView, + viewModel: UdfpsKeyguardViewModel, + udfpsKeyguardInternalViewModel: UdfpsKeyguardInternalViewModel, + aodViewModel: UdfpsAodViewModel, + fingerprintViewModel: FingerprintViewModel, + backgroundViewModel: BackgroundViewModel, + ) { + view.useExpandedOverlay(viewModel.useExpandedOverlay()) + + val layoutInflaterFinishListener = + AsyncLayoutInflater.OnInflateFinishedListener { inflatedInternalView, _, parent -> + UdfpsKeyguardInternalViewBinder.bind( + inflatedInternalView, + udfpsKeyguardInternalViewModel, + aodViewModel, + fingerprintViewModel, + backgroundViewModel, + ) + if (viewModel.useExpandedOverlay()) { + val lp = inflatedInternalView.layoutParams as FrameLayout.LayoutParams + lp.width = viewModel.sensorBounds.width() + lp.height = viewModel.sensorBounds.height() + val relativeToView = + getBoundsRelativeToView( + inflatedInternalView, + RectF(viewModel.sensorBounds), + ) + lp.setMarginsRelative( + relativeToView.left.toInt(), + relativeToView.top.toInt(), + relativeToView.right.toInt(), + relativeToView.bottom.toInt(), + ) + parent!!.addView(inflatedInternalView, lp) + } else { + parent!!.addView(inflatedInternalView) + } + } + val inflater = AsyncLayoutInflater(view.context) + inflater.inflate(R.layout.udfps_keyguard_view_internal, view, layoutInflaterFinishListener) + + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.CREATED) { + launch { + combine(aodViewModel.isVisible, fingerprintViewModel.visible) { + isAodVisible, + isFingerprintVisible -> + isAodVisible || isFingerprintVisible + } + .collect { view.setVisible(it) } + } + } + } + } + + /** + * Converts coordinates of RectF relative to the screen to coordinates relative to this view. + * + * @param bounds RectF based off screen coordinates in current orientation + */ + private fun getBoundsRelativeToView(view: View, bounds: RectF): RectF { + val pos: IntArray = view.locationOnScreen + return RectF( + bounds.left - pos[0], + bounds.top - pos[1], + bounds.right - pos[0], + bounds.bottom - pos[1] + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt index a62f383ed704..0077f2d68c7e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt @@ -19,10 +19,7 @@ package com.android.systemui.keyguard.ui.view import android.content.Context import android.util.AttributeSet -import android.view.Gravity -import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.view.ViewGroup.LayoutParams.WRAP_CONTENT -import android.widget.FrameLayout +import androidx.constraintlayout.widget.ConstraintLayout import com.android.keyguard.LockIconView import com.android.systemui.R @@ -31,7 +28,7 @@ class KeyguardRootView( context: Context, private val attrs: AttributeSet?, ) : - FrameLayout( + ConstraintLayout( context, attrs, ) { @@ -43,31 +40,11 @@ class KeyguardRootView( private fun addIndicationTextArea() { val view = KeyguardIndicationArea(context, attrs) - addView( - view, - FrameLayout.LayoutParams( - MATCH_PARENT, - WRAP_CONTENT, - ) - .apply { - gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL - bottomMargin = R.dimen.keyguard_indication_margin_bottom.dp() - } - ) + addView(view) } private fun addLockIconView() { val view = LockIconView(context, attrs).apply { id = R.id.lock_icon_view } - addView( - view, - LayoutParams( - WRAP_CONTENT, - WRAP_CONTENT, - ) - ) - } - - private fun Int.dp(): Int { - return context.resources.getDimensionPixelSize(this) + addView(view) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt new file mode 100644 index 000000000000..baaeb60f364e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayout.kt @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.view.layout + +import android.content.Context +import android.graphics.Point +import android.graphics.Rect +import android.util.DisplayMetrics +import android.view.View +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT +import android.view.WindowManager +import androidx.annotation.VisibleForTesting +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.ConstraintSet.BOTTOM +import androidx.constraintlayout.widget.ConstraintSet.END +import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID +import androidx.constraintlayout.widget.ConstraintSet.START +import androidx.constraintlayout.widget.ConstraintSet.TOP +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.R +import com.android.systemui.biometrics.AuthController +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.ui.view.KeyguardRootView +import javax.inject.Inject + +/** + * Positions elements of the lockscreen to the default position. + * + * This will be the most common use case for phones in portrait mode. + */ +@SysUISingleton +class DefaultLockscreenLayout +@Inject +constructor( + private val authController: AuthController, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val windowManager: WindowManager, + private val context: Context, +) : LockscreenLayout { + override val id: String = DEFAULT + + override fun layoutIndicationArea(rootView: KeyguardRootView) { + val indicationArea = rootView.findViewById<View>(R.id.keyguard_indication_area) ?: return + + rootView.getConstraintSet().apply { + constrainWidth(indicationArea.id, MATCH_PARENT) + constrainHeight(indicationArea.id, WRAP_CONTENT) + connect( + indicationArea.id, + BOTTOM, + PARENT_ID, + BOTTOM, + R.dimen.keyguard_indication_margin_bottom.dp() + ) + connect(indicationArea.id, START, PARENT_ID, START) + connect(indicationArea.id, END, PARENT_ID, END) + applyTo(rootView) + } + } + + override fun layoutLockIcon(rootView: KeyguardRootView) { + val isUdfpsSupported = keyguardUpdateMonitor.isUdfpsSupported + val scaleFactor: Float = authController.scaleFactor + val mBottomPaddingPx = R.dimen.lock_icon_margin_bottom.dp() + val mDefaultPaddingPx = R.dimen.lock_icon_padding.dp() + val scaledPadding: Int = (mDefaultPaddingPx * scaleFactor).toInt() + val bounds = windowManager.currentWindowMetrics.bounds + val widthPixels = bounds.right.toFloat() + val heightPixels = bounds.bottom.toFloat() + val defaultDensity = + DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() / + DisplayMetrics.DENSITY_DEFAULT.toFloat() + val lockIconRadiusPx = (defaultDensity * 36).toInt() + + if (isUdfpsSupported) { + authController.udfpsLocation?.let { udfpsLocation -> + centerLockIcon(udfpsLocation, authController.udfpsRadius, scaledPadding, rootView) + } + } else { + centerLockIcon( + Point( + (widthPixels / 2).toInt(), + (heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt() + ), + lockIconRadiusPx * scaleFactor, + scaledPadding, + rootView + ) + } + } + + @VisibleForTesting + internal fun centerLockIcon( + center: Point, + radius: Float, + drawablePadding: Int, + rootView: KeyguardRootView, + ) { + val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return + val lockIcon = lockIconView.findViewById<View>(R.id.lock_icon) ?: return + lockIcon.setPadding(drawablePadding, drawablePadding, drawablePadding, drawablePadding) + + val sensorRect = + Rect().apply { + set( + center.x - radius.toInt(), + center.y - radius.toInt(), + center.x + radius.toInt(), + center.y + radius.toInt(), + ) + } + + rootView.getConstraintSet().apply { + constrainWidth(lockIconView.id, sensorRect.right - sensorRect.left) + constrainHeight(lockIconView.id, sensorRect.bottom - sensorRect.top) + connect(lockIconView.id, TOP, PARENT_ID, TOP, sensorRect.top) + connect(lockIconView.id, START, PARENT_ID, START, sensorRect.left) + applyTo(rootView) + } + } + + private fun Int.dp(): Int { + return context.resources.getDimensionPixelSize(this) + } + + private fun ConstraintLayout.getConstraintSet(): ConstraintSet { + val cs = ConstraintSet() + cs.clone(this) + return cs + } + + companion object { + const val DEFAULT = "default" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt new file mode 100644 index 000000000000..9bc630265a3f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManager.kt @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.view.layout + +import android.content.res.Configuration +import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.keyguard.ui.view.layout.DefaultLockscreenLayout.Companion.DEFAULT +import com.android.systemui.statusbar.policy.ConfigurationController +import javax.inject.Inject + +/** + * Manages layout changes for the lockscreen. + * + * To add a layout, add an entry to the map with a unique id and call #transitionToLayout(string). + */ +@SysUISingleton +class KeyguardLayoutManager +@Inject +constructor( + configurationController: ConfigurationController, + layouts: Set<@JvmSuppressWildcards LockscreenLayout>, + private val keyguardRootView: KeyguardRootView, +) { + internal val layoutIdMap: Map<String, LockscreenLayout> = layouts.associateBy { it.id } + private var layout: LockscreenLayout? = layoutIdMap[DEFAULT] + + init { + configurationController.addCallback( + object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + layoutViews() + } + } + ) + } + + /** + * Transitions to a layout. + * + * @param layoutId + * @return whether the transition has succeeded. + */ + fun transitionToLayout(layoutId: String): Boolean { + layout = layoutIdMap[layoutId] ?: return false + layoutViews() + return true + } + + fun layoutViews() { + layout?.layoutViews(keyguardRootView) + } + + companion object { + const val TAG = "KeyguardLayoutManager" + } +} + +interface LockscreenLayout { + val id: String + + fun layoutViews(rootView: KeyguardRootView) { + // Clear constraints. + ConstraintSet() + .apply { + clone(rootView) + knownIds.forEach { getConstraint(it).layout.copyFrom(ConstraintSet.Layout()) } + } + .applyTo(rootView) + layoutIndicationArea(rootView) + layoutLockIcon(rootView) + } + fun layoutIndicationArea(rootView: KeyguardRootView) + fun layoutLockIcon(rootView: KeyguardRootView) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt new file mode 100644 index 000000000000..b351ea8923f6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListener.kt @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.view.layout + +import com.android.systemui.statusbar.commandline.Command +import com.android.systemui.statusbar.commandline.CommandRegistry +import java.io.PrintWriter +import javax.inject.Inject + +/** Uses $ adb shell cmd statusbar layout <LayoutId> */ +class KeyguardLayoutManagerCommandListener +@Inject +constructor( + private val commandRegistry: CommandRegistry, + private val keyguardLayoutManager: KeyguardLayoutManager +) { + private val layoutCommand = KeyguardLayoutManagerCommand() + + fun start() { + commandRegistry.registerCommand(COMMAND) { layoutCommand } + } + + internal inner class KeyguardLayoutManagerCommand : Command { + override fun execute(pw: PrintWriter, args: List<String>) { + val arg = args.getOrNull(0) + if (arg == null || arg.lowercase() == "help") { + help(pw) + return + } + + if (keyguardLayoutManager.transitionToLayout(arg)) { + pw.println("Transition succeeded!") + } else { + pw.println("Invalid argument! To see available layout ids, run:") + pw.println("$ adb shell cmd statusbar layout help") + } + } + + override fun help(pw: PrintWriter) { + pw.println("Usage: $ adb shell cmd statusbar layout <layoutId>") + pw.println("Existing Layout Ids: ") + keyguardLayoutManager.layoutIdMap.forEach { entry -> pw.println("${entry.key}") } + } + } + + companion object { + internal const val COMMAND = "layout" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt new file mode 100644 index 000000000000..00f93e33f370 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/LockscreenLayoutModule.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.view.layout + +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoSet + +@Module +abstract class LockscreenLayoutModule { + @Binds + @IntoSet + abstract fun bindDefaultLayout( + defaultLockscreenLayout: DefaultLockscreenLayout + ): LockscreenLayout +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt index 9ca4bd62b6fe..e24d326850e0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt @@ -48,7 +48,7 @@ constructor( ) val transitionEnded = - keyguardTransitionInteractor.dreamingToLockscreenTransition.filter { step -> + keyguardTransitionInteractor.fromDreamingTransition.filter { step -> step.transitionState == TransitionState.FINISHED || step.transitionState == TransitionState.CANCELED } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt new file mode 100644 index 000000000000..667c2f1bd998 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModel.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.viewmodel + +import android.content.Context +import com.android.systemui.R +import com.android.systemui.keyguard.domain.interactor.BurnInOffsets +import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor +import javax.inject.Inject +import kotlin.math.roundToInt +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** View-model for UDFPS AOD view. */ +@ExperimentalCoroutinesApi +class UdfpsAodViewModel +@Inject +constructor( + val interactor: UdfpsKeyguardInteractor, + val context: Context, +) { + val alpha: Flow<Float> = interactor.dozeAmount + val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets + val isVisible: Flow<Boolean> = alpha.map { it != 0f } + + // Padding between the fingerprint icon and its bounding box in pixels. + val padding: Flow<Int> = + interactor.scaleForResolution.map { scale -> + (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale) + .roundToInt() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt new file mode 100644 index 000000000000..d894a1139eeb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardInternalViewModel.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.viewmodel + +import com.android.systemui.biometrics.UdfpsKeyguardAccessibilityDelegate +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +class UdfpsKeyguardInternalViewModel +@Inject +constructor(val accessibilityDelegate: UdfpsKeyguardAccessibilityDelegate) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt new file mode 100644 index 000000000000..929f27f4fea3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModel.kt @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.viewmodel + +import android.graphics.Rect +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +class UdfpsKeyguardViewModel +@Inject +constructor( + private val featureFlags: FeatureFlags, +) { + var sensorBounds: Rect = Rect() + + fun useExpandedOverlay(): Boolean { + return featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt new file mode 100644 index 000000000000..098b481491de --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsKeyguardViewModels.kt @@ -0,0 +1,36 @@ +package com.android.systemui.keyguard.ui.viewmodel + +import android.graphics.Rect +import com.android.systemui.biometrics.UdfpsKeyguardView +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.ui.binder.UdfpsKeyguardViewBinder +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +@SysUISingleton +class UdfpsKeyguardViewModels +@Inject +constructor( + private val viewModel: UdfpsKeyguardViewModel, + private val internalViewModel: UdfpsKeyguardInternalViewModel, + private val aodViewModel: UdfpsAodViewModel, + private val lockscreenFingerprintViewModel: FingerprintViewModel, + private val lockscreenBackgroundViewModel: BackgroundViewModel, +) { + + fun setSensorBounds(sensorBounds: Rect) { + viewModel.sensorBounds = sensorBounds + } + + fun bindViews(view: UdfpsKeyguardView) { + UdfpsKeyguardViewBinder.bind( + view, + viewModel, + internalViewModel, + aodViewModel, + lockscreenFingerprintViewModel, + lockscreenBackgroundViewModel + ) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt new file mode 100644 index 000000000000..fd4b666a80fd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModel.kt @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.viewmodel + +import android.content.Context +import androidx.annotation.ColorInt +import com.android.settingslib.Utils.getColorAttrDefaultColor +import com.android.systemui.R +import com.android.systemui.keyguard.domain.interactor.BurnInOffsets +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import javax.inject.Inject +import kotlin.math.roundToInt +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +/** View-model for UDFPS lockscreen views. */ +@ExperimentalCoroutinesApi +open class UdfpsLockscreenViewModel( + context: Context, + lockscreenColorResId: Int, + alternateBouncerColorResId: Int, + transitionInteractor: KeyguardTransitionInteractor, +) { + private val toLockscreen: Flow<TransitionViewModel> = + transitionInteractor.anyStateToLockscreenTransition.map { + TransitionViewModel( + alpha = + if (it.from == KeyguardState.AOD) { + it.value // animate + } else { + 1f + }, + scale = 1f, + color = getColorAttrDefaultColor(context, lockscreenColorResId), + ) + } + + private val toAlternateBouncer: Flow<TransitionViewModel> = + transitionInteractor.anyStateToAlternateBouncerTransition.map { + TransitionViewModel( + alpha = 1f, + scale = + if (visibleInKeyguardState(it.from)) { + 1f + } else { + it.value + }, + color = getColorAttrDefaultColor(context, alternateBouncerColorResId), + ) + } + + private val fadeOut: Flow<TransitionViewModel> = + merge( + transitionInteractor.anyStateToGoneTransition, + transitionInteractor.anyStateToAodTransition, + transitionInteractor.anyStateToOccludedTransition, + transitionInteractor.anyStateToPrimaryBouncerTransition, + transitionInteractor.anyStateToDreamingTransition, + ) + .map { + TransitionViewModel( + alpha = + if (visibleInKeyguardState(it.from)) { + 1f - it.value + } else { + 0f + }, + scale = 1f, + color = + if (it.from == KeyguardState.ALTERNATE_BOUNCER) { + getColorAttrDefaultColor(context, alternateBouncerColorResId) + } else { + getColorAttrDefaultColor(context, lockscreenColorResId) + }, + ) + } + + private fun visibleInKeyguardState(state: KeyguardState): Boolean { + return when (state) { + KeyguardState.OFF, + KeyguardState.DOZING, + KeyguardState.DREAMING, + KeyguardState.AOD, + KeyguardState.PRIMARY_BOUNCER, + KeyguardState.GONE, + KeyguardState.OCCLUDED -> false + KeyguardState.LOCKSCREEN, + KeyguardState.ALTERNATE_BOUNCER -> true + } + } + + val transition: Flow<TransitionViewModel> = + merge( + toAlternateBouncer, + toLockscreen, + fadeOut, + ) + val visible: Flow<Boolean> = transition.map { it.alpha != 0f } +} + +@ExperimentalCoroutinesApi +class FingerprintViewModel +@Inject +constructor( + val context: Context, + transitionInteractor: KeyguardTransitionInteractor, + interactor: UdfpsKeyguardInteractor, +) : + UdfpsLockscreenViewModel( + context, + android.R.attr.textColorPrimary, + com.android.internal.R.attr.materialColorOnPrimaryFixed, + transitionInteractor, + ) { + val dozeAmount: Flow<Float> = interactor.dozeAmount + val burnInOffsets: Flow<BurnInOffsets> = interactor.burnInOffsets + + // Padding between the fingerprint icon and its bounding box in pixels. + val padding: Flow<Int> = + interactor.scaleForResolution.map { scale -> + (context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding) * scale) + .roundToInt() + } +} + +@ExperimentalCoroutinesApi +class BackgroundViewModel +@Inject +constructor( + val context: Context, + transitionInteractor: KeyguardTransitionInteractor, +) : + UdfpsLockscreenViewModel( + context, + com.android.internal.R.attr.colorSurface, + com.android.internal.R.attr.materialColorPrimaryFixed, + transitionInteractor, + ) + +data class TransitionViewModel( + val alpha: Float, + val scale: Float, + @ColorInt val color: Int, +) diff --git a/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt b/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt index 3226865d1d82..d4b799f444e5 100644 --- a/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/log/BouncerLogger.kt @@ -18,6 +18,7 @@ package com.android.systemui.log import com.android.systemui.bouncer.shared.model.BouncerMessageModel import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.BouncerLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt index fefa1b29b576..68cdfb6d5865 100644 --- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt @@ -6,7 +6,7 @@ import com.android.keyguard.FaceAuthUiEvent import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.DEBUG import com.android.systemui.log.dagger.FaceAuthLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt index 27301e92eca2..150de26c12c7 100644 --- a/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/log/ScreenDecorationsLogger.kt @@ -21,9 +21,9 @@ import android.graphics.Rect import android.graphics.RectF import androidx.core.graphics.toRectF import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.ERROR +import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.log.dagger.ScreenDecorationsLog import com.google.errorprone.annotations.CompileTimeConstant import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt index 8d622ae1ca03..67a985eb44bc 100644 --- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt +++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt @@ -21,8 +21,8 @@ import android.os.Trace import com.android.systemui.Dumpable import com.android.systemui.common.buffer.RingBuffer import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.log.LogLevel import com.android.systemui.log.LogcatEchoTracker +import com.android.systemui.log.core.LogLevel import com.android.systemui.plugins.log.TableLogBufferBase import com.android.systemui.util.time.SystemClock import java.io.PrintWriter diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt index e2e269de71a0..534241edb253 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutLogger.kt @@ -19,7 +19,7 @@ package com.android.systemui.media.controls.pipeline import android.media.session.PlaybackState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.MediaTimeoutListenerLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt index 9e53d77dec99..888b9c7cc901 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowserLogger.kt @@ -19,7 +19,7 @@ package com.android.systemui.media.controls.resume import android.content.ComponentName import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.MediaBrowserLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt index 30ee147e302a..2883210805d3 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/KeyguardMediaController.kt @@ -128,6 +128,15 @@ constructor( var visibilityChangedListener: ((Boolean) -> Unit)? = null + /** + * Whether the doze wake up animation is delayed and we are currently waiting for it to start. + */ + var isDozeWakeUpAnimationWaiting: Boolean = false + set(value) { + field = value + refreshMediaPosition() + } + /** single pane media container placed at the top of the notifications list */ var singlePaneContainer: MediaContainerView? = null private set @@ -221,7 +230,13 @@ constructor( // by the clock. This is not the case for single-line clock though. // For single shade, we don't need to do it, because media is a child of NSSL, which already // gets hidden on AOD. - return !statusBarStateController.isDozing + // Media also has to be hidden when waking up from dozing, and the doze wake up animation is + // delayed and waiting to be started. + // This is to stay in sync with the delaying of the horizontal alignment of the rest of the + // keyguard container, that is also delayed until the "wait" is over. + // If we show media during this waiting period, the shade will still be centered, and using + // the entire width of the screen, and making media show fully stretched. + return !statusBarStateController.isDozing && !isDozeWakeUpAnimationWaiting } private fun showMediaPlayer() { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt index 0ed24349bdf4..3dc00004e900 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselControllerLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.media.controls.ui import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.MediaCarouselControllerLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt index c781b7699b26..8f1595d7d7a2 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.media.controls.ui import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.MediaViewLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java index 318cd99a06ed..26a7d048cf27 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java @@ -20,6 +20,7 @@ import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECT import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_NONE; import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER; +import android.annotation.DrawableRes; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.drawable.AnimatedVectorDrawable; @@ -181,27 +182,23 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mController.getSelectedMediaDevice(), device))); boolean isHost = device.isHostForOngoingSession() && isActiveWithOngoingSession; - if (isHost) { + if (isActiveWithOngoingSession) { mCurrentActivePosition = position; updateTitleIcon(R.drawable.media_output_icon_volume, mController.getColorItemContent()); mSubTitleText.setText(device.getSubtextString()); updateTwoLineLayoutContentAlpha(DEVICE_CONNECTED_ALPHA); - updateEndClickAreaAsSessionEditing(device); + updateEndClickAreaAsSessionEditing(device, + isHost ? R.drawable.media_output_status_edit_session + : R.drawable.ic_sound_bars_anim); setTwoLineLayout(device, null /* title */, true /* bFocused */, true /* showSeekBar */, false /* showProgressBar */, true /* showSubtitle */, false /* showStatus */, true /* showEndTouchArea */, false /* isFakeActive */); initSeekbar(device, isCurrentSeekbarInvisible); } else { - if (isActiveWithOngoingSession) { - //Selected device which has ongoing session, disable seekbar since we - //only allow volume control on Host + if (currentlyConnected) { mCurrentActivePosition = position; - } - boolean showSeekbar = - (!device.hasOngoingSession() && currentlyConnected); - if (showSeekbar) { updateTitleIcon(R.drawable.media_output_icon_volume, mController.getColorItemContent()); initSeekbar(device, isCurrentSeekbarInvisible); @@ -222,10 +219,10 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { updateClickActionBasedOnSelectionBehavior(device) ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA); setTwoLineLayout(device, currentlyConnected /* bFocused */, - showSeekbar /* showSeekBar */, + currentlyConnected /* showSeekBar */, false /* showProgressBar */, true /* showSubtitle */, deviceStatusIcon != null /* showStatus */, - isActiveWithOngoingSession /* isFakeActive */); + false /* isFakeActive */); } } else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) { setUpDeviceIcon(device); @@ -267,25 +264,16 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { setSingleLineLayout(getItemTitle(device)); } else if (device.hasOngoingSession()) { mCurrentActivePosition = position; - if (device.isHostForOngoingSession()) { - updateTitleIcon(R.drawable.media_output_icon_volume, - mController.getColorItemContent()); - updateEndClickAreaAsSessionEditing(device); - mEndClickIcon.setVisibility(View.VISIBLE); - setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, - false /* showProgressBar */, false /* showCheckBox */, - true /* showEndTouchArea */); - initSeekbar(device, isCurrentSeekbarInvisible); - } else { - updateDeviceStatusIcon(mContext.getDrawable( - R.drawable.ic_sound_bars_anim)); - mStatusIcon.setVisibility(View.VISIBLE); - updateSingleLineLayoutContentAlpha( - updateClickActionBasedOnSelectionBehavior(device) - ? DEVICE_CONNECTED_ALPHA : DEVICE_DISCONNECTED_ALPHA); - setSingleLineLayout(getItemTitle(device)); - initFakeActiveDevice(); - } + updateTitleIcon(R.drawable.media_output_icon_volume, + mController.getColorItemContent()); + updateEndClickAreaAsSessionEditing(device, device.isHostForOngoingSession() + ? R.drawable.media_output_status_edit_session + : R.drawable.ic_sound_bars_anim); + mEndClickIcon.setVisibility(View.VISIBLE); + setSingleLineLayout(getItemTitle(device), true /* showSeekBar */, + false /* showProgressBar */, false /* showCheckBox */, + true /* showEndTouchArea */); + initSeekbar(device, isCurrentSeekbarInvisible); } else if (mController.isCurrentConnectedDeviceRemote() && !mController.getSelectableMediaDevice().isEmpty()) { //If device is connected and there's other selectable devices, layout as @@ -362,7 +350,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mStatusIcon.setAlpha(alphaValue); } - private void updateEndClickAreaAsSessionEditing(MediaDevice device) { + private void updateEndClickAreaAsSessionEditing(MediaDevice device, @DrawableRes int id) { mEndClickIcon.setOnClickListener(null); mEndTouchArea.setOnClickListener(null); updateEndClickAreaColor(mController.getColorSeekbarProgress()); @@ -371,6 +359,11 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter { mEndClickIcon.setOnClickListener( v -> mController.tryToLaunchInAppRoutingIntent(device.getId(), v)); mEndTouchArea.setOnClickListener(v -> mEndClickIcon.performClick()); + Drawable drawable = mContext.getDrawable(id); + mEndClickIcon.setImageDrawable(drawable); + if (drawable instanceof AnimatedVectorDrawable) { + ((AnimatedVectorDrawable) drawable).start(); + } } public void updateEndClickAreaColor(int color) { diff --git a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt index bbcf259418c8..417168209b43 100644 --- a/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt @@ -3,7 +3,7 @@ package com.android.systemui.media.muteawait import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.dagger.MediaMuteAwaitLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import javax.inject.Inject /** Log messages for [MediaMuteAwaitConnectionManager]. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt index 66399d580582..46c0132deff7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt @@ -3,7 +3,7 @@ package com.android.systemui.media.nearby import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.dagger.NearbyMediaDevicesLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import javax.inject.Inject /** Log messages for [NearbyMediaDevicesManager]. */ diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt index eeda102702d2..3c2226f6a240 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLoggerUtils.kt @@ -17,7 +17,7 @@ package com.android.systemui.media.taptotransfer.common import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel /** A helper for logging media tap-to-transfer events. */ object MediaTttLoggerUtils { diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt index 206e5e3ee090..503afd3a675a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderLogger.kt @@ -20,7 +20,7 @@ import android.app.StatusBarManager import com.android.internal.logging.InstanceId import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.media.taptotransfer.common.MediaTttLoggerUtils import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java index 8d809908d78b..07846b56d784 100644 --- a/packages/SystemUI/src/com/android/systemui/model/SysUiState.java +++ b/packages/SystemUI/src/com/android/systemui/model/SysUiState.java @@ -114,7 +114,7 @@ public class SysUiState implements Dumpable { pw.print(" mSysUiStateFlags="); pw.println(mFlags); pw.println(" " + QuickStepContract.getSystemUiStateString(mFlags)); pw.print(" backGestureDisabled="); - pw.println(QuickStepContract.isBackGestureDisabled(mFlags)); + pw.println(QuickStepContract.isBackGestureDisabled(mFlags, false /* forTrackpad */)); pw.print(" assistantGestureDisabled="); pw.println(QuickStepContract.isAssistantGestureDisabled(mFlags)); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java index 99c591f25edb..8225c47d904b 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java @@ -462,7 +462,7 @@ public final class NavBarHelper implements * @return Whether the IME is shown on top of the screen given the {@code vis} flag of * {@link InputMethodService} and the keyguard states. */ - public boolean isImeShown(@InputMethodService.ImeWindowVisibility int vis) { + public boolean isImeShown(int vis) { View shadeWindowView = mNotificationShadeWindowController.getWindowRootView(); boolean isKeyguardShowing = mKeyguardStateController.isShowing(); boolean imeVisibleOnShade = shadeWindowView != null && shadeWindowView.isAttachedToWindow() diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 5bae1cba4ac4..682335e0b419 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -66,7 +66,6 @@ import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; -import android.inputmethodservice.InputMethodService; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -1048,9 +1047,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements // ----- CommandQueue Callbacks ----- @Override - public void setImeWindowStatus(int displayId, IBinder token, - @InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition, boolean showImeSwitcher) { + public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, + boolean showImeSwitcher) { if (displayId != mDisplayId) { return; } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index cecf043c572e..3b32313e76a0 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -338,9 +338,8 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } @Override - public void setImeWindowStatus(int displayId, IBinder token, - @InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition, boolean showImeSwitcher) { + public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, + boolean showImeSwitcher) { boolean imeShown = mNavBarHelper.isImeShown(vis); if (!imeShown) { // Count imperceptible changes as visible so we transition taskbar out quickly. diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 7b86d0a6ebce..a8af67a9fc97 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -1023,7 +1023,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY()); boolean isBackAllowedCommon = !mDisabledForQuickstep && mIsBackGestureAllowed && !mGestureBlockingActivityRunning - && !QuickStepContract.isBackGestureDisabled(mSysUiFlags) + && !QuickStepContract.isBackGestureDisabled(mSysUiFlags, + mIsTrackpadThreeFingerSwipe) && !isTrackpadScroll(mIsTrackpadGestureFeaturesEnabled, ev); if (mIsTrackpadThreeFingerSwipe) { // Trackpad back gestures don't have zones, so we don't need to check if the down @@ -1056,8 +1057,9 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack + " disp=%s, wl=%d, il=%d, wr=%d, ir=%d, excl=%s]", curTime, curTimeStr, mAllowGesture, mIsTrackpadThreeFingerSwipe, mIsOnLeftEdge, mDeferSetIsOnLeftEdge, mIsBackGestureAllowed, - QuickStepContract.isBackGestureDisabled(mSysUiFlags), mDisabledForQuickstep, - mGestureBlockingActivityRunning, mIsInPip, mDisplaySize, + QuickStepContract.isBackGestureDisabled(mSysUiFlags, + mIsTrackpadThreeFingerSwipe), + mDisabledForQuickstep, mGestureBlockingActivityRunning, mIsInPip, mDisplaySize, mEdgeWidthLeft, mLeftInset, mEdgeWidthRight, mRightInset, mExcludeRegion)); } else if (mAllowGesture || mLogGesture) { if (!mThresholdCrossed) { diff --git a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt index e56106d1c065..f934346d9775 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt @@ -20,8 +20,8 @@ import android.icu.text.SimpleDateFormat import android.permission.PermissionGroupUsage import com.android.systemui.log.dagger.PrivacyLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogMessage +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogMessage import com.android.systemui.privacy.PrivacyDialog import com.android.systemui.privacy.PrivacyItem import java.util.Locale diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt index ac6aabb2e5bd..6563e425190d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt @@ -2,7 +2,7 @@ package com.android.systemui.qs import com.android.systemui.log.dagger.QSFragmentDisableLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.disableflags.DisableFlagsLogger import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt index 6265b3c056e7..3432628e6d67 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.external import android.content.Context import android.graphics.drawable.Icon +import android.view.ContextThemeWrapper import android.view.LayoutInflater import android.view.ViewGroup import android.widget.TextView @@ -66,7 +67,8 @@ class TileRequestDialog( } private fun createTileView(tileData: TileData): QSTileView { - val tile = QSTileViewImpl(context, QSIconViewImpl(context), true) + val themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) + val tile = QSTileViewImpl(themedContext, QSIconViewImpl(themedContext), true) val state = QSTile.BooleanState().apply { label = tileData.label handlesLongClick = false diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt index 9c9ad33e4918..3c53d77c6beb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt @@ -244,8 +244,8 @@ class FooterActionsViewBinder @Inject constructor() { val backgroundResource = when (model.backgroundColor) { - R.attr.offStateColor -> R.drawable.qs_footer_action_circle - com.android.internal.R.attr.colorAccent -> R.drawable.qs_footer_action_circle_color + R.attr.shadeInactive -> R.drawable.qs_footer_action_circle + R.attr.shadeActive -> R.drawable.qs_footer_action_circle_color else -> error("Unsupported icon background resource ${model.backgroundColor}") } buttonView.setBackgroundResource(backgroundResource) diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt index b3596a254b7d..32146b5b00e4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt @@ -145,8 +145,12 @@ class FooterActionsViewModel( R.drawable.ic_settings, ContentDescription.Resource(R.string.accessibility_quick_settings_settings) ), - iconTint = null, - backgroundColor = R.attr.offStateColor, + iconTint = + Utils.getColorAttrDefaultColor( + context, + R.attr.onShadeInactiveVariant, + ), + backgroundColor = R.attr.shadeInactive, this::onSettingsButtonClicked, ) @@ -162,9 +166,9 @@ class FooterActionsViewModel( iconTint = Utils.getColorAttrDefaultColor( context, - com.android.internal.R.attr.textColorOnAccent, + R.attr.onShadeActive, ), - backgroundColor = com.android.internal.R.attr.colorAccent, + backgroundColor = R.attr.shadeActive, this::onPowerButtonClicked, ) } else { @@ -264,7 +268,7 @@ class FooterActionsViewModel( ), ), iconTint = null, - backgroundColor = R.attr.offStateColor, + backgroundColor = R.attr.shadeInactive, onClick = this::onUserSwitcherClicked, ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt index c00a81cbf12b..39745c8cbeea 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -24,9 +24,9 @@ import android.view.View import com.android.systemui.log.ConstantStringsLogger import com.android.systemui.log.ConstantStringsLoggerImpl import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.VERBOSE +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.ERROR +import com.android.systemui.log.core.LogLevel.VERBOSE import com.android.systemui.log.dagger.QSConfigLog import com.android.systemui.log.dagger.QSLog import com.android.systemui.plugins.qs.QSTile diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt index 498f403e8c7a..6e7e09959697 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepository.kt @@ -51,12 +51,26 @@ class InstalledTilesComponentRepositoryImpl @Inject constructor( @Application private val applicationContext: Context, - private val packageManager: PackageManager, @Background private val backgroundDispatcher: CoroutineDispatcher, ) : InstalledTilesComponentRepository { - override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> = - conflatedCallbackFlow { + override fun getInstalledTilesComponents(userId: Int): Flow<Set<ComponentName>> { + /* + * In order to query [PackageManager] for different users, this implementation will call + * [Context.createContextAsUser] and retrieve the [PackageManager] from that context. + */ + val packageManager = + if (applicationContext.userId == userId) { + applicationContext.packageManager + } else { + applicationContext + .createContextAsUser( + UserHandle.of(userId), + /* flags */ 0, + ) + .packageManager + } + return conflatedCallbackFlow { val receiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { @@ -74,12 +88,13 @@ constructor( awaitClose { applicationContext.unregisterReceiver(receiver) } } .onStart { emit(Unit) } - .map { reloadComponents(userId) } + .map { reloadComponents(userId, packageManager) } .distinctUntilChanged() .flowOn(backgroundDispatcher) + } @WorkerThread - private fun reloadComponents(userId: Int): Set<ComponentName> { + private fun reloadComponents(userId: Int, packageManager: PackageManager): Set<ComponentName> { return packageManager .queryIntentServicesAsUser(INTENT, FLAGS, userId) .mapNotNull { it.serviceInfo } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt index d400faa3091e..573cb7154c4f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.qs.pipeline.shared.logging import android.annotation.UserIdInt import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.qs.pipeline.dagger.QSAutoAddLog import com.android.systemui.qs.pipeline.dagger.QSTileListLog import com.android.systemui.qs.pipeline.shared.TileSpec diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java index e54168162de6..7e45491adc83 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java @@ -248,13 +248,11 @@ public class QSIconViewImpl extends QSIconView { */ private static int getIconColorForState(Context context, QSTile.State state) { if (state.disabledByPolicy || state.state == Tile.STATE_UNAVAILABLE) { - return Utils.getColorAttrDefaultColor( - context, com.android.internal.R.attr.textColorTertiary); + return Utils.getColorAttrDefaultColor(context, R.attr.outline); } else if (state.state == Tile.STATE_INACTIVE) { - return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary); + return Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactiveVariant); } else if (state.state == Tile.STATE_ACTIVE) { - return Utils.getColorAttrDefaultColor(context, - com.android.internal.R.attr.textColorOnAccent); + return Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive); } else { Log.e("QSIconView", "Invalid state " + state); return 0; 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 2a9e7d05c187..1ca2a961744b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -330,7 +330,9 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy final int eventId = mClickEventId++; mQSLogger.logTileLongClick(mTileSpec, mStatusBarStateController.getState(), mState.state, eventId); - mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget(); + if (!mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) { + mHandler.obtainMessage(H.LONG_CLICK, eventId, 0, view).sendToTarget(); + } } public LogMaker populate(LogMaker logMaker) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt index b80668379e49..d81e4c229aa7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt @@ -29,6 +29,7 @@ import android.os.Trace import android.service.quicksettings.Tile import android.text.TextUtils import android.util.Log +import android.util.TypedValue import android.view.Gravity import android.view.LayoutInflater import android.view.View @@ -92,24 +93,21 @@ open class QSTileViewImpl @JvmOverloads constructor( updateHeight() } - private val colorActive = Utils.getColorAttrDefaultColor(context, - com.android.internal.R.attr.colorAccentPrimary) - private val colorInactive = Utils.getColorAttrDefaultColor(context, R.attr.offStateColor) - private val colorUnavailable = Utils.applyAlpha(UNAVAILABLE_ALPHA, colorInactive) + private val colorActive = Utils.getColorAttrDefaultColor(context, R.attr.shadeActive) + private val colorInactive = Utils.getColorAttrDefaultColor(context, R.attr.shadeInactive) + private val colorUnavailable = Utils.getColorAttrDefaultColor(context, R.attr.shadeDisabled) - private val colorLabelActive = - Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorOnAccent) - private val colorLabelInactive = - Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) + private val colorLabelActive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeActive) + private val colorLabelInactive = Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactive) private val colorLabelUnavailable = - Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorTertiary) + Utils.getColorAttrDefaultColor(context, R.attr.outline) private val colorSecondaryLabelActive = - Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondaryInverse) + Utils.getColorAttrDefaultColor(context, R.attr.onShadeActiveVariant) private val colorSecondaryLabelInactive = - Utils.getColorAttrDefaultColor(context, android.R.attr.textColorSecondary) + Utils.getColorAttrDefaultColor(context, R.attr.onShadeInactiveVariant) private val colorSecondaryLabelUnavailable = - Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorTertiary) + Utils.getColorAttrDefaultColor(context, R.attr.outline) private lateinit var label: TextView protected lateinit var secondaryLabel: TextView @@ -151,6 +149,11 @@ open class QSTileViewImpl @JvmOverloads constructor( private val locInScreen = IntArray(2) init { + val typedValue = TypedValue() + if (!getContext().theme.resolveAttribute(R.attr.isQsTheme, typedValue, true)) { + throw IllegalStateException("QSViewImpl must be inflated with a theme that contains " + + "Theme.SystemUI.QuickSettings") + } setId(generateViewId()) orientation = LinearLayout.HORIZONTAL gravity = Gravity.CENTER_VERTICAL or Gravity.START diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 2ef9e0772d93..d97db3b27c87 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -117,6 +117,7 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants; import com.android.systemui.classifier.Classifier; import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeLog; @@ -208,7 +209,6 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; import com.android.systemui.statusbar.phone.TapAgainViewController; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; -import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController; @@ -238,7 +238,7 @@ import kotlin.Unit; import kotlinx.coroutines.CoroutineDispatcher; -@CentralSurfacesComponent.CentralSurfacesScope +@SysUISingleton public final class NotificationPanelViewController implements ShadeSurface, Dumpable { public static final String TAG = NotificationPanelView.class.getSimpleName(); @@ -1175,6 +1175,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardStatusViewComponentFactory.build(keyguardStatusView); mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController(); mKeyguardStatusViewController.init(); + mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); updateClockAppearance(); if (mKeyguardUserSwitcherController != null) { @@ -1227,6 +1228,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private void onSplitShadeEnabledChanged() { mShadeLog.logSplitShadeChanged(mSplitShadeEnabled); + mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled); // Reset any left over overscroll state. It is a rare corner case but can happen. mQsController.setOverScrollAmount(0); mScrimController.setNotificationsOverScrollAmount(0); @@ -1407,11 +1409,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mKeyguardBottomArea = keyguardBottomArea; } - void setOpenCloseListener(OpenCloseListener openCloseListener) { + @Override + public void setOpenCloseListener(OpenCloseListener openCloseListener) { mOpenCloseListener = openCloseListener; } - void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) { + @Override + public void setTrackingStartedListener(TrackingStartedListener trackingStartedListener) { mTrackingStartedListener = trackingStartedListener; } @@ -1623,6 +1627,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mWillPlayDelayedDozeAmountAnimation = willPlay; mWakeUpCoordinator.logDelayingClockWakeUpAnimation(willPlay); + mKeyguardMediaController.setDozeWakeUpAnimationWaiting(willPlay); // Once changing this value, see if we should move the clock. positionClockAndNotifications(); @@ -3378,11 +3383,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump ViewGroupFadeHelper.reset(mView); } - void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) { + @Override + public void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) { mView.getViewTreeObserver().addOnGlobalLayoutListener(listener); } - void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) { + @Override + public void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) { mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener); } @@ -3565,6 +3572,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) { + mShadeLog.logEndMotionEvent("endMotionEvent called", forceCancel, false); mTrackingPointer = -1; mAmbientState.setSwipingUp(false); if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialExpandX) > mTouchSlop @@ -3586,15 +3594,19 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } else if (event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) { if (onKeyguard) { expand = true; + mShadeLog.logEndMotionEvent("endMotionEvent: cancel while on keyguard", + forceCancel, expand); } else if (mCentralSurfaces.isBouncerShowingOverDream()) { expand = false; } else { // If we get a cancel, put the shade back to the state it was in when the // gesture started expand = !mPanelClosedOnDown; + mShadeLog.logEndMotionEvent("endMotionEvent: cancel", forceCancel, expand); } } else { expand = flingExpands(vel, vectorVel, x, y); + mShadeLog.logEndMotionEvent("endMotionEvent: flingExpands", forceCancel, expand); } mDozeLog.traceFling( @@ -3847,8 +3859,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return !isFullyCollapsed() && !mTracking && !mClosing; } - /** Collapses the shade instantly without animation. */ - void instantCollapse() { + @Override + public void instantCollapse() { abortAnimations(); setExpandedFraction(0f); if (mExpanding) { @@ -4021,8 +4033,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mFixedDuration = NO_FIXED_DURATION; } - /** */ - boolean postToView(Runnable action) { + @Override + public boolean postToView(Runnable action) { return mView.post(action); } @@ -4731,6 +4743,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mTouchSlopExceeded = mTouchSlopExceededBeforeDown; mMotionAborted = false; mPanelClosedOnDown = isFullyCollapsed(); + mShadeLog.logPanelClosedOnDown("intercept down touch", mPanelClosedOnDown, + mExpandedFraction); mCollapsedAndHeadsUpOnDown = false; mHasLayoutedSinceDown = false; mUpdateFlingOnLayout = false; @@ -4948,6 +4962,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump startExpandMotion(x, y, false /* startTracking */, mExpandedHeight); mMinExpandHeight = 0.0f; mPanelClosedOnDown = isFullyCollapsed(); + mShadeLog.logPanelClosedOnDown("handle down touch", mPanelClosedOnDown, + mExpandedFraction); mHasLayoutedSinceDown = false; mUpdateFlingOnLayout = false; mMotionAborted = false; @@ -5113,18 +5129,5 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return super.performAccessibilityAction(host, action, args); } } - - /** Listens for when touch tracking begins. */ - interface TrackingStartedListener { - void onTrackingStarted(); - } - - /** Listens for when shade begins opening of finishes closing. */ - interface OpenCloseListener { - /** Called when the shade finishes closing. */ - void onClosingFinished(); - /** Called when the shade starts opening. */ - void onOpenStarted(); - } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index 1361c9f25eff..025c461110ef 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -68,6 +68,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.classifier.Classifier; import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentHostManager; @@ -98,7 +99,6 @@ import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager; -import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent; import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.LargeScreenUtils; @@ -113,7 +113,7 @@ import javax.inject.Inject; /** Handles QuickSettings touch handling, expansion and animation state * TODO (b/264460656) make this dumpable */ -@CentralSurfacesComponent.CentralSurfacesScope +@SysUISingleton public class QuickSettingsController implements Dumpable { public static final String TAG = "QuickSettingsController"; @@ -1220,14 +1220,15 @@ public class QuickSettingsController implements Dumpable { if (mIsFullWidth) { clipStatusView = qsVisible; float screenCornerRadius = - !mSplitShadeEnabled || mRecordingController.isRecording() - || mCastController.hasConnectedCastDevice() + mRecordingController.isRecording() || mCastController.hasConnectedCastDevice() ? 0 : mScreenCornerRadius; radius = (int) MathUtils.lerp(screenCornerRadius, mScrimCornerRadius, Math.min(top / (float) mScrimCornerRadius, 1f)); - float bottomRadius = mExpanded ? screenCornerRadius : - calculateBottomCornerRadius(screenCornerRadius); + float bottomRadius = mSplitShadeEnabled ? screenCornerRadius : 0; + if (!mExpanded) { + bottomRadius = calculateBottomCornerRadius(bottomRadius); + } mScrimController.setNotificationBottomRadius(bottomRadius); } if (isQsFragmentCreated()) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java index 9ed0e9a8b359..317d88585958 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java @@ -165,8 +165,7 @@ public interface ShadeController { NotificationShadeWindowViewController notificationShadeWindowViewController); /** */ - void setNotificationPanelViewController( - NotificationPanelViewController notificationPanelViewController); + void setShadeViewController(ShadeViewController shadeViewController); /** Listens for shade visibility changes. */ interface ShadeVisibilityListener { diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java index c9338b3614ea..b92afac047fa 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java @@ -70,7 +70,8 @@ public final class ShadeControllerImpl implements ShadeController { private boolean mExpandedVisible; - private NotificationPanelViewController mNotificationPanelViewController; + // TODO(b/237661616): Rename this variable to mShadeViewController. + private ShadeViewController mNotificationPanelViewController; private NotificationPresenter mPresenter; private NotificationShadeWindowViewController mNotificationShadeWindowViewController; private ShadeVisibilityListener mShadeVisibilityListener; @@ -426,12 +427,11 @@ public final class ShadeControllerImpl implements ShadeController { } @Override - public void setNotificationPanelViewController( - NotificationPanelViewController notificationPanelViewController) { - mNotificationPanelViewController = notificationPanelViewController; + public void setShadeViewController(ShadeViewController shadeViewController) { + mNotificationPanelViewController = shadeViewController; mNotificationPanelViewController.setTrackingStartedListener(this::runPostCollapseRunnables); mNotificationPanelViewController.setOpenCloseListener( - new NotificationPanelViewController.OpenCloseListener() { + new OpenCloseListener() { @Override public void onClosingFinished() { ShadeControllerImpl.this.onClosingFinished(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index 3af75cef3d4c..8789a8b3b7f4 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt @@ -195,7 +195,9 @@ constructor( set(value) { if (visible && field != value) { field = value + iconContainer.setQsExpansionTransitioning(value > 0f && value < 1.0f) updatePosition() + updateIgnoredSlots() } } @@ -216,6 +218,8 @@ constructor( view.onApplyWindowInsets(insets) } + private var singleCarrier = false + private val demoModeReceiver = object : DemoMode { override fun demoCommands() = listOf(DemoMode.COMMAND_CLOCK) @@ -479,17 +483,20 @@ constructor( private fun updateListeners() { mShadeCarrierGroupController.setListening(visible) if (visible) { - updateSingleCarrier(mShadeCarrierGroupController.isSingleCarrier) + singleCarrier = mShadeCarrierGroupController.isSingleCarrier + updateIgnoredSlots() mShadeCarrierGroupController.setOnSingleCarrierChangedListener { - updateSingleCarrier(it) + singleCarrier = it + updateIgnoredSlots() } } else { mShadeCarrierGroupController.setOnSingleCarrierChangedListener(null) } } - private fun updateSingleCarrier(singleCarrier: Boolean) { - if (singleCarrier) { + private fun updateIgnoredSlots() { + // switching from QQS to QS state halfway through the transition + if (singleCarrier || qsExpandedFraction < 0.5) { iconContainer.removeIgnoredSlots(carrierIconSlots) } else { iconContainer.addIgnoredSlots(carrierIconSlots) diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt index 2da8d5f4d921..1c30bddfe859 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt @@ -19,7 +19,7 @@ package com.android.systemui.shade import android.view.MotionEvent import com.android.systemui.log.dagger.ShadeLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.shade.ShadeViewController.Companion.FLING_COLLAPSE import com.android.systemui.shade.ShadeViewController.Companion.FLING_EXPAND import com.android.systemui.shade.ShadeViewController.Companion.FLING_HIDE @@ -90,7 +90,7 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { double1 = event.y.toDouble() }, { - "$str1\neventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2" + "$str1: eventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2" } ) } @@ -280,6 +280,42 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { ) } + fun logEndMotionEvent( + msg: String, + forceCancel: Boolean, + expand: Boolean, + ) + { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = msg + bool1 = forceCancel + bool2 = expand + }, + { "$str1; force=$bool1; expand=$bool2" } + ) + } + + fun logPanelClosedOnDown( + msg: String, + panelClosedOnDown: Boolean, + expandFraction: Float, + ) + { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = msg + bool1 = panelClosedOnDown + double1 = expandFraction.toDouble() + }, + { "$str1; mPanelClosedOnDown=$bool1; mExpandedFraction=$double1" } + ) + } + fun flingQs(flingType: Int, isClick: Boolean) { buffer.log( TAG, diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt index 3686b7eda0e0..2c560c952732 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt @@ -44,6 +44,7 @@ import com.android.systemui.statusbar.NotificationShelfController import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout +import com.android.systemui.statusbar.phone.KeyguardBottomAreaView import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.phone.TapAgainView import com.android.systemui.statusbar.policy.BatteryController @@ -66,6 +67,12 @@ abstract class ShadeModule { @ClassKey(AuthRippleController::class) abstract fun bindAuthRippleController(controller: AuthRippleController): CoreStartable + @Binds + @SysUISingleton + abstract fun bindsShadeViewController( + notificationPanelViewController: NotificationPanelViewController + ): ShadeViewController + companion object { const val SHADE_HEADER = "large_screen_shade_header" @@ -147,6 +154,20 @@ abstract class ShadeModule { return notificationShadeWindowView.findViewById(R.id.notification_panel) } + /** + * Constructs a new, unattached [KeyguardBottomAreaView]. + * + * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it + */ + @Provides + fun providesKeyguardBottomAreaView( + npv: NotificationPanelView, + layoutInflater: LayoutInflater, + ): KeyguardBottomAreaView { + return layoutInflater.inflate(R.layout.keyguard_bottom_area, npv, false) + as KeyguardBottomAreaView + } + @Provides @SysUISingleton fun providesLightRevealScrim( @@ -176,9 +197,15 @@ abstract class ShadeModule { @Provides @SysUISingleton fun providesLockIconView( - notificationShadeWindowView: NotificationShadeWindowView, + keyguardRootView: KeyguardRootView, + notificationPanelView: NotificationPanelView, + featureFlags: FeatureFlags ): LockIconView { - return notificationShadeWindowView.findViewById(R.id.lock_icon_view) + if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) { + return keyguardRootView.findViewById(R.id.lock_icon_view) + } else { + return notificationPanelView.findViewById(R.id.lock_icon_view) + } } // TODO(b/277762009): Only allow this view's controller to inject the view. See above. diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt index 3d9fcf9cdecb..9aa5eb0cd68b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt @@ -17,6 +17,7 @@ package com.android.systemui.shade import android.view.MotionEvent import android.view.ViewGroup +import android.view.ViewTreeObserver import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.statusbar.RemoteInputController import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow @@ -77,6 +78,9 @@ interface ShadeViewController { /** Collapses the shade with an animation duration in milliseconds. */ fun collapseWithDuration(animationDuration: Int) + /** Collapses the shade instantly without animation. */ + fun instantCollapse() + /** * Animate QS collapse by flinging it. If QS is expanded, it will collapse into QQS and stop. If * in split shade, it will collapse the whole shade. @@ -100,6 +104,9 @@ interface ShadeViewController { /** Returns whether the shade's top level view is enabled. */ val isViewEnabled: Boolean + /** Sets a listener to be notified when the shade starts opening or finishes closing. */ + fun setOpenCloseListener(openCloseListener: OpenCloseListener) + /** Returns whether status bar icons should be hidden when the shade is expanded. */ fun shouldHideStatusBarIconsWhenExpanded(): Boolean @@ -109,6 +116,9 @@ interface ShadeViewController { */ fun blockExpansionForCurrentTouch() + /** Sets a listener to be notified when touch tracking begins. */ + fun setTrackingStartedListener(trackingStartedListener: TrackingStartedListener) + /** * Disables the shade header. * @@ -178,6 +188,15 @@ interface ShadeViewController { /** Ensures that the touchable region is updated. */ fun updateTouchableRegion() + /** Adds a global layout listener. */ + fun addOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) + + /** Removes a global layout listener. */ + fun removeOnGlobalLayoutListener(listener: ViewTreeObserver.OnGlobalLayoutListener) + + /** Posts the given runnable to the view. */ + fun postToView(action: Runnable): Boolean + // ******* Begin Keyguard Section ********* /** Animate to expanded shade after a delay in ms. Used for lockscreen to shade transition. */ fun transitionToExpandedShade(delay: Long) @@ -337,3 +356,17 @@ interface ShadeViewStateProvider { /** Return the fraction of the shade that's expanded, when in lockscreen. */ val lockscreenShadeDragProgress: Float } + +/** Listens for when touch tracking begins. */ +interface TrackingStartedListener { + fun onTrackingStarted() +} + +/** Listens for when shade begins opening or finishes closing. */ +interface OpenCloseListener { + /** Called when the shade finishes closing. */ + fun onClosingFinished() + + /** Called when the shade starts opening. */ + fun onOpenStarted() +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt index d06634b63b6b..51a27cf8989a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt @@ -21,9 +21,9 @@ import com.android.systemui.log.dagger.ShadeWindowLog import com.android.systemui.log.ConstantStringsLogger import com.android.systemui.log.ConstantStringsLoggerImpl import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogMessage +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogMessage import javax.inject.Inject private const val TAG = "systemui.shadewindow" diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt index eceda8453902..6fde84a35fb1 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt @@ -17,38 +17,52 @@ package com.android.systemui.shade.domain.interactor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.user.domain.interactor.UserInteractor import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn /** Business logic for shade interactions. */ @SysUISingleton class ShadeInteractor @Inject constructor( + @Application scope: CoroutineScope, disableFlagsRepository: DisableFlagsRepository, keyguardRepository: KeyguardRepository, userSetupRepository: UserSetupRepository, deviceProvisionedController: DeviceProvisionedController, userInteractor: UserInteractor, ) { + /** Emits true if the shade is currently allowed and false otherwise. */ + val isShadeEnabled: StateFlow<Boolean> = + disableFlagsRepository.disableFlags + .map { it.isShadeEnabled() } + .stateIn(scope, SharingStarted.Eagerly, initialValue = false) + /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */ val isExpandToQsEnabled: Flow<Boolean> = combine( disableFlagsRepository.disableFlags, + isShadeEnabled, keyguardRepository.isDozing, userSetupRepository.isUserSetupFlow, - ) { disableFlags, isDozing, isUserSetup -> + ) { disableFlags, isShadeEnabled, isDozing, isUserSetup -> deviceProvisionedController.isDeviceProvisioned && // Disallow QS during setup if it's a simple user switcher. (The user intends to // use the lock screen user switcher, QS is not needed.) (isUserSetup || !userInteractor.isSimpleUserSwitcher) && - disableFlags.isShadeEnabled() && + isShadeEnabled && disableFlags.isQuickSettingsEnabled() && !isDozing } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt index e008ec0dc75c..d3c19b75a71d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar import android.app.PendingIntent import com.android.systemui.log.dagger.NotifInteractionLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.notification.collection.NotificationEntry import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 6c2c0cf12aad..a532195c5b9f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar; import static android.app.StatusBarManager.DISABLE2_NONE; import static android.app.StatusBarManager.DISABLE_NONE; import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT; +import static android.inputmethodservice.InputMethodService.IME_INVISIBLE; import static android.view.Display.INVALID_DISPLAY; import android.annotation.Nullable; @@ -36,7 +37,7 @@ import android.hardware.biometrics.IBiometricContextListener; import android.hardware.biometrics.IBiometricSysuiReceiver; import android.hardware.biometrics.PromptInfo; import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; -import android.inputmethodservice.InputMethodService; +import android.inputmethodservice.InputMethodService.BackDispositionMode; import android.media.INearbyMediaDevicesProvider; import android.media.MediaRoute2Info; import android.os.Binder; @@ -225,10 +226,8 @@ public class CommandQueue extends IStatusBar.Stub implements * @param backDisposition Disposition mode of back button. It should be one of below flags: * @param showImeSwitcher {@code true} to show IME switch button. */ - default void setImeWindowStatus(int displayId, IBinder token, - @InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition, - boolean showImeSwitcher) { } + default void setImeWindowStatus(int displayId, IBinder token, int vis, + @BackDispositionMode int backDisposition, boolean showImeSwitcher) { } default void showRecentApps(boolean triggeredFromAltTab) { } default void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) { } default void toggleTaskbar() { } @@ -679,9 +678,7 @@ public class CommandQueue extends IStatusBar.Stub implements } @Override - public void setImeWindowStatus(int displayId, IBinder token, - @InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition, + public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, boolean showImeSwitcher) { synchronized (mLock) { mHandler.removeMessages(MSG_SHOW_IME_BUTTON); @@ -1095,9 +1092,7 @@ public class CommandQueue extends IStatusBar.Stub implements } } - private void handleShowImeButton(int displayId, IBinder token, - @InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition, + private void handleShowImeButton(int displayId, IBinder token, int vis, int backDisposition, boolean showImeSwitcher) { if (displayId == INVALID_DISPLAY) return; @@ -1117,7 +1112,7 @@ public class CommandQueue extends IStatusBar.Stub implements private void sendImeInvisibleStatusForPrevNavBar() { for (int i = 0; i < mCallbacks.size(); i++) { mCallbacks.get(i).setImeWindowStatus(mLastUpdatedImeDisplayId, - null /* token */, 0 /* vis */, BACK_DISPOSITION_DEFAULT, + null /* token */, IME_INVISIBLE, BACK_DISPOSITION_DEFAULT, false /* showImeSwitcher */); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java index 39181449aaa0..ec66e994b58a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java @@ -557,19 +557,7 @@ public final class KeyboardShortcutListSearch { new ShortcutKeyGroupMultiMappingInfo( context.getString(R.string.group_system_access_google_assistant), Arrays.asList( - Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON))), - /* Lock screen: Meta + L */ - new ShortcutKeyGroupMultiMappingInfo( - context.getString(R.string.group_system_lock_screen), - Arrays.asList( - Pair.create(KeyEvent.KEYCODE_L, KeyEvent.META_META_ON))), - /* Pull up Notes app for quick memo: Meta + Ctrl + N */ - new ShortcutKeyGroupMultiMappingInfo( - context.getString(R.string.group_system_quick_memo), - Arrays.asList( - Pair.create( - KeyEvent.KEYCODE_N, - KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON))) + Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON))) ); for (ShortcutKeyGroupMultiMappingInfo info : infoList) { systemGroup.addItem(info.getShortcutMultiMappingInfo()); @@ -611,21 +599,12 @@ public final class KeyboardShortcutListSearch { new ArrayList<>()); // System multitasking shortcuts: - // Enter Split screen with current app to RHS: Meta + Ctrl + Right arrow - // Enter Split screen with current app to LHS: Meta + Ctrl + Left arrow // Switch from Split screen to full screen: Meta + Ctrl + Up arrow - // During Split screen: replace an app from one to another: Meta + Ctrl + Down arrow String[] shortcutLabels = { - context.getString(R.string.system_multitasking_rhs), - context.getString(R.string.system_multitasking_lhs), context.getString(R.string.system_multitasking_full_screen), - context.getString(R.string.system_multitasking_replace) }; int[] keyCodes = { - KeyEvent.KEYCODE_DPAD_RIGHT, - KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_UP, - KeyEvent.KEYCODE_DPAD_DOWN }; for (int i = 0; i < shortcutLabels.length; i++) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java index a3fd82e9b140..ae3d41e19b5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java @@ -440,10 +440,15 @@ public final class KeyboardShortcuts { mContext.getString(R.string.keyboard_shortcut_group_system_back), KeyEvent.KEYCODE_DEL, KeyEvent.META_META_ON)); - systemGroup.addItem(new KeyboardShortcutInfo( - mContext.getString(R.string.keyboard_shortcut_group_system_recents), - KeyEvent.KEYCODE_TAB, - KeyEvent.META_ALT_ON)); + + // Some devices (like TV) don't have recents + if (mContext.getResources().getBoolean(com.android.internal.R.bool.config_hasRecents)) { + systemGroup.addItem(new KeyboardShortcutInfo( + mContext.getString(R.string.keyboard_shortcut_group_system_recents), + KeyEvent.KEYCODE_TAB, + KeyEvent.META_ALT_ON)); + } + systemGroup.addItem(new KeyboardShortcutInfo( mContext.getString( R.string.keyboard_shortcut_group_system_notifications), @@ -683,8 +688,10 @@ public final class KeyboardShortcuts { ViewGroup shortcutItemsContainer = (ViewGroup) shortcutView .findViewById(R.id.keyboard_shortcuts_item_container); final int shortcutKeysSize = shortcutKeys.size(); + final List<String> humanReadableShortcuts = new ArrayList<>(); for (int k = 0; k < shortcutKeysSize; k++) { StringDrawableContainer shortcutRepresentation = shortcutKeys.get(k); + humanReadableShortcuts.add(shortcutRepresentation.mString); if (shortcutRepresentation.mDrawable != null) { ImageView shortcutKeyIconView = (ImageView) inflater.inflate( R.layout.keyboard_shortcuts_key_icon_view, shortcutItemsContainer, @@ -714,6 +721,11 @@ public final class KeyboardShortcuts { shortcutItemsContainer.addView(shortcutKeyTextView); } } + CharSequence contentDescription = info.getLabel(); + if (!humanReadableShortcuts.isEmpty()) { + contentDescription += ": " + String.join(", ", humanReadableShortcuts); + } + shortcutView.setContentDescription(contentDescription); shortcutContainer.addView(shortcutView); } keyboardShortcutsLayout.addView(shortcutContainer); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 96924821cc1d..42ebaa3877d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -41,7 +41,7 @@ import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewCont import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST; import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED; import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON; -import static com.android.systemui.log.LogLevel.ERROR; +import static com.android.systemui.log.core.LogLevel.ERROR; import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY; import android.app.AlarmManager; @@ -97,7 +97,7 @@ import com.android.systemui.keyguard.KeyguardIndication; import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.util.IndicationHelper; -import com.android.systemui.log.LogLevel; +import com.android.systemui.log.core.LogLevel; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserTracker; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index e2d2ac0fcb58..4710574ac20d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -32,6 +32,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.shade.ShadeViewController import com.android.systemui.shade.data.repository.ShadeRepository +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView @@ -76,6 +77,7 @@ class LockscreenShadeTransitionController @Inject constructor( dumpManager: DumpManager, qsTransitionControllerFactory: LockscreenShadeQsTransitionController.Factory, private val shadeRepository: ShadeRepository, + private val shadeInteractor: ShadeInteractor, private val powerInteractor: PowerInteractor, ) : Dumpable { private var pulseHeight: Float = 0f @@ -558,7 +560,7 @@ class LockscreenShadeTransitionController @Inject constructor( animationHandler: ((Long) -> Unit)? = null, cancelAction: Runnable? = null ) { - if (centralSurfaces.isShadeDisabled) { + if (!shadeInteractor.isShadeEnabled.value) { cancelAction?.run() logger.logShadeDisabledOnGoToLockedShade() return diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt new file mode 100644 index 000000000000..de369c35345c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandParser.kt @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.commandline + +/** + * [CommandParser] defines the collection of tokens which can be parsed from an incoming command + * list, and parses them into their respective containers. Supported tokens are of the following + * forms: + * ``` + * Flag: boolean value, false by default. always optional. + * Param: named parameter, taking N args all of a given type. Currently only single arg parameters + * are supported. + * SubCommand: named command created by adding a command to a parent. Supports all fields above, but + * not other subcommands. + * ``` + * + * Tokens are added via the factory methods for each token type. They can be made `required` by + * calling the [require] method for the appropriate type, as follows: + * ``` + * val requiredParam = parser.require(parser.param(...)) + * ``` + * + * The reason for having an explicit require is so that generic type arguments can be handled + * properly. See [SingleArgParam] and [SingleArgParamOptional] for the difference between an + * optional parameter and a required one. + * + * Typical usage of a required parameter, however, will occur within the context of a + * [ParseableCommand], which defines a convenience `require()` method: + * ``` + * class MyCommand : ParseableCommand { + * val requiredParam = param(...).require() + * } + * ``` + * + * This parser defines two modes of parsing, both of which validate for required parameters. + * 1. [parse] is a top-level parsing method. This parser will walk the given arg list and populate + * all of the delegate classes based on their type. It will handle SubCommands, and after parsing + * will check for any required-but-missing SubCommands or Params. + * + * **This method requires that every received token is represented in its grammar.** + * 2. [parseAsSubCommand] is a second-level parsing method suitable for any [SubCommand]. This + * method will handle _only_ flags and params. It will return parsing control to its parent + * parser on the first unknown token rather than throwing. + */ +class CommandParser { + private val _flags = mutableListOf<Flag>() + val flags: List<Flag> = _flags + private val _params = mutableListOf<Param>() + val params: List<Param> = _params + private val _subCommands = mutableListOf<SubCommand>() + val subCommands: List<SubCommand> = _subCommands + + private val tokenSet = mutableSetOf<String>() + + /** + * Parse the arg list into the fields defined in the containing class. + * + * @return true if all required fields are present after parsing + * @throws ArgParseError on any failure to process args + */ + fun parse(args: List<String>): Boolean { + if (args.isEmpty()) { + return false + } + + val iterator = args.listIterator() + var tokenHandled: Boolean + while (iterator.hasNext()) { + val token = iterator.next() + tokenHandled = false + + flags + .find { it.matches(token) } + ?.let { + it.inner = true + tokenHandled = true + } + + if (tokenHandled) continue + + params + .find { it.matches(token) } + ?.let { + it.parseArgsFromIter(iterator) + tokenHandled = true + } + + if (tokenHandled) continue + + subCommands + .find { it.matches(token) } + ?.let { + it.parseSubCommandArgs(iterator) + tokenHandled = true + } + + if (!tokenHandled) { + throw ArgParseError("Unknown token: $token") + } + } + + return validateRequiredParams() + } + + /** + * Parse a subset of the commands that came in from the top-level [parse] method, for the + * subcommand that this parser represents. Note that subcommands may not contain other + * subcommands. But they may contain flags and params. + * + * @return true if all required fields are present after parsing + * @throws ArgParseError on any failure to process args + */ + fun parseAsSubCommand(iter: ListIterator<String>): Boolean { + // arg[-1] is our subcommand name, so the rest of the args are either for this + // subcommand, OR for the top-level command to handle. Therefore, we bail on the first + // failure, but still check our own required params + + // The mere presence of a subcommand (similar to a flag) is a valid subcommand + if (flags.isEmpty() && params.isEmpty()) { + return validateRequiredParams() + } + + var tokenHandled: Boolean + while (iter.hasNext()) { + val token = iter.next() + tokenHandled = false + + flags + .find { it.matches(token) } + ?.let { + it.inner = true + tokenHandled = true + } + + if (tokenHandled) continue + + params + .find { it.matches(token) } + ?.let { + it.parseArgsFromIter(iter) + tokenHandled = true + } + + if (!tokenHandled) { + // Move the cursor position backwards since we've arrived at a token + // that we don't own + iter.previous() + break + } + } + + return validateRequiredParams() + } + + /** + * If [parse] or [parseAsSubCommand] does not produce a valid result, generate a list of errors + * based on missing elements + */ + fun generateValidationErrorMessages(): List<String> { + val missingElements = mutableListOf<String>() + + if (unhandledParams.isNotEmpty()) { + val names = unhandledParams.map { it.longName } + missingElements.add("No values passed for required params: $names") + } + + if (unhandledSubCmds.isNotEmpty()) { + missingElements.addAll(unhandledSubCmds.map { it.longName }) + val names = unhandledSubCmds.map { it.shortName } + missingElements.add("No values passed for required sub-commands: $names") + } + + return missingElements + } + + /** Check for any missing, required params, or any invalid subcommands */ + private fun validateRequiredParams(): Boolean = + unhandledParams.isEmpty() && unhandledSubCmds.isEmpty() && unvalidatedSubCmds.isEmpty() + + // If any required param (aka non-optional) hasn't handled a field, then return false + private val unhandledParams: List<Param> + get() = params.filter { (it is SingleArgParam<*>) && !it.handled } + + private val unhandledSubCmds: List<SubCommand> + get() = subCommands.filter { (it is RequiredSubCommand<*> && !it.handled) } + + private val unvalidatedSubCmds: List<SubCommand> + get() = subCommands.filter { !it.validationStatus } + + private fun checkCliNames(short: String?, long: String): String? { + if (short != null && tokenSet.contains(short)) { + return short + } + + if (tokenSet.contains(long)) { + return long + } + + return null + } + + private fun subCommandContainsSubCommands(cmd: ParseableCommand): Boolean = + cmd.parser.subCommands.isNotEmpty() + + private fun registerNames(short: String?, long: String) { + if (short != null) { + tokenSet.add(short) + } + tokenSet.add(long) + } + + /** + * Turns a [SingleArgParamOptional]<T> into a [SingleArgParam] by converting the [T?] into [T] + * + * @return a [SingleArgParam] property delegate + */ + fun <T : Any> require(old: SingleArgParamOptional<T>): SingleArgParam<T> { + val newParam = + SingleArgParam( + longName = old.longName, + shortName = old.shortName, + description = old.description, + valueParser = old.valueParser, + ) + + replaceWithRequired(old, newParam) + return newParam + } + + private fun <T : Any> replaceWithRequired( + old: SingleArgParamOptional<T>, + new: SingleArgParam<T>, + ) { + _params.remove(old) + _params.add(new) + } + + /** + * Turns an [OptionalSubCommand] into a [RequiredSubCommand] by converting the [T?] in to [T] + * + * @return a [RequiredSubCommand] property delegate + */ + fun <T : ParseableCommand> require(optional: OptionalSubCommand<T>): RequiredSubCommand<T> { + val newCmd = RequiredSubCommand(optional.cmd) + replaceWithRequired(optional, newCmd) + return newCmd + } + + private fun <T : ParseableCommand> replaceWithRequired( + old: OptionalSubCommand<T>, + new: RequiredSubCommand<T>, + ) { + _subCommands.remove(old) + _subCommands.add(new) + } + + internal fun flag( + longName: String, + shortName: String? = null, + description: String = "", + ): Flag { + checkCliNames(shortName, longName)?.let { + throw IllegalArgumentException("Detected reused flag name ($it)") + } + registerNames(shortName, longName) + + val flag = Flag(shortName, longName, description) + _flags.add(flag) + return flag + } + + internal fun <T : Any> param( + longName: String, + shortName: String? = null, + description: String = "", + valueParser: ValueParser<T>, + ): SingleArgParamOptional<T> { + checkCliNames(shortName, longName)?.let { + throw IllegalArgumentException("Detected reused param name ($it)") + } + registerNames(shortName, longName) + + val param = + SingleArgParamOptional( + shortName = shortName, + longName = longName, + description = description, + valueParser = valueParser, + ) + _params.add(param) + return param + } + + internal fun <T : ParseableCommand> subCommand( + command: T, + ): OptionalSubCommand<T> { + checkCliNames(null, command.name)?.let { + throw IllegalArgumentException("Cannot re-use name for subcommand ($it)") + } + + if (subCommandContainsSubCommands(command)) { + throw IllegalArgumentException( + "SubCommands may not contain other SubCommands. $command" + ) + } + + registerNames(null, command.name) + + val subCmd = OptionalSubCommand(command) + _subCommands.add(subCmd) + return subCmd + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/Parameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/Parameters.kt new file mode 100644 index 000000000000..6ed5eed79c82 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/Parameters.kt @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.commandline + +import android.util.IndentingPrintWriter +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +/** + * Definitions for all parameter types usable by [ParseableCommand]. Parameters are command line + * tokens that accept a fixed number of arguments and convert them to a parsed type. + * + * Example: + * ``` + * my_command --single-arg-param arg + * ``` + * + * In the example, `my_command` is the name of the command, `--single-arg-param` is the parameter, + * and `arg` is the value parsed by that parameter into its eventual type. + * + * Note on generics: The intended usage for parameters is to be able to return the parsed type from + * the given command as a `val` via property delegation. For example, let's say we have a command + * that has one optional and one required parameter: + * ``` + * class MyCommand : ParseableCommand { + * val requiredParam: Int by parser.param(...).required() + * val optionalParam: Int? by parser.param(...) + * } + * ``` + * + * In order to make the simple `param` method return the correct type, we need to do two things: + * 1. Break out the generic type into 2 pieces (TParsed and T) + * 2. Create two different underlying Parameter subclasses to handle the property delegation. One + * handles `T?` and the other handles `T`. Note that in both cases, `TParsed` is always non-null + * since the value parsed from the argument will throw an exception if missing or if it cannot be + * parsed. + */ + +/** A param type knows the number of arguments it expects */ +sealed interface Param : Describable { + val numArgs: Int + + /** + * Consume [numArgs] items from the iterator and relay the result into its corresponding + * delegated type. + */ + fun parseArgsFromIter(iterator: Iterator<String>) +} + +/** + * Base class for required and optional SingleArgParam classes. For convenience, UnaryParam is + * defined as a [MultipleArgParam] where numArgs = 1. The benefit is that we can define the parsing + * in a single place, and yet on the client side we can unwrap the underlying list of params + * automatically. + */ +abstract class UnaryParamBase<out T, out TParsed : T>(val wrapped: MultipleArgParam<T, TParsed>) : + Param, ReadOnlyProperty<Any?, T> { + var handled = false + + override fun describe(pw: IndentingPrintWriter) { + if (shortName != null) { + pw.print("$shortName, ") + } + pw.print(longName) + pw.println(" ${typeDescription()}") + if (description != null) { + pw.indented { pw.println(description) } + } + } + + /** + * Try to describe the arg type. We can know if it's one of the base types what kind of input it + * takes. Otherwise just print "<arg>" and let the clients describe in the help text + */ + private fun typeDescription() = + when (wrapped.valueParser) { + Type.Int -> "<int>" + Type.Float -> "<float>" + Type.String -> "<string>" + Type.Boolean -> "<boolean>" + else -> "<arg>" + } +} + +/** Required single-arg parameter, delegating a non-null type to the client. */ +class SingleArgParam<out T : Any>( + override val longName: String, + override val shortName: String? = null, + override val description: String? = null, + val valueParser: ValueParser<T>, +) : + UnaryParamBase<T, T>( + MultipleArgParam( + longName, + shortName, + 1, + description, + valueParser, + ) + ) { + + override fun getValue(thisRef: Any?, property: KProperty<*>): T = + if (handled) { + wrapped.getValue(thisRef, property)[0] + } else { + throw IllegalStateException("Attempt to read property before parse() has executed") + } + + override val numArgs: Int = 1 + + override fun parseArgsFromIter(iterator: Iterator<String>) { + wrapped.parseArgsFromIter(iterator) + handled = true + } +} + +/** Optional single-argument parameter, delegating a nullable type to the client. */ +class SingleArgParamOptional<out T : Any>( + override val longName: String, + override val shortName: String? = null, + override val description: String? = null, + val valueParser: ValueParser<T>, +) : + UnaryParamBase<T?, T>( + MultipleArgParam( + longName, + shortName, + 1, + description, + valueParser, + ) + ) { + override fun getValue(thisRef: Any?, property: KProperty<*>): T? = + wrapped.getValue(thisRef, property).getOrNull(0) + + override val numArgs: Int = 1 + + override fun parseArgsFromIter(iterator: Iterator<String>) { + wrapped.parseArgsFromIter(iterator) + handled = true + } +} + +/** + * Parses a list of args into the underlying [T] data type. The resultant value is an ordered list + * of type [TParsed]. + * + * [T] and [TParsed] are split out here in the case where the entire param is optional. I.e., a + * MultipleArgParam<T?, T> indicates a command line argument that can be omitted. In that case, the + * inner list is List<T>?, NOT List<T?>. If the argument is provided, then the type is always going + * to be parsed into T rather than T?. + */ +class MultipleArgParam<out T, out TParsed : T>( + override val longName: String, + override val shortName: String? = null, + override val numArgs: Int = 1, + override val description: String? = null, + val valueParser: ValueParser<TParsed>, +) : ReadOnlyProperty<Any?, List<TParsed>>, Param { + private val inner: MutableList<TParsed> = mutableListOf() + + override fun getValue(thisRef: Any?, property: KProperty<*>): List<TParsed> = inner + + /** + * Consumes [numArgs] values of the iterator and parses them into [TParsed]. + * + * @throws ArgParseError on the first failure + */ + override fun parseArgsFromIter(iterator: Iterator<String>) { + if (!iterator.hasNext()) { + throw ArgParseError("no argument provided for $shortName") + } + for (i in 0 until numArgs) { + valueParser + .parseValue(iterator.next()) + .fold(onSuccess = { inner.add(it) }, onFailure = { throw it }) + } + } +} + +data class ArgParseError(override val message: String) : Exception(message) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ParseableCommand.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ParseableCommand.kt new file mode 100644 index 000000000000..ecd3fa6cc299 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ParseableCommand.kt @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.commandline + +import android.util.IndentingPrintWriter +import java.io.PrintWriter +import java.lang.IllegalArgumentException +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +/** + * An implementation of [Command] that includes a [CommandParser] which can set all delegated + * properties. + * + * As the number of registrants to [CommandRegistry] grows, we should have a default mechanism for + * parsing common command line arguments. We are not expecting to build an arbitrarily-functional + * CLI, nor a GNU arg parse compliant interface here, we simply want to be able to empower clients + * to create simple CLI grammars such as: + * ``` + * $ my_command [-f|--flag] + * $ my_command [-a|--arg] <params...> + * $ my_command [subcommand1] [subcommand2] + * $ my_command <positional_arg ...> # not-yet implemented + * ``` + * + * Note that the flags `-h` and `--help` are reserved for the base class. It seems prudent to just + * avoid them in your implementation. + * + * Usage: + * + * The intended usage tries to be clever enough to enable good ergonomics, while not too clever as + * to be unmaintainable. Using the default parser is done using property delegates, and looks like: + * ``` + * class MyCommand( + * onExecute: (cmd: MyCommand, pw: PrintWriter) -> () + * ) : ParseableCommand(name) { + * val flag1 by flag( + * shortName = "-f", + * longName = "--flag", + * required = false, + * ) + * val param1: String by param( + * shortName = "-a", + * longName = "--args", + * valueParser = Type.String + * ).required() + * val param2: Int by param(..., valueParser = Type.Int) + * val subCommand by subCommand(...) + * + * override fun execute(pw: PrintWriter) { + * onExecute(this, pw) + * } + * + * companion object { + * const val name = "my_command" + * } + * } + * + * fun main() { + * fun printArgs(cmd: MyCommand, pw: PrintWriter) { + * pw.println("${cmd.flag1}") + * pw.println("${cmd.param1}") + * pw.println("${cmd.param2}") + * pw.println("${cmd.subCommand}") + * } + * + * commandRegistry.registerCommand(MyCommand.companion.name) { + * MyCommand() { (cmd, pw) -> + * printArgs(cmd, pw) + * } + * } + * } + * + * ``` + */ +abstract class ParseableCommand(val name: String, val description: String? = null) : Command { + val parser: CommandParser = CommandParser() + + val help by flag(longName = "help", shortName = "h", description = "Print help and return") + + /** + * After [execute(pw, args)] is called, this class goes through a parsing stage and sets all + * delegated properties. It is safe to read any delegated properties here. + * + * This method is never called for [SubCommand]s, since they are associated with a top-level + * command that handles [execute] + */ + abstract fun execute(pw: PrintWriter) + + /** + * Given a command string list, [execute] parses the incoming command and validates the input. + * If this command or any of its subcommands is passed `-h` or `--help`, then execute will only + * print the relevant help message and exit. + * + * If any error is thrown during parsing, we will catch and log the error. This process should + * _never_ take down its process. Override [onParseFailed] to handle an [ArgParseError]. + * + * Important: none of the delegated fields can be read before this stage. + */ + override fun execute(pw: PrintWriter, args: List<String>) { + val success: Boolean + try { + success = parser.parse(args) + } catch (e: ArgParseError) { + pw.println(e.message) + onParseFailed(e) + return + } catch (e: Exception) { + pw.println("Unknown exception encountered during parse") + pw.println(e) + return + } + + // Now we've parsed the incoming command without error. There are two things to check: + // 1. If any help is requested, print the help message and return + // 2. Otherwise, make sure required params have been passed in, and execute + + val helpSubCmds = subCmdsRequestingHelp() + + // Top-level help encapsulates subcommands. Otherwise, if _any_ subcommand requests + // help then defer to them. Else, just execute + if (help) { + help(pw) + } else if (helpSubCmds.isNotEmpty()) { + helpSubCmds.forEach { it.help(pw) } + } else { + if (!success) { + parser.generateValidationErrorMessages().forEach { pw.println(it) } + } else { + execute(pw) + } + } + } + + /** + * Returns a list of all commands that asked for help. If non-empty, parsing will stop to print + * help. It is not guaranteed that delegates are fulfilled if help is requested + */ + private fun subCmdsRequestingHelp(): List<ParseableCommand> = + parser.subCommands.filter { it.cmd.help }.map { it.cmd } + + /** Override to do something when parsing fails */ + open fun onParseFailed(error: ArgParseError) {} + + /** Override to print a usage clause. E.g. `usage: my-cmd <arg1> <arg2>` */ + open fun usage(pw: IndentingPrintWriter) {} + + /** + * Print out the list of tokens, their received types if any, and their description in a + * formatted string. + * + * Example: + * ``` + * my-command: + * MyCmd.description + * + * [optional] usage block + * + * Flags: + * -f + * description + * --flag2 + * description + * + * Parameters: + * Required: + * -p1 [Param.Type] + * description + * --param2 [Param.Type] + * description + * Optional: + * same as above + * + * SubCommands: + * Required: + * ... + * Optional: + * ... + * ``` + */ + override fun help(pw: PrintWriter) { + val ipw = IndentingPrintWriter(pw) + ipw.printBoxed(name) + ipw.println() + + // Allow for a simple `usage` block for clients + ipw.indented { usage(ipw) } + + if (description != null) { + ipw.indented { ipw.println(description) } + ipw.println() + } + + val flags = parser.flags + if (flags.isNotEmpty()) { + ipw.println("FLAGS:") + ipw.indented { + flags.forEach { + it.describe(ipw) + ipw.println() + } + } + } + + val (required, optional) = parser.params.partition { it is SingleArgParam<*> } + if (required.isNotEmpty()) { + ipw.println("REQUIRED PARAMS:") + required.describe(ipw) + } + if (optional.isNotEmpty()) { + ipw.println("OPTIONAL PARAMS:") + optional.describe(ipw) + } + + val (reqSub, optSub) = parser.subCommands.partition { it is RequiredSubCommand<*> } + if (reqSub.isNotEmpty()) { + ipw.println("REQUIRED SUBCOMMANDS:") + reqSub.describe(ipw) + } + if (optSub.isNotEmpty()) { + ipw.println("OPTIONAL SUBCOMMANDS:") + optSub.describe(ipw) + } + } + + fun flag( + longName: String, + shortName: String? = null, + description: String = "", + ): Flag { + if (!checkShortName(shortName)) { + throw IllegalArgumentException( + "Flag short name must be one character long, or null. Got ($shortName)" + ) + } + + if (!checkLongName(longName)) { + throw IllegalArgumentException("Flags must not start with '-'. Got $($longName)") + } + + val short = shortName?.let { "-$shortName" } + val long = "--$longName" + + return parser.flag(long, short, description) + } + + fun <T : Any> param( + longName: String, + shortName: String? = null, + description: String = "", + valueParser: ValueParser<T>, + ): SingleArgParamOptional<T> { + if (!checkShortName(shortName)) { + throw IllegalArgumentException( + "Parameter short name must be one character long, or null. Got ($shortName)" + ) + } + + if (!checkLongName(longName)) { + throw IllegalArgumentException("Parameters must not start with '-'. Got $($longName)") + } + + val short = shortName?.let { "-$shortName" } + val long = "--$longName" + + return parser.param(long, short, description, valueParser) + } + + fun <T : ParseableCommand> subCommand( + command: T, + ) = parser.subCommand(command) + + /** For use in conjunction with [param], makes the parameter required */ + fun <T : Any> SingleArgParamOptional<T>.required(): SingleArgParam<T> = parser.require(this) + + /** For use in conjunction with [subCommand], makes the given [SubCommand] required */ + fun <T : ParseableCommand> OptionalSubCommand<T>.required(): RequiredSubCommand<T> = + parser.require(this) + + private fun checkShortName(short: String?): Boolean { + return short == null || short.length == 1 + } + + private fun checkLongName(long: String): Boolean { + return !long.startsWith("-") + } + + companion object { + fun Iterable<Describable>.describe(pw: IndentingPrintWriter) { + pw.indented { + forEach { + it.describe(pw) + pw.println() + } + } + } + } +} + +/** + * A flag is a boolean value passed over the command line. It can have a short form or long form. + * The value is [Boolean.true] if the flag is found, else false + */ +data class Flag( + override val shortName: String? = null, + override val longName: String, + override val description: String? = null, +) : ReadOnlyProperty<Any?, Boolean>, Describable { + var inner: Boolean = false + + override fun getValue(thisRef: Any?, property: KProperty<*>) = inner +} + +/** + * Named CLI token. Can have a short or long name. Note: consider renaming to "primary" and + * "secondary" names since we don't actually care what the strings are + * + * Flags and params will have [shortName]s that are always prefixed with a single dash, while + * [longName]s are prefixed by a double dash. E.g., `my_command -f --flag`. + * + * Subcommands do not do any prefixing, and register their name as the [longName] + * + * Can be matched against an incoming token + */ +interface CliNamed { + val shortName: String? + val longName: String + + fun matches(token: String) = shortName == token || longName == token +} + +interface Describable : CliNamed { + val description: String? + + fun describe(pw: IndentingPrintWriter) { + if (shortName != null) { + pw.print("$shortName, ") + } + pw.print(longName) + pw.println() + if (description != null) { + pw.indented { pw.println(description) } + } + } +} + +/** + * Print [s] inside of a unicode character box, like so: + * ``` + * ╔═══════════╗ + * ║ my-string ║ + * ╚═══════════╝ + * ``` + */ +fun PrintWriter.printDoubleBoxed(s: String) { + val length = s.length + println("╔${"═".repeat(length + 2)}╗") + println("║ $s ║") + println("╚${"═".repeat(length + 2)}╝") +} + +/** + * Print [s] inside of a unicode character box, like so: + * ``` + * ┌───────────┐ + * │ my-string │ + * └───────────┘ + * ``` + */ +fun PrintWriter.printBoxed(s: String) { + val length = s.length + println("┌${"─".repeat(length + 2)}┐") + println("│ $s │") + println("└${"─".repeat(length + 2)}┘") +} + +fun IndentingPrintWriter.indented(block: () -> Unit) { + increaseIndent() + block() + decreaseIndent() +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/SubCommand.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/SubCommand.kt new file mode 100644 index 000000000000..41bac86fd6c9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/SubCommand.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.commandline + +import android.util.IndentingPrintWriter +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +/** + * Sub commands wrap [ParseableCommand]s and are attached to a parent [ParseableCommand]. As such + * they have their own parser which will parse the args as a subcommand. I.e., the subcommand's + * parser will consume the iterator created by the parent, reversing the index when it reaches an + * unknown token. + * + * In order to keep subcommands relatively simple and not have to do complicated validation, sub + * commands will return control to the parent parser as soon as they discover a token that they do + * not own. They will throw an [ArgParseError] if parsing fails or if they don't receive arguments + * for a required parameter. + */ +sealed interface SubCommand : Describable { + val cmd: ParseableCommand + + /** Checks if all of the required elements were passed in to [parseSubCommandArgs] */ + var validationStatus: Boolean + + /** + * To keep parsing simple, [parseSubCommandArgs] requires a [ListIterator] so that it can rewind + * the iterator when it yields control upwards + */ + fun parseSubCommandArgs(iterator: ListIterator<String>) +} + +/** + * Note that the delegated type from the subcommand is `T: ParseableCommand?`. SubCommands are + * created via adding a fully-formed [ParseableCommand] to parent command. + * + * At this point in time, I don't recommend nesting subcommands. + */ +class OptionalSubCommand<T : ParseableCommand>( + override val cmd: T, +) : SubCommand, ReadOnlyProperty<Any?, ParseableCommand?> { + override val shortName: String? = null + override val longName: String = cmd.name + override val description: String? = cmd.description + override var validationStatus = true + + private var isPresent = false + + /** Consume tokens from the iterator and pass them to the wrapped command */ + override fun parseSubCommandArgs(iterator: ListIterator<String>) { + validationStatus = cmd.parser.parseAsSubCommand(iterator) + isPresent = true + } + + override fun getValue(thisRef: Any?, property: KProperty<*>): T? = + if (isPresent) { + cmd + } else { + null + } + + override fun describe(pw: IndentingPrintWriter) { + cmd.help(pw) + } +} + +/** + * Non-optional subcommand impl. Top-level parser is expected to throw [ArgParseError] if this token + * is not present in the incoming command + */ +class RequiredSubCommand<T : ParseableCommand>( + override val cmd: T, +) : SubCommand, ReadOnlyProperty<Any?, ParseableCommand> { + override val shortName: String? = null + override val longName: String = cmd.name + override val description: String? = cmd.description + override var validationStatus = true + + /** Unhandled, required subcommands are an error */ + var handled = false + + override fun parseSubCommandArgs(iterator: ListIterator<String>) { + validationStatus = cmd.parser.parseAsSubCommand(iterator) + handled = true + } + + override fun getValue(thisRef: Any?, property: KProperty<*>): ParseableCommand = cmd + + override fun describe(pw: IndentingPrintWriter) { + cmd.help(pw) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt new file mode 100644 index 000000000000..01083d9a7907 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/commandline/ValueParser.kt @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.commandline + +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Utilities for parsing the [String] command line arguments. Arguments are related to the + * [Parameter] type, which declares the number of, and resulting type of, the arguments that it + * takes when parsing. For Example: + * ``` + * my-command --param <str> --param2 <int> + * ``` + * + * Defines 2 parameters, the first of which takes a string, and the second requires an int. Because + * fundamentally _everything_ is a string, we have to define a convenient way to get from the + * incoming `StringArg` to the resulting `T`-arg, where `T` is the type required by the client. + * + * Parsing is therefore a relatively straightforward operation: (String) -> T. However, since + * parsing can always fail, the type is actually (String) -> Result<T>. We will always want to fail + * on the first error and propagate it to the caller (typically this results in printing the `help` + * message of the command`). + * + * The identity parsing is trivial: + * ``` + * (s: String) -> String = { s -> s } + * ``` + * + * Basic mappings are actually even provided by Kotlin's stdlib: + * ``` + * (s: String) -> Boolean = { s -> s.toBooleanOrNull() } + * (s: String) -> Int = { s -> s.toIntOrNull() } + * ... + * ``` + * + * In order to properly encode errors, we will ascribe an error type to any `null` values, such that + * parsing looks like this: + * ``` + * val mapping: (String) -> T? = {...} // for some T + * val parser: (String) -> Result<T> = { s -> + * mapping(s)?.let { + * Result.success(it) + * } ?: Result.failure(/* some failure type */) + * } + * ``` + * + * Composition + * + * The ability to compose value parsing enables us to provide a couple of reasonable default parsers + * and allow clients to seamlessly build upon that using map functions. Consider the case where we + * want to validate that a value is an [Int] between 0 and 100. We start with the generic [Int] + * parser, and a validator, of the type (Int) -> Result<Int>: + * ``` + * val intParser = { s -> + * s.toStringOrNull().?let {...} ?: ... + * } + * + * val validator = { i -> + * if (i > 100 || i < 0) { + * Result.failure(...) + * } else { + * Result.success(i) + * } + * ``` + * + * In order to combine these functions, we need to define a new [flatMap] function that can get us + * from a `Result<T>` to a `Result<R>`, and short-circuit on any error. We want to see this: + * ``` + * val validatingParser = { s -> + * intParser.invoke(s).flatMap { i -> + * validator(i) + * } + * } + * ``` + * + * The flatMap is relatively simply defined, we can mimic the existing definition for [Result.map], + * though the implementation is uglier because of the `internal` definition for `value` + * + * ``` + * inline fun <R, T> Result<T>.flatMap(transform: (value: T) -> Result<R>): Result<R> { + * return when { + * isSuccess -> transform(getOrThrow()) + * else -> Result.failure(exceptionOrNull()!!) + * } + * } + * ``` + */ + +/** + * Given a [transform] that returns a [Result], apply the transform to this result, unwrapping the + * return value so that + * + * These [contract] and [callsInPlace] methods are copied from the [Result.map] definition + */ +@OptIn(ExperimentalContracts::class) +inline fun <R, T> Result<T>.flatMap(transform: (value: T) -> Result<R>): Result<R> { + contract { callsInPlace(transform, InvocationKind.AT_MOST_ONCE) } + + return when { + // Should never throw, we just don't have access to [this.value] + isSuccess -> transform(getOrThrow()) + // Exception should never be null here + else -> Result.failure(exceptionOrNull()!!) + } +} + +/** + * ValueParser turns a [String] into a Result<A> by applying a transform. See the default + * implementations below for starting points. The intention here is to provide the base mappings and + * allow clients to attach their own transforms. They are expected to succeed or return null on + * failure. The failure is propagated to the command parser as a Result and will fail on any + * [Result.failure] + */ +fun interface ValueParser<out A> { + fun parseValue(value: String): Result<A> +} + +/** Map a [ValueParser] of type A to one of type B, by applying the given [transform] */ +inline fun <A, B> ValueParser<A>.map(crossinline transform: (A) -> B?): ValueParser<B> { + return ValueParser<B> { value -> + this.parseValue(value).flatMap { a -> + transform(a)?.let { b -> Result.success(b) } + ?: Result.failure(ArgParseError("Failed to transform value $value")) + } + } +} + +/** + * Base type parsers are provided by the lib, and can be simply composed upon by [ValueParser.map] + * functions on the parser + */ + +/** String parsing always succeeds if the value exists */ +private val parseString: ValueParser<String> = ValueParser { value -> Result.success(value) } + +private val parseBoolean: ValueParser<Boolean> = ValueParser { value -> + value.toBooleanStrictOrNull()?.let { Result.success(it) } + ?: Result.failure(ArgParseError("Failed to parse $value as a boolean")) +} + +private val parseInt: ValueParser<Int> = ValueParser { value -> + value.toIntOrNull()?.let { Result.success(it) } + ?: Result.failure(ArgParseError("Failed to parse $value as an int")) +} + +private val parseFloat: ValueParser<Float> = ValueParser { value -> + value.toFloatOrNull()?.let { Result.success(it) } + ?: Result.failure(ArgParseError("Failed to parse $value as a float")) +} + +/** Default parsers that can be use as-is, or [map]ped to another type */ +object Type { + val Boolean = parseBoolean + val Int = parseInt + val Float = parseFloat + val String = parseString +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java index 2465c21c956f..73f181b8c734 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java @@ -74,7 +74,7 @@ import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; import com.android.systemui.dump.DumpManager; import com.android.systemui.log.LogBuffer; -import com.android.systemui.log.LogLevel; +import com.android.systemui.log.core.LogLevel; import com.android.systemui.log.dagger.StatusBarNetworkControllerLog; import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; import com.android.systemui.settings.UserTracker; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java index 075b41b91d97..035fa0454bfc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java @@ -41,6 +41,8 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.settings.DisplayTracker; +import com.android.systemui.shade.NotificationPanelViewController; +import com.android.systemui.shade.ShadeSurface; import com.android.systemui.shade.carrier.ShadeCarrierGroupController; import com.android.systemui.statusbar.ActionClickLogger; import com.android.systemui.statusbar.CommandQueue; @@ -273,6 +275,21 @@ public interface CentralSurfacesDependenciesModule { return ongoingCallController; } + /** + * {@link NotificationPanelViewController} implements two interfaces: + * - {@link com.android.systemui.shade.ShadeViewController}, which can be used by any class + * needing access to the shade. + * - {@link ShadeSurface}, which should *only* be used by {@link CentralSurfacesImpl}. + * + * Since {@link ShadeSurface} should only be accessible by {@link CentralSurfacesImpl}, it's + * *only* bound in this CentralSurfaces dependencies module. + * The {@link com.android.systemui.shade.ShadeViewController} interface is bound in + * {@link com.android.systemui.shade.ShadeModule} so others can access it. + */ + @Binds + @SysUISingleton + ShadeSurface provideShadeSurface(NotificationPanelViewController impl); + /** */ @Binds ShadeCarrierGroupController.SlotIndexResolver provideSlotIndexResolver( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt index ac05248a2b87..2bb476523cb8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/disableflags/data/model/DisableFlagsModel.kt @@ -20,7 +20,7 @@ import android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS import android.app.StatusBarManager.DISABLE_NONE import android.app.StatusBarManager.DISABLE_NOTIFICATION_ALERTS import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.disableflags.DisableFlagsLogger /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt index 3d6d48917dd3..84796f9acbc0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt @@ -22,6 +22,8 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.LogBufferFactory import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.time.SystemClock @@ -36,6 +38,13 @@ interface StatusBarEventsModule { @Provides @SysUISingleton + @SystemStatusAnimationSchedulerLog + fun provideSystemStatusAnimationSchedulerLogBuffer(factory: LogBufferFactory): LogBuffer { + return factory.create("SystemStatusAnimationSchedulerLog", 60) + } + + @Provides + @SysUISingleton fun provideSystemStatusAnimationScheduler( featureFlags: FeatureFlags, coordinator: SystemEventCoordinator, @@ -44,7 +53,8 @@ interface StatusBarEventsModule { dumpManager: DumpManager, systemClock: SystemClock, @Application coroutineScope: CoroutineScope, - @Main executor: DelayableExecutor + @Main executor: DelayableExecutor, + logger: SystemStatusAnimationSchedulerLogger ): SystemStatusAnimationScheduler { return if (featureFlags.isEnabled(Flags.PLUG_IN_STATUS_BAR_CHIP)) { SystemStatusAnimationSchedulerImpl( @@ -53,7 +63,8 @@ interface StatusBarEventsModule { statusBarWindowController, dumpManager, systemClock, - coroutineScope + coroutineScope, + logger ) } else { SystemStatusAnimationSchedulerLegacyImpl( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt index 56ea703668d0..6fc715a2b578 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.events import android.os.Process import android.provider.DeviceConfig -import android.util.Log import androidx.core.animation.Animator import androidx.core.animation.AnimatorListenerAdapter import androidx.core.animation.AnimatorSet @@ -69,7 +68,8 @@ constructor( private val statusBarWindowController: StatusBarWindowController, dumpManager: DumpManager, private val systemClock: SystemClock, - @Application private val coroutineScope: CoroutineScope + @Application private val coroutineScope: CoroutineScope, + private val logger: SystemStatusAnimationSchedulerLogger? ) : SystemStatusAnimationScheduler { companion object { @@ -121,6 +121,10 @@ constructor( } } } + + coroutineScope.launch { + animationState.collect { logger?.logAnimationStateUpdate(it) } + } } @SystemAnimationState override fun getAnimationState(): Int = animationState.value @@ -140,32 +144,17 @@ constructor( ) { // a event can only be scheduled if no other event is in progress or it has a higher // priority. If a persistent dot is currently displayed, don't schedule the event. - if (DEBUG) { - Log.d(TAG, "scheduling event $event") - } - + logger?.logScheduleEvent(event) scheduleEvent(event) } else if (currentlyDisplayedEvent?.shouldUpdateFromEvent(event) == true) { - if (DEBUG) { - Log.d( - TAG, - "updating current event from: $event. animationState=${animationState.value}" - ) - } + logger?.logUpdateEvent(event, animationState.value) currentlyDisplayedEvent?.updateFromEvent(event) if (event.forceVisible) hasPersistentDot = true } else if (scheduledEvent.value?.shouldUpdateFromEvent(event) == true) { - if (DEBUG) { - Log.d( - TAG, - "updating scheduled event from: $event. animationState=${animationState.value}" - ) - } + logger?.logUpdateEvent(event, animationState.value) scheduledEvent.value?.updateFromEvent(event) } else { - if (DEBUG) { - Log.d(TAG, "ignoring event $event") - } + logger?.logIgnoreEvent(event) } } @@ -356,6 +345,7 @@ constructor( } private fun notifyTransitionToPersistentDot(): Animator? { + logger?.logTransitionToPersistentDotCallbackInvoked() val anims: List<Animator> = listeners.mapNotNull { it.onSystemStatusAnimationTransitionToPersistentDot( @@ -373,6 +363,7 @@ constructor( private fun notifyHidePersistentDot(): Animator? { Assert.isMainThread() + logger?.logHidePersistentDotCallbackInvoked() val anims: List<Animator> = listeners.mapNotNull { it.onHidePersistentDot() } if (animationState.value == SHOWING_PERSISTENT_DOT) { @@ -424,5 +415,4 @@ constructor( } } -private const val DEBUG = false private const val TAG = "SystemStatusAnimationSchedulerImpl" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLog.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLog.kt new file mode 100644 index 000000000000..4ac94a60d4c6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLog.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.events + +import javax.inject.Qualifier + +/** Logs for the SystemStatusAnimationScheduler. */ +@Qualifier +@MustBeDocumented +@kotlin.annotation.Retention(AnnotationRetention.RUNTIME) +annotation class SystemStatusAnimationSchedulerLog diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLogger.kt new file mode 100644 index 000000000000..22b0b691ad3b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLogger.kt @@ -0,0 +1,92 @@ +package com.android.systemui.statusbar.events + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.LogBuffer +import com.android.systemui.log.core.LogLevel +import javax.inject.Inject + +/** Logs for the SystemStatusAnimationScheduler. */ +@SysUISingleton +class SystemStatusAnimationSchedulerLogger +@Inject +constructor( + @SystemStatusAnimationSchedulerLog private val logBuffer: LogBuffer, +) { + + fun logScheduleEvent(event: StatusEvent) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = event.javaClass.simpleName + int1 = event.priority + bool1 = event.forceVisible + bool2 = event.showAnimation + }, + { "Scheduling event: $str1(forceVisible=$bool1, priority=$int1, showAnimation=$bool2)" } + ) + } + + fun logUpdateEvent(event: StatusEvent, @SystemAnimationState animationState: Int) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = event.javaClass.simpleName + int1 = event.priority + bool1 = event.forceVisible + bool2 = event.showAnimation + int2 = animationState + }, + { + "Updating current event from: $str1(forceVisible=$bool1, priority=$int1, " + + "showAnimation=$bool2), animationState=${animationState.name()}" + } + ) + } + + fun logIgnoreEvent(event: StatusEvent) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = event.javaClass.simpleName + int1 = event.priority + bool1 = event.forceVisible + bool2 = event.showAnimation + }, + { "Ignore event: $str1(forceVisible=$bool1, priority=$int1, showAnimation=$bool2)" } + ) + } + + fun logHidePersistentDotCallbackInvoked() { + logBuffer.log(TAG, LogLevel.DEBUG, "Hide persistent dot callback invoked") + } + + fun logTransitionToPersistentDotCallbackInvoked() { + logBuffer.log(TAG, LogLevel.DEBUG, "Transition to persistent dot callback invoked") + } + + fun logAnimationStateUpdate(@SystemAnimationState animationState: Int) { + logBuffer.log( + TAG, + LogLevel.DEBUG, + { int1 = animationState }, + { "AnimationState update: ${int1.name()}" } + ) + animationState.name() + } + + private fun @receiver:SystemAnimationState Int.name() = + when (this) { + IDLE -> "IDLE" + ANIMATION_QUEUED -> "ANIMATION_QUEUED" + ANIMATING_IN -> "ANIMATING_IN" + RUNNING_CHIP_ANIM -> "RUNNING_CHIP_ANIM" + ANIMATING_OUT -> "ANIMATING_OUT" + SHOWING_PERSISTENT_DOT -> "SHOWING_PERSISTENT_DOT" + else -> "UNKNOWN_ANIMATION_STATE" + } +} + +private const val TAG = "SystemStatusAnimationSchedulerLog" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt index a67c26c06cb9..96725fc09d6a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.gesture import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.dagger.SwipeUpLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import javax.inject.Inject /** Log messages for [SwipeUpGestureHandler]. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index a7496030b965..94251ffc74c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -124,6 +124,7 @@ constructor( private var showSensitiveContentForCurrentUser = false private var showSensitiveContentForManagedUser = false private var managedUserHandle: UserHandle? = null + private var mSplitShadeEnabled = false // TODO(b/202758428): refactor so that we can test color updates via region samping, similar to // how we test color updates when theme changes (See testThemeChangeUpdatesTextColor). @@ -131,6 +132,7 @@ constructor( // TODO: Move logic into SmartspaceView var stateChangeListener = object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { + (v as SmartspaceView).setSplitShadeEnabled(mSplitShadeEnabled) smartspaceViews.add(v as SmartspaceView) connectSession() @@ -221,6 +223,11 @@ constructor( execution.assertIsMainThread() smartspaceViews.forEach { it.setDozeAmount(eased) } } + + override fun onDozingChanged(isDozing: Boolean) { + execution.assertIsMainThread() + smartspaceViews.forEach { it.setDozing(isDozing) } + } } private val deviceProvisionedListener = @@ -427,6 +434,11 @@ constructor( reloadSmartspace() } + fun setSplitShadeEnabled(enabled: Boolean) { + mSplitShadeEnabled = enabled + smartspaceViews.forEach { it.setSplitShadeEnabled(enabled) } + } + /** * Requests the smartspace session for an update. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt index a3a72d92c2e4..cea2b595f595 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification import com.android.systemui.log.dagger.NotifInteractionLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.notification.collection.NotificationEntry import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt index f7679ed058c6..502e1d9ea639 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLogger.kt @@ -14,7 +14,7 @@ package com.android.systemui.statusbar.notification import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.DEBUG import com.android.systemui.log.dagger.NotificationLockscreenLog import com.android.systemui.statusbar.StatusBarState import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt index 487a5f87d0bd..7809eaafe0b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/RemoteInputControllerLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.DEBUG import com.android.systemui.log.dagger.NotificationRemoteInputLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt index 39d0833c57d4..0ab348d174b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coalescer import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import javax.inject.Inject class GroupCoalescerLogger @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt index 79c63e6b0db1..bd1141e79278 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt @@ -2,7 +2,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.notification.row.NotificationGuts import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt index e17ce5cff37d..496fb83c1cf9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt @@ -4,7 +4,7 @@ import android.util.Log import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import javax.inject.Inject private const val TAG = "HeadsUpCoordinator" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt index 1f8ec3411bcd..4c33524346eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.UnseenNotificationLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt index 6271d38f1efa..bf65043ea534 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt index 1f4861a10e75..0d9681f801f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import javax.inject.Inject private const val TAG = "ShadeEventCoordinator" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt index f13ff6814df8..a8409d0c6fa0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt @@ -18,9 +18,9 @@ package com.android.systemui.statusbar.notification.collection.listbuilder import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.WARNING +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.WARNING import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.GroupEntry import com.android.systemui.statusbar.notification.collection.ListEntry diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt index 73227ab9f4fb..014ac549591e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt @@ -22,11 +22,11 @@ import android.service.notification.NotificationListenerService.RankingMap import android.service.notification.StatusBarNotification import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.WARNING -import com.android.systemui.log.LogLevel.WTF +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.ERROR +import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.WARNING +import com.android.systemui.log.core.LogLevel.WTF import com.android.systemui.statusbar.notification.collection.NotifCollection import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason import com.android.systemui.statusbar.notification.collection.NotifCollection.FutureDismissal diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt index 07fd349d3786..e61f9bdda3d9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.collection.render import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.notification.NotifPipelineFlags import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection import com.android.systemui.util.Compile diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt index a880b7157708..082f308bc731 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.collection.render import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import java.lang.RuntimeException import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt index 0b31265963ed..c6d2861a8c68 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt @@ -2,7 +2,7 @@ package com.android.systemui.statusbar.notification.interruption import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt index 5bac2a9350a5..4a823a40a272 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt @@ -20,9 +20,9 @@ import android.util.Log import com.android.systemui.log.dagger.NotificationInterruptLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.WARNING +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.WARNING import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import com.android.systemui.util.Compile diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt index fe03b2ad6a32..0e1f66f7cbd6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationRoundnessLogger.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.logging import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.log.dagger.NotificationRenderLog import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 8af488ea443d..27510d47b5ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -359,9 +359,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } @Override - public long performRemoveAnimation(long duration, long delay, - float translationDirection, boolean isHeadsUpAnimation, float endLocation, - Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { + public long performRemoveAnimation(long duration, long delay, float translationDirection, + boolean isHeadsUpAnimation, Runnable onFinishedRunnable, + AnimatorListenerAdapter animationListener) { enableAppearDrawing(true); mIsHeadsUpAnimation = isHeadsUpAnimation; if (mDrawingAppearAnimation) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 30747db0fb64..b34c28163abb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -2975,7 +2975,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView long delay, float translationDirection, boolean isHeadsUpAnimation, - float endLocation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { if (mMenuRow != null && mMenuRow.isMenuVisible()) { @@ -2986,7 +2985,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView public void onAnimationEnd(Animator animation) { ExpandableNotificationRow.super.performRemoveAnimation( duration, delay, translationDirection, isHeadsUpAnimation, - endLocation, onFinishedRunnable, animationListener); + onFinishedRunnable, animationListener); } }); anim.start(); @@ -2994,7 +2993,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } } return super.performRemoveAnimation(duration, delay, translationDirection, - isHeadsUpAnimation, endLocation, onFinishedRunnable, animationListener); + isHeadsUpAnimation, onFinishedRunnable, animationListener); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index f0e15c27b7a7..f98624409e56 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -367,7 +367,6 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro * such that the child appears to be going away to the top. 1 * Should mean the opposite. * @param isHeadsUpAnimation Is this a headsUp animation. - * @param endLocation The location where the horizonal heads up disappear animation should end. * @param onFinishedRunnable A runnable which should be run when the animation is finished. * @param animationListener An animation listener to add to the animation. * @@ -375,7 +374,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro * animation starts. */ public abstract long performRemoveAnimation(long duration, - long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation, + long delay, float translationDirection, boolean isHeadsUpAnimation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt index 45be0b151870..385670080f63 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.row import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt index 89338f9eeed3..4f5a04f2bdc9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.row import android.view.ViewGroup import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.dagger.NotificationRenderLog import com.android.systemui.statusbar.notification.collection.NotificationEntry diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt index 684a276ed635..02627fd8f975 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification.row import com.android.systemui.log.dagger.NotificationLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.INFO import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java index b24cec150941..0c686be0406d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java @@ -235,7 +235,7 @@ public abstract class StackScrollerDecorView extends ExpandableView { @Override public long performRemoveAnimation(long duration, long delay, - float translationDirection, boolean isHeadsUpAnimation, float endLocation, + float translationDirection, boolean isHeadsUpAnimation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener) { // TODO: Use duration diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt index b8f28b5a60ea..04308b47abc9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt @@ -69,11 +69,14 @@ class MediaContainerView(context: Context, attrs: AttributeSet?) : ExpandableVie canvas.clipPath(clipPath) } - - override fun performRemoveAnimation(duration: Long, delay: Long, translationDirection: Float, - isHeadsUpAnimation: Boolean, endLocation: Float, - onFinishedRunnable: Runnable?, - animationListener: AnimatorListenerAdapter?): Long { + override fun performRemoveAnimation( + duration: Long, + delay: Long, + translationDirection: Float, + isHeadsUpAnimation: Boolean, + onFinishedRunnable: Runnable?, + animationListener: AnimatorListenerAdapter? + ): Long { return 0 } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerLogger.kt index 6be1ef8711df..4986b632472d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerLogger.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.notification.stack import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.dagger.NotificationRenderLog import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt index f9531876e30d..2da55828e30d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.stack import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.dagger.NotificationSectionLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import javax.inject.Inject private const val TAG = "NotifSections" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 7c66bbe548a5..ef7375aa690b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -424,7 +424,8 @@ public class NotificationStackScrollLayoutController { } }; - private final NotificationSwipeHelper.NotificationCallback mNotificationCallback = + @VisibleForTesting + final NotificationSwipeHelper.NotificationCallback mNotificationCallback = new NotificationSwipeHelper.NotificationCallback() { @Override @@ -483,10 +484,11 @@ public class NotificationStackScrollLayoutController { */ public void handleChildViewDismissed(View view) { + // The View needs to clean up the Swipe states, e.g. roundness. + mView.onSwipeEnd(); if (mView.getClearAllInProgress()) { return; } - mView.onSwipeEnd(); if (view instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) view; if (row.isHeadsUp()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt index 9c1bd174107e..2c38b8dfca04 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt @@ -1,11 +1,11 @@ package com.android.systemui.statusbar.notification.stack import android.view.ViewGroup -import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.ERROR +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.ERROR +import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.dagger.NotificationRenderLog import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java index d73919b82c42..2742a23d5fad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java @@ -27,8 +27,6 @@ import com.android.keyguard.KeyguardSliceView; import com.android.systemui.R; import com.android.systemui.shared.clocks.AnimatableClockView; import com.android.systemui.statusbar.NotificationShelf; -import com.android.systemui.statusbar.StatusBarIconView; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.row.StackScrollerDecorView; @@ -427,7 +425,7 @@ public class StackStateAnimator { } changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR, 0 /* delay */, translationDirection, false /* isHeadsUpAppear */, - 0, postAnimation, null); + postAnimation, null); } else if (event.animationType == NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) { if (mHostLayout.isFullySwipedOut(changingView)) { @@ -474,28 +472,12 @@ public class StackStateAnimator { mTmpState.initFrom(changingView); endRunnable = changingView::removeFromTransientContainer; } - float targetLocation = 0; boolean needsAnimation = true; if (changingView instanceof ExpandableNotificationRow) { ExpandableNotificationRow row = (ExpandableNotificationRow) changingView; if (row.isDismissed()) { needsAnimation = false; } - - NotificationEntry entry = row.getEntry(); - StatusBarIconView icon = entry.getIcons().getStatusBarIcon(); - final StatusBarIconView centeredIcon = entry.getIcons().getCenteredIcon(); - if (centeredIcon != null && centeredIcon.getParent() != null) { - icon = centeredIcon; - } - if (icon.getParent() != null) { - icon.getLocationOnScreen(mTmpLocation); - float iconPosition = mTmpLocation[0] - icon.getTranslationX() - + ViewState.getFinalTranslationX(icon) - + icon.getWidth() * 0.25f; - mHostLayout.getLocationOnScreen(mTmpLocation); - targetLocation = iconPosition - mTmpLocation[0]; - } } if (needsAnimation) { @@ -515,7 +497,7 @@ public class StackStateAnimator { } long removeAnimationDelay = changingView.performRemoveAnimation( ANIMATION_DURATION_HEADS_UP_DISAPPEAR, - 0, 0.0f, true /* isHeadsUpAppear */, targetLocation, + 0, 0.0f, true /* isHeadsUpAppear */, postAnimation, getGlobalAnimationFinishedListener()); mAnimationProperties.delay += removeAnimationDelay; } else if (endRunnable != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt index c7f80f311650..0b2c4863157c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt @@ -1,8 +1,8 @@ package com.android.systemui.statusbar.notification.stack -import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.dagger.NotificationRenderLog import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 4ba09e175b8b..478baf2f58d6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -203,8 +203,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { int getStatusBarHeight(); - boolean isShadeDisabled(); - boolean isLaunchingActivityOverLockscreen(); void onKeyguardViewManagerStatesUpdated(); @@ -380,14 +378,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner { void resendMessage(Object msg); - int getDisabled1(); - - void setDisabled1(int disabled); - - int getDisabled2(); - - void setDisabled2(int disabled); - void setLastCameraLaunchSource(int source); void setLaunchCameraOnFinishedGoingToSleep(boolean launch); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java index 332be2aa957d..6431ef958239 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java @@ -111,6 +111,9 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES = VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK); + private int mDisabled1; + private int mDisabled2; + @Inject CentralSurfacesCommandQueueCallbacks( CentralSurfaces centralSurfaces, @@ -256,22 +259,14 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba return; } - int state2BeforeAdjustment = state2; - state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2); - Log.d(CentralSurfaces.TAG, - mDisableFlagsLogger.getDisableFlagsString( - /* new= */ new DisableFlagsLogger.DisableState( - state1, state2BeforeAdjustment), - /* newStateAfterLocalModification= */ new DisableFlagsLogger.DisableState( - state1, state2))); - - final int old1 = mCentralSurfaces.getDisabled1(); + final int old1 = mDisabled1; final int diff1 = state1 ^ old1; - mCentralSurfaces.setDisabled1(state1); + mDisabled1 = state1; - final int old2 = mCentralSurfaces.getDisabled2(); + state2 = mRemoteInputQuickSettingsDisabler.adjustDisableFlags(state2); + final int old2 = mDisabled2; final int diff2 = state2 ^ old2; - mCentralSurfaces.setDisabled2(state2); + mDisabled2 = state2; if ((diff1 & StatusBarManager.DISABLE_EXPAND) != 0) { if ((state1 & StatusBarManager.DISABLE_EXPAND) != 0) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 88ccae624dd0..0d3dfaeb85b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -182,7 +182,6 @@ import com.android.systemui.scrim.ScrimView; import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.shade.CameraLauncher; -import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.shade.QuickSettingsController; @@ -363,26 +362,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @Override - public int getDisabled1() { - return mDisabled1; - } - - @Override - public void setDisabled1(int disabled) { - mDisabled1 = disabled; - } - - @Override - public int getDisabled2() { - return mDisabled2; - } - - @Override - public void setDisabled2(int disabled) { - mDisabled2 = disabled; - } - - @Override public void setLastCameraLaunchSource(int source) { mLastCameraLaunchSource = source; } @@ -497,14 +476,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final Lazy<LightRevealScrimViewModel> mLightRevealScrimViewModelLazy; /** Controller for the Shade. */ - @VisibleForTesting - ShadeSurface mShadeSurface; + private final ShadeSurface mShadeSurface; private final ShadeLogger mShadeLogger; // settings private QSPanelController mQSPanelController; - @VisibleForTesting - QuickSettingsController mQsController; + private final QuickSettingsController mQsController; KeyguardIndicationController mKeyguardIndicationController; @@ -530,11 +507,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private CentralSurfacesComponent mCentralSurfacesComponent; - // Flags for disabling the status bar - // Two variables because the first one evidently ran out of room for new flags. - private int mDisabled1 = 0; - private int mDisabled2 = 0; - /** * This keeps track of whether we have (or haven't) registered the predictive back callback. * Since we can have visible -> visible transitions, we need to avoid @@ -729,9 +701,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { MetricsLogger metricsLogger, ShadeLogger shadeLogger, @UiBackground Executor uiBgExecutor, + ShadeSurface shadeSurface, NotificationMediaManager notificationMediaManager, NotificationLockscreenUserManager lockScreenUserManager, NotificationRemoteInputManager remoteInputManager, + QuickSettingsController quickSettingsController, UserSwitcherController userSwitcherController, BatteryController batteryController, SysuiColorExtractor colorExtractor, @@ -830,9 +804,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mMetricsLogger = metricsLogger; mShadeLogger = shadeLogger; mUiBgExecutor = uiBgExecutor; + mShadeSurface = shadeSurface; mMediaManager = notificationMediaManager; mLockscreenUserManager = lockScreenUserManager; mRemoteInputManager = remoteInputManager; + mQsController = quickSettingsController; mUserSwitcherController = userSwitcherController; mBatteryController = batteryController; mColorExtractor = colorExtractor; @@ -1636,13 +1612,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // (Right now, there's a circular dependency.) mNotificationShadeWindowController.setWindowRootView(windowRootView); mNotificationShadeWindowViewController.setupExpandedStatusBar(); - NotificationPanelViewController npvc = - mCentralSurfacesComponent.getNotificationPanelViewController(); - mShadeSurface = npvc; - mShadeController.setNotificationPanelViewController(npvc); + mShadeController.setShadeViewController(mShadeSurface); mShadeController.setNotificationShadeWindowViewController( mNotificationShadeWindowViewController); - mQsController = mCentralSurfacesComponent.getQuickSettingsController(); mBackActionInteractor.setup(mQsController, mShadeSurface); mPresenter = mCentralSurfacesComponent.getNotificationPresenter(); mNotificationActivityStarter = mCentralSurfacesComponent.getNotificationActivityStarter(); @@ -1724,11 +1696,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return mStatusBarWindowController.getStatusBarHeight(); } - @Override - public boolean isShadeDisabled() { - return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0; - } - private void updateReportRejectedTouchVisibility() { if (mReportRejectedTouch == null) { return; @@ -1843,7 +1810,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void onStatusBarTrackpadEvent(MotionEvent event) { - mCentralSurfacesComponent.getNotificationPanelViewController().handleExternalTouch(event); + mShadeSurface.handleExternalTouch(event); } private void onExpandedInvisible() { @@ -2210,10 +2177,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { */ @Override public void setLockscreenUser(int newUserId) { - if (mLockscreenWallpaper != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) { - mLockscreenWallpaper.setCurrentUser(newUserId); - } - mScrimController.setCurrentUser(newUserId); if (mWallpaperSupported) { mWallpaperChangedReceiver.onReceive(mContext, null); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index 4d716c206908..680f19a79a05 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -45,7 +45,7 @@ import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.R; import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.log.LogLevel; +import com.android.systemui.log.core.LogLevel; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.ShadeViewStateProvider; import com.android.systemui.statusbar.CommandQueue; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt index 5c357d7cd3ab..686efb7f1553 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt @@ -21,7 +21,7 @@ import android.view.View import com.android.internal.logging.nano.MetricsProto.MetricsEvent import com.android.systemui.log.dagger.LSShadeTransitionLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java index c07b5e062d70..b2c39f7e289f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java @@ -40,12 +40,17 @@ import androidx.annotation.NonNull; import com.android.internal.util.IndentingPrintWriter; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.CoreStartable; import com.android.systemui.Dumpable; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.NotificationMediaManager; +import com.android.systemui.user.data.model.SelectedUserModel; +import com.android.systemui.user.data.model.SelectionStatus; +import com.android.systemui.user.data.repository.UserRepository; +import com.android.systemui.util.kotlin.JavaAdapter; import libcore.io.IoUtils; @@ -59,7 +64,7 @@ import javax.inject.Inject; */ @SysUISingleton public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implements Runnable, - Dumpable { + Dumpable, CoreStartable { private static final String TAG = "LockscreenWallpaper"; @@ -72,6 +77,8 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen private final WallpaperManager mWallpaperManager; private final KeyguardUpdateMonitor mUpdateMonitor; private final Handler mH; + private final JavaAdapter mJavaAdapter; + private final UserRepository mUserRepository; private boolean mCached; private Bitmap mCache; @@ -88,6 +95,8 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen DumpManager dumpManager, NotificationMediaManager mediaManager, @Main Handler mainHandler, + JavaAdapter javaAdapter, + UserRepository userRepository, UserTracker userTracker) { dumpManager.registerDumpable(getClass().getSimpleName(), this); mWallpaperManager = wallpaperManager; @@ -95,6 +104,8 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen mUpdateMonitor = keyguardUpdateMonitor; mMediaManager = mediaManager; mH = mainHandler; + mJavaAdapter = javaAdapter; + mUserRepository = userRepository; if (iWallpaperManager != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) { // Service is disabled on some devices like Automotive @@ -106,6 +117,14 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen } } + @Override + public void start() { + if (!isLockscreenLiveWallpaperEnabled()) { + mJavaAdapter.alwaysCollectFlow( + mUserRepository.getSelectedUser(), this::setSelectedUser); + } + } + public Bitmap getBitmap() { assertLockscreenLiveWallpaperNotEnabled(); @@ -169,9 +188,15 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen } } - public void setCurrentUser(int user) { + private void setSelectedUser(SelectedUserModel selectedUserModel) { assertLockscreenLiveWallpaperNotEnabled(); + if (selectedUserModel.getSelectionStatus().equals(SelectionStatus.SELECTION_IN_PROGRESS)) { + // Wait until the selection has finished before updating. + return; + } + + int user = selectedUserModel.getUserInfo().id; if (user != mCurrentUserId) { if (mSelectedUser == null || user != mSelectedUser.getIdentifier()) { mCached = false; 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 47c4023ca8aa..c16e13cd4af3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -1476,10 +1476,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } } - public void setCurrentUser(int currentUser) { - // Don't care in the base class. - } - private void updateThemeColors() { if (mScrimBehind == null) return; int background = Utils.getColorAttr(mScrimBehind.getContext(), diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt index 12f023b21701..d07378e86ced 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt @@ -19,10 +19,10 @@ package com.android.systemui.statusbar.phone import android.app.PendingIntent import com.android.systemui.log.dagger.NotifInteractionLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogLevel.ERROR -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.WARNING +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogLevel.ERROR +import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.WARNING import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java index c3322808b2b8..604b1f5008db 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java @@ -64,6 +64,7 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { private boolean mNeedsUnderflow; // Individual StatusBarIconViews draw their etc dots centered in this width private int mIconDotFrameWidth; + private boolean mQsExpansionTransitioning; private boolean mShouldRestrictIcons = true; // Used to count which states want to be visible during layout private ArrayList<StatusIconState> mLayoutStates = new ArrayList<>(); @@ -87,6 +88,10 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { super.onFinishInflate(); } + public void setQsExpansionTransitioning(boolean expansionTransitioning) { + mQsExpansionTransitioning = expansionTransitioning; + } + public void setShouldRestrictIcons(boolean should) { mShouldRestrictIcons = should; } @@ -386,6 +391,7 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { StatusIconState vs = getViewStateFromChild(child); if (vs != null) { vs.applyToView(child); + vs.qsExpansionTransitioning = mQsExpansionTransitioning; } } } @@ -420,6 +426,7 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { /// StatusBarIconView.STATE_* public int visibleState = STATE_ICON; public boolean justAdded = true; + public boolean qsExpansionTransitioning = false; // How far we are from the end of the view actually is the most relevant for animation float distanceToViewEnd = -1; @@ -462,12 +469,13 @@ public class StatusIconContainer extends AlphaOptimizedLinearLayout { } icon.setVisibleState(visibleState, animateVisibility); - if (animationProperties != null) { + if (animationProperties != null && !qsExpansionTransitioning) { animateTo(view, animationProperties); } else { super.applyToView(view); } + qsExpansionTransitioning = false; justAdded = false; distanceToViewEnd = currentDistanceToEnd; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java index c618be843832..4ae460a3f0e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java @@ -21,10 +21,8 @@ import static com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.ST import static java.lang.annotation.RetentionPolicy.RUNTIME; import com.android.systemui.scene.ui.view.WindowRootView; -import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; -import com.android.systemui.shade.QuickSettingsController; import com.android.systemui.shade.ShadeHeaderController; import com.android.systemui.statusbar.NotificationPresenter; import com.android.systemui.statusbar.notification.NotificationActivityStarter; @@ -89,14 +87,6 @@ public interface CentralSurfacesComponent { NotificationShadeWindowViewController getNotificationShadeWindowViewController(); /** - * Creates a NotificationPanelViewController. - */ - NotificationPanelViewController getNotificationPanelViewController(); - - /** Creates a QuickSettingsController. */ - QuickSettingsController getQuickSettingsController(); - - /** * Creates a StatusBarHeadsUpChangeListener. */ StatusBarHeadsUpChangeListener getStatusBarHeadsUpChangeListener(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index 6b0746f08df0..ebdde78e4ea4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -16,28 +16,20 @@ package com.android.systemui.statusbar.phone.dagger; -import android.view.LayoutInflater; - import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.shade.NotificationPanelView; -import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shade.ShadeViewController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; -import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; import com.android.systemui.statusbar.phone.NotificationIconAreaController; -import com.android.systemui.statusbar.phone.StatusBarBoundsProvider; import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarLocationPublisher; -import com.android.systemui.statusbar.phone.SystemBarAttributesListener; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger; import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent; @@ -49,10 +41,8 @@ import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.util.CarrierConfigTracker; import com.android.systemui.util.settings.SecureSettings; -import dagger.Binds; import dagger.Module; import dagger.Provides; -import dagger.multibindings.IntoSet; import java.util.concurrent.Executor; @@ -70,17 +60,6 @@ public abstract class StatusBarViewModule { public static final String STATUS_BAR_FRAGMENT = "status_bar_fragment"; - /** */ - @Binds - @CentralSurfacesComponent.CentralSurfacesScope - abstract ShadeViewController bindsShadeViewController( - NotificationPanelViewController notificationPanelViewController); - - @Binds - @IntoSet - abstract StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener( - SystemBarAttributesListener systemBarAttributesListener); - /** * Creates a new {@link CollapsedStatusBarFragment}. * @@ -145,17 +124,4 @@ public abstract class StatusBarViewModule { statusBarWindowStateController, keyguardUpdateMonitor); } - - /** - * Constructs a new, unattached {@link KeyguardBottomAreaView}. - * - * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it - */ - @Provides - public static KeyguardBottomAreaView providesKeyguardBottomAreaView( - NotificationPanelView npv, LayoutInflater layoutInflater) { - return (KeyguardBottomAreaView) layoutInflater.inflate(R - .layout.keyguard_bottom_area, npv, false); - } - } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 0d057f349dd3..2efdf8a0b181 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -14,9 +14,6 @@ package com.android.systemui.statusbar.phone.fragment; -import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE; -import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.SHOWING_PERSISTENT_DOT; - import android.annotation.Nullable; import android.annotation.SuppressLint; import android.app.Fragment; @@ -39,6 +36,7 @@ import androidx.annotation.VisibleForTesting; import androidx.core.animation.Animator; import com.android.app.animation.Interpolators; +import com.android.app.animation.InterpolatorsAndroidX; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dumpable; import com.android.systemui.R; @@ -77,6 +75,8 @@ import com.android.systemui.util.CarrierConfigTracker.CarrierConfigChangedListen import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener; import com.android.systemui.util.settings.SecureSettings; +import kotlin.Unit; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -99,12 +99,16 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private static final String EXTRA_PANEL_STATE = "panel_state"; public static final String STATUS_BAR_ICON_MANAGER_TAG = "status_bar_icon_manager"; public static final int FADE_IN_DURATION = 320; + public static final int FADE_OUT_DURATION = 160; public static final int FADE_IN_DELAY = 50; + private static final int SOURCE_SYSTEM_EVENT_ANIMATOR = 1; + private static final int SOURCE_OTHER = 2; private StatusBarFragmentComponent mStatusBarFragmentComponent; private PhoneStatusBarView mStatusBar; private final StatusBarStateController mStatusBarStateController; private final KeyguardStateController mKeyguardStateController; private final ShadeViewController mShadeViewController; + private MultiSourceMinAlphaController mEndSideAlphaController; private LinearLayout mEndSideContent; private View mClockView; private View mOngoingCallChip; @@ -149,7 +153,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } }; private OperatorNameViewController mOperatorNameViewController; - private StatusBarSystemEventAnimator mSystemEventAnimator; + private StatusBarSystemEventDefaultAnimator mSystemEventAnimator; private final CarrierConfigChangedListener mCarrierConfigCallback = new CarrierConfigChangedListener() { @@ -297,14 +301,14 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue updateBlockedIcons(); mStatusBarIconController.addIconGroup(mDarkIconManager); mEndSideContent = mStatusBar.findViewById(R.id.status_bar_end_side_content); + mEndSideAlphaController = new MultiSourceMinAlphaController(mEndSideContent); mClockView = mStatusBar.findViewById(R.id.clock); mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip); showEndSideContent(false); showClock(false); initOperatorName(); initNotificationIconArea(); - mSystemEventAnimator = - new StatusBarSystemEventAnimator(mEndSideContent, getResources()); + mSystemEventAnimator = getSystemEventAnimator(); mCarrierConfigTracker.addCallback(mCarrierConfigCallback); mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener); @@ -593,18 +597,27 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private void hideEndSideContent(boolean animate) { - animateHide(mEndSideContent, animate); + if (!animate) { + mEndSideAlphaController.setAlpha(/*alpha*/ 0f, SOURCE_OTHER); + } else { + mEndSideAlphaController.animateToAlpha(/*alpha*/ 0f, SOURCE_OTHER, FADE_OUT_DURATION, + InterpolatorsAndroidX.ALPHA_OUT, /*startDelay*/ 0); + } } private void showEndSideContent(boolean animate) { - // Only show the system icon area if we are not currently animating - int state = mAnimationScheduler.getAnimationState(); - if (state == IDLE || state == SHOWING_PERSISTENT_DOT) { - animateShow(mEndSideContent, animate); + if (!animate) { + mEndSideAlphaController.setAlpha(1f, SOURCE_OTHER); + return; + } + if (mKeyguardStateController.isKeyguardFadingAway()) { + mEndSideAlphaController.animateToAlpha(/*alpha*/ 1f, SOURCE_OTHER, + mKeyguardStateController.getKeyguardFadingAwayDuration(), + InterpolatorsAndroidX.LINEAR_OUT_SLOW_IN, + mKeyguardStateController.getKeyguardFadingAwayDelay()); } else { - // We are in the middle of a system status event animation, which will animate the - // alpha (but not the visibility). Allow the view to become visible again - mEndSideContent.setVisibility(View.VISIBLE); + mEndSideAlphaController.animateToAlpha(/*alpha*/ 1f, SOURCE_OTHER, FADE_IN_DURATION, + InterpolatorsAndroidX.ALPHA_IN, FADE_IN_DELAY); } } @@ -671,7 +684,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue v.animate() .alpha(0f) - .setDuration(160) + .setDuration(FADE_OUT_DURATION) .setStartDelay(0) .setInterpolator(Interpolators.ALPHA_OUT) .withEndAction(() -> v.setVisibility(state)); @@ -754,6 +767,16 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue return mSystemEventAnimator.onSystemEventAnimationFinish(hasPersistentDot); } + private StatusBarSystemEventDefaultAnimator getSystemEventAnimator() { + return new StatusBarSystemEventDefaultAnimator(getResources(), (alpha) -> { + mEndSideAlphaController.setAlpha(alpha, SOURCE_SYSTEM_EVENT_ANIMATOR); + return Unit.INSTANCE; + }, (translationX) -> { + mEndSideContent.setTranslationX(translationX); + return Unit.INSTANCE; + }, /*isAnimationRunning*/ false); + } + private void updateStatusBarLocation(int left, int right) { int leftMargin = left - mStatusBar.getLeft(); int rightMargin = mStatusBar.getRight() - right; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt index f4ab408cc275..7cdb9c0a7aa8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.phone.fragment import com.android.systemui.log.dagger.CollapsedSbFragmentLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.disableflags.DisableFlagsLogger import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt new file mode 100644 index 000000000000..c8836e4235dc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaController.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.fragment + +import android.view.View +import androidx.core.animation.Interpolator +import androidx.core.animation.ValueAnimator +import com.android.app.animation.InterpolatorsAndroidX + +/** + * A controller that keeps track of multiple sources applying alpha value changes to a view. It will + * always apply the minimum alpha value of all sources. + */ +internal class MultiSourceMinAlphaController +@JvmOverloads +constructor(private val view: View, private val initialAlpha: Float = 1f) { + + private val alphas = mutableMapOf<Int, Float>() + private val animators = mutableMapOf<Int, ValueAnimator>() + + /** + * Sets the alpha of the provided source and applies it to the view (if no other source has set + * a lower alpha currently). If an animator of the same source is still running (i.e. + * [animateToAlpha] was called before), that animator is cancelled. + */ + fun setAlpha(alpha: Float, sourceId: Int) { + animators[sourceId]?.cancel() + updateAlpha(alpha, sourceId) + } + + /** Animates to the alpha of the provided source. */ + fun animateToAlpha( + alpha: Float, + sourceId: Int, + duration: Long, + interpolator: Interpolator = InterpolatorsAndroidX.ALPHA_IN, + startDelay: Long = 0 + ) { + animators[sourceId]?.cancel() + val animator = ValueAnimator.ofFloat(getMinAlpha(), alpha) + animator.duration = duration + animator.startDelay = startDelay + animator.interpolator = interpolator + animator.addUpdateListener { updateAlpha(animator.animatedValue as Float, sourceId) } + animator.start() + animators[sourceId] = animator + } + + fun reset() { + alphas.clear() + animators.forEach { it.value.cancel() } + animators.clear() + applyAlphaToView() + } + + private fun updateAlpha(alpha: Float, sourceId: Int) { + alphas[sourceId] = alpha + applyAlphaToView() + } + + private fun applyAlphaToView() { + val minAlpha = getMinAlpha() + view.visibility = if (minAlpha != 0f) View.VISIBLE else View.INVISIBLE + view.alpha = minAlpha + } + + private fun getMinAlpha() = alphas.minOfOrNull { it.value } ?: initialAlpha +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java index 8f9f0196abfa..eb4d963e8525 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java @@ -27,20 +27,23 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions; import com.android.systemui.statusbar.phone.PhoneStatusBarView; import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.StatusBarBoundsProvider; +import com.android.systemui.statusbar.phone.SystemBarAttributesListener; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer; import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.window.StatusBarWindowController; +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import dagger.multibindings.IntoSet; +import dagger.multibindings.Multibinds; + import java.util.Optional; import java.util.Set; import javax.inject.Named; -import dagger.Module; -import dagger.Provides; -import dagger.multibindings.Multibinds; - /** Dagger module for {@link StatusBarFragmentComponent}. */ @Module public interface StatusBarFragmentModule { @@ -152,4 +155,10 @@ public interface StatusBarFragmentModule { /** */ @Multibinds Set<StatusBarBoundsProvider.BoundsChangeListener> boundsChangeListeners(); + + /** */ + @Binds + @IntoSet + StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener( + SystemBarAttributesListener systemBarAttributesListener); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt index b3a1c4075d87..051e88f9264d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt @@ -23,7 +23,7 @@ import com.android.settingslib.SignalIcon import com.android.settingslib.mobile.MobileMappings import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.pipeline.dagger.MobileInputLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt index 74352d29cd9b..54948a4a41c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt @@ -250,15 +250,8 @@ constructor( .distinctUntilChanged() .onEach { logger.logDefaultMobileIconGroup(it) } - override fun getRepoForSubId(subId: Int): FullMobileConnectionRepository { - if (!isValidSubId(subId)) { - throw IllegalArgumentException( - "subscriptionId $subId is not in the list of valid subscriptions" - ) - } - - return getOrCreateRepoForSubId(subId) - } + override fun getRepoForSubId(subId: Int): FullMobileConnectionRepository = + getOrCreateRepoForSubId(subId) private fun getOrCreateRepoForSubId(subId: Int) = subIdRepositoryCache[subId] diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt index 7e0c145696c9..cea6654a48de 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt @@ -21,7 +21,7 @@ import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.pipeline.dagger.MobileViewLog import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel import java.io.PrintWriter diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt index 507549b1e234..f4c572308e59 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt @@ -20,7 +20,7 @@ import android.view.View import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.pipeline.dagger.VerboseMobileViewLog import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger.Companion.getIdForLogging import com.android.systemui.statusbar.pipeline.mobile.ui.model.SignalIconModel diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt index cac0ae3dbab4..8a4d14e69652 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityInputLogger.kt @@ -20,7 +20,7 @@ import android.net.Network import android.net.NetworkCapabilities import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.pipeline.dagger.SharedConnectivityInputLog import com.android.systemui.statusbar.pipeline.shared.data.model.DefaultConnectionModel import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt index 328d901b541d..4b9de85915ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt @@ -19,7 +19,7 @@ package com.android.systemui.statusbar.pipeline.shared import android.net.Network import android.net.NetworkCapabilities import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel /** Helper object for logs that are shared between wifi and mobile. */ object LoggerHelper { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt index 058eda4400df..9a504c9534f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DefaultConnectionModel.kt @@ -17,7 +17,7 @@ package com.android.systemui.statusbar.pipeline.shared.data.model import android.net.NetworkCapabilities -import com.android.systemui.log.LogMessage +import com.android.systemui.log.core.LogMessage /** * A model for all of the current default connections(s). diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt index a4fbc2c93647..a57be665f105 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt @@ -96,7 +96,7 @@ constructor( networkId = DEMO_NET_ID, isValidated = validated ?: true, level = level ?: 0, - ssid = ssid, + ssid = ssid ?: DEMO_NET_SSID, // These fields below aren't supported in demo mode, since they aren't needed to satisfy // the interface. @@ -115,5 +115,6 @@ constructor( companion object { private const val DEMO_NET_ID = 1234 + private const val DEMO_NET_SSID = "Demo SSID" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt index 4a9ceacb0bd1..f244376f5a5a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt @@ -20,7 +20,7 @@ import android.net.Network import android.net.NetworkCapabilities import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.pipeline.dagger.WifiInputLog import com.android.systemui.statusbar.pipeline.shared.LoggerHelper import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt index 6ba2a81b4b13..096ad1f45841 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerLogger.kt @@ -22,7 +22,7 @@ import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED import com.android.internal.R import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.VERBOSE +import com.android.systemui.log.core.LogLevel.VERBOSE import com.android.systemui.log.dagger.DeviceStateAutoRotationLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt index 8cf9493586bc..ef07eed467dc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt @@ -18,8 +18,8 @@ package com.android.systemui.statusbar.policy import com.android.systemui.log.dagger.NotificationHeadsUpLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel.INFO -import com.android.systemui.log.LogLevel.VERBOSE +import com.android.systemui.log.core.LogLevel.INFO +import com.android.systemui.log.core.LogLevel.VERBOSE import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.logKey import javax.inject.Inject 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 a82646aba825..710588c82a4a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.policy; +import static android.hardware.biometrics.BiometricSourceType.FACE; + import android.annotation.NonNull; import android.content.BroadcastReceiver; import android.content.Context; @@ -199,6 +201,11 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum Trace.endSection(); } + private void notifyKeyguardFaceAuthEnabledChanged() { + // Copy the list to allow removal during callback. + new ArrayList<>(mCallbacks).forEach(Callback::onFaceAuthEnabledChanged); + } + private void notifyUnlockedChanged() { Trace.beginSection("KeyguardStateController#notifyUnlockedChanged"); // Copy the list to allow removal during callback. @@ -419,6 +426,16 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum } @Override + public void onBiometricEnrollmentStateChanged(BiometricSourceType biometricSourceType) { + if (biometricSourceType == FACE) { + // We only care about enrollment state here. Keyguard face auth enabled is just + // same as face auth enrolled + update(false); + notifyKeyguardFaceAuthEnabledChanged(); + } + } + + @Override public void onStartedWakingUp() { update(false /* updateAlways */); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt index 21d03386b9e2..cac5e3290a26 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt @@ -22,6 +22,13 @@ import android.app.PendingIntent import android.app.RemoteInput import android.content.Context import android.content.Intent +import android.graphics.Bitmap +import android.graphics.ImageDecoder +import android.graphics.drawable.AdaptiveIconDrawable +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.Icon import android.os.Build import android.os.Bundle import android.os.SystemClock @@ -48,7 +55,13 @@ import com.android.systemui.statusbar.policy.InflatedSmartReplyState.SuppressedA import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions import com.android.systemui.statusbar.policy.SmartReplyView.SmartButtonType import com.android.systemui.statusbar.policy.SmartReplyView.SmartReplies +import java.util.concurrent.FutureTask +import java.util.concurrent.SynchronousQueue +import java.util.concurrent.ThreadPoolExecutor +import java.util.concurrent.TimeUnit import javax.inject.Inject +import kotlin.system.measureTimeMillis + /** Returns whether we should show the smart reply view and its smart suggestions. */ fun shouldShowSmartReplyView( @@ -281,6 +294,51 @@ interface SmartActionInflater { ): Button } +private const val ICON_TASK_TIMEOUT_MS = 500L +private val iconTaskThreadPool = ThreadPoolExecutor(0, 25, 1, TimeUnit.MINUTES, SynchronousQueue()) + +private fun loadIconDrawableWithTimeout( + icon: Icon, + packageContext: Context, + targetSize: Int, +): Drawable? { + if (icon.type != Icon.TYPE_URI && icon.type != Icon.TYPE_URI_ADAPTIVE_BITMAP) { + return icon.loadDrawable(packageContext) + } + val bitmapTask = FutureTask { + val bitmap: Bitmap? + val durationMillis = measureTimeMillis { + val source = ImageDecoder.createSource(packageContext.contentResolver, icon.uri) + bitmap = ImageDecoder.decodeBitmap(source) { decoder, _, _ -> + decoder.setTargetSize(targetSize, targetSize) + decoder.allocator = ImageDecoder.ALLOCATOR_DEFAULT + } + } + if (durationMillis > ICON_TASK_TIMEOUT_MS) { + Log.w(TAG, "Loading $icon took ${durationMillis / 1000f} sec") + } + checkNotNull(bitmap) { "ImageDecoder.decodeBitmap() returned null" } + } + val bitmap = runCatching { + iconTaskThreadPool.execute(bitmapTask) + bitmapTask.get(ICON_TASK_TIMEOUT_MS, TimeUnit.MILLISECONDS) + }.getOrElse { ex -> + Log.e(TAG, "Failed to load $icon: $ex") + bitmapTask.cancel(true) + return null + } + // TODO(b/288561520): rewrite Icon so that we don't need to duplicate this logic + val bitmapDrawable = BitmapDrawable(packageContext.resources, bitmap) + val result = if (icon.type == Icon.TYPE_URI_ADAPTIVE_BITMAP) + AdaptiveIconDrawable(null, bitmapDrawable) else bitmapDrawable + if (icon.hasTint()) { + result.mutate() + result.setTintList(icon.tintList) + result.setTintBlendMode(icon.tintBlendMode) + } + return result +} + /* internal */ class SmartActionInflaterImpl @Inject constructor( private val constants: SmartReplyConstants, private val activityStarter: ActivityStarter, @@ -304,12 +362,12 @@ interface SmartActionInflater { // We received the Icon from the application - so use the Context of the application to // reference icon resources. - val iconDrawable = action.getIcon().loadDrawable(packageContext) - .apply { - val newIconSize: Int = context.resources.getDimensionPixelSize( - R.dimen.smart_action_button_icon_size) - setBounds(0, 0, newIconSize, newIconSize) - } + val newIconSize = context.resources + .getDimensionPixelSize(R.dimen.smart_action_button_icon_size) + val iconDrawable = + loadIconDrawableWithTimeout(action.getIcon(), packageContext, newIconSize) + ?: GradientDrawable() + iconDrawable.setBounds(0, 0, newIconSize, newIconSize) // Add the action icon to the Smart Action button. setCompoundDrawablesRelative(iconDrawable, null, null, null) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java index b135d0d8c9dc..1c3a8850df8d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java @@ -28,6 +28,7 @@ import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.HandlerExecutor; +import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings.Global; @@ -122,7 +123,12 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { userTracker.getUserId()) { @Override protected void handleValueChanged(int value, boolean observedChange) { - updateZenModeConfig(); + try { + Trace.beginSection("updateZenModeConfig"); + updateZenModeConfig(); + } finally { + Trace.endSection(); + } } }; mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 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 b1b8341d9584..d35d34063d1e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -27,11 +27,12 @@ import com.android.systemui.CoreStartable; import com.android.systemui.assist.AssistManager; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.statusbar.CommandQueue; - -import javax.inject.Inject; +import com.android.systemui.statusbar.KeyboardShortcuts; import dagger.Lazy; +import javax.inject.Inject; + /** * Status bar implementation for "large screen" products that mostly present no on-screen nav. * Serves as a collection of UI components, rather than showing its own UI. @@ -78,4 +79,9 @@ public class TvStatusBar implements CoreStartable, CommandQueue.Callbacks { new Intent(ACTION_SHOW_PIP_MENU).setPackage(mContext.getPackageName()), SYSTEMUI_PERMISSION); } + + @Override + public void toggleKeyboardShortcutsMenu(int deviceId) { + KeyboardShortcuts.show(mContext, deviceId); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java index bcf3b0cbfc86..4a9921eb2d83 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java @@ -240,8 +240,10 @@ public class StatusBarWindowController { Insets.of(0, safeTouchRegionHeight, 0, 0)); } lp.providedInsets = new InsetsFrameProvider[] { - new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()), - new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()), + new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()) + .setInsetsSize(Insets.of(0, height, 0, 0)), + new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()) + .setInsetsSize(Insets.of(0, height, 0, 0)), gestureInsetsProvider }; return lp; diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt index 066ac04c2727..a9d202911e93 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.temporarydisplay import android.view.View import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel /** A logger for temporary view changes -- see [TemporaryViewDisplayController]. */ open class TemporaryViewLogger<T : TemporaryViewInfo>( diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt index d55751b9d8a0..6706873ebeb4 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.temporarydisplay.chipbar import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.temporarydisplay.TemporaryViewLogger import com.android.systemui.temporarydisplay.dagger.ChipbarLog import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt index dfe748afbd41..c109eb4134bb 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt @@ -18,9 +18,9 @@ package com.android.systemui.toast import com.android.systemui.log.dagger.ToastLog import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel -import com.android.systemui.log.LogLevel.DEBUG -import com.android.systemui.log.LogMessage +import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.LogLevel.DEBUG +import com.android.systemui.log.core.LogMessage import javax.inject.Inject private const val TAG = "ToastLog" diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java index d9a8e0cfb53a..3f31ff94fee7 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java @@ -48,6 +48,7 @@ import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeControllerImpl; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.KeyboardShortcutsModule; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl; @@ -96,6 +97,7 @@ import javax.inject.Named; ReferenceScreenshotModule.class, StatusBarEventsModule.class, VolumeModule.class, + KeyboardShortcutsModule.class } ) public abstract class TvSystemUIModule { diff --git a/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt b/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt new file mode 100644 index 000000000000..cefd466374c0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/user/data/model/SelectedUserModel.kt @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.user.data.model + +import android.content.pm.UserInfo + +/** A model for the currently selected user. */ +data class SelectedUserModel( + /** Information about the user. */ + val userInfo: UserInfo, + /** The current status of the selection. */ + val selectionStatus: SelectionStatus, +) + +/** The current status of the selection. */ +enum class SelectionStatus { + /** This user has started being selected but the selection hasn't completed. */ + SELECTION_IN_PROGRESS, + /** The selection of this user has completed. */ + SELECTION_COMPLETE, +} diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt index 3de75ca2ed87..954765c4581d 100644 --- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt @@ -33,6 +33,8 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.settings.UserTracker +import com.android.systemui.user.data.model.SelectedUserModel +import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.model.UserSwitcherSettingsModel import com.android.systemui.util.settings.GlobalSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow @@ -69,6 +71,9 @@ interface UserRepository { /** List of all users on the device. */ val userInfos: Flow<List<UserInfo>> + /** Information about the currently-selected user, including [UserInfo] and other details. */ + val selectedUser: StateFlow<SelectedUserModel> + /** [UserInfo] of the currently-selected user. */ val selectedUserInfo: Flow<UserInfo> @@ -146,9 +151,6 @@ constructor( private val _userInfos = MutableStateFlow<List<UserInfo>?>(null) override val userInfos: Flow<List<UserInfo>> = _userInfos.filterNotNull() - private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null) - override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull() - override var mainUserId: Int = UserHandle.USER_NULL private set override var lastSelectedNonGuestUserId: Int = UserHandle.USER_NULL @@ -174,12 +176,57 @@ constructor( override var isRefreshUsersPaused: Boolean = false init { - observeSelectedUser() if (featureFlags.isEnabled(FACE_AUTH_REFACTOR)) { observeUserSwitching() } } + override val selectedUser: StateFlow<SelectedUserModel> = run { + // Some callbacks don't modify the selection status, so maintain the current value. + var currentSelectionStatus = SelectionStatus.SELECTION_COMPLETE + conflatedCallbackFlow { + fun send(selectionStatus: SelectionStatus) { + currentSelectionStatus = selectionStatus + trySendWithFailureLogging( + SelectedUserModel(tracker.userInfo, selectionStatus), + TAG, + ) + } + + val callback = + object : UserTracker.Callback { + override fun onUserChanging(newUser: Int, userContext: Context) { + send(SelectionStatus.SELECTION_IN_PROGRESS) + } + + override fun onUserChanged(newUser: Int, userContext: Context) { + send(SelectionStatus.SELECTION_COMPLETE) + } + + override fun onProfilesChanged(profiles: List<UserInfo>) { + send(currentSelectionStatus) + } + } + + tracker.addCallback(callback, mainDispatcher.asExecutor()) + send(currentSelectionStatus) + + awaitClose { tracker.removeCallback(callback) } + } + .onEach { + if (!it.userInfo.isGuest) { + lastSelectedNonGuestUserId = it.userInfo.id + } + } + .stateIn( + applicationScope, + SharingStarted.Eagerly, + initialValue = SelectedUserModel(tracker.userInfo, currentSelectionStatus) + ) + } + + override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo } + override fun refreshUsers() { applicationScope.launch { val result = withContext(backgroundDispatcher) { manager.aliveUsers } @@ -201,7 +248,7 @@ constructor( } override fun getSelectedUserInfo(): UserInfo { - return checkNotNull(_selectedUserInfo.value) + return selectedUser.value.userInfo } override fun isSimpleUserSwitcher(): Boolean { @@ -234,38 +281,6 @@ constructor( .launchIn(applicationScope) } - private fun observeSelectedUser() { - conflatedCallbackFlow { - fun send() { - trySendWithFailureLogging(tracker.userInfo, TAG) - } - - val callback = - object : UserTracker.Callback { - override fun onUserChanging(newUser: Int, userContext: Context) { - send() - } - - override fun onProfilesChanged(profiles: List<UserInfo>) { - send() - } - } - - tracker.addCallback(callback, mainDispatcher.asExecutor()) - send() - - awaitClose { tracker.removeCallback(callback) } - } - .onEach { - if (!it.isGuest) { - lastSelectedNonGuestUserId = it.id - } - - _selectedUserInfo.value = it - } - .launchIn(applicationScope) - } - private suspend fun getSettings(): UserSwitcherSettingsModel { return withContext(backgroundDispatcher) { val isSimpleUserSwitcher = diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt index 891ee0cf66d7..e32ed5fd3671 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/PackageManagerExt.kt @@ -16,12 +16,19 @@ package com.android.systemui.util.kotlin +import android.annotation.UserHandleAware import android.annotation.WorkerThread import android.content.pm.ComponentInfo import android.content.pm.PackageManager import com.android.systemui.util.Assert +/** + * Determines whether a component is actually enabled (not just its default value). + * + * @throws IllegalArgumentException if the component is not found + */ @WorkerThread +@UserHandleAware fun PackageManager.isComponentActuallyEnabled(componentInfo: ComponentInfo): Boolean { Assert.isNotMainThread() return when (getComponentEnabledSetting(componentInfo.componentName)) { diff --git a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt index 09268007dddc..d69b10ffb210 100644 --- a/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/util/wakelock/WakeLockLogger.kt @@ -18,7 +18,7 @@ package com.android.systemui.util.wakelock import android.os.PowerManager import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import javax.inject.Inject class WakeLockLogger @Inject constructor(@WakeLockLog private val buffer: LogBuffer) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 7456d349a933..93622200ad46 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -476,7 +476,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); - mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 4c7e6b007f38..5144d1966222 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -334,10 +334,8 @@ public final class WMShell implements } @Override - public void setImeWindowStatus(int displayId, IBinder token, - @InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition, - boolean showImeSwitcher) { + public void setImeWindowStatus(int displayId, IBinder token, int vis, + int backDisposition, boolean showImeSwitcher) { if (displayId == mDisplayTracker.getDefaultDisplayId() && (vis & InputMethodService.IME_VISIBLE) != 0) { oneHanded.stopOneHanded( diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 0dcd404d2fc5..231898884adf 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -21,21 +21,22 @@ import android.view.View import android.widget.TextView import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.flags.FeatureFlags -import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory +import com.android.systemui.log.LogBuffer import com.android.systemui.plugins.ClockAnimations import com.android.systemui.plugins.ClockController import com.android.systemui.plugins.ClockEvents -import com.android.systemui.plugins.ClockFaceController import com.android.systemui.plugins.ClockFaceConfig +import com.android.systemui.plugins.ClockFaceController import com.android.systemui.plugins.ClockFaceEvents import com.android.systemui.plugins.ClockTickRate -import com.android.systemui.log.LogBuffer import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.ConfigurationController @@ -64,7 +65,6 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import java.util.TimeZone import java.util.concurrent.Executor -import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import org.mockito.Mockito.`when` as whenever @RunWith(AndroidTestingRunner::class) @@ -122,7 +122,9 @@ class ClockEventControllerTest : SysuiTestCase() { bouncerRepository = bouncerRepository, configurationRepository = FakeConfigurationRepository(), ), - KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + ).keyguardTransitionInteractor, broadcastDispatcher, batteryController, keyguardUpdateMonitor, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index b21cc6dde815..9e561ed290f7 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -408,4 +408,18 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { any(ClockRegistry.ClockChangeListener.class)); verify(mClockEventController, times).registerListeners(mView); } + + @Test + public void testSplitShadeEnabledSetToSmartspaceController() { + mController.setSplitShadeEnabled(true); + verify(mSmartspaceController, times(1)).setSplitShadeEnabled(true); + verify(mSmartspaceController, times(0)).setSplitShadeEnabled(false); + } + + @Test + public void testSplitShadeDisabledSetToSmartspaceController() { + mController.setSplitShadeEnabled(false); + verify(mSmartspaceController, times(1)).setSplitShadeEnabled(false); + verify(mSmartspaceController, times(0)).setSplitShadeEnabled(true); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index 5a56bafc1992..d3b41902499c 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -97,7 +97,21 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { `when`(keyguardPinView.findViewById<NumPadButton>(R.id.delete_button)) .thenReturn(deleteButton) `when`(keyguardPinView.findViewById<View>(R.id.key_enter)).thenReturn(enterButton) - constructViewController() + pinViewController = + KeyguardPinViewController( + keyguardPinView, + keyguardUpdateMonitor, + securityMode, + lockPatternUtils, + mKeyguardSecurityCallback, + keyguardMessageAreaControllerFactory, + mLatencyTracker, + liftToActivateListener, + mEmergencyButtonController, + falsingCollector, + postureController, + featureFlags + ) } @Test @@ -121,10 +135,8 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true) `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3) `when`(passwordTextView.text).thenReturn("") - constructViewController() pinViewController.startAppearAnimation() - verify(deleteButton).visibility = View.INVISIBLE verify(enterButton).visibility = View.INVISIBLE verify(passwordTextView).setUsePinShapes(true) @@ -138,10 +150,8 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true) `when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6) `when`(passwordTextView.text).thenReturn("") - constructViewController() pinViewController.startAppearAnimation() - verify(deleteButton).visibility = View.VISIBLE verify(enterButton).visibility = View.VISIBLE verify(passwordTextView).setUsePinShapes(true) @@ -153,22 +163,4 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { pinViewController.handleAttemptLockout(0) verify(lockPatternUtils).getCurrentFailedPasswordAttempts(anyInt()) } - - fun constructViewController() { - pinViewController = - KeyguardPinViewController( - keyguardPinView, - keyguardUpdateMonitor, - securityMode, - lockPatternUtils, - mKeyguardSecurityCallback, - keyguardMessageAreaControllerFactory, - mLatencyTracker, - liftToActivateListener, - mEmergencyButtonController, - falsingCollector, - postureController, - featureFlags - ) - } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index e561f1f233b4..58b1edcc5511 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -62,12 +62,12 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.SideFpsController; import com.android.systemui.biometrics.SideFpsUiRequestSource; +import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor; import com.android.systemui.classifier.FalsingA11yDelegate; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; -import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.ActivityStarter; @@ -395,6 +395,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { // WHEN a request is made from the SimPin screens to show the next security method when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None); + when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true); mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish( /* authenticated= */true, TARGET_USER_ID, @@ -423,6 +424,28 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { } @Test + public void showNextSecurityScreenOrFinish_SimPin_Swipe() { + // GIVEN the current security method is SimPin + when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false); + when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)).thenReturn(false); + mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.SimPin); + + // WHEN a request is made from the SimPin screens to show the next security method + when(mKeyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.None); + // WHEN security method is SWIPE + when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false); + mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish( + /* authenticated= */true, + TARGET_USER_ID, + /* bypassSecondaryLockScreen= */true, + SecurityMode.SimPin); + + // THEN the next security method of None will dismiss keyguard. + verify(mViewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt()); + } + + + @Test public void onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() { KeyguardSecurityContainer.SwipeListener registeredSwipeListener = getRegisteredSwipeListener(); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index a2c632936047..512e5dc1a0d6 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -17,6 +17,7 @@ package com.android.keyguard; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -155,4 +156,18 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 20f, true); verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 20f, true); } + + @Test + public void splitShadeEnabledPassedToClockSwitchController() { + mController.setSplitShadeEnabled(true); + verify(mKeyguardClockSwitchController, times(1)).setSplitShadeEnabled(true); + verify(mKeyguardClockSwitchController, times(0)).setSplitShadeEnabled(false); + } + + @Test + public void splitShadeDisabledPassedToClockSwitchController() { + mController.setSplitShadeEnabled(false); + verify(mKeyguardClockSwitchController, times(1)).setSplitShadeEnabled(false); + verify(mKeyguardClockSwitchController, times(0)).setSplitShadeEnabled(true); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 901c3fb05b2f..5abab6239b1e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -83,6 +83,7 @@ import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.hardware.SensorPrivacyManager; +import android.hardware.biometrics.BiometricAuthenticator; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; @@ -3000,6 +3001,34 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { TelephonyManager.SIM_STATE_UNKNOWN); } + @Test + public void testOnSimStateChanged_HandleSimStateNotReady() { + KeyguardUpdateMonitorCallback keyguardUpdateMonitorCallback = spy( + KeyguardUpdateMonitorCallback.class); + mKeyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback); + mKeyguardUpdateMonitor.handleSimStateChange(-1, 0, TelephonyManager.SIM_STATE_NOT_READY); + verify(keyguardUpdateMonitorCallback).onSimStateChanged(-1, 0, + TelephonyManager.SIM_STATE_NOT_READY); + } + + @Test + public void onAuthEnrollmentChangesCallbacksAreNotified() { + KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class); + ArgumentCaptor<AuthController.Callback> authCallback = ArgumentCaptor.forClass( + AuthController.Callback.class); + verify(mAuthController).addCallback(authCallback.capture()); + + mKeyguardUpdateMonitor.registerCallback(callback); + + authCallback.getValue().onEnrollmentsChanged(TYPE_FINGERPRINT); + mTestableLooper.processAllMessages(); + verify(callback).onBiometricEnrollmentStateChanged(BiometricSourceType.FINGERPRINT); + + authCallback.getValue().onEnrollmentsChanged(BiometricAuthenticator.TYPE_FACE); + mTestableLooper.processAllMessages(); + verify(callback).onBiometricEnrollmentStateChanged(BiometricSourceType.FACE); + } + private void verifyFingerprintAuthenticateNeverCalled() { verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any()); verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(), diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java index c88c4d65f412..e7e1cc94b7c8 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java @@ -19,6 +19,7 @@ package com.android.keyguard; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1; import static com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR; +import static com.android.systemui.flags.Flags.MIGRATE_LOCK_ICON; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; @@ -143,6 +144,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { mFeatureFlags = new FakeFeatureFlags(); mFeatureFlags.set(FACE_AUTH_REFACTOR, false); + mFeatureFlags.set(MIGRATE_LOCK_ICON, false); mUnderTest = new LockIconViewController( mLockIconView, mStatusBarStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 79c87cfd1f3e..796e66514afe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -96,6 +96,7 @@ import com.android.systemui.log.ScreenDecorationsLogger; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.commandline.CommandRegistry; import com.android.systemui.statusbar.events.PrivacyDotViewController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.concurrency.FakeThreadFactory; @@ -139,6 +140,8 @@ public class ScreenDecorationsTest extends SysuiTestCase { @Mock private Display mDisplay; @Mock + private CommandRegistry mCommandRegistry; + @Mock private UserTracker mUserTracker; @Mock private PrivacyDotViewController mDotViewController; @@ -231,8 +234,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { mExecutor, new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")))); - mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings, - mUserTracker, mDisplayTracker, mDotViewController, mThreadFactory, + mScreenDecorations = spy(new ScreenDecorations(mContext, mSecureSettings, + mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController, + mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory, new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mAuthController) { @@ -1226,8 +1230,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { mFaceScanningProviders.add(mFaceScanningDecorProvider); when(mFaceScanningProviderFactory.getProviders()).thenReturn(mFaceScanningProviders); when(mFaceScanningProviderFactory.getHasProviders()).thenReturn(true); - ScreenDecorations screenDecorations = new ScreenDecorations(mContext, mExecutor, - mSecureSettings, mUserTracker, mDisplayTracker, mDotViewController, + ScreenDecorations screenDecorations = new ScreenDecorations(mContext, + mSecureSettings, mCommandRegistry, mUserTracker, mDisplayTracker, + mDotViewController, mThreadFactory, mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory, new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")), mAuthController); screenDecorations.start(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java index a63652c27a6c..f0425e923f26 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java @@ -53,6 +53,7 @@ import com.android.systemui.util.settings.SecureSettings; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; @@ -65,6 +66,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +// TODO(b/288926821): un-ignore once fixed +@Ignore("Needs deeper investigation/refactoring, see b/289212459 and b/289392705") @LargeTest @RunWith(AndroidTestingRunner.class) public class WindowMagnificationAnimationControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index 224875590d75..0e0d0e3f3748 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -43,11 +43,12 @@ import com.android.systemui.R import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityLaunchAnimator +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.statusbar.LockscreenShadeTransitionController @@ -58,6 +59,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.settings.SecureSettings import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Before import org.junit.Rule import org.junit.Test @@ -70,6 +72,7 @@ import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit +import javax.inject.Provider import org.mockito.Mockito.`when` as whenever private const val REQUEST_ID = 2L @@ -80,6 +83,7 @@ private const val DISPLAY_HEIGHT = 1920 private const val SENSOR_WIDTH = 30 private const val SENSOR_HEIGHT = 60 +@ExperimentalCoroutinesApi @SmallTest @RoboPilotTest @RunWith(AndroidJUnit4::class) @@ -116,6 +120,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Mock private lateinit var udfpsUtils: UdfpsUtils @Mock private lateinit var udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate + @Mock private lateinit var udfpsKeyguardViewModels: Provider<UdfpsKeyguardViewModels> @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams> private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true } @@ -148,6 +153,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { controllerCallback, onTouch, activityLaunchAnimator, featureFlags, primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, udfpsUtils, udfpsKeyguardAccessibilityDelegate, + udfpsKeyguardViewModels, ) block() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 30e54474bbde..58982d13481d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -22,6 +22,7 @@ import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_UP; import static com.android.internal.util.FunctionalUtils.ThrowingConsumer; +import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; @@ -59,6 +60,7 @@ import android.os.PowerManager; import android.os.RemoteException; import android.os.VibrationAttributes; import android.testing.TestableLooper.RunWithLooper; +import android.util.Pair; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; @@ -83,13 +85,14 @@ import com.android.systemui.biometrics.udfps.InteractionEvent; import com.android.systemui.biometrics.udfps.NormalizedTouchData; import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor; import com.android.systemui.biometrics.udfps.TouchProcessorResult; +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; +import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.ScreenLifecycle; -import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; +import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -219,6 +222,8 @@ public class UdfpsControllerTest extends SysuiTestCase { private SecureSettings mSecureSettings; @Mock private UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate; + @Mock + private Provider<UdfpsKeyguardViewModels> mUdfpsKeyguardViewModels; // Capture listeners so that they can be used to send events @Captor @@ -318,7 +323,7 @@ public class UdfpsControllerTest extends SysuiTestCase { mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker, mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils, mock(KeyguardFaceAuthInteractor.class), - mUdfpsKeyguardAccessibilityDelegate); + mUdfpsKeyguardAccessibilityDelegate, mUdfpsKeyguardViewModels); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture()); @@ -1200,8 +1205,53 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test + public void fingerDown_falsingManagerInformed() throws RemoteException { + final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = + givenAcceptFingerDownEvent(); + + // WHEN ACTION_DOWN is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + touchProcessorResult.first); + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + mBiometricExecutor.runAllReady(); + downEvent.recycle(); + + // THEN falsing manager is informed of the touch + verify(mFalsingManager).isFalseTouch(UDFPS_AUTHENTICATION); + } + + @Test public void onTouch_withNewTouchDetection_shouldCallNewFingerprintManagerPath() throws RemoteException { + final Pair<TouchProcessorResult, TouchProcessorResult> processorResultDownAndUp = + givenAcceptFingerDownEvent(); + + // WHEN ACTION_DOWN is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultDownAndUp.first); + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + mBiometricExecutor.runAllReady(); + downEvent.recycle(); + + // AND ACTION_UP is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultDownAndUp.second); + MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); + mBiometricExecutor.runAllReady(); + upEvent.recycle(); + + // THEN the new FingerprintManager path is invoked. + verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); + verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); + } + + private Pair<TouchProcessorResult, TouchProcessorResult> givenAcceptFingerDownEvent() + throws RemoteException { final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, 0L); final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( @@ -1227,27 +1277,7 @@ public class UdfpsControllerTest extends SysuiTestCase { verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - // WHEN ACTION_DOWN is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDown); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // AND ACTION_UP is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultUp); - MotionEvent upEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); - mBiometricExecutor.runAllReady(); - upEvent.recycle(); - - // THEN the new FingerprintManager path is invoked. - verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); - verify(mFingerprintManager).onPointerUp(anyLong(), anyInt(), anyInt(), anyFloat(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); + return new Pair<>(processorResultDown, processorResultUp); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt index 263ce1a2e9f5..8f8004f1cbb8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/LogContextInteractorImplTest.kt @@ -7,7 +7,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -54,10 +54,11 @@ class LogContextInteractorImplTest : SysuiTestCase() { LogContextInteractorImpl( testScope.backgroundScope, foldProvider, - KeyguardTransitionInteractor( - keyguardTransitionRepository, - testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + repository = keyguardTransitionRepository, + scope = testScope.backgroundScope, + ) + .keyguardTransitionInteractor, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt index 69547105d419..9260f63c1195 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/PackageUpdateMonitorTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.controls.controller import android.content.Context +import android.content.pm.PackageManager import android.os.Handler import android.os.UserHandle import android.testing.AndroidTestingRunner @@ -34,6 +35,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest @@ -42,12 +44,14 @@ class PackageUpdateMonitorTest : SysuiTestCase() { @Mock private lateinit var context: Context @Mock private lateinit var bgHandler: Handler + @Mock private lateinit var packageManager: PackageManager private lateinit var underTest: PackageUpdateMonitor @Before fun setup() { MockitoAnnotations.initMocks(this) + whenever(context.packageManager).thenReturn(packageManager) } @Test @@ -58,9 +62,16 @@ class PackageUpdateMonitorTest : SysuiTestCase() { // There are two receivers registered verify(context, times(2)) .registerReceiverAsUser(any(), eq(USER), any(), eq(null), eq(bgHandler)) + verify(packageManager).registerPackageMonitorCallback(any(), eq(USER.getIdentifier())) + // context will be used to get PackageManager, the test should clear invocations + // for next startMonitoring() assertion + clearInvocations(context) underTest.startMonitoring() + // No more interactions for registerReceiverAsUser verifyNoMoreInteractions(context) + // No more interactions for registerPackageMonitorCallback + verifyNoMoreInteractions(packageManager) } @Test @@ -69,12 +80,20 @@ class PackageUpdateMonitorTest : SysuiTestCase() { underTest.startMonitoring() clearInvocations(context) + clearInvocations(packageManager) underTest.stopMonitoring() verify(context).unregisterReceiver(any()) + verify(packageManager).unregisterPackageMonitorCallback(any()) + // context will be used to get PackageManager, the test should clear invocations + // for next stopMonitoring() assertion + clearInvocations(context) underTest.stopMonitoring() + // No more interactions for unregisterReceiver verifyNoMoreInteractions(context) + // No more interactions for unregisterPackageMonitorCallback + verifyNoMoreInteractions(packageManager) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt index bd029a727ee3..a341ca365ada 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt @@ -17,7 +17,7 @@ package com.android.systemui.dump import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.LogcatEchoTracker /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 88d527c17c9d..ddcbe7993697 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -217,6 +217,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { when(mStatusBarKeyguardViewManager.getViewRootImpl()).thenReturn(testViewRoot); when(mDreamingToLockscreenTransitionViewModel.getDreamOverlayAlpha()) .thenReturn(mock(Flow.class)); + when(mDreamingToLockscreenTransitionViewModel.getTransitionEnded()) + .thenReturn(mock(Flow.class)); mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext, mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController, mConfigurationController, mViewMediator, mKeyguardBypassController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt index b4bd473b8b8c..925ac30b99fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt @@ -10,7 +10,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.shared.model.WakeSleepReason @@ -67,10 +67,11 @@ class ResourceTrimmerTest : SysuiTestCase() { resourceTrimmer = ResourceTrimmer( keyguardInteractor, - KeyguardTransitionInteractor( - keyguardTransitionRepository, - testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor, globalWindowManager, testScope.backgroundScope, testDispatcher, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt index 5922cbf6268e..d62db5d1b890 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt @@ -49,7 +49,7 @@ import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.AuthenticationStatus import com.android.systemui.keyguard.shared.model.DetectionStatus import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus @@ -216,7 +216,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { ) keyguardTransitionRepository = FakeKeyguardTransitionRepository() val keyguardTransitionInteractor = - KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope) + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor return DeviceEntryFaceAuthRepositoryImpl( mContext, fmOverride, @@ -260,6 +264,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { assertThat(authStatus()).isEqualTo(SuccessAuthenticationStatus(successResult)) assertThat(authenticated()).isTrue() assertThat(authRunning()).isFalse() + assertThat(canFaceAuthRun()).isFalse() } private fun uiEventIsLogged(faceAuthUiEvent: FaceAuthUiEvent) { @@ -417,13 +422,9 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { FACE_ERROR_CANCELED, "First auth attempt cancellation completed" ) - assertThat(authStatus()) - .isEqualTo( - ErrorAuthenticationStatus( - FACE_ERROR_CANCELED, - "First auth attempt cancellation completed" - ) - ) + val value = authStatus() as ErrorAuthenticationStatus + assertThat(value.msgId).isEqualTo(FACE_ERROR_CANCELED) + assertThat(value.msg).isEqualTo("First auth attempt cancellation completed") faceAuthenticateIsCalled() uiEventIsLogged(FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN) @@ -551,20 +552,6 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } @Test - fun authenticateDoesNotRunWhenDeviceIsSleeping() = - testScope.runTest { - testGatingCheckForFaceAuth { - keyguardRepository.setWakefulnessModel( - WakefulnessModel( - state = WakefulnessState.ASLEEP, - lastWakeReason = WakeSleepReason.OTHER, - lastSleepReason = WakeSleepReason.OTHER, - ) - ) - } - } - - @Test fun authenticateDoesNotRunWhenNonStrongBiometricIsNotAllowed() = testScope.runTest { testGatingCheckForFaceAuth { @@ -586,6 +573,29 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } @Test + fun authenticateRunsWhenSecureCameraIsActiveIfBouncerIsShowing() = + testScope.runTest { + initCollectors() + allPreconditionsToRunFaceAuthAreTrue() + bouncerRepository.setAlternateVisible(false) + bouncerRepository.setPrimaryShow(false) + + assertThat(canFaceAuthRun()).isTrue() + + // launch secure camera + fakeCommandQueue.doForEachCallback { + it.onCameraLaunchGestureDetected(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP) + } + keyguardRepository.setKeyguardOccluded(true) + runCurrent() + assertThat(canFaceAuthRun()).isFalse() + + // but bouncer is shown after that. + bouncerRepository.setPrimaryShow(true) + assertThat(canFaceAuthRun()).isTrue() + } + + @Test fun authenticateDoesNotRunOnUnsupportedPosture() = testScope.runTest { testGatingCheckForFaceAuth { @@ -616,6 +626,27 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } @Test + fun authenticateFallbacksToDetectionWhenUserIsAlreadyTrustedByTrustManager() = + testScope.runTest { + whenever(faceManager.sensorPropertiesInternal) + .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true))) + whenever(bypassController.bypassEnabled).thenReturn(true) + underTest = createDeviceEntryFaceAuthRepositoryImpl() + initCollectors() + allPreconditionsToRunFaceAuthAreTrue() + + trustRepository.setCurrentUserTrusted(true) + assertThat(canFaceAuthRun()).isFalse() + underTest.authenticate( + FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, + fallbackToDetection = true + ) + faceAuthenticateIsNotCalled() + + faceDetectIsCalled() + } + + @Test fun everythingWorksWithFaceAuthRefactorFlagDisabled() = testScope.runTest { featureFlags.set(FACE_AUTH_REFACTOR, false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index 80700e59a2d4..3e81cd336824 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor +import android.hardware.biometrics.BiometricFaceConstants import android.os.Handler import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -30,6 +31,7 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInte import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.bouncer.ui.BouncerView import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.coroutines.collectLastValue import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags @@ -39,6 +41,7 @@ import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepo import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.FakeTrustRepository +import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -86,10 +89,15 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { faceAuthRepository = FakeDeviceEntryFaceAuthRepository() keyguardTransitionRepository = FakeKeyguardTransitionRepository() keyguardTransitionInteractor = - KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope) + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor underTest = SystemUIKeyguardFaceAuthInteractor( + mContext, testScope.backgroundScope, dispatcher, faceAuthRepository, @@ -144,6 +152,22 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { } @Test + fun whenFaceIsLockedOutAnyAttemptsToTriggerFaceAuthMustProvideLockoutError() = + testScope.runTest { + underTest.start() + val authenticationStatus = collectLastValue(underTest.authenticationStatus) + faceAuthRepository.setLockedOut(true) + + underTest.onDeviceLifted() + + val outputValue = authenticationStatus()!! as ErrorAuthenticationStatus + assertThat(outputValue.msgId) + .isEqualTo(BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT) + assertThat(outputValue.msg).isEqualTo("Face Unlock unavailable") + assertThat(faceAuthRepository.runningAuthRequest.value).isNull() + } + + @Test fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromAodState() = testScope.runTest { underTest.start() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt index e9c22f9d551d..0050d64d7505 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt @@ -292,10 +292,11 @@ class KeyguardLongPressInteractorTest : SysuiTestCase() { appContext = mContext, scope = testScope.backgroundScope, transitionInteractor = - KeyguardTransitionInteractor( - keyguardTransitionRepository, - testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = testScope.backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor, repository = keyguardRepository, logger = logger, featureFlags = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt index fa4941cbb895..9e9c25eafa33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt @@ -17,9 +17,9 @@ package com.android.systemui.keyguard.domain.interactor -import com.android.systemui.RoboPilotTest import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository @@ -54,7 +54,10 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - underTest = KeyguardTransitionInteractor(repository, testScope.backgroundScope) + underTest = KeyguardTransitionInteractorFactory.create( + scope = testScope.backgroundScope, + repository = repository, + ).keyguardTransitionInteractor } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index b5590154f7f4..d01a46e06b9b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN +import com.android.keyguard.TestScopeProvider import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.flags.FakeFeatureFlags @@ -92,76 +93,97 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - testScope = TestScope() + testScope = TestScopeProvider.getTestScope() keyguardRepository = FakeKeyguardRepository() bouncerRepository = FakeKeyguardBouncerRepository() shadeRepository = FakeShadeRepository() transitionRepository = spy(FakeKeyguardTransitionRepository()) - transitionInteractor = KeyguardTransitionInteractor( - transitionRepository, testScope.backgroundScope) whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN) featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) } - fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - shadeRepository = shadeRepository, - ).apply { start() } - - fromPrimaryBouncerTransitionInteractor = FromPrimaryBouncerTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - keyguardSecurityModel = keyguardSecurityModel, - ).apply { start() } - - fromDreamingTransitionInteractor = FromDreamingTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromAodTransitionInteractor = FromAodTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromGoneTransitionInteractor = FromGoneTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromDozingTransitionInteractor = FromDozingTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromOccludedTransitionInteractor = FromOccludedTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } - - fromAlternateBouncerTransitionInteractor = FromAlternateBouncerTransitionInteractor( - scope = testScope, - keyguardInteractor = createKeyguardInteractor(), - transitionRepository = transitionRepository, - transitionInteractor = transitionInteractor, - ).apply { start() } + transitionInteractor = + KeyguardTransitionInteractorFactory.create( + scope = testScope, + repository = transitionRepository, + ) + .keyguardTransitionInteractor + + fromLockscreenTransitionInteractor = + FromLockscreenTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + shadeRepository = shadeRepository, + ) + .apply { start() } + + fromPrimaryBouncerTransitionInteractor = + FromPrimaryBouncerTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + keyguardSecurityModel = keyguardSecurityModel, + ) + .apply { start() } + + fromDreamingTransitionInteractor = + FromDreamingTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromAodTransitionInteractor = + FromAodTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromGoneTransitionInteractor = + FromGoneTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromDozingTransitionInteractor = + FromDozingTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromOccludedTransitionInteractor = + FromOccludedTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } + + fromAlternateBouncerTransitionInteractor = + FromAlternateBouncerTransitionInteractor( + scope = testScope, + keyguardInteractor = createKeyguardInteractor(), + transitionRepository = transitionRepository, + transitionInteractor = transitionInteractor, + ) + .apply { start() } } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt index 08e99dc6b7d0..6e7ba6dd1ecb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt @@ -46,7 +46,11 @@ class LightRevealScrimInteractorTest : SysuiTestCase() { private val fakeLightRevealScrimRepository = FakeLightRevealScrimRepository() private val keyguardTransitionInteractor = - KeyguardTransitionInteractor(fakeKeyguardTransitionRepository, TestScope().backgroundScope) + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = fakeKeyguardTransitionRepository, + ) + .keyguardTransitionInteractor private lateinit var underTest: LightRevealScrimInteractor diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt new file mode 100644 index 000000000000..1baca2184e9b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.domain.interactor + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.doze.util.BurnInHelperWrapper +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeCommandQueue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.keyguard.shared.model.WakeSleepReason +import com.android.systemui.keyguard.shared.model.WakefulnessModel +import com.android.systemui.keyguard.shared.model.WakefulnessState +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class UdfpsKeyguardInteractorTest : SysuiTestCase() { + private val burnInProgress = 1f + private val burnInYOffset = 20 + private val burnInXOffset = 10 + + private lateinit var testScope: TestScope + private lateinit var configRepository: FakeConfigurationRepository + private lateinit var bouncerRepository: KeyguardBouncerRepository + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var fakeCommandQueue: FakeCommandQueue + private lateinit var featureFlags: FakeFeatureFlags + private lateinit var burnInInteractor: BurnInInteractor + + @Mock private lateinit var burnInHelper: BurnInHelperWrapper + + private lateinit var underTest: UdfpsKeyguardInteractor + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + testScope = TestScope() + configRepository = FakeConfigurationRepository() + keyguardRepository = FakeKeyguardRepository() + bouncerRepository = FakeKeyguardBouncerRepository() + fakeCommandQueue = FakeCommandQueue() + featureFlags = + FakeFeatureFlags().apply { + set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true) + set(Flags.FACE_AUTH_REFACTOR, false) + } + burnInInteractor = + BurnInInteractor( + context, + burnInHelper, + testScope.backgroundScope, + configRepository, + FakeSystemClock(), + ) + + underTest = + UdfpsKeyguardInteractor( + configRepository, + burnInInteractor, + KeyguardInteractor( + keyguardRepository, + fakeCommandQueue, + featureFlags, + bouncerRepository, + configRepository, + ), + ) + } + + @Test + fun dozeChanges_updatesUdfpsAodModel() = + testScope.runTest { + val burnInOffsets by collectLastValue(underTest.burnInOffsets) + initializeBurnInOffsets() + + // WHEN we're not dozing + setAwake() + runCurrent() + + // THEN burn in offsets are 0 + assertThat(burnInOffsets?.burnInProgress).isEqualTo(0f) + assertThat(burnInOffsets?.burnInYOffset).isEqualTo(0) + assertThat(burnInOffsets?.burnInXOffset).isEqualTo(0) + + // WHEN we're in the middle of the doze amount change + keyguardRepository.setDozeAmount(.50f) + runCurrent() + + // THEN burn in is updated (between 0 and the full offset) + assertThat(burnInOffsets?.burnInProgress).isGreaterThan(0f) + assertThat(burnInOffsets?.burnInYOffset).isGreaterThan(0) + assertThat(burnInOffsets?.burnInXOffset).isGreaterThan(0) + assertThat(burnInOffsets?.burnInProgress).isLessThan(burnInProgress) + assertThat(burnInOffsets?.burnInYOffset).isLessThan(burnInYOffset) + assertThat(burnInOffsets?.burnInXOffset).isLessThan(burnInXOffset) + + // WHEN we're fully dozing + keyguardRepository.setDozeAmount(1f) + runCurrent() + + // THEN burn in offsets are updated to final current values (for the given time) + assertThat(burnInOffsets?.burnInProgress).isEqualTo(burnInProgress) + assertThat(burnInOffsets?.burnInYOffset).isEqualTo(burnInYOffset) + assertThat(burnInOffsets?.burnInXOffset).isEqualTo(burnInXOffset) + } + + private fun initializeBurnInOffsets() { + whenever(burnInHelper.burnInProgressOffset()).thenReturn(burnInProgress) + whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(true))) + .thenReturn(burnInXOffset) + whenever(burnInHelper.burnInOffset(anyInt(), /* xAxis */ eq(false))) + .thenReturn(burnInYOffset) + } + + private fun setAwake() { + keyguardRepository.setDozeAmount(0f) + burnInInteractor.dozeTimeTick() + + bouncerRepository.setAlternateVisible(false) + keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) + bouncerRepository.setPrimaryShow(false) + keyguardRepository.setWakefulnessModel( + WakefulnessModel( + WakefulnessState.AWAKE, + WakeSleepReason.POWER_BUTTON, + WakeSleepReason.POWER_BUTTON, + ) + ) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt new file mode 100644 index 000000000000..2e97208e44de --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/DefaultLockscreenLayoutTest.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.view.layout + +import android.graphics.Point +import android.view.ViewGroup +import android.view.WindowManager +import androidx.constraintlayout.widget.ConstraintSet +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.AuthController +import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.monet.utils.ArgbSubject.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Answers +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@RunWith(JUnit4::class) +@SmallTest +class DefaultLockscreenLayoutTest : SysuiTestCase() { + private lateinit var defaultLockscreenLayout: DefaultLockscreenLayout + private lateinit var rootView: KeyguardRootView + @Mock private lateinit var authController: AuthController + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + rootView = KeyguardRootView(context, null) + defaultLockscreenLayout = + DefaultLockscreenLayout(authController, keyguardUpdateMonitor, windowManager, context) + } + + @Test + fun testLayoutViews_KeyguardIndicationArea() { + defaultLockscreenLayout.layoutViews(rootView) + val constraint = getViewConstraint(R.id.keyguard_indication_area) + assertThat(constraint.layout.bottomToBottom).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.mWidth).isEqualTo(ViewGroup.LayoutParams.MATCH_PARENT) + assertThat(constraint.layout.mHeight).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT) + } + + @Test + fun testLayoutViews_lockIconView() { + defaultLockscreenLayout.layoutViews(rootView) + val constraint = getViewConstraint(R.id.lock_icon_view) + assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID) + } + + @Test + fun testCenterLockIcon() { + defaultLockscreenLayout.centerLockIcon(Point(5, 6), 1F, 5, rootView) + val constraint = getViewConstraint(R.id.lock_icon_view) + + assertThat(constraint.layout.mWidth).isEqualTo(2) + assertThat(constraint.layout.mHeight).isEqualTo(2) + assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID) + assertThat(constraint.layout.topMargin).isEqualTo(5) + assertThat(constraint.layout.startMargin).isEqualTo(4) + } + + /** Get the ConstraintLayout constraint of the view. */ + private fun getViewConstraint(viewId: Int): ConstraintSet.Constraint { + val constraintSet = ConstraintSet() + constraintSet.clone(rootView) + return constraintSet.getConstraint(viewId) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt new file mode 100644 index 000000000000..145b2fdb000f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerCommandListenerTest.kt @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.view.layout + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.commandline.Command +import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.mockito.withArgCaptor +import java.io.PrintWriter +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mock +import org.mockito.Mockito.atLeastOnce +import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(JUnit4::class) +@SmallTest +class KeyguardLayoutManagerCommandListenerTest : SysuiTestCase() { + private lateinit var keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener + @Mock private lateinit var commandRegistry: CommandRegistry + @Mock private lateinit var keyguardLayoutManager: KeyguardLayoutManager + @Mock private lateinit var pw: PrintWriter + private lateinit var command: () -> Command + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + keyguardLayoutManagerCommandListener = + KeyguardLayoutManagerCommandListener( + commandRegistry, + keyguardLayoutManager, + ) + keyguardLayoutManagerCommandListener.start() + command = + withArgCaptor<() -> Command> { + verify(commandRegistry).registerCommand(eq("layout"), capture()) + } + } + + @Test + fun testHelp() { + command().execute(pw, listOf("help")) + verify(pw, atLeastOnce()).println(anyString()) + verify(keyguardLayoutManager, never()).transitionToLayout(anyString()) + } + + @Test + fun testBlank() { + command().execute(pw, listOf()) + verify(pw, atLeastOnce()).println(anyString()) + verify(keyguardLayoutManager, never()).transitionToLayout(anyString()) + } + + @Test + fun testValidArg() { + bindFakeIdMapToLayoutManager() + command().execute(pw, listOf("fake")) + verify(keyguardLayoutManager).transitionToLayout("fake") + } + + private fun bindFakeIdMapToLayoutManager() { + val map = mapOf("fake" to mock(LockscreenLayout::class.java)) + whenever(keyguardLayoutManager.layoutIdMap).thenReturn(map) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt new file mode 100644 index 000000000000..95b2030de923 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardLayoutManagerTest.kt @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.view.layout + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.ui.view.KeyguardRootView +import com.android.systemui.keyguard.ui.view.layout.DefaultLockscreenLayout.Companion.DEFAULT +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(JUnit4::class) +@SmallTest +class KeyguardLayoutManagerTest : SysuiTestCase() { + private lateinit var keyguardLayoutManager: KeyguardLayoutManager + @Mock lateinit var configurationController: ConfigurationController + @Mock lateinit var defaultLockscreenLayout: DefaultLockscreenLayout + @Mock lateinit var keyguardRootView: KeyguardRootView + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + whenever(defaultLockscreenLayout.id).thenReturn(DEFAULT) + keyguardLayoutManager = + KeyguardLayoutManager( + configurationController, + setOf(defaultLockscreenLayout), + keyguardRootView + ) + } + + @Test + fun testDefaultLayout() { + keyguardLayoutManager.transitionToLayout(DEFAULT) + verify(defaultLockscreenLayout).layoutViews(keyguardRootView) + } + + @Test + fun testTransitionToLayout_validId() { + assertThat(keyguardLayoutManager.transitionToLayout(DEFAULT)).isTrue() + } + @Test + fun testTransitionToLayout_invalidId() { + assertThat(keyguardLayoutManager.transitionToLayout("abc")).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt index a3413466d62e..c67f53519957 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt @@ -22,8 +22,18 @@ import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.KeyguardState.AOD +import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING +import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING +import com.android.systemui.keyguard.shared.model.KeyguardState.GONE +import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED +import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED +import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING +import com.android.systemui.keyguard.shared.model.TransitionState.STARTED import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.util.mockito.mock import com.google.common.collect.Range @@ -47,7 +57,12 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = DreamingToLockscreenTransitionViewModel(interactor, mock()) } @@ -60,7 +75,7 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.dreamOverlayTranslationY(pixels).onEach { values.add(it) }.launchIn(this) - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0f, STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.5f)) @@ -82,7 +97,7 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.dreamOverlayAlpha.onEach { values.add(it) }.launchIn(this) // Should start running here... - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0f, STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.1f)) repository.sendTransitionStep(step(0.5f)) @@ -104,7 +119,7 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this) - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0f, STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.1f)) repository.sendTransitionStep(step(0.2f)) @@ -126,7 +141,7 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { val job = underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this) - repository.sendTransitionStep(step(0f, TransitionState.STARTED)) + repository.sendTransitionStep(step(0f, STARTED)) repository.sendTransitionStep(step(0f)) repository.sendTransitionStep(step(0.3f)) repository.sendTransitionStep(step(0.5f)) @@ -138,13 +153,44 @@ class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() { job.cancel() } - private fun step( - value: Float, - state: TransitionState = TransitionState.RUNNING - ): TransitionStep { + @Test + fun transitionEnded() = + runTest(UnconfinedTestDispatcher()) { + val values = mutableListOf<TransitionStep>() + + val job = underTest.transitionEnded.onEach { values.add(it) }.launchIn(this) + + repository.sendTransitionStep(TransitionStep(DOZING, DREAMING, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(DOZING, DREAMING, 1.0f, FINISHED)) + + repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 0.1f, RUNNING)) + repository.sendTransitionStep(TransitionStep(DREAMING, LOCKSCREEN, 1.0f, FINISHED)) + + repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 0.5f, RUNNING)) + repository.sendTransitionStep(TransitionStep(LOCKSCREEN, DREAMING, 1.0f, FINISHED)) + + repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 0.5f, RUNNING)) + repository.sendTransitionStep(TransitionStep(DREAMING, GONE, 1.0f, CANCELED)) + + repository.sendTransitionStep(TransitionStep(DREAMING, AOD, 0.0f, STARTED)) + repository.sendTransitionStep(TransitionStep(DREAMING, AOD, 1.0f, FINISHED)) + + assertThat(values.size).isEqualTo(3) + values.forEach { + assertThat(it.transitionState == FINISHED || it.transitionState == CANCELED) + .isTrue() + } + + job.cancel() + } + + private fun step(value: Float, state: TransitionState = RUNNING): TransitionStep { return TransitionStep( - from = KeyguardState.DREAMING, - to = KeyguardState.LOCKSCREEN, + from = DREAMING, + to = LOCKSCREEN, value = value, transitionState = state, ownerName = "DreamingToLockscreenTransitionViewModelTest" diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt index 694539b0cbfe..75c8bff326b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt @@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -46,7 +46,12 @@ class GoneToDreamingTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = GoneToDreamingTransitionViewModel(interactor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 29886d5481b9..d02b3fccef1f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -41,13 +41,12 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry import com.android.systemui.keyguard.shared.quickaffordance.ActivationState import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition @@ -210,10 +209,10 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { appContext = mContext, scope = testScope.backgroundScope, transitionInteractor = - KeyguardTransitionInteractor( - repository = FakeKeyguardTransitionRepository(), - scope = testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + ) + .keyguardTransitionInteractor, repository = repository, logger = UiEventLoggerFake(), featureFlags = featureFlags, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt index ea17751782c4..12fe07f0827e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt @@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -46,7 +46,12 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = LockscreenToDreamingTransitionViewModel(interactor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt index bf56a981fa31..83ae631ed164 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt @@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -46,7 +46,12 @@ class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = LockscreenToOccludedTransitionViewModel(interactor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt index 34da26ecc0bf..88603999cdd7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt @@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -46,7 +46,12 @@ class OccludedToLockscreenTransitionViewModelTest : SysuiTestCase() { @Before fun setUp() { repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = OccludedToLockscreenTransitionViewModel(interactor) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt index f88b71d469cf..d8c78ebdca49 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt @@ -22,7 +22,7 @@ import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.ScrimAlpha import com.android.systemui.keyguard.shared.model.TransitionState @@ -55,7 +55,12 @@ class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) repository = FakeKeyguardTransitionRepository() - val interactor = KeyguardTransitionInteractor(repository, TestScope().backgroundScope) + val interactor = + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = repository, + ) + .keyguardTransitionInteractor underTest = PrimaryBouncerToGoneTransitionViewModel( interactor, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt new file mode 100644 index 000000000000..436c09ca4a05 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsAodViewModelTest.kt @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.RoboPilotTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.doze.util.BurnInHelperWrapper +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeCommandQueue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.domain.interactor.BurnInInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@ExperimentalCoroutinesApi +@SmallTest +@RoboPilotTest +@RunWith(AndroidJUnit4::class) +class UdfpsAodViewModelTest : SysuiTestCase() { + private val defaultPadding = 12 + private lateinit var underTest: UdfpsAodViewModel + + private lateinit var testScope: TestScope + private lateinit var configRepository: FakeConfigurationRepository + private lateinit var bouncerRepository: KeyguardBouncerRepository + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var fakeCommandQueue: FakeCommandQueue + private lateinit var featureFlags: FakeFeatureFlags + + @Mock private lateinit var burnInHelper: BurnInHelperWrapper + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + overrideResource(com.android.systemui.R.dimen.lock_icon_padding, defaultPadding) + testScope = TestScope() + configRepository = FakeConfigurationRepository() + keyguardRepository = FakeKeyguardRepository() + bouncerRepository = FakeKeyguardBouncerRepository() + fakeCommandQueue = FakeCommandQueue() + featureFlags = + FakeFeatureFlags().apply { + set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true) + set(Flags.FACE_AUTH_REFACTOR, false) + } + + val udfpsKeyguardInteractor = + UdfpsKeyguardInteractor( + configRepository, + BurnInInteractor( + context, + burnInHelper, + testScope.backgroundScope, + configRepository, + FakeSystemClock(), + ), + KeyguardInteractor( + keyguardRepository, + fakeCommandQueue, + featureFlags, + bouncerRepository, + configRepository, + ), + ) + + underTest = + UdfpsAodViewModel( + udfpsKeyguardInteractor, + context, + ) + } + + @Test + fun alphaAndVisibleUpdates_onDozeAmountChanges() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha) + val visible by collectLastValue(underTest.isVisible) + + keyguardRepository.setDozeAmount(0f) + runCurrent() + assertThat(alpha).isEqualTo(0f) + assertThat(visible).isFalse() + + keyguardRepository.setDozeAmount(.65f) + runCurrent() + assertThat(alpha).isEqualTo(.65f) + assertThat(visible).isTrue() + + keyguardRepository.setDozeAmount(.23f) + runCurrent() + assertThat(alpha).isEqualTo(.23f) + assertThat(visible).isTrue() + + keyguardRepository.setDozeAmount(1f) + runCurrent() + assertThat(alpha).isEqualTo(1f) + assertThat(visible).isTrue() + } + + @Test + fun paddingUpdates_onScaleForResolutionChanges() = + testScope.runTest { + val padding by collectLastValue(underTest.padding) + + configRepository.setScaleForResolution(1f) + runCurrent() + assertThat(padding).isEqualTo(defaultPadding) + + configRepository.setScaleForResolution(2f) + runCurrent() + assertThat(padding).isEqualTo(defaultPadding * 2) + + configRepository.setScaleForResolution(.5f) + runCurrent() + assertThat(padding).isEqualTo((defaultPadding * .5f).toInt()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt new file mode 100644 index 000000000000..a30e2a601e9d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.doze.util.BurnInHelperWrapper +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.FakeCommandQueue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.BurnInInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +/** Tests UdfpsFingerprintViewModel specific flows. */ +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class UdfpsFingerprintViewModelTest : SysuiTestCase() { + private val defaultPadding = 12 + private lateinit var underTest: FingerprintViewModel + + private lateinit var testScope: TestScope + private lateinit var configRepository: FakeConfigurationRepository + private lateinit var bouncerRepository: KeyguardBouncerRepository + private lateinit var keyguardRepository: FakeKeyguardRepository + private lateinit var fakeCommandQueue: FakeCommandQueue + private lateinit var featureFlags: FakeFeatureFlags + private lateinit var transitionRepository: FakeKeyguardTransitionRepository + + @Mock private lateinit var burnInHelper: BurnInHelperWrapper + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + overrideResource(com.android.systemui.R.dimen.lock_icon_padding, defaultPadding) + testScope = TestScope() + configRepository = FakeConfigurationRepository() + keyguardRepository = FakeKeyguardRepository() + bouncerRepository = FakeKeyguardBouncerRepository() + fakeCommandQueue = FakeCommandQueue() + featureFlags = + FakeFeatureFlags().apply { + set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true) + set(Flags.FACE_AUTH_REFACTOR, false) + } + bouncerRepository = FakeKeyguardBouncerRepository() + transitionRepository = FakeKeyguardTransitionRepository() + val transitionInteractor = + KeyguardTransitionInteractor( + transitionRepository, + testScope.backgroundScope, + ) + val udfpsKeyguardInteractor = + UdfpsKeyguardInteractor( + configRepository, + BurnInInteractor( + context, + burnInHelper, + testScope.backgroundScope, + configRepository, + FakeSystemClock(), + ), + KeyguardInteractor( + keyguardRepository, + fakeCommandQueue, + featureFlags, + bouncerRepository, + configRepository, + ), + ) + + underTest = + FingerprintViewModel( + context, + transitionInteractor, + udfpsKeyguardInteractor, + ) + } + + @Test + fun paddingUpdates_onScaleForResolutionChanges() = + testScope.runTest { + val padding by collectLastValue(underTest.padding) + + configRepository.setScaleForResolution(1f) + runCurrent() + assertThat(padding).isEqualTo(defaultPadding) + + configRepository.setScaleForResolution(2f) + runCurrent() + assertThat(padding).isEqualTo(defaultPadding * 2) + + configRepository.setScaleForResolution(.5f) + runCurrent() + assertThat(padding).isEqualTo((defaultPadding * .5).toInt()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt new file mode 100644 index 000000000000..d58ceee40c68 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt @@ -0,0 +1,505 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.settingslib.Utils +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.google.common.collect.Range +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.MockitoAnnotations + +/** Tests UDFPS lockscreen view model transitions. */ +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class UdfpsLockscreenViewModelTest : SysuiTestCase() { + private val lockscreenColorResId = android.R.attr.textColorPrimary + private val alternateBouncerResId = com.android.internal.R.attr.materialColorOnPrimaryFixed + private val lockscreenColor = Utils.getColorAttrDefaultColor(context, lockscreenColorResId) + private val alternateBouncerColor = + Utils.getColorAttrDefaultColor(context, alternateBouncerResId) + + private lateinit var underTest: UdfpsLockscreenViewModel + private lateinit var testScope: TestScope + private lateinit var transitionRepository: FakeKeyguardTransitionRepository + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testScope = TestScope() + transitionRepository = FakeKeyguardTransitionRepository() + val transitionInteractor = + KeyguardTransitionInteractor( + transitionRepository, + testScope.backgroundScope, + ) + underTest = + UdfpsLockscreenViewModel( + context, + lockscreenColorResId, + alternateBouncerResId, + transitionInteractor, + ) + } + + @Test + fun goneToAodTransition() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: gone -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "goneToAodTransition", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(visible).isFalse() + + // TransitionState.RUNNING: gone -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "goneToAodTransition", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(visible).isFalse() + + // TransitionState.FINISHED: gone -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.GONE, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "goneToAodTransition", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(visible).isFalse() + } + + @Test + fun lockscreenToAod() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: lockscreen -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "lockscreenToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.RUNNING: lockscreen -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "lockscreenToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f)) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: lockscreen -> AOD + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "lockscreenToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isFalse() + } + + @Test + fun aodToLockscreen() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: AOD -> lockscreen + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "aodToLockscreen", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isFalse() + + // TransitionState.RUNNING: AOD -> lockscreen + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "aodToLockscreen", + ) + ) + runCurrent() + assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f)) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: AOD -> lockscreen + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "aodToLockscreen", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + } + + @Test + fun lockscreenToAlternateBouncer() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: lockscreen -> alternate bouncer + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.ALTERNATE_BOUNCER, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "lockscreenToAlternateBouncer", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + + // TransitionState.RUNNING: lockscreen -> alternate bouncer + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.ALTERNATE_BOUNCER, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "lockscreenToAlternateBouncer", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: lockscreen -> alternate bouncer + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.ALTERNATE_BOUNCER, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "lockscreenToAlternateBouncer", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + } + + fun alternateBouncerToPrimaryBouncer() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: alternate bouncer -> primary bouncer + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.PRIMARY_BOUNCER, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "alternateBouncerToPrimaryBouncer", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + + // TransitionState.RUNNING: alternate bouncer -> primary bouncer + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.PRIMARY_BOUNCER, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "alternateBouncerToPrimaryBouncer", + ) + ) + runCurrent() + assertThat(transition?.alpha).isIn(Range.closed(.59f, .61f)) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: alternate bouncer -> primary bouncer + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.PRIMARY_BOUNCER, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "alternateBouncerToPrimaryBouncer", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isFalse() + } + + fun alternateBouncerToAod() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: alternate bouncer -> aod + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.AOD, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "alternateBouncerToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + + // TransitionState.RUNNING: alternate bouncer -> aod + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.AOD, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "alternateBouncerToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f)) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: alternate bouncer -> aod + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "alternateBouncerToAod", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(alternateBouncerColor) + assertThat(visible).isFalse() + } + + @Test + fun lockscreenToOccluded() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: lockscreen -> occluded + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "lockscreenToOccluded", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.RUNNING: lockscreen -> occluded + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "lockscreenToOccluded", + ) + ) + runCurrent() + assertThat(transition?.alpha).isIn(Range.closed(.39f, .41f)) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: lockscreen -> occluded + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.OCCLUDED, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "lockscreenToOccluded", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(0f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isFalse() + } + + @Test + fun occludedToLockscreen() = + testScope.runTest { + val transition by collectLastValue(underTest.transition) + val visible by collectLastValue(underTest.visible) + + // TransitionState.STARTED: occluded -> lockscreen + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.OCCLUDED, + to = KeyguardState.LOCKSCREEN, + value = 0f, + transitionState = TransitionState.STARTED, + ownerName = "occludedToLockscreen", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.RUNNING: occluded -> lockscreen + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.OCCLUDED, + to = KeyguardState.LOCKSCREEN, + value = .6f, + transitionState = TransitionState.RUNNING, + ownerName = "occludedToLockscreen", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + + // TransitionState.FINISHED: occluded -> lockscreen + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.OCCLUDED, + to = KeyguardState.LOCKSCREEN, + value = 1f, + transitionState = TransitionState.FINISHED, + ownerName = "occludedToLockscreen", + ) + ) + runCurrent() + assertThat(transition?.alpha).isEqualTo(1f) + assertThat(transition?.scale).isEqualTo(1f) + assertThat(transition?.color).isEqualTo(lockscreenColor) + assertThat(visible).isTrue() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt index 0cf6d3da7e9c..b5eae5b03b74 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/log/LogBufferTest.kt @@ -2,6 +2,7 @@ package com.android.systemui.log import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.log.core.Logger import com.google.common.truth.Truth.assertThat import java.io.PrintWriter import java.io.StringWriter @@ -32,7 +33,8 @@ class LogBufferTest : SysuiTestCase() { @Test fun log_shouldSaveLogToBuffer() { - buffer.log("Test", LogLevel.INFO, "Some test message") + val logger = Logger(buffer, "Test") + logger.i("Some test message") val dumpedString = dumpBuffer() @@ -41,8 +43,9 @@ class LogBufferTest : SysuiTestCase() { @Test fun log_shouldRotateIfLogBufferIsFull() { - buffer.log("Test", LogLevel.INFO, "This should be rotated") - buffer.log("Test", LogLevel.INFO, "New test message") + val logger = Logger(buffer, "Test") + logger.i("This should be rotated") + logger.i("New test message") val dumpedString = dumpBuffer() @@ -53,7 +56,8 @@ class LogBufferTest : SysuiTestCase() { fun dump_writesExceptionAndStacktrace() { buffer = createBuffer() val exception = createTestException("Exception message", "TestClass") - buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception) + val logger = Logger(buffer, "Test") + logger.e("Extra message", exception) val dumpedString = dumpBuffer() @@ -72,7 +76,8 @@ class LogBufferTest : SysuiTestCase() { "TestClass", cause = createTestException("The real cause!", "TestClass") ) - buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception) + val logger = Logger(buffer, "Test") + logger.e("Extra message", exception) val dumpedString = dumpBuffer() @@ -93,7 +98,8 @@ class LogBufferTest : SysuiTestCase() { ) ) exception.addSuppressed(createTestException("Second suppressed exception", "SecondClass")) - buffer.log("Tag", LogLevel.ERROR, { str1 = "Extra message" }, { str1!! }, exception) + val logger = Logger(buffer, "Test") + logger.e("Extra message", exception) val dumpedStr = dumpBuffer() diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt new file mode 100644 index 000000000000..ab19b3aeceb0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt @@ -0,0 +1,140 @@ +package com.android.systemui.log.core + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.log.LogMessageImpl +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.nullable +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.anyString +import org.mockito.Mockito.isNull +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.mockito.junit.MockitoJUnitRunner + +@SmallTest +@RunWith(MockitoJUnitRunner::class) +class LoggerTest : SysuiTestCase() { + @Mock private lateinit var buffer: MessageBuffer + private lateinit var message: LogMessage + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + whenever(buffer.obtain(any(), any(), any(), isNull())).thenAnswer { + message = LogMessageImpl.Factory.create() + return@thenAnswer message + } + } + + @Test + fun log_shouldCommitLogMessage() { + val logger = Logger(buffer, "LoggerTest") + logger.log(LogLevel.DEBUG, { "count=$int1" }) { + int1 = 1 + str1 = "test" + bool1 = true + } + + assertThat(message.int1).isEqualTo(1) + assertThat(message.str1).isEqualTo("test") + assertThat(message.bool1).isEqualTo(true) + } + + @Test + fun log_shouldUseCorrectLoggerTag() { + val logger = Logger(buffer, "LoggerTest") + logger.log(LogLevel.DEBUG, { "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(eq("LoggerTest"), any(), any(), nullable()) + } + + @Test + fun v_withMessageInitializer_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.v({ "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(anyString(), eq(LogLevel.VERBOSE), any(), nullable()) + } + + @Test + fun v_withCompileTimeMessage_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.v("Message") + verify(buffer).obtain(anyString(), eq(LogLevel.VERBOSE), any(), nullable()) + } + + @Test + fun d_withMessageInitializer_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.d({ "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(anyString(), eq(LogLevel.DEBUG), any(), nullable()) + } + + @Test + fun d_withCompileTimeMessage_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.d("Message") + verify(buffer).obtain(anyString(), eq(LogLevel.DEBUG), any(), nullable()) + } + + @Test + fun i_withMessageInitializer_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.i({ "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(anyString(), eq(LogLevel.INFO), any(), nullable()) + } + + @Test + fun i_withCompileTimeMessage_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.i("Message") + verify(buffer).obtain(anyString(), eq(LogLevel.INFO), any(), nullable()) + } + + @Test + fun w_withMessageInitializer_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.w({ "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(anyString(), eq(LogLevel.WARNING), any(), nullable()) + } + + @Test + fun w_withCompileTimeMessage_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.w("Message") + verify(buffer).obtain(anyString(), eq(LogLevel.WARNING), any(), nullable()) + } + + @Test + fun e_withMessageInitializer_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.e({ "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(anyString(), eq(LogLevel.ERROR), any(), nullable()) + } + + @Test + fun e_withCompileTimeMessage_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.e("Message") + verify(buffer).obtain(anyString(), eq(LogLevel.ERROR), any(), nullable()) + } + + @Test + fun wtf_withMessageInitializer_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.wtf({ "count=$int1" }) { int1 = 1 } + verify(buffer).obtain(anyString(), eq(LogLevel.WTF), any(), nullable()) + } + + @Test + fun wtf_withCompileTimeMessage_shouldLogAtCorrectLevel() { + val logger = Logger(buffer, "LoggerTest") + logger.wtf("Message") + verify(buffer).obtain(anyString(), eq(LogLevel.WTF), any(), nullable()) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt index 12f46898ab8d..83182c5cf1b0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/log/table/TableLogBufferTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.log.table import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.log.LogLevel import com.android.systemui.log.LogcatEchoTracker +import com.android.systemui.log.core.LogLevel import com.android.systemui.log.table.TableChange.Companion.IS_INITIAL_PREFIX import com.android.systemui.log.table.TableChange.Companion.MAX_STRING_LENGTH import com.android.systemui.util.mockito.any diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt index b40ebc9bb156..91b0245be8d3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/KeyguardMediaControllerTest.kt @@ -193,6 +193,17 @@ class KeyguardMediaControllerTest : SysuiTestCase() { } @Test + fun dozeWakeUpAnimationWaiting_inSplitShade_mediaIsHidden() { + val splitShadeContainer = FrameLayout(context) + keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) + keyguardMediaController.useSplitShade = true + + keyguardMediaController.isDozeWakeUpAnimationWaiting = true + + assertThat(splitShadeContainer.visibility).isEqualTo(GONE) + } + + @Test fun dozing_inSingleShade_mediaIsVisible() { val splitShadeContainer = FrameLayout(context) keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) @@ -203,6 +214,17 @@ class KeyguardMediaControllerTest : SysuiTestCase() { assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE) } + @Test + fun dozeWakeUpAnimationWaiting_inSingleShade_mediaIsVisible() { + val splitShadeContainer = FrameLayout(context) + keyguardMediaController.attachSplitShadeContainer(splitShadeContainer) + keyguardMediaController.useSplitShade = false + + keyguardMediaController.isDozeWakeUpAnimationWaiting = true + + assertThat(mediaContainerView.visibility).isEqualTo(VISIBLE) + } + private fun setDozing() { whenever(statusBarStateController.isDozing).thenReturn(true) statusBarStateListener.onDozingChanged(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 7df54d44e69e..e4f89a226a34 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -291,13 +291,13 @@ public class MediaOutputAdapterTest extends SysuiTestCase { assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1); - assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE); } @Test @@ -525,16 +525,16 @@ public class MediaOutputAdapterTest extends SysuiTestCase { mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0); assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.GONE); + assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE); - assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mViewHolder.mStatusIcon.getVisibility()).isEqualTo(View.GONE); assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mViewHolder.mSubTitleText.getText().toString()).isEqualTo(TEST_CUSTOM_SUBTEXT); assertThat(mViewHolder.mTwoLineTitleText.getText().toString()).isEqualTo( TEST_DEVICE_NAME_1); - assertThat(mViewHolder.mContainerLayout.hasOnClickListeners()).isTrue(); + assertThat(mViewHolder.mContainerLayout.hasOnClickListeners()).isFalse(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt index 9e5422470d8b..5890cbd06476 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt @@ -25,7 +25,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -97,10 +97,11 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() { multiShadeInteractor = interactor, featureFlags = featureFlags, keyguardTransitionInteractor = - KeyguardTransitionInteractor( - repository = keyguardTransitionRepository, - scope = testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor, falsingManager = falsingManager, shadeController = shadeController, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 697d1a3b775c..25d494cee5e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -20,6 +20,7 @@ import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT; import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SHOWN; import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN; import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT; +import static android.inputmethodservice.InputMethodService.IME_INVISIBLE; import static android.inputmethodservice.InputMethodService.IME_VISIBLE; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; @@ -363,7 +364,7 @@ public class NavigationBarTest extends SysuiTestCase { externalNavBar.setImeWindowStatus(EXTERNAL_DISPLAY_ID, null, IME_VISIBLE, BACK_DISPOSITION_DEFAULT, true); defaultNavBar.setImeWindowStatus( - DEFAULT_DISPLAY, null, 0 /* vis */, BACK_DISPOSITION_DEFAULT, false); + DEFAULT_DISPLAY, null, IME_INVISIBLE, BACK_DISPOSITION_DEFAULT, false); // Verify IME window state will be updated in external NavBar & default NavBar state reset. assertEquals(NAVIGATION_HINT_BACK_ALT | NAVIGATION_HINT_IME_SHOWN | NAVIGATION_HINT_IME_SWITCHER_SHOWN, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt index a60dad4a14fe..fe6c9b3fe7b1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt @@ -15,9 +15,11 @@ package com.android.systemui.qs import android.graphics.Rect import android.testing.AndroidTestingRunner +import android.testing.TestableContext import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.testing.ViewUtils +import android.view.ContextThemeWrapper import android.view.View import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.accessibility.AccessibilityNodeInfo @@ -55,19 +57,24 @@ class QSPanelTest : SysuiTestCase() { private lateinit var footer: View + private val themedContext = TestableContext( + ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) + ) + @Before @Throws(Exception::class) fun setup() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) + // Apply only the values of the theme that are not defined testableLooper.runWithLooper { - qsPanel = QSPanel(context, null) + qsPanel = QSPanel(themedContext, null) qsPanel.mUsingMediaPlayer = true qsPanel.initialize(qsLogger) // QSPanel inflates a footer inside of it, mocking it here - footer = LinearLayout(context).apply { id = R.id.qs_footer } + footer = LinearLayout(themedContext).apply { id = R.id.qs_footer } qsPanel.addView(footer, MATCH_PARENT, 100) qsPanel.onFinishInflate() // Provides a parent with non-zero size for QSPanel @@ -105,12 +112,12 @@ class QSPanelTest : SysuiTestCase() { qsPanel.tileLayout?.addTile( QSPanelControllerBase.TileRecord( mock(QSTile::class.java), - QSTileViewImpl(context, QSIconViewImpl(context)) + QSTileViewImpl(themedContext, QSIconViewImpl(themedContext)) ) ) - val mediaView = FrameLayout(context) - mediaView.addView(View(context), MATCH_PARENT, 800) + val mediaView = FrameLayout(themedContext) + mediaView.addView(View(themedContext), MATCH_PARENT, 800) qsPanel.setUsingHorizontalLayout(/* horizontal */ true, mediaView, /* force */ true) qsPanel.measure( @@ -135,12 +142,12 @@ class QSPanelTest : SysuiTestCase() { qsPanel.tileLayout?.addTile( QSPanelControllerBase.TileRecord( mock(QSTile::class.java), - QSTileViewImpl(context, QSIconViewImpl(context)) + QSTileViewImpl(themedContext, QSIconViewImpl(themedContext)) ) ) - val mediaView = FrameLayout(context) - mediaView.addView(View(context), MATCH_PARENT, 800) + val mediaView = FrameLayout(themedContext) + mediaView.addView(View(themedContext), MATCH_PARENT, 800) qsPanel.setUsingHorizontalLayout(/* horizontal */ true, mediaView, /* force */ true) qsPanel.measure( @@ -161,7 +168,10 @@ class QSPanelTest : SysuiTestCase() { @Test fun testBottomPadding() { val padding = 10 - context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_bottom, padding) + themedContext.orCreateTestableResources.addOverride( + R.dimen.qs_panel_padding_bottom, + padding + ) qsPanel.updatePadding() assertThat(qsPanel.paddingBottom).isEqualTo(padding) } @@ -170,8 +180,11 @@ class QSPanelTest : SysuiTestCase() { fun testTopPadding() { val padding = 10 val paddingCombined = 100 - context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding) - context.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, paddingCombined) + themedContext.orCreateTestableResources.addOverride(R.dimen.qs_panel_padding_top, padding) + themedContext.orCreateTestableResources.addOverride( + R.dimen.qs_panel_padding_top, + paddingCombined + ) qsPanel.updatePadding() assertThat(qsPanel.paddingTop).isEqualTo(paddingCombined) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java index 87892539ccfe..f55ef65a8fc1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileLayoutTest.java @@ -26,12 +26,14 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; import android.test.suitebuilder.annotation.SmallTest; import android.testing.TestableLooper; +import android.view.ContextThemeWrapper; import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; @@ -56,14 +58,17 @@ public class TileLayoutTest extends SysuiTestCase { private Resources mResources; private int mLayoutSizeForOneTile; private TileLayout mTileLayout; // under test + private Context mSpyContext; + @Before public void setUp() throws Exception { - Context context = Mockito.spy(mContext); - mResources = Mockito.spy(context.getResources()); - Mockito.when(mContext.getResources()).thenReturn(mResources); + mSpyContext = Mockito.spy( + new ContextThemeWrapper(mContext, R.style.Theme_SystemUI_QuickSettings)); + mResources = Mockito.spy(mSpyContext.getResources()); + when(mSpyContext.getResources()).thenReturn(mResources); - mTileLayout = new TileLayout(context); + mTileLayout = new TileLayout(mSpyContext); // Layout needs to leave space for the tile margins. Three times the margin size is // sufficient for any number of columns. mLayoutSizeForOneTile = @@ -73,7 +78,7 @@ public class TileLayoutTest extends SysuiTestCase { private QSPanelControllerBase.TileRecord createTileRecord() { return new QSPanelControllerBase.TileRecord( mock(QSTile.class), - spy(new QSTileViewImpl(mContext, new QSIconViewImpl(mContext)))); + spy(new QSTileViewImpl(mSpyContext, new QSIconViewImpl(mSpyContext)))); } @Test @@ -161,7 +166,7 @@ public class TileLayoutTest extends SysuiTestCase { .layout(left2.capture(), top2.capture(), right2.capture(), bottom2.capture()); // We assume two tiles will always fit side-by-side. - assertTrue(mContext.getResources().getInteger(R.integer.quick_settings_num_columns) > 1); + assertTrue(mSpyContext.getResources().getInteger(R.integer.quick_settings_num_columns) > 1); // left <= right, top <= bottom assertTrue(left1.getValue() <= right1.getValue()); @@ -218,16 +223,16 @@ public class TileLayoutTest extends SysuiTestCase { @Test public void resourcesChanged_updateResources_returnsTrue() { - Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1); + when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1); mTileLayout.updateResources(); // setup with 1 - Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(2); + when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(2); assertEquals(true, mTileLayout.updateResources()); } @Test public void resourcesSame_updateResources_returnsFalse() { - Mockito.when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1); + when(mResources.getInteger(R.integer.quick_settings_num_columns)).thenReturn(1); mTileLayout.updateResources(); // setup with 1 assertEquals(false, mTileLayout.updateResources()); @@ -250,7 +255,7 @@ public class TileLayoutTest extends SysuiTestCase { QSPanelControllerBase.TileRecord tileRecord = createTileRecord(); mTileLayout.addTile(tileRecord); - FakeTileView tileView = new FakeTileView(mContext); + FakeTileView tileView = new FakeTileView(mSpyContext); QSTile.State state = new QSTile.State(); state.label = "TEST LABEL"; state.secondaryLabel = "TEST SECONDARY LABEL"; @@ -276,9 +281,10 @@ public class TileLayoutTest extends SysuiTestCase { } private void changeFontScaling(float scale) { - Configuration configuration = new Configuration(mContext.getResources().getConfiguration()); + Configuration configuration = + new Configuration(mSpyContext.getResources().getConfiguration()); configuration.fontScale = scale; // updateConfiguration could help update on both resource configuration and displayMetrics - mContext.getResources().updateConfiguration(configuration, null, null); + mSpyContext.getResources().updateConfiguration(configuration, null, null); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt index 2cc6709d0f37..d647d6add512 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt @@ -21,6 +21,7 @@ import android.os.UserManager import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper +import android.view.ContextThemeWrapper import androidx.test.filters.SmallTest import com.android.settingslib.Utils import com.android.settingslib.drawable.UserIconDrawable @@ -63,6 +64,8 @@ class FooterActionsViewModelTest : SysuiTestCase() { private val testScope = TestScope() private lateinit var utils: FooterActionsTestUtils + private val themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) + @Before fun setUp() { utils = FooterActionsTestUtils(context, TestableLooper.get(this), testScope.testScheduler) @@ -84,8 +87,14 @@ class FooterActionsViewModelTest : SysuiTestCase() { ContentDescription.Resource(R.string.accessibility_quick_settings_settings) ) ) - assertThat(settings.backgroundColor).isEqualTo(R.attr.offStateColor) - assertThat(settings.iconTint).isNull() + assertThat(settings.backgroundColor).isEqualTo(R.attr.shadeInactive) + assertThat(settings.iconTint) + .isEqualTo( + Utils.getColorAttrDefaultColor( + themedContext, + R.attr.onShadeInactiveVariant, + ) + ) } @Test @@ -105,12 +114,12 @@ class FooterActionsViewModelTest : SysuiTestCase() { ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu) ) ) - assertThat(power.backgroundColor).isEqualTo(com.android.internal.R.attr.colorAccent) + assertThat(power.backgroundColor).isEqualTo(R.attr.shadeActive) assertThat(power.iconTint) .isEqualTo( Utils.getColorAttrDefaultColor( - context, - com.android.internal.R.attr.textColorOnAccent, + themedContext, + R.attr.onShadeActive, ), ) } @@ -170,7 +179,7 @@ class FooterActionsViewModelTest : SysuiTestCase() { assertThat(userSwitcher).isNotNull() assertThat(userSwitcher!!.icon) .isEqualTo(Icon.Loaded(picture, ContentDescription.Loaded("Signed in as foo"))) - assertThat(userSwitcher.backgroundColor).isEqualTo(R.attr.offStateColor) + assertThat(userSwitcher.backgroundColor).isEqualTo(R.attr.shadeInactive) // Change the current user name. userSwitcherControllerWrapper.currentUserName = "bar" diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt index 18f3837a7d36..dc0fae53f0c5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.util.mockito.argThat import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -75,6 +76,9 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) + whenever(context.createContextAsUser(any(), anyInt())).thenReturn(context) + whenever(context.packageManager).thenReturn(packageManager) + // Use the default value set in the ServiceInfo whenever(packageManager.getComponentEnabledSetting(any())) .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) @@ -86,7 +90,6 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() { underTest = InstalledTilesComponentRepositoryImpl( context, - packageManager, testDispatcher, ) } @@ -224,6 +227,52 @@ class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() { assertThat(componentNames).isEmpty() } + @Test + fun packageOnlyInSecondaryUser_noException() = + testScope.runTest { + val userId = 10 + val secondaryUserContext: Context = mock() + whenever(context.userId).thenReturn(0) // System context + whenever(context.createContextAsUser(eq(UserHandle.of(userId)), anyInt())) + .thenReturn(secondaryUserContext) + + val secondaryUserPackageManager: PackageManager = mock() + whenever(secondaryUserContext.packageManager).thenReturn(secondaryUserPackageManager) + + // Use the default value set in the ServiceInfo + whenever(secondaryUserPackageManager.getComponentEnabledSetting(any())) + .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) + // System User package manager throws exception if the component doesn't exist for that + // user + whenever(packageManager.getComponentEnabledSetting(TEST_COMPONENT)) + .thenThrow(IllegalArgumentException()) // The package is not in the system user + + val resolveInfo = + ResolveInfo(TEST_COMPONENT, hasPermission = true, defaultEnabled = true) + // Both package manager should return the same (because the query is for the secondary + // user) + whenever( + secondaryUserPackageManager.queryIntentServicesAsUser( + matchIntent(), + matchFlags(), + eq(userId) + ) + ) + .thenReturn(listOf(resolveInfo)) + whenever( + packageManager.queryIntentServicesAsUser( + matchIntent(), + matchFlags(), + eq(userId) + ) + ) + .thenReturn(listOf(resolveInfo)) + + val componentNames by collectLastValue(underTest.getInstalledTilesComponents(userId)) + + assertThat(componentNames).containsExactly(TEST_COMPONENT) + } + private fun getRegisteredReceiver(): BroadcastReceiver { verify(context) .registerReceiverAsUser( diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java index 962b53737274..22b1c7b58ab3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java @@ -204,6 +204,19 @@ public class QSTileImplTest extends SysuiTestCase { } @Test + public void testLongClick_falsing() { + mFalsingManager.setFalseLongTap(true); + mTile.longClick(null /* view */); + mTestableLooper.processAllMessages(); + assertThat(mTile.mLongClicked).isFalse(); + + mFalsingManager.setFalseLongTap(false); + mTile.longClick(null /* view */); + mTestableLooper.processAllMessages(); + assertThat(mTile.mLongClicked).isTrue(); + } + + @Test public void testSecondaryClick_Metrics() { mTile.secondaryClick(null /* view */); verify(mMetricsLogger).write(argThat(new TileLogMatcher(ACTION_QS_SECONDARY_CLICK))); @@ -518,6 +531,7 @@ public class QSTileImplTest extends SysuiTestCase { } private static class TileImpl extends QSTileImpl<QSTile.BooleanState> { boolean mClicked; + boolean mLongClicked; int mRefreshes = 0; protected TileImpl( @@ -551,6 +565,11 @@ public class QSTileImplTest extends SysuiTestCase { } @Override + protected void handleLongClick(@Nullable View view) { + mLongClicked = true; + } + + @Override protected void handleUpdateState(BooleanState state, Object arg) { mRefreshes++; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt index 28aeba461c50..3c667725c78a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt @@ -22,6 +22,7 @@ import android.service.quicksettings.Tile import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.text.TextUtils +import android.view.ContextThemeWrapper import android.view.View import android.view.accessibility.AccessibilityNodeInfo import android.widget.TextView @@ -386,7 +387,11 @@ class QSTileViewImplTest : SysuiTestCase() { context: Context, icon: QSIconView, collapsed: Boolean - ) : QSTileViewImpl(context, icon, collapsed) { + ) : QSTileViewImpl( + ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings), + icon, + collapsed + ) { fun changeState(state: QSTile.State) { handleStateChanged(state) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt index f0e4e3adda7c..77a443666442 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt @@ -25,6 +25,7 @@ import android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS import android.provider.Settings.Global.ZEN_MODE_OFF import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import android.view.ContextThemeWrapper import android.view.View import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger @@ -110,7 +111,9 @@ class DndTileTest : SysuiTestCase() { whenever(qsHost.userId).thenReturn(DEFAULT_USER) - val wrappedContext = object : ContextWrapper(context) { + val wrappedContext = object : ContextWrapper( + ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings) + ) { override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences { return sharedPreferences } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index ef7c7bc0844d..9188293dc751 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -80,6 +80,7 @@ import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.common.ui.view.LongPressHandlingView; @@ -91,7 +92,6 @@ import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardViewConfigurator; import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; -import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; @@ -629,7 +629,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mHeadsUpManager); mNotificationPanelViewController.setTrackingStartedListener(() -> {}); mNotificationPanelViewController.setOpenCloseListener( - new NotificationPanelViewController.OpenCloseListener() { + new OpenCloseListener() { @Override public void onClosingFinished() {} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 5802eb3d9618..eb4ae1a743ef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -514,6 +514,17 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo } @Test + public void keyguardStatusView_willPlayDelayedDoze_notifiesKeyguardMediaController() { + when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2); + mStatusBarStateController.setState(KEYGUARD); + enableSplitShade(/* enabled= */ true); + + mNotificationPanelViewController.setWillPlayDelayedDozeAmountAnimation(true); + + verify(mKeyguardMediaController).setDozeWakeUpAnimationWaiting(true); + } + + @Test public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenStillCenteredIfNoNotifs() { when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0); mStatusBarStateController.setState(KEYGUARD); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 2a9b403cb2e6..5fb3a7955b5c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -28,6 +28,11 @@ import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.back.domain.interactor.BackActionInteractor +import com.android.systemui.bouncer.data.factory.BouncerMessageFactory +import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository +import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor +import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil +import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.dock.DockManager @@ -35,14 +40,9 @@ import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.KeyguardUnlockAnimationController -import com.android.systemui.bouncer.data.factory.BouncerMessageFactory -import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor -import com.android.systemui.bouncer.domain.interactor.CountDownTimerUtil -import com.android.systemui.bouncer.data.repository.FakeBouncerMessageRepository -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.log.BouncerLogger import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy @@ -80,9 +80,9 @@ import org.mockito.Mockito.anyFloat import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations import java.util.Optional +import org.mockito.Mockito.`when` as whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -198,10 +198,9 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { multiShadeInteractor = multiShadeInteractor, featureFlags = featureFlags, keyguardTransitionInteractor = - KeyguardTransitionInteractor( - repository = FakeKeyguardTransitionRepository(), - scope = testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + ).keyguardTransitionInteractor, falsingManager = FalsingManagerFake(), shadeController = shadeController, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index 252a03bb07d2..544137e95779 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -40,8 +40,8 @@ import com.android.systemui.dump.logcatLogBuffer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.KeyguardUnlockAnimationController -import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.log.BouncerLogger import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy @@ -211,10 +211,10 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { multiShadeInteractor = multiShadeInteractor, featureFlags = featureFlags, keyguardTransitionInteractor = - KeyguardTransitionInteractor( - repository = FakeKeyguardTransitionRepository(), - scope = testScope.backgroundScope - ), + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + ) + .keyguardTransitionInteractor, falsingManager = FalsingManagerFake(), shadeController = shadeController, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java index c72f4e77d4eb..a2c291281fdf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java @@ -169,6 +169,7 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase { when(mDeviceProvisionedController.isDeviceProvisioned()).thenReturn(true); mShadeInteractor = new ShadeInteractor( + mTestScope.getBackgroundScope(), mDisableFlagsRepository, mKeyguardRepository, new FakeUserSetupRepository(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt index 00a056708f07..729c4a9145c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt @@ -56,7 +56,7 @@ class ShadeControllerImplTest : SysuiTestCase() { @Mock private lateinit var windowManager: WindowManager @Mock private lateinit var assistManager: AssistManager @Mock private lateinit var gutsManager: NotificationGutsManager - @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController + @Mock private lateinit var shadeViewController: ShadeViewController @Mock private lateinit var nswvc: NotificationShadeWindowViewController @Mock private lateinit var display: Display @@ -82,7 +82,7 @@ class ShadeControllerImplTest : SysuiTestCase() { Lazy { gutsManager }, ) shadeController.setNotificationShadeWindowViewController(nswvc) - shadeController.setNotificationPanelViewController(notificationPanelViewController) + shadeController.setShadeViewController(shadeViewController) } @Test @@ -91,9 +91,9 @@ class ShadeControllerImplTest : SysuiTestCase() { // Trying to open it does nothing. shadeController.animateExpandShade() - verify(notificationPanelViewController, never()).expandToNotifications() + verify(shadeViewController, never()).expandToNotifications() shadeController.animateExpandQs() - verify(notificationPanelViewController, never()).expand(ArgumentMatchers.anyBoolean()) + verify(shadeViewController, never()).expand(ArgumentMatchers.anyBoolean()) } @Test @@ -102,15 +102,15 @@ class ShadeControllerImplTest : SysuiTestCase() { // Can now be opened. shadeController.animateExpandShade() - verify(notificationPanelViewController).expandToNotifications() + verify(shadeViewController).expandToNotifications() shadeController.animateExpandQs() - verify(notificationPanelViewController).expandToQs() + verify(shadeViewController).expandToQs() } @Test fun cancelExpansionAndCollapseShade_callsCancelCurrentTouch() { // GIVEN the shade is tracking a touch - whenever(notificationPanelViewController.isTracking).thenReturn(true) + whenever(shadeViewController.isTracking).thenReturn(true) // WHEN cancelExpansionAndCollapseShade is called shadeController.cancelExpansionAndCollapseShade() @@ -122,7 +122,7 @@ class ShadeControllerImplTest : SysuiTestCase() { @Test fun cancelExpansionAndCollapseShade_doesNotCallAnimateCollapseShade_whenCollapsed() { // GIVEN the shade is tracking a touch - whenever(notificationPanelViewController.isTracking).thenReturn(false) + whenever(shadeViewController.isTracking).thenReturn(false) // WHEN cancelExpansionAndCollapseShade is called shadeController.cancelExpansionAndCollapseShade() diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt index 2da2e9238d0a..f542ab099517 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt @@ -237,11 +237,22 @@ class ShadeHeaderControllerTest : SysuiTestCase() { whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(false) makeShadeVisible() + shadeHeaderController.qsExpandedFraction = 1.0f verify(statusIcons).addIgnoredSlots(carrierIconSlots) } @Test + fun dualCarrier_enablesCarrierIconsInStatusIcons_qsExpanded() { + whenever(mShadeCarrierGroupController.isSingleCarrier).thenReturn(false) + + makeShadeVisible() + shadeHeaderController.qsExpandedFraction = 0.0f + + verify(statusIcons, times(2)).removeIgnoredSlots(carrierIconSlots) + } + + @Test fun disableQS_notDisabled_visible() { makeShadeVisible() shadeHeaderController.disable(0, 0, false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java index 44613103a5b2..dae9c975b997 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierTest.java @@ -20,8 +20,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import android.content.Context; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; @@ -48,7 +50,9 @@ public class ShadeCarrierTest extends SysuiTestCase { @Before public void setUp() throws Exception { mTestableLooper = TestableLooper.get(this); - LayoutInflater inflater = LayoutInflater.from(mContext); + Context themedContext = + new ContextThemeWrapper(mContext, R.style.Theme_SystemUI_QuickSettings); + LayoutInflater inflater = LayoutInflater.from(themedContext); mContext.ensureTestableResources(); mTestableLooper.runWithLooper(() -> mShadeCarrier = (ShadeCarrier) inflater.inflate(R.layout.shade_carrier, null)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt index 7392a94a04c2..3ea8f5412b40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt @@ -84,6 +84,7 @@ class ShadeInteractorTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) featureFlags.set(Flags.FACE_AUTH_REFACTOR, false) + featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) val refreshUsersScheduler = RefreshUsersScheduler( @@ -117,6 +118,7 @@ class ShadeInteractorTest : SysuiTestCase() { ) underTest = ShadeInteractor( + testScope.backgroundScope, disableFlagsRepository, keyguardRepository, userSetupRepository, @@ -126,6 +128,20 @@ class ShadeInteractorTest : SysuiTestCase() { } @Test + fun isShadeEnabled_matchesDisableFlagsRepo() = + testScope.runTest { + val actual by collectLastValue(underTest.isShadeEnabled) + + disableFlagsRepository.disableFlags.value = + DisableFlagsModel(disable2 = DISABLE2_NOTIFICATION_SHADE) + assertThat(actual).isFalse() + + disableFlagsRepository.disableFlags.value = DisableFlagsModel(disable2 = DISABLE2_NONE) + + assertThat(actual).isTrue() + } + + @Test fun isExpandToQsEnabled_deviceNotProvisioned_false() = testScope.runTest { whenever(deviceProvisionedController.isDeviceProvisioned).thenReturn(false) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java index 385d556092a6..1643e174ee13 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import static android.inputmethodservice.InputMethodService.BACK_DISPOSITION_DEFAULT; +import static android.inputmethodservice.InputMethodService.IME_INVISIBLE; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT; @@ -200,7 +201,7 @@ public class CommandQueueTest extends SysuiTestCase { mCommandQueue.setImeWindowStatus(SECONDARY_DISPLAY, null, 1, 2, true); waitForIdleSync(); - verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(null), eq(0), + verify(mCallbacks).setImeWindowStatus(eq(DEFAULT_DISPLAY), eq(null), eq(IME_INVISIBLE), eq(BACK_DISPOSITION_DEFAULT), eq(false)); verify(mCallbacks).setImeWindowStatus( eq(SECONDARY_DISPLAY), eq(null), eq(1), eq(2), eq(true)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt index 21e0f68cff2d..ff2f1065049b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt @@ -1,5 +1,6 @@ package com.android.systemui.statusbar +import android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper @@ -11,6 +12,7 @@ import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.media.controls.ui.MediaHierarchyManager import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.FalsingManager @@ -19,6 +21,9 @@ import com.android.systemui.power.data.repository.FakePowerRepository import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.shade.ShadeViewController import com.android.systemui.shade.data.repository.FakeShadeRepository +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel +import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.NotificationTestHelper import com.android.systemui.statusbar.notification.stack.AmbientState @@ -28,8 +33,13 @@ import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.LSShadeTransitionLogger import com.android.systemui.statusbar.phone.ScrimController +import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository import com.android.systemui.statusbar.policy.FakeConfigurationController import com.android.systemui.util.mockito.mock +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent import org.junit.After import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull @@ -60,8 +70,11 @@ private fun <T> anyObject(): T { @SmallTest @RunWithLooper(setAsMainLooper = true) @RunWith(AndroidTestingRunner::class) +@OptIn(ExperimentalCoroutinesApi::class) class LockscreenShadeTransitionControllerTest : SysuiTestCase() { + private val testScope = TestScope(StandardTestDispatcher()) + lateinit var transitionController: LockscreenShadeTransitionController lateinit var row: ExpandableNotificationRow @Mock lateinit var statusbarStateController: SysuiStatusBarStateController @@ -87,6 +100,15 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { @Mock lateinit var qsTransitionController: LockscreenShadeQsTransitionController @Mock lateinit var activityStarter: ActivityStarter @Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback + private val disableFlagsRepository = FakeDisableFlagsRepository() + private val shadeInteractor = ShadeInteractor( + testScope.backgroundScope, + disableFlagsRepository, + keyguardRepository = FakeKeyguardRepository(), + userSetupRepository = FakeUserSetupRepository(), + deviceProvisionedController = mock(), + userInteractor = mock(), + ) private val powerInteractor = PowerInteractor( FakePowerRepository(), FalsingCollectorFake(), @@ -99,6 +121,10 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { @Before fun setup() { + // By default, have the shade enabled + disableFlagsRepository.disableFlags.value = DisableFlagsModel() + testScope.runCurrent() + val helper = NotificationTestHelper( mContext, mDependency, @@ -139,6 +165,7 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { qsTransitionControllerFactory = { qsTransitionController }, activityStarter = activityStarter, shadeRepository = FakeShadeRepository(), + shadeInteractor = shadeInteractor, powerInteractor = powerInteractor, ) transitionController.addCallback(transitionControllerCallback) @@ -214,7 +241,10 @@ class LockscreenShadeTransitionControllerTest : SysuiTestCase() { @Test fun testDontGoWhenShadeDisabled() { - whenever(mCentralSurfaces.isShadeDisabled).thenReturn(true) + disableFlagsRepository.disableFlags.value = DisableFlagsModel( + disable2 = DISABLE2_NOTIFICATION_SHADE, + ) + testScope.runCurrent() transitionController.goToLockedShade(null) verify(statusbarStateController, never()).setState(anyInt()) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt new file mode 100644 index 000000000000..cfbe8e36537d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandParserTest.kt @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.commandline + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import org.junit.Assert.assertFalse +import org.junit.Assert.assertThrows +import org.junit.Assert.assertTrue +import org.junit.Test + +@SmallTest +class CommandParserTest : SysuiTestCase() { + private val parser = CommandParser() + + @Test + fun registerToken_cannotReuseNames() { + parser.flag("-f") + assertThrows(IllegalArgumentException::class.java) { parser.flag("-f") } + } + + @Test + fun unknownToken_throws() { + assertThrows(ArgParseError::class.java) { parser.parse(listOf("unknown-token")) } + } + + @Test + fun parseSingleFlag_present() { + val flag by parser.flag("-f") + parser.parse(listOf("-f")) + assertTrue(flag) + } + + @Test + fun parseSingleFlag_notPresent() { + val flag by parser.flag("-f") + parser.parse(listOf()) + assertFalse(flag) + } + + @Test + fun parseSingleOptionalParam_present() { + val param by parser.param("-p", valueParser = Type.Int) + parser.parse(listOf("-p", "123")) + assertThat(param).isEqualTo(123) + } + + @Test + fun parseSingleOptionalParam_notPresent() { + val param by parser.param("-p", valueParser = Type.Int) + parser.parse(listOf()) + assertThat(param).isNull() + } + + @Test + fun parseSingleOptionalParam_missingArg_throws() { + val param by parser.param("-p", valueParser = Type.Int) + assertThrows(ArgParseError::class.java) { parser.parse(listOf("-p")) } + } + + @Test + fun parseSingleRequiredParam_present() { + val param by parser.require(parser.param("-p", valueParser = Type.Int)) + parser.parse(listOf("-p", "123")) + assertThat(param).isEqualTo(123) + } + + @Test + fun parseSingleRequiredParam_notPresent_failsValidation() { + val param by parser.require(parser.param("-p", valueParser = Type.Int)) + assertFalse(parser.parse(listOf())) + } + + @Test + fun parseSingleRequiredParam_missingArg_throws() { + val param by parser.require(parser.param("-p", valueParser = Type.Int)) + assertThrows(ArgParseError::class.java) { parser.parse(listOf("-p")) } + } + + @Test + fun parseAsSubCommand_singleFlag_present() { + val flag by parser.flag("-f") + val args = listOf("-f").listIterator() + parser.parseAsSubCommand(args) + + assertTrue(flag) + } + + @Test + fun parseAsSubCommand_singleFlag_notPresent() { + val flag by parser.flag("-f") + val args = listOf("--other-flag").listIterator() + parser.parseAsSubCommand(args) + + assertFalse(flag) + } + + @Test + fun parseAsSubCommand_singleOptionalParam_present() { + val param by parser.param("-p", valueParser = Type.Int) + parser.parseAsSubCommand(listOf("-p", "123", "--other-arg", "321").listIterator()) + assertThat(param).isEqualTo(123) + } + + @Test + fun parseAsSubCommand_singleOptionalParam_notPresent() { + val param by parser.param("-p", valueParser = Type.Int) + parser.parseAsSubCommand(listOf("--other-arg", "321").listIterator()) + assertThat(param).isNull() + } + + @Test + fun parseAsSubCommand_singleRequiredParam_present() { + val param by parser.require(parser.param("-p", valueParser = Type.Int)) + parser.parseAsSubCommand(listOf("-p", "123", "--other-arg", "321").listIterator()) + assertThat(param).isEqualTo(123) + } + + @Test + fun parseAsSubCommand_singleRequiredParam_notPresent() { + parser.require(parser.param("-p", valueParser = Type.Int)) + assertFalse(parser.parseAsSubCommand(listOf("--other-arg", "321").listIterator())) + } + + @Test + fun parseCommandWithSubCommand_required_provided() { + val topLevelFlag by parser.flag("flag", shortName = "-f") + + val cmd = + object : ParseableCommand("test") { + val flag by flag("flag1") + override fun execute(pw: PrintWriter) {} + } + + parser.require(parser.subCommand(cmd)) + parser.parse(listOf("-f", "test", "--flag1")) + + assertTrue(topLevelFlag) + assertThat(cmd).isNotNull() + assertTrue(cmd.flag) + } + + @Test + fun parseCommandWithSubCommand_required_notProvided() { + val topLevelFlag by parser.flag("-f") + + val cmd = + object : ParseableCommand("test") { + val flag by parser.flag("flag1") + override fun execute(pw: PrintWriter) {} + } + + parser.require(parser.subCommand(cmd)) + + assertFalse(parser.parse(listOf("-f"))) + } + + @Test + fun flag_requiredParam_optionalParam_allProvided_failsValidation() { + val flag by parser.flag("-f") + val optionalParam by parser.param("-p", valueParser = Type.Int) + val requiredParam by parser.require(parser.param("-p2", valueParser = Type.Boolean)) + + parser.parse( + listOf( + "-f", + "-p", + "123", + "-p2", + "false", + ) + ) + + assertTrue(flag) + assertThat(optionalParam).isEqualTo(123) + assertFalse(requiredParam) + } + + @Test + fun flag_requiredParam_optionalParam_optionalExcluded() { + val flag by parser.flag("-f") + val optionalParam by parser.param("-p", valueParser = Type.Int) + val requiredParam by parser.require(parser.param("-p2", valueParser = Type.Boolean)) + + parser.parse( + listOf( + "-p2", + "true", + ) + ) + + assertFalse(flag) + assertThat(optionalParam).isNull() + assertTrue(requiredParam) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt new file mode 100644 index 000000000000..e391d6b11cd6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParametersTest.kt @@ -0,0 +1,55 @@ +package com.android.systemui.statusbar.commandline + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertFalse +import org.junit.Assert.assertThrows +import org.junit.Assert.assertTrue +import org.junit.Test + +@SmallTest +class ParametersTest : SysuiTestCase() { + @Test + fun singleArgOptional_returnsNullBeforeParse() { + val optional by SingleArgParamOptional(longName = "longName", valueParser = Type.Int) + assertThat(optional).isNull() + } + + @Test + fun singleArgOptional_returnsParsedValue() { + val param = SingleArgParamOptional(longName = "longName", valueParser = Type.Int) + param.parseArgsFromIter(listOf("3").listIterator()) + val optional by param + assertThat(optional).isEqualTo(3) + } + + @Test + fun singleArgRequired_throwsBeforeParse() { + val req by SingleArgParam(longName = "param", valueParser = Type.Boolean) + assertThrows(IllegalStateException::class.java) { req } + } + + @Test + fun singleArgRequired_returnsParsedValue() { + val param = SingleArgParam(longName = "param", valueParser = Type.Boolean) + param.parseArgsFromIter(listOf("true").listIterator()) + val req by param + assertTrue(req) + } + + @Test + fun param_handledAfterParse() { + val optParam = SingleArgParamOptional(longName = "string1", valueParser = Type.String) + val reqParam = SingleArgParam(longName = "string2", valueParser = Type.Float) + + assertFalse(optParam.handled) + assertFalse(reqParam.handled) + + optParam.parseArgsFromIter(listOf("test").listIterator()) + reqParam.parseArgsFromIter(listOf("1.23").listIterator()) + + assertTrue(optParam.handled) + assertTrue(reqParam.handled) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt new file mode 100644 index 000000000000..86548d079003 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ParseableCommandTest.kt @@ -0,0 +1,317 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.commandline + +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import java.io.PrintWriter +import org.junit.Assert.assertThrows +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +@SmallTest +class ParseableCommandTest : SysuiTestCase() { + @Mock private lateinit var pw: PrintWriter + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + } + + /** + * A little change-detector-y, but this is just a general assertion that building up a command + * parser via its wrapper works as expected. + */ + @Test + fun testFactoryMethods() { + val mySubCommand = + object : ParseableCommand("subCommand") { + val flag by flag("flag") + override fun execute(pw: PrintWriter) {} + } + + val mySubCommand2 = + object : ParseableCommand("subCommand2") { + val flag by flag("flag") + override fun execute(pw: PrintWriter) {} + } + + // Verify that the underlying parser contains the correct types + val myCommand = + object : ParseableCommand("testName") { + val flag by flag("flag", shortName = "f") + val requiredParam by + param(longName = "required-param", shortName = "r", valueParser = Type.String) + .required() + val optionalParam by + param(longName = "optional-param", shortName = "o", valueParser = Type.Boolean) + val optionalSubCommand by subCommand(mySubCommand) + val requiredSubCommand by subCommand(mySubCommand2).required() + + override fun execute(pw: PrintWriter) {} + } + + val flags = myCommand.parser.flags + val params = myCommand.parser.params + val subCommands = myCommand.parser.subCommands + + assertThat(flags).hasSize(2) + assertThat(flags[0]).isInstanceOf(Flag::class.java) + assertThat(flags[1]).isInstanceOf(Flag::class.java) + + assertThat(params).hasSize(2) + val req = params.filter { it is SingleArgParam<*> } + val opt = params.filter { it is SingleArgParamOptional<*> } + assertThat(req).hasSize(1) + assertThat(opt).hasSize(1) + + val reqSub = subCommands.filter { it is RequiredSubCommand<*> } + val optSub = subCommands.filter { it is OptionalSubCommand<*> } + assertThat(reqSub).hasSize(1) + assertThat(optSub).hasSize(1) + } + + @Test + fun factoryMethods_enforceShortNameRules() { + // Short names MUST be one character long + assertThrows(IllegalArgumentException::class.java) { + val myCommand = + object : ParseableCommand("test-command") { + val flag by flag("longName", "invalidShortName") + + override fun execute(pw: PrintWriter) {} + } + } + + assertThrows(IllegalArgumentException::class.java) { + val myCommand = + object : ParseableCommand("test-command") { + val param by param("longName", "invalidShortName", valueParser = Type.String) + + override fun execute(pw: PrintWriter) {} + } + } + } + + @Test + fun factoryMethods_enforceLongNames_notPrefixed() { + // Long names must not start with "-", since they will be added + assertThrows(IllegalArgumentException::class.java) { + val myCommand = + object : ParseableCommand("test-command") { + val flag by flag("--invalid") + + override fun execute(pw: PrintWriter) {} + } + } + + assertThrows(IllegalArgumentException::class.java) { + val myCommand = + object : ParseableCommand("test-command") { + val param by param("-invalid", valueParser = Type.String) + + override fun execute(pw: PrintWriter) {} + } + } + } + + @Test + fun executeDoesNotPropagateExceptions() { + val cmd = + object : ParseableCommand("test-command") { + val flag by flag("flag") + override fun execute(pw: PrintWriter) {} + } + + val throwingCommand = listOf("unknown-token") + + // Given a command that would cause an ArgParseError + assertThrows(ArgParseError::class.java) { cmd.parser.parse(throwingCommand) } + + // The parser consumes that error + cmd.execute(pw, throwingCommand) + } + + @Test + fun executeFailingCommand_callsOnParseFailed() { + val cmd = + object : ParseableCommand("test-command") { + val flag by flag("flag") + + var onParseFailedCalled = false + + override fun execute(pw: PrintWriter) {} + override fun onParseFailed(error: ArgParseError) { + onParseFailedCalled = true + } + } + + val throwingCommand = listOf("unknown-token") + cmd.execute(pw, throwingCommand) + + assertTrue(cmd.onParseFailedCalled) + } + + @Test + fun baseCommand() { + val myCommand = MyCommand() + myCommand.execute(pw, baseCommand) + + assertThat(myCommand.flag1).isFalse() + assertThat(myCommand.singleParam).isNull() + } + + @Test + fun commandWithFlags() { + val command = MyCommand() + command.execute(pw, cmdWithFlags) + + assertThat(command.flag1).isTrue() + assertThat(command.flag2).isTrue() + } + + @Test + fun commandWithArgs() { + val cmd = MyCommand() + cmd.execute(pw, cmdWithSingleArgParam) + + assertThat(cmd.singleParam).isEqualTo("single_param") + } + + @Test + fun commandWithRequiredParam_provided() { + val cmd = + object : ParseableCommand(name) { + val singleRequiredParam: String by + param( + longName = "param1", + shortName = "p", + valueParser = Type.String, + ) + .required() + + override fun execute(pw: PrintWriter) {} + } + + val cli = listOf("-p", "value") + cmd.execute(pw, cli) + + assertThat(cmd.singleRequiredParam).isEqualTo("value") + } + + @Test + fun commandWithRequiredParam_not_provided_throws() { + val cmd = + object : ParseableCommand(name) { + val singleRequiredParam by + param(shortName = "p", longName = "param1", valueParser = Type.String) + .required() + + override fun execute(pw: PrintWriter) {} + + override fun execute(pw: PrintWriter, args: List<String>) { + parser.parse(args) + execute(pw) + } + } + + val cli = listOf("") + assertThrows(ArgParseError::class.java) { cmd.execute(pw, cli) } + } + + @Test + fun commandWithSubCommand() { + val subName = "sub-command" + val subCmd = + object : ParseableCommand(subName) { + val singleOptionalParam: String? by param("param", valueParser = Type.String) + + override fun execute(pw: PrintWriter) {} + } + + val cmd = + object : ParseableCommand(name) { + val subCmd by subCommand(subCmd) + override fun execute(pw: PrintWriter) {} + } + + cmd.execute(pw, listOf("sub-command", "--param", "test")) + assertThat(cmd.subCmd?.singleOptionalParam).isEqualTo("test") + } + + @Test + fun complexCommandWithSubCommands_reusedNames() { + val commandLine = "-f --param1 arg1 sub-command1 -f -p arg2 --param2 arg3".split(" ") + + val subName = "sub-command1" + val subCmd = + object : ParseableCommand(subName) { + val flag1 by flag("flag", shortName = "f") + val param1: String? by param("param1", shortName = "p", valueParser = Type.String) + + override fun execute(pw: PrintWriter) {} + } + + val myCommand = + object : ParseableCommand(name) { + val flag1 by flag(longName = "flag", shortName = "f") + val param1 by param("param1", shortName = "p", valueParser = Type.String).required() + val param2: String? by param(longName = "param2", valueParser = Type.String) + val subCommand by subCommand(subCmd) + + override fun execute(pw: PrintWriter) {} + } + + myCommand.execute(pw, commandLine) + + assertThat(myCommand.flag1).isTrue() + assertThat(myCommand.param1).isEqualTo("arg1") + assertThat(myCommand.param2).isEqualTo("arg3") + assertThat(myCommand.subCommand).isNotNull() + assertThat(myCommand.subCommand?.flag1).isTrue() + assertThat(myCommand.subCommand?.param1).isEqualTo("arg2") + } + + class MyCommand( + private val onExecute: ((MyCommand) -> Unit)? = null, + ) : ParseableCommand(name) { + + val flag1 by flag(shortName = "f", longName = "flag1", description = "flag 1 for test") + val flag2 by flag(shortName = "g", longName = "flag2", description = "flag 2 for test") + val singleParam: String? by + param( + shortName = "a", + longName = "arg1", + valueParser = Type.String, + ) + + override fun execute(pw: PrintWriter) { + onExecute?.invoke(this) + } + } + + companion object { + const val name = "my_command" + val baseCommand = listOf("") + val cmdWithFlags = listOf("-f", "--flag2") + val cmdWithSingleArgParam = listOf("--arg1", "single_param") + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt new file mode 100644 index 000000000000..759f0bcd6ea8 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/ValueParserTest.kt @@ -0,0 +1,61 @@ +package com.android.systemui.statusbar.commandline + +import android.graphics.Rect +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertTrue +import org.junit.Test + +@SmallTest +class ValueParserTest : SysuiTestCase() { + @Test + fun parseString() { + assertThat(Type.String.parseValue("test")).isEqualTo(Result.success("test")) + } + + @Test + fun parseInt() { + assertThat(Type.Int.parseValue("123")).isEqualTo(Result.success(123)) + + assertTrue(Type.Int.parseValue("not an Int").isFailure) + } + + @Test + fun parseFloat() { + assertThat(Type.Float.parseValue("1.23")).isEqualTo(Result.success(1.23f)) + + assertTrue(Type.Int.parseValue("not a Float").isFailure) + } + + @Test + fun parseBoolean() { + assertThat(Type.Boolean.parseValue("true")).isEqualTo(Result.success(true)) + assertThat(Type.Boolean.parseValue("false")).isEqualTo(Result.success(false)) + + assertTrue(Type.Boolean.parseValue("not a Boolean").isFailure) + } + + @Test + fun mapToComplexType() { + val parseSquare = Type.Int.map { Rect(it, it, it, it) } + + assertThat(parseSquare.parseValue("10")).isEqualTo(Result.success(Rect(10, 10, 10, 10))) + } + + @Test + fun mapToFallibleComplexType() { + val fallibleParseSquare = + Type.Int.map { + if (it > 0) { + Rect(it, it, it, it) + } else { + null + } + } + + assertThat(fallibleParseSquare.parseValue("10")) + .isEqualTo(Result.success(Rect(10, 10, 10, 10))) + assertTrue(fallibleParseSquare.parseValue("-10").isFailure) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt index 914301f2e830..2af0cebf3519 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt @@ -69,6 +69,8 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() { @Mock private lateinit var listener: SystemStatusAnimationCallback + @Mock private lateinit var logger: SystemStatusAnimationSchedulerLogger + private lateinit var systemClock: FakeSystemClock private lateinit var chipAnimationController: SystemEventChipAnimationController private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler @@ -538,7 +540,8 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() { statusBarWindowController, dumpManager, systemClock, - CoroutineScope(StandardTestDispatcher(testScope.testScheduler)) + CoroutineScope(StandardTestDispatcher(testScope.testScheduler)), + logger ) // add a mock listener systemStatusAnimationScheduler.addCallback(listener) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt index a37c38669bfd..902dd51d3a87 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorLoggerTest.kt @@ -20,8 +20,8 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.LogcatEchoTracker +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.StatusBarState import com.google.common.truth.Truth.assertThat import org.junit.Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt index 7707a7e8bf8c..47c5e5b021ae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/StackStateLoggerTest.kt @@ -20,8 +20,8 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.log.LogBuffer -import com.android.systemui.log.LogLevel import com.android.systemui.log.LogcatEchoTracker +import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.notification.stack.StackStateLogger import com.google.common.truth.Truth import org.junit.Before diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java index 6ae7dca296c8..ee8325ec02b5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java @@ -87,6 +87,7 @@ import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent; +import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback; import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel; import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; import com.android.systemui.statusbar.phone.KeyguardBypassController; @@ -415,6 +416,36 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase { } @Test + public void callSwipeCallbacksDuringClearAll() { + initController(/* viewIsAttached= */ true); + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + NotificationCallback notificationCallback = mController.mNotificationCallback; + + when(mNotificationStackScrollLayout.getClearAllInProgress()).thenReturn(true); + + notificationCallback.onBeginDrag(row); + verify(mNotificationStackScrollLayout).onSwipeBegin(row); + + notificationCallback.handleChildViewDismissed(row); + verify(mNotificationStackScrollLayout).onSwipeEnd(); + } + + @Test + public void callSwipeCallbacksDuringClearNotification() { + initController(/* viewIsAttached= */ true); + ExpandableNotificationRow row = mock(ExpandableNotificationRow.class); + NotificationCallback notificationCallback = mController.mNotificationCallback; + + when(mNotificationStackScrollLayout.getClearAllInProgress()).thenReturn(false); + + notificationCallback.onBeginDrag(row); + verify(mNotificationStackScrollLayout).onSwipeBegin(row); + + notificationCallback.handleChildViewDismissed(row); + verify(mNotificationStackScrollLayout).onSwipeEnd(); + } + + @Test public void testOnMenuClickedLogging() { ExpandableNotificationRow row = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS); when(row.getEntry().getSbn().getLogMaker()).thenReturn(new LogMaker( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java index 036b8becfbbc..8545b894ad41 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java @@ -20,6 +20,7 @@ import static android.view.Display.DEFAULT_DISPLAY; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; @@ -144,23 +145,32 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase { @Test public void testDisableNotificationShade() { - when(mCentralSurfaces.getDisabled1()).thenReturn(StatusBarManager.DISABLE_NONE); - when(mCentralSurfaces.getDisabled2()).thenReturn(StatusBarManager.DISABLE_NONE); + // Start with nothing disabled + mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE, + StatusBarManager.DISABLE2_NONE, false); + when(mCommandQueue.panelsEnabled()).thenReturn(false); + // WHEN the new disable flags have the shade disabled mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE, StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false); + // THEN the shade is collapsed verify(mShadeController).animateCollapseShade(); } @Test public void testEnableNotificationShade() { - when(mCentralSurfaces.getDisabled1()).thenReturn(StatusBarManager.DISABLE_NONE); - when(mCentralSurfaces.getDisabled2()) - .thenReturn(StatusBarManager.DISABLE2_NOTIFICATION_SHADE); + // Start with the shade disabled + mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE, + StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false); + reset(mShadeController); + when(mCommandQueue.panelsEnabled()).thenReturn(true); + // WHEN the new disable flags have the shade enabled mSbcqCallbacks.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE, StatusBarManager.DISABLE2_NONE, false); + + // THEN the shade is not collapsed verify(mShadeController, never()).animateCollapseShade(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 1ffffe4dca75..88d8dfc50b47 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -450,7 +450,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { () -> mAssistManager, () -> mNotificationGutsManager )); - mShadeController.setNotificationPanelViewController(mNotificationPanelViewController); + mShadeController.setShadeViewController(mNotificationPanelViewController); mShadeController.setNotificationShadeWindowViewController( mNotificationShadeWindowViewController); mShadeController.setNotificationPresenter(mNotificationPresenter); @@ -490,9 +490,11 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mMetricsLogger, mShadeLogger, mUiBgExecutor, + mNotificationPanelViewController, mNotificationMediaManager, mLockscreenUserManager, mRemoteInputManager, + mQuickSettingsController, mUserSwitcherController, mBatteryController, mColorExtractor, @@ -587,8 +589,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // TODO: we should be able to call mCentralSurfaces.start() and have all the below values // initialized automatically and make NPVC private. mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView; - mCentralSurfaces.mShadeSurface = mNotificationPanelViewController; - mCentralSurfaces.mQsController = mQuickSettingsController; mCentralSurfaces.mDozeScrimController = mDozeScrimController; mCentralSurfaces.mPresenter = mNotificationPresenter; mCentralSurfaces.mKeyguardIndicationController = mKeyguardIndicationController; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt index 3e90ed9811d0..4aac8419af3b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt @@ -42,6 +42,8 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito.anyBoolean +import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify @@ -135,6 +137,19 @@ class KeyguardBypassControllerTest : SysuiTestCase() { } @Test + fun onFaceAuthEnabledChanged_notifiesBypassEnabledListeners() { + initKeyguardBypassController() + val bypassListener = mock(KeyguardBypassController.OnBypassStateChangedListener::class.java) + val callback = ArgumentCaptor.forClass(KeyguardStateController.Callback::class.java) + + keyguardBypassController.registerOnBypassStateChangedListener(bypassListener) + verify(keyguardStateController).addCallback(callback.capture()) + + callback.value.onFaceAuthEnabledChanged() + verify(bypassListener).onBypassStateChanged(anyBoolean()) + } + + @Test fun configDevicePostureClosed_matchState_isPostureAllowedForFaceAuth_returnTrue() { defaultConfigPostureClosed() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt new file mode 100644 index 000000000000..47671fbadd0a --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LockscreenWallpaperTest.kt @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.systemui.statusbar.phone + +import android.app.WallpaperManager +import android.content.pm.UserInfo +import android.os.Looper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.user.data.model.SelectionStatus +import com.android.systemui.user.data.repository.FakeUserRepository +import com.android.systemui.util.kotlin.JavaAdapter +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.utils.os.FakeHandler +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito.verify + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +class LockscreenWallpaperTest : SysuiTestCase() { + + private lateinit var underTest: LockscreenWallpaper + + private val testScope = TestScope(StandardTestDispatcher()) + private val userRepository = FakeUserRepository() + + private val wallpaperManager: WallpaperManager = mock() + + @Before + fun setUp() { + whenever(wallpaperManager.isLockscreenLiveWallpaperEnabled).thenReturn(false) + whenever(wallpaperManager.isWallpaperSupported).thenReturn(true) + underTest = + LockscreenWallpaper( + /* wallpaperManager= */ wallpaperManager, + /* iWallpaperManager= */ mock(), + /* keyguardUpdateMonitor= */ mock(), + /* dumpManager= */ mock(), + /* mediaManager= */ mock(), + /* mainHandler= */ FakeHandler(Looper.getMainLooper()), + /* javaAdapter= */ JavaAdapter(testScope.backgroundScope), + /* userRepository= */ userRepository, + /* userTracker= */ mock(), + ) + underTest.start() + } + + @Test + fun getBitmap_matchesUserIdFromUserRepo() = + testScope.runTest { + val info = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0) + userRepository.setUserInfos(listOf(info)) + userRepository.setSelectedUserInfo(info) + + underTest.bitmap + + verify(wallpaperManager).getWallpaperFile(any(), eq(5)) + } + + @Test + fun getBitmap_usesOldUserIfNewUserInProgress() = + testScope.runTest { + val info5 = UserInfo(/* id= */ 5, /* name= */ "id5", /* flags= */ 0) + val info6 = UserInfo(/* id= */ 6, /* name= */ "id6", /* flags= */ 0) + userRepository.setUserInfos(listOf(info5, info6)) + userRepository.setSelectedUserInfo(info5) + + // WHEN the selection of user 6 is only in progress + userRepository.setSelectedUserInfo( + info6, + selectionStatus = SelectionStatus.SELECTION_IN_PROGRESS + ) + + underTest.bitmap + + // THEN we still use user 5 for wallpaper selection + verify(wallpaperManager).getWallpaperFile(any(), eq(5)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 9b1d93b691c3..5dcb90144b70 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -18,10 +18,6 @@ import static android.view.Display.DEFAULT_DISPLAY; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN; -import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_IN; -import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.ANIMATING_OUT; -import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.IDLE; -import static com.android.systemui.statusbar.events.SystemStatusAnimationSchedulerKt.RUNNING_CHIP_ANIM; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -49,7 +45,7 @@ import android.view.View; import android.view.ViewPropertyAnimator; import android.widget.FrameLayout; -import androidx.core.animation.Animator; +import androidx.core.animation.AnimatorTestRule; import androidx.test.filters.SmallTest; import com.android.keyguard.KeyguardUpdateMonitor; @@ -85,6 +81,7 @@ import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; +import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -139,6 +136,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { private StatusBarWindowStateController mStatusBarWindowStateController; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @ClassRule + public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); private List<StatusBarWindowStateListener> mStatusBarWindowStateListeners = new ArrayList<>(); @@ -172,7 +171,6 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test public void testDisableSystemInfo_systemAnimationIdle_doesHide() { - when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE); CollapsedStatusBarFragment fragment = resumeAndGetFragment(); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false); @@ -192,24 +190,26 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { public void testSystemStatusAnimation_startedDisabled_finishedWithAnimator_showsSystemInfo() { // GIVEN the status bar hides the system info via disable flags, while there is no event CollapsedStatusBarFragment fragment = resumeAndGetFragment(); - when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false); assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); + // WHEN the system event animation starts + fragment.onSystemEventAnimationBegin().start(); + + // THEN the view remains invisible during the animation + assertEquals(0f, getEndSideContentView().getAlpha(), 0.01); + mAnimatorTestRule.advanceTimeBy(500); + assertEquals(0f, getEndSideContentView().getAlpha(), 0.01); + // WHEN the disable flags are cleared during a system event animation - when(mAnimationScheduler.getAnimationState()).thenReturn(RUNNING_CHIP_ANIM); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - // THEN the view is made visible again, but still low alpha - assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); + // THEN the view remains invisible assertEquals(0, getEndSideContentView().getAlpha(), 0.01); // WHEN the system event animation finishes - when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT); - Animator anim = fragment.onSystemEventAnimationFinish(false); - anim.start(); - processAllMessages(); - anim.end(); + fragment.onSystemEventAnimationFinish(false).start(); + mAnimatorTestRule.advanceTimeBy(500); // THEN the system info is full alpha assertEquals(1, getEndSideContentView().getAlpha(), 0.01); @@ -219,20 +219,15 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { public void testSystemStatusAnimation_systemInfoDisabled_staysInvisible() { // GIVEN the status bar hides the system info via disable flags, while there is no event CollapsedStatusBarFragment fragment = resumeAndGetFragment(); - when(mAnimationScheduler.getAnimationState()).thenReturn(IDLE); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false); assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); // WHEN the system event animation finishes - when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT); - Animator anim = fragment.onSystemEventAnimationFinish(false); - anim.start(); - processAllMessages(); - anim.end(); + fragment.onSystemEventAnimationFinish(false).start(); + mAnimatorTestRule.advanceTimeBy(500); - // THEN the system info is at full alpha, but still INVISIBLE (since the disable flag is - // still set) - assertEquals(1, getEndSideContentView().getAlpha(), 0.01); + // THEN the system info remains invisible (since the disable flag is still set) + assertEquals(0, getEndSideContentView().getAlpha(), 0.01); assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); } @@ -241,15 +236,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { public void testSystemStatusAnimation_notDisabled_animatesAlphaZero() { // GIVEN the status bar is not disabled CollapsedStatusBarFragment fragment = resumeAndGetFragment(); - when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_IN); + assertEquals(1, getEndSideContentView().getAlpha(), 0.01); + // WHEN the system event animation begins - Animator anim = fragment.onSystemEventAnimationBegin(); - anim.start(); - processAllMessages(); - anim.end(); + fragment.onSystemEventAnimationBegin().start(); + mAnimatorTestRule.advanceTimeBy(500); - // THEN the system info is visible but alpha 0 - assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); + // THEN the system info is invisible + assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); assertEquals(0, getEndSideContentView().getAlpha(), 0.01); } @@ -257,25 +251,21 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { public void testSystemStatusAnimation_notDisabled_animatesBackToAlphaOne() { // GIVEN the status bar is not disabled CollapsedStatusBarFragment fragment = resumeAndGetFragment(); - when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_IN); + assertEquals(1, getEndSideContentView().getAlpha(), 0.01); + // WHEN the system event animation begins - Animator anim = fragment.onSystemEventAnimationBegin(); - anim.start(); - processAllMessages(); - anim.end(); + fragment.onSystemEventAnimationBegin().start(); + mAnimatorTestRule.advanceTimeBy(500); - // THEN the system info is visible but alpha 0 - assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); + // THEN the system info is invisible + assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility()); assertEquals(0, getEndSideContentView().getAlpha(), 0.01); // WHEN the system event animation finishes - when(mAnimationScheduler.getAnimationState()).thenReturn(ANIMATING_OUT); - anim = fragment.onSystemEventAnimationFinish(false); - anim.start(); - processAllMessages(); - anim.end(); + fragment.onSystemEventAnimationFinish(false).start(); + mAnimatorTestRule.advanceTimeBy(500); - // THEN the syste info is full alpha and VISIBLE + // THEN the system info is full alpha and VISIBLE assertEquals(View.VISIBLE, getEndSideContentView().getVisibility()); assertEquals(1, getEndSideContentView().getAlpha(), 0.01); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt new file mode 100644 index 000000000000..2617613d1fc5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/MultiSourceMinAlphaControllerTest.kt @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.fragment + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.View +import androidx.core.animation.AnimatorTestRule +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import junit.framework.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +private const val TEST_SOURCE_1 = 1 +private const val TEST_SOURCE_2 = 2 +private const val TEST_ANIMATION_DURATION = 100L +private const val INITIAL_ALPHA = 1f + +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class MultiSourceMinAlphaControllerTest : SysuiTestCase() { + + private val view = View(context) + private val multiSourceMinAlphaController = + MultiSourceMinAlphaController(view, initialAlpha = INITIAL_ALPHA) + + @get:Rule val animatorTestRule = AnimatorTestRule() + + @Before + fun setup() { + multiSourceMinAlphaController.reset() + } + + @Test + fun testSetAlpha() { + multiSourceMinAlphaController.setAlpha(alpha = 0.5f, sourceId = TEST_SOURCE_1) + assertEquals(0.5f, view.alpha) + } + + @Test + fun testAnimateToAlpha() { + multiSourceMinAlphaController.animateToAlpha( + alpha = 0.5f, + sourceId = TEST_SOURCE_1, + duration = TEST_ANIMATION_DURATION + ) + animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION) + assertEquals(0.5f, view.alpha) + } + + @Test + fun testReset() { + multiSourceMinAlphaController.animateToAlpha( + alpha = 0.5f, + sourceId = TEST_SOURCE_1, + duration = TEST_ANIMATION_DURATION + ) + multiSourceMinAlphaController.setAlpha(alpha = 0.7f, sourceId = TEST_SOURCE_2) + multiSourceMinAlphaController.reset() + // advance time to ensure that animators are cancelled when the controller is reset + animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION) + assertEquals(INITIAL_ALPHA, view.alpha) + } + + @Test + fun testMinOfTwoSourcesIsApplied() { + multiSourceMinAlphaController.setAlpha(alpha = 0f, sourceId = TEST_SOURCE_1) + multiSourceMinAlphaController.setAlpha(alpha = 0.5f, sourceId = TEST_SOURCE_2) + assertEquals(0f, view.alpha) + multiSourceMinAlphaController.setAlpha(alpha = 1f, sourceId = TEST_SOURCE_1) + assertEquals(0.5f, view.alpha) + } + + @Test + fun testSetAlphaForSameSourceCancelsAnimator() { + multiSourceMinAlphaController.animateToAlpha( + alpha = 0f, + sourceId = TEST_SOURCE_1, + duration = TEST_ANIMATION_DURATION + ) + animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION / 2) + multiSourceMinAlphaController.setAlpha(alpha = 1f, sourceId = TEST_SOURCE_1) + animatorTestRule.advanceTimeBy(TEST_ANIMATION_DURATION / 2) + // verify that animation was cancelled and the setAlpha call overrides the alpha value of + // the animation + assertEquals(1f, view.alpha) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt index 74bcdeec25cd..862eb001becc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt @@ -73,7 +73,6 @@ import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest -import org.junit.Assert.assertThrows import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test @@ -588,11 +587,10 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { } @Test - fun testConnectionRepository_invalidSubId_throws() = + fun testConnectionRepository_invalidSubId_doesNotThrow() = testScope.runTest { - assertThrows(IllegalArgumentException::class.java) { - underTest.getRepoForSubId(SUB_1_ID) - } + underTest.getRepoForSubId(SUB_1_ID) + // No exception } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt index 6301fa0be463..842d548c8358 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt @@ -20,7 +20,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectValues import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep @@ -48,7 +48,11 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { keyguardTransitionRepository = FakeKeyguardTransitionRepository() val interactor = - KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope) + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor underTest = CollapsedStatusBarViewModelImpl(interactor, testScope.backgroundScope) } 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 index d787ada90a73..5cabcd4163b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.hardware.biometrics.BiometricSourceType; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -43,6 +44,7 @@ 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; @@ -66,6 +68,9 @@ public class KeyguardStateControllerTest extends SysuiTestCase { @Mock private KeyguardUpdateMonitorLogger mLogger; + @Captor + private ArgumentCaptor<KeyguardUpdateMonitorCallback> mUpdateCallbackCaptor; + @Before public void setup() { MockitoAnnotations.initMocks(this); @@ -84,6 +89,23 @@ public class KeyguardStateControllerTest extends SysuiTestCase { } @Test + public void testFaceAuthEnabledChanged_calledWhenFaceEnrollmentStateChanges() { + KeyguardStateController.Callback callback = mock(KeyguardStateController.Callback.class); + + when(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(anyInt())).thenReturn(false); + verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture()); + mKeyguardStateController.addCallback(callback); + assertThat(mKeyguardStateController.isFaceAuthEnabled()).isFalse(); + + when(mKeyguardUpdateMonitor.isFaceAuthEnabledForUser(anyInt())).thenReturn(true); + mUpdateCallbackCaptor.getValue().onBiometricEnrollmentStateChanged( + BiometricSourceType.FACE); + + assertThat(mKeyguardStateController.isFaceAuthEnabled()).isTrue(); + verify(callback).onFaceAuthEnabledChanged(); + } + + @Test public void testIsShowing() { assertThat(mKeyguardStateController.isShowing()).isFalse(); mKeyguardStateController.notifyKeyguardState(true /* showing */, false /* occluded */); @@ -177,16 +199,14 @@ public class KeyguardStateControllerTest extends SysuiTestCase { @Test public void testOnEnabledTrustAgentsChangedCallback() { final Random random = new Random(); - final ArgumentCaptor<KeyguardUpdateMonitorCallback> updateCallbackCaptor = - ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); - verify(mKeyguardUpdateMonitor).registerCallback(updateCallbackCaptor.capture()); + verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture()); final KeyguardStateController.Callback stateCallback = mock(KeyguardStateController.Callback.class); mKeyguardStateController.addCallback(stateCallback); when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true); - updateCallbackCaptor.getValue().onEnabledTrustAgentsChanged(random.nextInt()); + mUpdateCallbackCaptor.getValue().onEnabledTrustAgentsChanged(random.nextInt()); verify(stateCallback).onUnlockedChanged(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt index 079fbcd0304c..0c28cbb52831 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt @@ -26,6 +26,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.user.data.model.SelectedUserModel +import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.model.UserSwitcherSettingsModel import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat @@ -224,6 +226,40 @@ class UserRepositoryImplTest : SysuiTestCase() { } @Test + fun userTrackerCallback_updatesSelectionStatus() = runSelfCancelingTest { + underTest = create(this) + var selectedUser: SelectedUserModel? = null + underTest.selectedUser.onEach { selectedUser = it }.launchIn(this) + setUpUsers(count = 2, selectedIndex = 1) + + // WHEN the user is changing + tracker.onUserChanging(userId = 1) + + // THEN the selection status is IN_PROGRESS + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS) + + // WHEN the user has finished changing + tracker.onUserChanged(userId = 1) + + // THEN the selection status is COMPLETE + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE) + + tracker.onProfileChanged() + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_COMPLETE) + + setUpUsers(count = 2, selectedIndex = 0) + + tracker.onUserChanging(userId = 0) + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS) + + // WHEN a profile change occurs while a user is changing + tracker.onProfileChanged() + + // THEN the selection status remains as IN_PROGRESS + assertThat(selectedUser!!.selectionStatus).isEqualTo(SelectionStatus.SELECTION_IN_PROGRESS) + } + + @Test fun userSwitchingInProgress_registersUserTrackerCallback() = runSelfCancelingTest { underTest = create(this) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt index 738f09ddce3d..2715aaa82253 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt @@ -41,7 +41,9 @@ class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository { fun setDetectionStatus(status: DetectionStatus) { _detectionStatus.value = status } - override val isLockedOut = MutableStateFlow(false) + + private val _isLockedOut = MutableStateFlow(false) + override val isLockedOut = _isLockedOut private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null) val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> = _runningAuthRequest.asStateFlow() @@ -56,6 +58,10 @@ class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository { _isAuthRunning.value = true } + fun setLockedOut(value: Boolean) { + _isLockedOut.value = value + } + override fun cancel() { _isAuthRunning.value = false _runningAuthRequest.value = null diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt new file mode 100644 index 000000000000..312ade510784 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.keyguard.domain.interactor + +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import kotlinx.coroutines.CoroutineScope + +/** + * Helper to create a new KeyguardTransitionInteractor in a way that doesn't require modifying 20+ + * tests whenever we add a constructor param. + */ +object KeyguardTransitionInteractorFactory { + @JvmOverloads + @JvmStatic + fun create( + scope: CoroutineScope, + repository: KeyguardTransitionRepository = FakeKeyguardTransitionRepository(), + ): WithDependencies { + return WithDependencies( + repository = repository, + KeyguardTransitionInteractor( + scope = scope, + repository = repository, + ) + ) + } + + data class WithDependencies( + val repository: KeyguardTransitionRepository, + val keyguardTransitionInteractor: KeyguardTransitionInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt index 61e5b5fc27ea..51ee0c00cb0d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt @@ -19,18 +19,28 @@ package com.android.systemui.user.data.repository import android.content.pm.UserInfo import android.os.UserHandle +import com.android.systemui.user.data.model.SelectedUserModel +import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.model.UserSwitcherSettingsModel import java.util.concurrent.atomic.AtomicBoolean import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.yield class FakeUserRepository : UserRepository { companion object { // User id to represent a non system (human) user id. We presume this is the main user. private const val MAIN_USER_ID = 10 + + private val DEFAULT_SELECTED_USER = 0 + private val DEFAULT_SELECTED_USER_INFO = + UserInfo( + /* id= */ DEFAULT_SELECTED_USER, + /* name= */ "default selected user", + /* flags= */ 0, + ) } private val _userSwitcherSettings = MutableStateFlow(UserSwitcherSettingsModel()) @@ -40,8 +50,11 @@ class FakeUserRepository : UserRepository { private val _userInfos = MutableStateFlow<List<UserInfo>>(emptyList()) override val userInfos: Flow<List<UserInfo>> = _userInfos.asStateFlow() - private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null) - override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull() + override val selectedUser = + MutableStateFlow( + SelectedUserModel(DEFAULT_SELECTED_USER_INFO, SelectionStatus.SELECTION_COMPLETE) + ) + override val selectedUserInfo: Flow<UserInfo> = selectedUser.map { it.userInfo } private val _userSwitchingInProgress = MutableStateFlow(false) override val userSwitchingInProgress: Flow<Boolean> @@ -72,7 +85,7 @@ class FakeUserRepository : UserRepository { } override fun getSelectedUserInfo(): UserInfo { - return checkNotNull(_selectedUserInfo.value) + return selectedUser.value.userInfo } override fun isSimpleUserSwitcher(): Boolean { @@ -87,12 +100,15 @@ class FakeUserRepository : UserRepository { _userInfos.value = infos } - suspend fun setSelectedUserInfo(userInfo: UserInfo) { + suspend fun setSelectedUserInfo( + userInfo: UserInfo, + selectionStatus: SelectionStatus = SelectionStatus.SELECTION_COMPLETE, + ) { check(_userInfos.value.contains(userInfo)) { "Cannot select the following user, it is not in the list of user infos: $userInfo!" } - _selectedUserInfo.value = userInfo + selectedUser.value = SelectedUserModel(userInfo, selectionStatus) yield() } diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java index c2ebddf00fb4..502ee4db80c8 100644 --- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java +++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java @@ -75,6 +75,7 @@ import android.util.Range; import android.util.Size; import android.view.Surface; +import androidx.annotation.NonNull; import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl; import androidx.camera.extensions.impl.AutoPreviewExtenderImpl; import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl; @@ -204,7 +205,7 @@ public class CameraExtensionsProxyService extends Service { * A per-process global camera extension manager instance, to track and * initialize/release extensions depending on client activity. */ - private static final class CameraExtensionManagerGlobal { + private static final class CameraExtensionManagerGlobal implements IBinder.DeathRecipient { private static final String TAG = "CameraExtensionManagerGlobal"; private final int EXTENSION_DELAY_MS = 1000; @@ -212,8 +213,9 @@ public class CameraExtensionsProxyService extends Service { private final HandlerThread mHandlerThread; private final Object mLock = new Object(); - private long mCurrentClientCount = 0; - private ArraySet<Long> mActiveClients = new ArraySet<>(); + private ArraySet<IBinder> mActiveClients = new ArraySet<>(); + private HashMap<IBinder, ArraySet<IBinder.DeathRecipient>> mClientDeathRecipient = + new HashMap<>(); private IInitializeSessionCallback mInitializeCb = null; // Singleton instance @@ -314,8 +316,20 @@ public class CameraExtensionsProxyService extends Service { return GLOBAL_CAMERA_MANAGER; } - public long registerClient(Context ctx) { + public boolean registerClient(Context ctx, IBinder token) { synchronized (mLock) { + if (mActiveClients.contains(token)) { + Log.e(TAG, "Failed to register existing client!"); + return false; + } + + try { + token.linkToDeath(this, 0); + } catch (RemoteException e) { + Log.e(TAG, "Failed to link to binder token!"); + return false; + } + if (INIT_API_SUPPORTED) { if (mActiveClients.isEmpty()) { InitializerFuture status = new InitializerFuture(); @@ -327,47 +341,80 @@ public class CameraExtensionsProxyService extends Service { TimeUnit.MILLISECONDS); } catch (TimeoutException e) { Log.e(TAG, "Timed out while initializing camera extensions!"); - return -1; + return false; } if (!initSuccess) { Log.e(TAG, "Failed while initializing camera extensions!"); - return -1; + return false; } } } - long ret = mCurrentClientCount; - mCurrentClientCount++; - if (mCurrentClientCount < 0) { - mCurrentClientCount = 0; - } - mActiveClients.add(ret); + mActiveClients.add(token); + mClientDeathRecipient.put(token, new ArraySet<>()); - return ret; + return true; } } - public void unregisterClient(long clientId) { + public void unregisterClient(IBinder token) { synchronized (mLock) { - if (mActiveClients.remove(clientId) && mActiveClients.isEmpty() && - INIT_API_SUPPORTED) { - InitializerFuture status = new InitializerFuture(); - InitializerImpl.deinit(new ReleaseHandler(status), - new HandlerExecutor(mHandler)); - boolean releaseSuccess; - try { - releaseSuccess = status.get(EXTENSION_DELAY_MS, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - Log.e(TAG, "Timed out while releasing camera extensions!"); - return; - } - if (!releaseSuccess) { - Log.e(TAG, "Failed while releasing camera extensions!"); + if (mActiveClients.remove(token)) { + token.unlinkToDeath(this, 0); + mClientDeathRecipient.remove(token); + if (mActiveClients.isEmpty() && INIT_API_SUPPORTED) { + InitializerFuture status = new InitializerFuture(); + InitializerImpl.deinit(new ReleaseHandler(status), + new HandlerExecutor(mHandler)); + boolean releaseSuccess; + try { + releaseSuccess = status.get(EXTENSION_DELAY_MS, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + Log.e(TAG, "Timed out while releasing camera extensions!"); + return; + } + if (!releaseSuccess) { + Log.e(TAG, "Failed while releasing camera extensions!"); + } } } } } + @Override + public void binderDied() { + // Do nothing, handled below + } + + @Override + public void binderDied(@NonNull IBinder who) { + synchronized (mLock) { + if (mClientDeathRecipient.containsKey(who)) { + mClientDeathRecipient.get(who).stream().forEach( + recipient -> recipient.binderDied(who)); + } + unregisterClient(who); + } + } + + public void registerDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) { + synchronized (mLock) { + if (mClientDeathRecipient.containsKey(token)) { + ArraySet<IBinder.DeathRecipient> recipients = mClientDeathRecipient.get(token); + recipients.add(recipient); + } + } + } + + public void unregisterDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) { + synchronized (mLock) { + if (mClientDeathRecipient.containsKey(token)) { + ArraySet<IBinder.DeathRecipient> recipients = mClientDeathRecipient.get(token); + recipients.remove(recipient); + } + } + } + private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { @@ -406,21 +453,35 @@ public class CameraExtensionsProxyService extends Service { /** * @hide */ - private static long registerClient(Context ctx) { + private static boolean registerClient(Context ctx, IBinder token) { if (!EXTENSIONS_PRESENT) { - return -1; + return false; } - return CameraExtensionManagerGlobal.get().registerClient(ctx); + return CameraExtensionManagerGlobal.get().registerClient(ctx, token); } /** * @hide */ - public static void unregisterClient(long clientId) { + public static void unregisterClient(IBinder token) { if (!EXTENSIONS_PRESENT) { return; } - CameraExtensionManagerGlobal.get().unregisterClient(clientId); + CameraExtensionManagerGlobal.get().unregisterClient(token); + } + + /** + * @hide + */ + private static void registerDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) { + CameraExtensionManagerGlobal.get().registerDeathRecipient(token, recipient); + } + + /** + * @hide + */ + private static void unregisterDeathRecipient(IBinder token, IBinder.DeathRecipient recipient) { + CameraExtensionManagerGlobal.get().unregisterDeathRecipient(token, recipient); } /** @@ -649,13 +710,14 @@ public class CameraExtensionsProxyService extends Service { private class CameraExtensionsProxyServiceStub extends ICameraExtensionsProxyService.Stub { @Override - public long registerClient() { - return CameraExtensionsProxyService.registerClient(CameraExtensionsProxyService.this); + public boolean registerClient(IBinder token) { + return CameraExtensionsProxyService.registerClient(CameraExtensionsProxyService.this, + token); } @Override - public void unregisterClient(long clientId) { - CameraExtensionsProxyService.unregisterClient(clientId); + public void unregisterClient(IBinder token) { + CameraExtensionsProxyService.unregisterClient(token); } private boolean checkCameraPermission() { @@ -1192,16 +1254,18 @@ public class CameraExtensionsProxyService extends Service { } } - private class SessionProcessorImplStub extends ISessionProcessorImpl.Stub { + private class SessionProcessorImplStub extends ISessionProcessorImpl.Stub implements + IBinder.DeathRecipient { private final SessionProcessorImpl mSessionProcessor; private String mCameraId = null; + private IBinder mToken; public SessionProcessorImplStub(SessionProcessorImpl sessionProcessor) { mSessionProcessor = sessionProcessor; } @Override - public CameraSessionConfig initSession(String cameraId, + public CameraSessionConfig initSession(IBinder token, String cameraId, Map<String, CameraMetadataNative> charsMapNative, OutputSurface previewSurface, OutputSurface imageCaptureSurface, OutputSurface postviewSurface) { OutputSurfaceImplStub outputPreviewSurfaceImpl = @@ -1253,12 +1317,14 @@ public class CameraExtensionsProxyService extends Service { ret.sessionParameter = initializeParcelableMetadata( sessionConfig.getSessionParameters(), cameraId); mCameraId = cameraId; - + mToken = token; + CameraExtensionsProxyService.registerDeathRecipient(mToken, this); return ret; } @Override - public void deInitSession() { + public void deInitSession(IBinder token) { + CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this); mSessionProcessor.deInitSession(); } @@ -1330,6 +1396,11 @@ public class CameraExtensionsProxyService extends Service { return null; } + + @Override + public void binderDied() { + mSessionProcessor.deInitSession(); + } } private class OutputSurfaceConfigurationImplStub implements OutputSurfaceConfigurationImpl { @@ -1395,24 +1466,31 @@ public class CameraExtensionsProxyService extends Service { } } - private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub { + private class PreviewExtenderImplStub extends IPreviewExtenderImpl.Stub implements + IBinder.DeathRecipient { private final PreviewExtenderImpl mPreviewExtender; private String mCameraId = null; + private boolean mSessionEnabled; + private IBinder mToken; public PreviewExtenderImplStub(PreviewExtenderImpl previewExtender) { mPreviewExtender = previewExtender; } @Override - public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) { + public void onInit(IBinder token, String cameraId, + CameraMetadataNative cameraCharacteristics) { mCameraId = cameraId; CameraCharacteristics chars = new CameraCharacteristics(cameraCharacteristics); mCameraManager.registerDeviceStateListener(chars); mPreviewExtender.onInit(cameraId, chars, CameraExtensionsProxyService.this); + mToken = token; + CameraExtensionsProxyService.registerDeathRecipient(mToken, this); } @Override - public void onDeInit() { + public void onDeInit(IBinder token) { + CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this); mPreviewExtender.onDeInit(); } @@ -1423,11 +1501,13 @@ public class CameraExtensionsProxyService extends Service { @Override public CaptureStageImpl onEnableSession() { + mSessionEnabled = true; return initializeParcelable(mPreviewExtender.onEnableSession(), mCameraId); } @Override public CaptureStageImpl onDisableSession() { + mSessionEnabled = false; return initializeParcelable(mPreviewExtender.onDisableSession(), mCameraId); } @@ -1516,26 +1596,41 @@ public class CameraExtensionsProxyService extends Service { } return null; } + + @Override + public void binderDied() { + if (mSessionEnabled) { + mPreviewExtender.onDisableSession(); + } + mPreviewExtender.onDeInit(); + } } - private class ImageCaptureExtenderImplStub extends IImageCaptureExtenderImpl.Stub { + private class ImageCaptureExtenderImplStub extends IImageCaptureExtenderImpl.Stub implements + IBinder.DeathRecipient { private final ImageCaptureExtenderImpl mImageExtender; private String mCameraId = null; + private boolean mSessionEnabled; + private IBinder mToken; public ImageCaptureExtenderImplStub(ImageCaptureExtenderImpl imageExtender) { mImageExtender = imageExtender; } @Override - public void onInit(String cameraId, CameraMetadataNative cameraCharacteristics) { + public void onInit(IBinder token, String cameraId, + CameraMetadataNative cameraCharacteristics) { CameraCharacteristics chars = new CameraCharacteristics(cameraCharacteristics); mCameraManager.registerDeviceStateListener(chars); mImageExtender.onInit(cameraId, chars, CameraExtensionsProxyService.this); mCameraId = cameraId; + mToken = token; + CameraExtensionsProxyService.registerDeathRecipient(mToken, this); } @Override - public void onDeInit() { + public void onDeInit(IBinder token) { + CameraExtensionsProxyService.unregisterDeathRecipient(mToken, this); mImageExtender.onDeInit(); } @@ -1564,11 +1659,13 @@ public class CameraExtensionsProxyService extends Service { @Override public CaptureStageImpl onEnableSession() { + mSessionEnabled = true; return initializeParcelable(mImageExtender.onEnableSession(), mCameraId); } @Override public CaptureStageImpl onDisableSession() { + mSessionEnabled = false; return initializeParcelable(mImageExtender.onDisableSession(), mCameraId); } @@ -1737,6 +1834,14 @@ public class CameraExtensionsProxyService extends Service { return null; } + + @Override + public void binderDied() { + if (mSessionEnabled) { + mImageExtender.onDisableSession(); + } + mImageExtender.onDeInit(); + } } private class ProcessResultCallback implements ProcessResultImpl { diff --git a/services/accessibility/TEST_MAPPING b/services/accessibility/TEST_MAPPING index b5e8214dd2c0..299d33fbbe6d 100644 --- a/services/accessibility/TEST_MAPPING +++ b/services/accessibility/TEST_MAPPING @@ -39,8 +39,12 @@ "name": "FrameworksCoreTests", "options": [ { - "include-filter": "android.accessibilityservice", - "include-filter": "android.view.accessibility", + "include-filter": "android.accessibilityservice" + }, + { + "include-filter": "android.view.accessibility" + }, + { "include-filter": "com.android.internal.accessibility" }, { @@ -74,8 +78,12 @@ "name": "FrameworksCoreTests", "options": [ { - "include-filter": "android.accessibilityservice", - "include-filter": "android.view.accessibility", + "include-filter": "android.accessibilityservice" + }, + { + "include-filter": "android.view.accessibility" + }, + { "include-filter": "com.android.internal.accessibility" } ] diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index c937447a67e4..05b6eb4a64c1 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -319,7 +319,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ void unbindImeLocked(AbstractAccessibilityServiceConnection connection); - void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc); + void attachAccessibilityOverlayToDisplay( + int interactionId, + int displayId, + SurfaceControl sc, + IAccessibilityInteractionConnectionCallback callback); } @@ -2654,17 +2658,26 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } @Override - public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) { + public void attachAccessibilityOverlayToDisplay( + int interactionId, + int displayId, + SurfaceControl sc, + IAccessibilityInteractionConnectionCallback callback) { final long identity = Binder.clearCallingIdentity(); try { - mSystemSupport.attachAccessibilityOverlayToDisplay(displayId, sc); + mSystemSupport.attachAccessibilityOverlayToDisplay( + interactionId, displayId, sc, callback); } finally { Binder.restoreCallingIdentity(identity); } } @Override - public void attachAccessibilityOverlayToWindow(int accessibilityWindowId, SurfaceControl sc) + public void attachAccessibilityOverlayToWindow( + int interactionId, + int accessibilityWindowId, + SurfaceControl sc, + IAccessibilityInteractionConnectionCallback callback) throws RemoteException { final long identity = Binder.clearCallingIdentity(); try { @@ -2677,14 +2690,17 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mSystemSupport.getCurrentUserIdLocked(), resolveAccessibilityWindowIdLocked(accessibilityWindowId)); if (connection == null) { - Slog.e(LOG_TAG, "unable to get remote accessibility connection."); + callback.sendAttachOverlayResult( + AccessibilityService.OVERLAY_RESULT_INVALID, interactionId); return; } - connection.getRemote().attachAccessibilityOverlayToWindow(sc); + connection + .getRemote() + .attachAccessibilityOverlayToWindow(sc, interactionId, callback); } } finally { Binder.restoreCallingIdentity(identity); } } -}
\ No newline at end of file +} diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 466cda3f2131..cbf4cced0e9b 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -131,6 +131,7 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowAttributes; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; +import android.view.accessibility.IAccessibilityInteractionConnectionCallback; import android.view.accessibility.IAccessibilityManager; import android.view.accessibility.IAccessibilityManagerClient; import android.view.accessibility.IWindowMagnificationConnection; @@ -5343,16 +5344,27 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @Override - public void attachAccessibilityOverlayToDisplay(int displayId, SurfaceControl sc) { + public void attachAccessibilityOverlayToDisplay( + int interactionId, + int displayId, + SurfaceControl sc, + IAccessibilityInteractionConnectionCallback callback) { mMainHandler.sendMessage( obtainMessage( AccessibilityManagerService::attachAccessibilityOverlayToDisplayInternal, this, + interactionId, displayId, - sc)); + sc, + callback)); } - void attachAccessibilityOverlayToDisplayInternal(int displayId, SurfaceControl sc) { + void attachAccessibilityOverlayToDisplayInternal( + int interactionId, + int displayId, + SurfaceControl sc, + IAccessibilityInteractionConnectionCallback callback) { + int result; if (!mA11yOverlayLayers.contains(displayId)) { mA11yOverlayLayers.put(displayId, mWindowManagerService.getA11yOverlayLayer(displayId)); } @@ -5360,10 +5372,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub if (parent == null) { Slog.e(LOG_TAG, "Unable to get accessibility overlay SurfaceControl."); mA11yOverlayLayers.remove(displayId); - return; + result = AccessibilityService.OVERLAY_RESULT_INVALID; + } else { + SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + t.reparent(sc, parent).setTrustedOverlay(sc, true).apply(); + t.close(); + result = AccessibilityService.OVERLAY_RESULT_SUCCESS; + } + // Send the result back to the service. + try { + callback.sendAttachOverlayResult(result, interactionId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Exception while attaching overlay.", re); + // the other side will time out } - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - t.reparent(sc, parent).setTrustedOverlay(sc, true).apply(); - t.close(); } } diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java index c08b6ab56f55..a525e7c64bc3 100644 --- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java +++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java @@ -16,6 +16,7 @@ package com.android.server.accessibility; +import android.accessibilityservice.AccessibilityService; import android.os.Binder; import android.os.RemoteException; import android.util.Slog; @@ -281,4 +282,11 @@ public class ActionReplacingCallback extends IAccessibilityInteractionConnection info.setDismissable(mNodeWithReplacementActions.isDismissable()); } } + + @Override + public void sendAttachOverlayResult( + @AccessibilityService.AttachOverlayResult int result, int interactionId) + throws RemoteException { + mServiceCallback.sendAttachOverlayResult(result, interactionId); + } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java index b1cdc5053c27..ba3d4340a157 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationController.java @@ -319,6 +319,10 @@ public class FullScreenMagnificationController implements FullScreenMagnificationController::onUserContextChanged, FullScreenMagnificationController.this, mDisplayId); mControllerCtx.getHandler().sendMessage(m); + + synchronized (mLock) { + refreshThumbnail(); + } } @Override @@ -344,7 +348,7 @@ public class FullScreenMagnificationController implements mMagnificationRegion.set(magnified); mMagnificationRegion.getBounds(mMagnificationBounds); - refreshThumbnail(getScale(), getCenterX(), getCenterY()); + refreshThumbnail(); // It's possible that our magnification spec is invalid with the new bounds. // Adjust the current spec's offsets if necessary. @@ -602,13 +606,13 @@ public class FullScreenMagnificationController implements } @GuardedBy("mLock") - void refreshThumbnail(float scale, float centerX, float centerY) { + void refreshThumbnail() { if (mMagnificationThumbnail != null) { mMagnificationThumbnail.setThumbnailBounds( mMagnificationBounds, - scale, - centerX, - centerY + getScale(), + getCenterX(), + getCenterY() ); } } @@ -627,7 +631,7 @@ public class FullScreenMagnificationController implements // We call refreshThumbnail when the thumbnail is just created to set current // magnification bounds to thumbnail. It to prevent the thumbnail size has not yet // updated properly and thus shows with huge size. (b/276314641) - refreshThumbnail(getScale(), getCenterX(), getCenterY()); + refreshThumbnail(); } } diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java index 03fa93d8a3bc..a7bdd5a09ac2 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationThumbnail.java @@ -99,15 +99,17 @@ public class MagnificationThumbnail { Log.d(LOG_TAG, "setThumbnailBounds " + currentBounds); } mHandler.post(() -> { - mWindowBounds = currentBounds; - setBackgroundBounds(); + refreshBackgroundBounds(currentBounds); if (mVisible) { updateThumbnailMainThread(scale, centerX, centerY); } }); } - private void setBackgroundBounds() { + @MainThread + private void refreshBackgroundBounds(Rect currentBounds) { + mWindowBounds = currentBounds; + Point magnificationBoundary = getMagnificationThumbnailPadding(mContext); mThumbnailWidth = (int) (mWindowBounds.width() / BG_ASPECT_RATIO); mThumbnailHeight = (int) (mWindowBounds.height() / BG_ASPECT_RATIO); @@ -117,6 +119,10 @@ public class MagnificationThumbnail { mBackgroundParams.height = mThumbnailHeight; mBackgroundParams.x = initX; mBackgroundParams.y = initY; + + if (mVisible) { + mWindowManager.updateViewLayout(mThumbnailLayout, mBackgroundParams); + } } @MainThread @@ -264,21 +270,16 @@ public class MagnificationThumbnail { mThumbnailView.setScaleX(scaleDown); mThumbnailView.setScaleY(scaleDown); } - float thumbnailWidth; - float thumbnailHeight; - if (mThumbnailView.getWidth() == 0 || mThumbnailView.getHeight() == 0) { - // if the thumbnail view size is not updated correctly, we just use the cached values. - thumbnailWidth = mThumbnailWidth; - thumbnailHeight = mThumbnailHeight; - } else { - thumbnailWidth = mThumbnailView.getWidth(); - thumbnailHeight = mThumbnailView.getHeight(); - } - if (!Float.isNaN(centerX)) { + + if (!Float.isNaN(centerX) + && !Float.isNaN(centerY) + && mThumbnailWidth > 0 + && mThumbnailHeight > 0 + ) { var padding = mThumbnailView.getPaddingTop(); var ratio = 1f / BG_ASPECT_RATIO; - var centerXScaled = centerX * ratio - (thumbnailWidth / 2f + padding); - var centerYScaled = centerY * ratio - (thumbnailHeight / 2f + padding); + var centerXScaled = centerX * ratio - (mThumbnailWidth / 2f + padding); + var centerYScaled = centerY * ratio - (mThumbnailHeight / 2f + padding); if (DEBUG) { Log.d( diff --git a/services/autofill/OWNERS b/services/autofill/OWNERS index edfb2112198a..4f170ca65337 100644 --- a/services/autofill/OWNERS +++ b/services/autofill/OWNERS @@ -1 +1,20 @@ +# Bug component: 351486 + include /core/java/android/view/autofill/OWNERS + +# co-own by both teams +per-file Session.java = file:/core/java/android/view/autofill/OWNERS +per-file Session.java = wangqi@google.com +per-file AutofillManagerService*. = file:/core/java/android/view/autofill/OWNERS +per-file Session.java = wangqi@google.com +per-file AutofillManagerService* = file:/core/java/android/view/autofill/OWNERS +per-file AutofillManagerService* = wangqi@google.com +per-file *FillUI* = file:/core/java/android/view/autofill/OWNERS +per-file *FillUI* = wangqi@google.com + +# Bug component: 543785 = per-file *Augmented* +per-file *Augmented* = file:/core/java/android/service/autofill/augmented/OWNERS + +# Bug component: 543785 = per-file *Inline* +per-file *Inline* = file:/core/java/android/widget/inline/OWNERS + diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index dc6f8584006e..9026c20fe529 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -17,7 +17,6 @@ package com.android.server.am; import static android.Manifest.permission.BATTERY_STATS; -import static android.Manifest.permission.BLUETOOTH_CONNECT; import static android.Manifest.permission.DEVICE_POWER; import static android.Manifest.permission.NETWORK_STACK; import static android.Manifest.permission.POWER_SAVER; @@ -96,6 +95,8 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BackgroundThread; import com.android.internal.os.BinderCallsStats; +import com.android.internal.os.CpuScalingPolicies; +import com.android.internal.os.CpuScalingPolicyReader; import com.android.internal.os.PowerProfile; import com.android.internal.os.RailStats; import com.android.internal.os.RpmStats; @@ -152,6 +153,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub private static IBatteryStats sService; private final PowerProfile mPowerProfile; + private final CpuScalingPolicies mCpuScalingPolicies; final BatteryStatsImpl mStats; final CpuWakeupStats mCpuWakeupStats; private final BatteryUsageStatsStore mBatteryUsageStatsStore; @@ -368,14 +370,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub mHandler = new Handler(mHandlerThread.getLooper()); mPowerProfile = new PowerProfile(context); + mCpuScalingPolicies = new CpuScalingPolicyReader().read(); mStats = new BatteryStatsImpl(systemDir, handler, this, - this, mUserManagerUserInfoProvider); + this, mUserManagerUserInfoProvider, mPowerProfile, mCpuScalingPolicies); mWorker = new BatteryExternalStatsWorker(context, mStats); mStats.setExternalStatsSyncLocked(mWorker); mStats.setRadioScanningTimeoutLocked(mContext.getResources().getInteger( com.android.internal.R.integer.config_radioScanningTimeout) * 1000L); - mStats.setPowerProfileLocked(mPowerProfile); final boolean resetOnUnplugHighBatteryLevel = context.getResources().getBoolean( com.android.internal.R.bool.config_batteryStatsResetOnUnplugHighBatteryLevel); @@ -2916,8 +2918,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub in.setDataPosition(0); BatteryStatsImpl checkinStats = new BatteryStatsImpl( null, mStats.mHandler, null, null, - mUserManagerUserInfoProvider); - checkinStats.setPowerProfileLocked(mPowerProfile); + mUserManagerUserInfoProvider, mPowerProfile, + mCpuScalingPolicies); checkinStats.readSummaryFromParcel(in); in.recycle(); checkinStats.dumpProtoLocked( @@ -2957,8 +2959,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub in.setDataPosition(0); BatteryStatsImpl checkinStats = new BatteryStatsImpl( null, mStats.mHandler, null, null, - mUserManagerUserInfoProvider); - checkinStats.setPowerProfileLocked(mPowerProfile); + mUserManagerUserInfoProvider, mPowerProfile, + mCpuScalingPolicies); checkinStats.readSummaryFromParcel(in); in.recycle(); checkinStats.dumpCheckin(mContext, pw, apps, flags, diff --git a/services/core/java/com/android/server/am/PendingIntentController.java b/services/core/java/com/android/server/am/PendingIntentController.java index 7ee96aaca21d..a20623cd1ee9 100644 --- a/services/core/java/com/android/server/am/PendingIntentController.java +++ b/services/core/java/com/android/server/am/PendingIntentController.java @@ -26,6 +26,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NA import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityManagerInternal; +import android.app.ActivityOptions; import android.app.AppGlobals; import android.app.PendingIntent; import android.app.PendingIntentStats; @@ -126,6 +127,18 @@ public class PendingIntentController { } } Bundle.setDefusable(bOptions, true); + ActivityOptions opts = ActivityOptions.fromBundle(bOptions); + if (opts != null && opts.getPendingIntentBackgroundActivityStartMode() + != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) { + Slog.wtf(TAG, "Resetting option setPendingIntentBackgroundActivityStartMode(" + + opts.getPendingIntentBackgroundActivityStartMode() + + ") to SYSTEM_DEFINED from the options provided by the pending " + + "intent creator (" + + packageName + + ") because this option is meant for the pending intent sender"); + opts.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED); + } final boolean noCreate = (flags & PendingIntent.FLAG_NO_CREATE) != 0; final boolean cancelCurrent = (flags & PendingIntent.FLAG_CANCEL_CURRENT) != 0; @@ -135,7 +148,7 @@ public class PendingIntentController { PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName, featureId, token, resultWho, requestCode, intents, resolvedTypes, flags, - SafeActivityOptions.fromBundle(bOptions), userId); + new SafeActivityOptions(opts), userId); WeakReference<PendingIntentRecord> ref; ref = mIntentSenderRecords.get(key); PendingIntentRecord rec = ref != null ? ref.get() : null; diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 4eaee4aa7b79..79292fec1ec2 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -456,6 +456,20 @@ public final class PendingIntentRecord extends IIntentSender.Stub { // can specify a consistent launch mode even if the PendingIntent is immutable final ActivityOptions opts = ActivityOptions.fromBundle(options); if (opts != null) { + if (opts.getPendingIntentCreatorBackgroundActivityStartMode() + != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED) { + Slog.wtf(TAG, + "Resetting option " + + "setPendingIntentCreatorBackgroundActivityStartMode(" + + opts.getPendingIntentCreatorBackgroundActivityStartMode() + + ") to SYSTEM_DEFINED from the options provided by the " + + "pending intent sender (" + + key.packageName + + ") because this option is meant for the pending intent " + + "creator"); + opts.setPendingIntentCreatorBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED); + } finalIntent.addFlags(opts.getPendingIntentLaunchFlags()); } diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index 5f8e211c42c3..09df2770776d 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -51,11 +51,11 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.CompositeRWLock; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.modules.expresslog.Counter; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.TimeoutRecord; import com.android.internal.os.anr.AnrLatencyTracker; import com.android.internal.util.FrameworkStatsLog; +import com.android.modules.expresslog.Counter; import com.android.server.ResourcePressureUtil; import com.android.server.criticalevents.CriticalEventLog; import com.android.server.stats.pull.ProcfsMemoryUtil.MemorySnapshot; @@ -458,6 +458,11 @@ class ProcessErrorStateRecord { String currentPsiState = ResourcePressureUtil.currentPsiState(); latencyTracker.currentPsiStateReturned(); report.append(currentPsiState); + // The 'processCpuTracker' variable is a shared resource that might be initialized and + // updated in a different thread. In order to prevent thread visibility issues, which + // can occur when one thread does not immediately see the changes made to + // 'processCpuTracker' by another thread, it is necessary to use synchronization whenever + // 'processCpuTracker' is accessed or modified. ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true); // We push the native pids collection task to the helper thread through @@ -519,12 +524,16 @@ class ProcessErrorStateRecord { } mService.updateCpuStatsNow(); mService.mAppProfiler.printCurrentCpuState(report, anrTime); - info.append(processCpuTracker.printCurrentLoad()); + synchronized (processCpuTracker) { + info.append(processCpuTracker.printCurrentLoad()); + } info.append(report); } report.append(tracesFileException.getBuffer()); - info.append(processCpuTracker.printCurrentState(anrTime)); + synchronized (processCpuTracker) { + info.append(processCpuTracker.printCurrentState(anrTime)); + } Slog.e(TAG, info.toString()); if (tracesFile == null) { diff --git a/services/core/java/com/android/server/am/StackTracesDumpHelper.java b/services/core/java/com/android/server/am/StackTracesDumpHelper.java index fc3d04f5adeb..c1374e1b5a05 100644 --- a/services/core/java/com/android/server/am/StackTracesDumpHelper.java +++ b/services/core/java/com/android/server/am/StackTracesDumpHelper.java @@ -464,28 +464,31 @@ public class StackTracesDumpHelper { latencyTracker.processCpuTrackerMethodsCalled(); } ArrayList<Integer> extraPids = new ArrayList<>(); - processCpuTracker.init(); + synchronized (processCpuTracker) { + processCpuTracker.init(); + } try { Thread.sleep(200); } catch (InterruptedException ignored) { } - processCpuTracker.update(); + synchronized (processCpuTracker) { + processCpuTracker.update(); + // We'll take the stack crawls of just the top apps using CPU. + final int workingStatsNumber = processCpuTracker.countWorkingStats(); + for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) { + ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i); + if (lastPids.indexOfKey(stats.pid) >= 0) { + if (DEBUG_ANR) { + Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid); + } - // We'll take the stack crawls of just the top apps using CPU. - final int workingStatsNumber = processCpuTracker.countWorkingStats(); - for (int i = 0; i < workingStatsNumber && extraPids.size() < 2; i++) { - ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i); - if (lastPids.indexOfKey(stats.pid) >= 0) { - if (DEBUG_ANR) { - Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid); + extraPids.add(stats.pid); + } else { + Slog.i(TAG, + "Skipping next CPU consuming process, not a java proc: " + + stats.pid); } - - extraPids.add(stats.pid); - } else { - Slog.i(TAG, - "Skipping next CPU consuming process, not a java proc: " - + stats.pid); } } if (latencyTracker != null) { diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index dae9c5361099..d426c797b981 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -1994,7 +1994,7 @@ class UserController implements Handler.Callback { } private void dismissUserSwitchDialog(Runnable onDismissed) { - mInjector.dismissUserSwitchingDialog(onDismissed); + mUiHandler.post(() -> mInjector.dismissUserSwitchingDialog(onDismissed)); } private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) { diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index e4a5a3e0ed00..ca15dd79adbc 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -2166,7 +2166,7 @@ public final class GameManagerService extends IGameManagerService.Stub { @Override public void onUidStateChanged(int uid, int procState, long procStateSeq, int capability) { synchronized (mUidObserverLock) { - if (ActivityManager.isProcStateBackground(procState)) { + if (procState != ActivityManager.PROCESS_STATE_TOP) { disableGameMode(uid); return; } diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index 50ffbcb9f4c4..3f4f981dc0b8 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -957,6 +957,7 @@ public final class PlaybackActivityMonitor players = (HashMap<Integer, AudioPlaybackConfiguration>) mPlayers.clone(); } mFadingManager.unfadeOutUid(uid, players); + mDuckingManager.unduckUid(uid, players); } //================================================================= diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 6b69e1caa985..79c50804ac5f 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -22,7 +22,6 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; -import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.RouteInfo.RTN_THROW; import static android.net.RouteInfo.RTN_UNREACHABLE; import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN; @@ -280,15 +279,21 @@ public class Vpn { private static final int VPN_DEFAULT_SCORE = 101; /** - * The reset session timer for data stall. If a session has not successfully revalidated after - * the delay, the session will be torn down and restarted in an attempt to recover. Delay + * The recovery timer for data stall. If a session has not successfully revalidated after + * the delay, the session will perform MOBIKE or be restarted in an attempt to recover. Delay * counter is reset on successful validation only. * + * <p>The first {@code MOBIKE_RECOVERY_ATTEMPT} timers are used for performing MOBIKE. + * System will perform session reset for the remaining timers. * <p>If retries have exceeded the length of this array, the last entry in the array will be * used as a repeating interval. */ - private static final long[] DATA_STALL_RESET_DELAYS_SEC = {30L, 60L, 120L, 240L, 480L, 960L}; - + private static final long[] DATA_STALL_RECOVERY_DELAYS_MS = + {1000L, 5000L, 30000L, 60000L, 120000L, 240000L, 480000L, 960000L}; + /** + * Maximum attempts to perform MOBIKE when the network is bad. + */ + private static final int MAX_MOBIKE_RECOVERY_ATTEMPT = 2; /** * The initial token value of IKE session. */ @@ -392,7 +397,6 @@ public class Vpn { private final UserManager mUserManager; private final VpnProfileStore mVpnProfileStore; - protected boolean mDataStallSuspected = false; @VisibleForTesting VpnProfileStore getVpnProfileStore() { @@ -685,14 +689,14 @@ public class Vpn { } /** - * Get the length of time to wait before resetting the ike session when a data stall is - * suspected. + * Get the length of time to wait before perform data stall recovery when the validation + * result is bad. */ - public long getDataStallResetSessionSeconds(int count) { - if (count >= DATA_STALL_RESET_DELAYS_SEC.length) { - return DATA_STALL_RESET_DELAYS_SEC[DATA_STALL_RESET_DELAYS_SEC.length - 1]; + public long getValidationFailRecoveryMs(int count) { + if (count >= DATA_STALL_RECOVERY_DELAYS_MS.length) { + return DATA_STALL_RECOVERY_DELAYS_MS[DATA_STALL_RECOVERY_DELAYS_MS.length - 1]; } else { - return DATA_STALL_RESET_DELAYS_SEC[count]; + return DATA_STALL_RECOVERY_DELAYS_MS[count]; } } @@ -3044,7 +3048,6 @@ public class Vpn { @Nullable private IkeSessionWrapper mSession; @Nullable private IkeSessionConnectionInfo mIkeConnectionInfo; - @Nullable private VpnConnectivityDiagnosticsCallback mDiagnosticsCallback; // mMobikeEnabled can only be updated after IKE AUTH is finished. private boolean mMobikeEnabled = false; @@ -3055,7 +3058,7 @@ public class Vpn { * <p>This variable controls the retry delay, and is reset when the VPN pass network * validation. */ - private int mDataStallRetryCount = 0; + private int mValidationFailRetryCount = 0; /** * The number of attempts since the last successful connection. @@ -3136,15 +3139,6 @@ public class Vpn { mConnectivityManager.registerSystemDefaultNetworkCallback(mNetworkCallback, new Handler(mLooper)); } - - // DiagnosticsCallback may return more than one alive VPNs, but VPN will filter based on - // Network object. - final NetworkRequest diagRequest = new NetworkRequest.Builder() - .addTransportType(TRANSPORT_VPN) - .removeCapability(NET_CAPABILITY_NOT_VPN).build(); - mDiagnosticsCallback = new VpnConnectivityDiagnosticsCallback(); - mConnectivityDiagnosticsManager.registerConnectivityDiagnosticsCallback( - diagRequest, mExecutor, mDiagnosticsCallback); } private boolean isActiveNetwork(@Nullable Network network) { @@ -3875,39 +3869,12 @@ public class Vpn { } } - class VpnConnectivityDiagnosticsCallback - extends ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback { - // The callback runs in the executor thread. - @Override - public void onDataStallSuspected( - ConnectivityDiagnosticsManager.DataStallReport report) { - synchronized (Vpn.this) { - // Ignore stale runner. - if (mVpnRunner != Vpn.IkeV2VpnRunner.this) return; - - // Handle the report only for current VPN network. If data stall is already - // reported, ignoring the other reports. It means that the stall is not - // recovered by MOBIKE and should be on the way to reset the ike session. - if (mNetworkAgent != null - && mNetworkAgent.getNetwork().equals(report.getNetwork()) - && !mDataStallSuspected) { - Log.d(TAG, "Data stall suspected"); - - // Trigger MOBIKE. - maybeMigrateIkeSessionAndUpdateVpnTransportInfo(mActiveNetwork); - mDataStallSuspected = true; - } - } - } - } - public void onValidationStatus(int status) { mEventChanges.log("[Validation] validation status " + status); if (status == NetworkAgent.VALIDATION_STATUS_VALID) { // No data stall now. Reset it. mExecutor.execute(() -> { - mDataStallSuspected = false; - mDataStallRetryCount = 0; + mValidationFailRetryCount = 0; if (mScheduledHandleDataStallFuture != null) { Log.d(TAG, "Recovered from stall. Cancel pending reset action."); mScheduledHandleDataStallFuture.cancel(false /* mayInterruptIfRunning */); @@ -3918,8 +3885,21 @@ public class Vpn { // Skip other invalid status if the scheduled recovery exists. if (mScheduledHandleDataStallFuture != null) return; + if (mValidationFailRetryCount < MAX_MOBIKE_RECOVERY_ATTEMPT) { + Log.d(TAG, "Validation failed"); + + // Trigger MOBIKE to recover first. + mExecutor.schedule(() -> { + maybeMigrateIkeSessionAndUpdateVpnTransportInfo(mActiveNetwork); + }, mDeps.getValidationFailRecoveryMs(mValidationFailRetryCount++), + TimeUnit.MILLISECONDS); + return; + } + + // Data stall is not recovered by MOBIKE. Try to reset session to recover it. mScheduledHandleDataStallFuture = mExecutor.schedule(() -> { - if (mDataStallSuspected) { + // Only perform the recovery when the network is still bad. + if (mValidationFailRetryCount > 0) { Log.d(TAG, "Reset session to recover stalled network"); // This will reset old state if it exists. startIkeSession(mActiveNetwork); @@ -3928,7 +3908,9 @@ public class Vpn { // Reset mScheduledHandleDataStallFuture since it's already run on executor // thread. mScheduledHandleDataStallFuture = null; - }, mDeps.getDataStallResetSessionSeconds(mDataStallRetryCount++), TimeUnit.SECONDS); + // TODO: compute the delay based on the last recovery timestamp + }, mDeps.getValidationFailRecoveryMs(mValidationFailRetryCount++), + TimeUnit.MILLISECONDS); } } @@ -4231,8 +4213,6 @@ public class Vpn { mCarrierConfigManager.unregisterCarrierConfigChangeListener( mCarrierConfigChangeListener); mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); - mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback( - mDiagnosticsCallback); clearVpnNetworkPreference(mSessionKey); mExecutor.shutdown(); @@ -5216,7 +5196,7 @@ public class Vpn { pw.println("MOBIKE " + (runner.mMobikeEnabled ? "enabled" : "disabled")); pw.println("Profile: " + runner.mProfile); pw.println("Token: " + runner.mCurrentToken); - if (mDataStallSuspected) pw.println("Data stall suspected"); + pw.println("Validation failed retry count:" + runner.mValidationFailRetryCount); if (runner.mScheduledHandleDataStallFuture != null) { pw.println("Reset session scheduled"); } diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java index f9c53c641c1c..1012bc1828c0 100644 --- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java +++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java @@ -172,6 +172,7 @@ public final class DeviceStateManagerService extends SystemService { private DeviceState mRearDisplayState; // TODO(259328837) Generalize for all pending feature requests in the future + @GuardedBy("mLock") @Nullable private OverrideRequest mRearDisplayPendingOverrideRequest; @@ -779,7 +780,7 @@ public final class DeviceStateManagerService extends SystemService { * {@link StatusBarManagerInternal} to notify SystemUI to display the educational dialog. */ @GuardedBy("mLock") - private void showRearDisplayEducationalOverlayLocked(OverrideRequest request) { + private void showRearDisplayEducationalOverlayLocked(@NonNull OverrideRequest request) { mRearDisplayPendingOverrideRequest = request; StatusBarManagerInternal statusBar = @@ -844,8 +845,8 @@ public final class DeviceStateManagerService extends SystemService { * request if it was dismissed in a way that should cancel the feature. */ private void onStateRequestOverlayDismissedInternal(boolean shouldCancelRequest) { - if (mRearDisplayPendingOverrideRequest != null) { - synchronized (mLock) { + synchronized (mLock) { + if (mRearDisplayPendingOverrideRequest != null) { if (shouldCancelRequest) { ProcessRecord processRecord = mProcessRecords.get( mRearDisplayPendingOverrideRequest.getPid()); diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java index 47cde1517450..5b11cfe7ff06 100644 --- a/services/core/java/com/android/server/display/BrightnessRangeController.java +++ b/services/core/java/com/android/server/display/BrightnessRangeController.java @@ -18,29 +18,42 @@ package com.android.server.display; import android.hardware.display.BrightnessInfo; import android.os.IBinder; +import android.provider.DeviceConfigInterface; + +import com.android.server.display.feature.DeviceConfigParameterProvider; import java.io.PrintWriter; import java.util.function.BooleanSupplier; class BrightnessRangeController { - private static final boolean NBM_FEATURE_FLAG = false; - private final HighBrightnessModeController mHbmController; private final NormalBrightnessModeController mNormalBrightnessModeController = new NormalBrightnessModeController(); private final Runnable mModeChangeCallback; + private final boolean mUseNbmController; + BrightnessRangeController(HighBrightnessModeController hbmController, Runnable modeChangeCallback) { + this(hbmController, modeChangeCallback, + new DeviceConfigParameterProvider(DeviceConfigInterface.REAL)); + } + + BrightnessRangeController(HighBrightnessModeController hbmController, + Runnable modeChangeCallback, DeviceConfigParameterProvider configParameterProvider) { mHbmController = hbmController; mModeChangeCallback = modeChangeCallback; + mUseNbmController = configParameterProvider.isNormalBrightnessControllerFeatureEnabled(); } - void dump(PrintWriter pw) { + pw.println("BrightnessRangeController:"); + pw.println(" mUseNormalBrightnessController=" + mUseNbmController); mHbmController.dump(pw); + mNormalBrightnessModeController.dump(pw); + } void onAmbientLuxChange(float ambientLux) { @@ -90,7 +103,7 @@ class BrightnessRangeController { float getCurrentBrightnessMax() { - if (NBM_FEATURE_FLAG && mHbmController.getHighBrightnessMode() + if (mUseNbmController && mHbmController.getHighBrightnessMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF) { return Math.min(mHbmController.getCurrentBrightnessMax(), mNormalBrightnessModeController.getCurrentBrightnessMax()); @@ -111,7 +124,7 @@ class BrightnessRangeController { } private void applyChanges(BooleanSupplier nbmChangesFunc, Runnable hbmChangesFunc) { - if (NBM_FEATURE_FLAG) { + if (mUseNbmController) { boolean nbmTransitionChanged = nbmChangesFunc.getAsBoolean(); hbmChangesFunc.run(); // if nbm transition changed - trigger callback diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java index cfdcd636904b..c421ec04d6f5 100644 --- a/services/core/java/com/android/server/display/BrightnessThrottler.java +++ b/services/core/java/com/android/server/display/BrightnessThrottler.java @@ -22,7 +22,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.hardware.display.BrightnessInfo; -import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.HandlerExecutor; import android.os.IThermalEventListener; @@ -38,6 +37,7 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel; +import com.android.server.display.feature.DeviceConfigParameterProvider; import java.io.PrintWriter; import java.util.ArrayList; @@ -63,7 +63,7 @@ class BrightnessThrottler { private final Runnable mThrottlingChangeCallback; private final SkinThermalStatusObserver mSkinThermalStatusObserver; private final DeviceConfigListener mDeviceConfigListener; - private final DeviceConfigInterface mDeviceConfig; + private final DeviceConfigParameterProvider mConfigParameterProvider; private int mThrottlingStatus; @@ -118,7 +118,7 @@ class BrightnessThrottler { mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler); mUniqueDisplayId = uniqueDisplayId; - mDeviceConfig = injector.getDeviceConfig(); + mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig()); mDeviceConfigListener = new DeviceConfigListener(); mThermalBrightnessThrottlingDataId = throttlingDataId; mDdcThermalThrottlingDataMap = thermalBrightnessThrottlingDataMap; @@ -145,7 +145,7 @@ class BrightnessThrottler { void stop() { mSkinThermalStatusObserver.stopObserving(); - mDeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListener); + mConfigParameterProvider.removeOnPropertiesChangedListener(mDeviceConfigListener); // We're asked to stop throttling, so reset brightness restrictions. mBrightnessCap = PowerManager.BRIGHTNESS_MAX; mBrightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; @@ -248,12 +248,6 @@ class BrightnessThrottler { mSkinThermalStatusObserver.dump(pw); } - private String getThermalBrightnessThrottlingDataString() { - return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, - /* defaultValue= */ null); - } - // The brightness throttling data id may or may not be specified in the string that is passed // in, if there is none specified, we assume it is for the default case. Each string passed in // here must be for one display and one throttling id. @@ -318,7 +312,8 @@ class BrightnessThrottler { private void loadThermalBrightnessThrottlingDataFromDeviceConfig() { HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>> tempThrottlingData = new HashMap<>(1); - mThermalBrightnessThrottlingDataString = getThermalBrightnessThrottlingDataString(); + mThermalBrightnessThrottlingDataString = + mConfigParameterProvider.getBrightnessThrottlingData(); boolean validConfig = true; mThermalBrightnessThrottlingDataOverride.clear(); if (mThermalBrightnessThrottlingDataString != null) { @@ -390,8 +385,7 @@ class BrightnessThrottler { public Executor mExecutor = new HandlerExecutor(mDeviceConfigHandler); public void startListening() { - mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - mExecutor, this); + mConfigParameterProvider.addOnPropertiesChangedListener(mExecutor, this); } @Override diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 7e8771e6d13d..1a8e2db303b3 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -24,7 +24,6 @@ import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE; import static android.hardware.display.DisplayManager.EventsMask; -import static android.hardware.display.DisplayManager.HDR_OUTPUT_CONTROL_FLAG; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR; import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD; @@ -42,7 +41,6 @@ import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL; import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_UNSUPPORTED; import static android.os.Process.FIRST_APPLICATION_UID; import static android.os.Process.ROOT_UID; -import static android.provider.DeviceConfig.NAMESPACE_DISPLAY_MANAGER; import android.Manifest; import android.annotation.NonNull; @@ -116,7 +114,7 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.UserManager; -import android.provider.DeviceConfig; +import android.provider.DeviceConfigInterface; import android.provider.Settings; import android.text.TextUtils; import android.util.ArraySet; @@ -152,6 +150,7 @@ import com.android.server.SystemService; import com.android.server.UiThread; import com.android.server.companion.virtual.VirtualDeviceManagerInternal; import com.android.server.display.DisplayDeviceConfig.SensorData; +import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.display.layout.Layout; import com.android.server.display.mode.DisplayModeDirector; import com.android.server.display.utils.SensorUtils; @@ -506,6 +505,8 @@ public final class DisplayManagerService extends SystemService { private final BrightnessSynchronizer mBrightnessSynchronizer; + private final DeviceConfigParameterProvider mConfigParameterProvider; + /** * Applications use {@link android.view.Display#getRefreshRate} and * {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate. @@ -558,6 +559,7 @@ public final class DisplayManagerService extends SystemService { mWideColorSpace = colorSpaces[1]; mOverlayProperties = SurfaceControl.getOverlaySupport(); mSystemReady = false; + mConfigParameterProvider = new DeviceConfigParameterProvider(DeviceConfigInterface.REAL); } public void setupSchedulerPolicies() { @@ -694,11 +696,11 @@ public final class DisplayManagerService extends SystemService { synchronized (mSyncRoot) { mSafeMode = safeMode; mSystemReady = true; - mIsHdrOutputControlEnabled = isDeviceConfigHdrOutputControlEnabled(); - DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_DISPLAY_MANAGER, - BackgroundThread.getExecutor(), + mIsHdrOutputControlEnabled = + mConfigParameterProvider.isHdrOutputControlFeatureEnabled(); + mConfigParameterProvider.addOnPropertiesChangedListener(BackgroundThread.getExecutor(), properties -> mIsHdrOutputControlEnabled = - isDeviceConfigHdrOutputControlEnabled()); + mConfigParameterProvider.isHdrOutputControlFeatureEnabled()); // Just in case the top inset changed before the system was ready. At this point, any // relevant configuration should be in place. recordTopInsetLocked(mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY)); @@ -729,12 +731,6 @@ public final class DisplayManagerService extends SystemService { mContext.registerReceiver(mIdleModeReceiver, filter); } - private boolean isDeviceConfigHdrOutputControlEnabled() { - return DeviceConfig.getBoolean(NAMESPACE_DISPLAY_MANAGER, - HDR_OUTPUT_CONTROL_FLAG, - true); - } - @VisibleForTesting Handler getDisplayHandler() { return mHandler; @@ -3145,8 +3141,7 @@ public final class DisplayManagerService extends SystemService { + "display: " + display.getDisplayIdLocked()); return null; } - if (DeviceConfig.getBoolean("display_manager", - "use_newly_structured_display_power_controller", true)) { + if (mConfigParameterProvider.isNewPowerControllerFeatureEnabled()) { displayPowerController = new DisplayPowerController2( mContext, /* injector= */ null, mDisplayPowerCallbacks, mPowerHandler, mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting, diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index ffecf2b7018d..7fc6cd0bebd6 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -2672,10 +2672,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call public void setBrightness(float brightnessValue, int userSerial) { // Update the setting, which will eventually call back into DPC to have us actually update // the display with the new value. + float clampedBrightnessValue = clampScreenBrightness(brightnessValue); mBrightnessSetting.setUserSerial(userSerial); - mBrightnessSetting.setBrightness(brightnessValue); + mBrightnessSetting.setBrightness(clampedBrightnessValue); if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) { - float nits = convertToNits(brightnessValue); + float nits = convertToNits(clampedBrightnessValue); if (nits >= 0) { mBrightnessSetting.setBrightnessNitsForDefaultDisplay(nits); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 7043af863301..37b9d5638dbc 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -2185,7 +2185,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal @Override public void setBrightness(float brightnessValue, int userSerial) { - mDisplayBrightnessController.setBrightness(brightnessValue, userSerial); + mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightnessValue), + userSerial); } @Override diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index dec9f62c8739..b00b7a1f0e2a 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -496,7 +496,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { private void loadDisplayDeviceConfig() { // Load display device config final Context context = getOverlayContext(); - mDisplayDeviceConfig = DisplayDeviceConfig.create(context, mPhysicalDisplayId, + mDisplayDeviceConfig = mInjector.createDisplayDeviceConfig(context, mPhysicalDisplayId, mIsFirstDisplay); // Load brightness HWC quirk @@ -1336,6 +1336,11 @@ final class LocalDisplayAdapter extends DisplayAdapter { public SurfaceControlProxy getSurfaceControlProxy() { return new SurfaceControlProxy(); } + + public DisplayDeviceConfig createDisplayDeviceConfig(Context context, + long physicalDisplayId, boolean isFirstDisplay) { + return DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay); + } } public interface DisplayEventListener { diff --git a/services/core/java/com/android/server/display/NormalBrightnessModeController.java b/services/core/java/com/android/server/display/NormalBrightnessModeController.java index dbabc2441224..135ebd8f4fbf 100644 --- a/services/core/java/com/android/server/display/NormalBrightnessModeController.java +++ b/services/core/java/com/android/server/display/NormalBrightnessModeController.java @@ -21,6 +21,7 @@ import android.os.PowerManager; import com.android.server.display.DisplayDeviceConfig.BrightnessLimitMapType; +import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; @@ -60,6 +61,14 @@ class NormalBrightnessModeController { return recalculateMaxBrightness(); } + void dump(PrintWriter pw) { + pw.println("NormalBrightnessModeController:"); + pw.println(" mAutoBrightnessEnabled=" + mAutoBrightnessEnabled); + pw.println(" mAmbientLux=" + mAmbientLux); + pw.println(" mMaxBrightness=" + mMaxBrightness); + pw.println(" mMaxBrightnessLimits=" + mMaxBrightnessLimits); + } + private boolean recalculateMaxBrightness() { float foundAmbientBoundary = Float.MAX_VALUE; float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX; diff --git a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java new file mode 100644 index 000000000000..feebdf1b9799 --- /dev/null +++ b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.display.feature; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.hardware.display.DisplayManager; +import android.provider.DeviceConfig; +import android.provider.DeviceConfigInterface; +import android.util.Slog; + +import java.util.concurrent.Executor; + +/** + * Helper class to access all DeviceConfig features for display_manager namespace + * + **/ +public class DeviceConfigParameterProvider { + + private static final String TAG = "DisplayFeatureProvider"; + + private final DeviceConfigInterface mDeviceConfig; + + public DeviceConfigParameterProvider(DeviceConfigInterface deviceConfig) { + mDeviceConfig = deviceConfig; + } + + // feature: revamping_display_power_controller_feature + // parameter: use_newly_structured_display_power_controller + public boolean isNewPowerControllerFeatureEnabled() { + return mDeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_NEW_POWER_CONTROLLER, true); + } + + // feature: hdr_output_control + // parameter: enable_hdr_output_control + public boolean isHdrOutputControlFeatureEnabled() { + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.HDR_OUTPUT_CONTROL_FLAG, true); + } + + // feature: flexible_brightness_range_feature + // parameter: normal_brightness_mode_controller_enabled + public boolean isNormalBrightnessControllerFeatureEnabled() { + return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_USE_NORMAL_BRIGHTNESS_MODE_CONTROLLER, false); + } + + // feature: smooth_display_feature + // parameter: peak_refresh_rate_default + public float getPeakRefreshRateDefault() { + return mDeviceConfig.getFloat(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, -1); + } + + // Test parameters + // usage e.g.: adb shell device_config put display_manager refresh_rate_in_hbm_sunlight 90 + + // allows to customize brightness throttling data + public String getBrightnessThrottlingData() { + return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, null); + } + + public int getRefreshRateInHbmSunlight() { + return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, -1); + } + + public int getRefreshRateInHbmHdr() { + return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR, -1); + } + + + public int getRefreshRateInHighZone() { + return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE, -1); + } + + public int getRefreshRateInLowZone() { + return mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, -1); + } + + /** Return null if no such property or wrong format (not comma separated integers). */ + @Nullable + public int[] getHighAmbientBrightnessThresholds() { + return getIntArrayProperty(DisplayManager.DeviceConfig + .KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS); + } + + /** Return null if no such property or wrong format (not comma separated integers). */ + @Nullable + public int[] getHighDisplayBrightnessThresholds() { + return getIntArrayProperty(DisplayManager.DeviceConfig + .KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS); + } + + /** Return null if no such property or wrong format (not comma separated integers). */ + @Nullable + public int[] getLowDisplayBrightnessThresholds() { + return getIntArrayProperty(DisplayManager.DeviceConfig + .KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS); + } + + /** Return null if no such property or wrong format (not comma separated integers). */ + @Nullable + public int[] getLowAmbientBrightnessThresholds() { + return getIntArrayProperty(DisplayManager.DeviceConfig + .KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS); + } + + /** add property change listener to DeviceConfig */ + public void addOnPropertiesChangedListener(Executor executor, + DeviceConfig.OnPropertiesChangedListener listener) { + mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + executor, listener); + } + + /** remove property change listener from DeviceConfig */ + public void removeOnPropertiesChangedListener( + DeviceConfig.OnPropertiesChangedListener listener) { + mDeviceConfig.removeOnPropertiesChangedListener(listener); + } + + @Nullable + private int[] getIntArrayProperty(String prop) { + String strArray = mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop, + null); + + if (strArray != null) { + return parseIntArray(strArray); + } + return null; + } + + @Nullable + private int[] parseIntArray(@NonNull String strArray) { + String[] items = strArray.split(","); + int[] array = new int[items.length]; + + try { + for (int i = 0; i < array.length; i++) { + array[i] = Integer.parseInt(items[i]); + } + } catch (NumberFormatException e) { + Slog.e(TAG, "Incorrect format for array: '" + strArray + "'", e); + array = null; + } + + return array; + } +} diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java index 18895788e4ec..11e35ce09c28 100644 --- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java +++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java @@ -20,6 +20,7 @@ import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLU import static android.hardware.display.DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE; import static android.os.PowerManager.BRIGHTNESS_INVALID; +import android.annotation.IntegerRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentResolver; @@ -68,6 +69,7 @@ import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; import com.android.server.display.DisplayDeviceConfig; +import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.display.utils.AmbientFilter; import com.android.server.display.utils.AmbientFilterFactory; import com.android.server.display.utils.SensorUtils; @@ -84,6 +86,7 @@ import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.concurrent.Callable; +import java.util.function.IntSupplier; /** * The DisplayModeDirector is responsible for determining what modes are allowed to be automatically @@ -117,7 +120,7 @@ public class DisplayModeDirector { private final SensorObserver mSensorObserver; private final HbmObserver mHbmObserver; private final SkinThermalStatusObserver mSkinThermalStatusObserver; - private final DeviceConfigInterface mDeviceConfig; + private final DeviceConfigParameterProvider mConfigParameterProvider; private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings; @GuardedBy("mLock") @@ -157,7 +160,7 @@ public class DisplayModeDirector { mSupportedModesByDisplay = new SparseArray<>(); mDefaultModeByDisplay = new SparseArray<>(); mAppRequestObserver = new AppRequestObserver(); - mDeviceConfig = injector.getDeviceConfig(); + mConfigParameterProvider = new DeviceConfigParameterProvider(injector.getDeviceConfig()); mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings(); mSettingsObserver = new SettingsObserver(context, handler); mBrightnessObserver = new BrightnessObserver(context, handler, injector); @@ -681,9 +684,9 @@ public class DisplayModeDirector { synchronized (mLock) { mDefaultDisplayDeviceConfig = displayDeviceConfig; mSettingsObserver.setRefreshRates(displayDeviceConfig, - /* attemptLoadingFromDeviceConfig= */ true); + /* attemptReadFromFeatureParams= */ true); mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig, - /* attemptLoadingFromDeviceConfig= */ true); + /* attemptReadFromFeatureParams= */ true); mBrightnessObserver.reloadLightSensor(displayDeviceConfig); mHbmObserver.setupHdrRefreshRates(displayDeviceConfig); } @@ -1087,7 +1090,7 @@ public class DisplayModeDirector { // reading from the DeviceConfig is an intensive IO operation and having it in the // startup phase where we thrive to keep the latency very low has significant impact. setRefreshRates(/* displayDeviceConfig= */ null, - /* attemptLoadingFromDeviceConfig= */ false); + /* attemptReadFromFeatureParams= */ false); } /** @@ -1095,8 +1098,8 @@ public class DisplayModeDirector { * if missing from DisplayDeviceConfig, and finally fallback to config.xml. */ public void setRefreshRates(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { - setDefaultPeakRefreshRate(displayDeviceConfig, attemptLoadingFromDeviceConfig); + boolean attemptReadFromFeatureParams) { + setDefaultPeakRefreshRate(displayDeviceConfig, attemptReadFromFeatureParams); mDefaultRefreshRate = (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger( R.integer.config_defaultRefreshRate) @@ -1113,9 +1116,9 @@ public class DisplayModeDirector { cr.registerContentObserver(mMatchContentFrameRateSetting, false /*notifyDescendants*/, this); - Float deviceConfigDefaultPeakRefresh = - mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate(); - if (deviceConfigDefaultPeakRefresh != null) { + float deviceConfigDefaultPeakRefresh = + mConfigParameterProvider.getPeakRefreshRateDefault(); + if (deviceConfigDefaultPeakRefresh != -1) { mDefaultPeakRefreshRate = deviceConfigDefaultPeakRefresh; } @@ -1137,7 +1140,7 @@ public class DisplayModeDirector { synchronized (mLock) { if (defaultPeakRefreshRate == null) { setDefaultPeakRefreshRate(mDefaultDisplayDeviceConfig, - /* attemptLoadingFromDeviceConfig= */ false); + /* attemptReadFromFeatureParams= */ false); updateRefreshRateSettingLocked(); } else if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) { mDefaultPeakRefreshRate = defaultPeakRefreshRate; @@ -1171,18 +1174,17 @@ public class DisplayModeDirector { } private void setDefaultPeakRefreshRate(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { - Float defaultPeakRefreshRate = null; + boolean attemptReadFromFeatureParams) { + float defaultPeakRefreshRate = -1; - if (attemptLoadingFromDeviceConfig) { + if (attemptReadFromFeatureParams) { try { - defaultPeakRefreshRate = - mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate(); + defaultPeakRefreshRate = mConfigParameterProvider.getPeakRefreshRateDefault(); } catch (Exception exception) { // Do nothing } } - if (defaultPeakRefreshRate == null) { + if (defaultPeakRefreshRate == -1) { defaultPeakRefreshRate = (displayDeviceConfig == null) ? (float) mContext.getResources().getInteger( R.integer.config_defaultPeakRefreshRate) @@ -1528,7 +1530,7 @@ public class DisplayModeDirector { mHandler = handler; mInjector = injector; updateBlockingZoneThresholds(/* displayDeviceConfig= */ null, - /* attemptLoadingFromDeviceConfig= */ false); + /* attemptReadFromFeatureParams= */ false); mRefreshRateInHighZone = context.getResources().getInteger( R.integer.config_fixedRefreshRateInHighZone); } @@ -1537,10 +1539,10 @@ public class DisplayModeDirector { * This is used to update the blocking zone thresholds from the DeviceConfig, which * if missing from DisplayDeviceConfig, and finally fallback to config.xml. */ - public void updateBlockingZoneThresholds(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { - loadLowBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig); - loadHighBrightnessThresholds(displayDeviceConfig, attemptLoadingFromDeviceConfig); + public void updateBlockingZoneThresholds(@Nullable DisplayDeviceConfig displayDeviceConfig, + boolean attemptReadFromFeatureParams) { + loadLowBrightnessThresholds(displayDeviceConfig, attemptReadFromFeatureParams); + loadHighBrightnessThresholds(displayDeviceConfig, attemptReadFromFeatureParams); } @VisibleForTesting @@ -1579,20 +1581,20 @@ public class DisplayModeDirector { return mRefreshRateInLowZone; } - private void loadLowBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { - loadRefreshRateInHighZone(displayDeviceConfig, attemptLoadingFromDeviceConfig); - loadRefreshRateInLowZone(displayDeviceConfig, attemptLoadingFromDeviceConfig); + private void loadLowBrightnessThresholds(@Nullable DisplayDeviceConfig displayDeviceConfig, + boolean attemptReadFromFeatureParams) { + loadRefreshRateInHighZone(displayDeviceConfig, attemptReadFromFeatureParams); + loadRefreshRateInLowZone(displayDeviceConfig, attemptReadFromFeatureParams); mLowDisplayBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(), + () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(), () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(), R.array.config_brightnessThresholdsOfPeakRefreshRate, - displayDeviceConfig, attemptLoadingFromDeviceConfig); + displayDeviceConfig, attemptReadFromFeatureParams); mLowAmbientBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(), + () -> mConfigParameterProvider.getLowAmbientBrightnessThresholds(), () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(), R.array.config_ambientThresholdsOfPeakRefreshRate, - displayDeviceConfig, attemptLoadingFromDeviceConfig); + displayDeviceConfig, attemptReadFromFeatureParams); if (mLowDisplayBrightnessThresholds.length != mLowAmbientBrightnessThresholds.length) { throw new RuntimeException("display low brightness threshold array and ambient " + "brightness threshold array have different length: " @@ -1604,55 +1606,55 @@ public class DisplayModeDirector { } private void loadRefreshRateInLowZone(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { - int refreshRateInLowZone = - (displayDeviceConfig == null) ? mContext.getResources().getInteger( - R.integer.config_defaultRefreshRateInZone) - : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(); - if (attemptLoadingFromDeviceConfig) { + boolean attemptReadFromFeatureParams) { + int refreshRateInLowZone = -1; + if (attemptReadFromFeatureParams) { try { - refreshRateInLowZone = mDeviceConfig.getInt( - DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, - refreshRateInLowZone); + refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone(); } catch (Exception exception) { // Do nothing } } + if (refreshRateInLowZone == -1) { + refreshRateInLowZone = (displayDeviceConfig == null) + ? mContext.getResources().getInteger( + R.integer.config_defaultRefreshRateInZone) + : displayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(); + } mRefreshRateInLowZone = refreshRateInLowZone; } private void loadRefreshRateInHighZone(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { - int refreshRateInHighZone = - (displayDeviceConfig == null) ? mContext.getResources().getInteger( - R.integer.config_fixedRefreshRateInHighZone) : displayDeviceConfig - .getDefaultHighBlockingZoneRefreshRate(); - if (attemptLoadingFromDeviceConfig) { + boolean attemptReadFromFeatureParams) { + int refreshRateInHighZone = -1; + if (attemptReadFromFeatureParams) { try { - refreshRateInHighZone = mDeviceConfig.getInt( - DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE, - refreshRateInHighZone); + refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone(); } catch (Exception exception) { // Do nothing } } + if (refreshRateInHighZone == -1) { + refreshRateInHighZone = (displayDeviceConfig == null) + ? mContext.getResources().getInteger( + R.integer.config_fixedRefreshRateInHighZone) + : displayDeviceConfig.getDefaultHighBlockingZoneRefreshRate(); + } mRefreshRateInHighZone = refreshRateInHighZone; } private void loadHighBrightnessThresholds(DisplayDeviceConfig displayDeviceConfig, - boolean attemptLoadingFromDeviceConfig) { + boolean attemptReadFromFeatureParams) { mHighDisplayBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(), + () -> mConfigParameterProvider.getHighDisplayBrightnessThresholds(), () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(), R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate, - displayDeviceConfig, attemptLoadingFromDeviceConfig); + displayDeviceConfig, attemptReadFromFeatureParams); mHighAmbientBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(), + () -> mConfigParameterProvider.getHighAmbientBrightnessThresholds(), () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(), R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate, - displayDeviceConfig, attemptLoadingFromDeviceConfig); + displayDeviceConfig, attemptReadFromFeatureParams); if (mHighDisplayBrightnessThresholds.length != mHighAmbientBrightnessThresholds.length) { throw new RuntimeException("display high brightness threshold array and ambient " @@ -1668,10 +1670,10 @@ public class DisplayModeDirector { Callable<int[]> loadFromDeviceConfigDisplaySettingsCallable, Callable<int[]> loadFromDisplayDeviceConfigCallable, int brightnessThresholdOfFixedRefreshRateKey, - DisplayDeviceConfig displayDeviceConfig, boolean attemptLoadingFromDeviceConfig) { + DisplayDeviceConfig displayDeviceConfig, boolean attemptReadFromFeatureParams) { int[] brightnessThresholds = null; - if (attemptLoadingFromDeviceConfig) { + if (attemptReadFromFeatureParams) { try { brightnessThresholds = loadFromDeviceConfigDisplaySettingsCallable.call(); @@ -1715,9 +1717,9 @@ public class DisplayModeDirector { // DeviceConfig is accessible after system ready. int[] lowDisplayBrightnessThresholds = - mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(); + mConfigParameterProvider.getLowDisplayBrightnessThresholds(); int[] lowAmbientBrightnessThresholds = - mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(); + mConfigParameterProvider.getLowAmbientBrightnessThresholds(); if (lowDisplayBrightnessThresholds != null && lowAmbientBrightnessThresholds != null && lowDisplayBrightnessThresholds.length @@ -1727,9 +1729,9 @@ public class DisplayModeDirector { } int[] highDisplayBrightnessThresholds = - mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(); + mConfigParameterProvider.getHighDisplayBrightnessThresholds(); int[] highAmbientBrightnessThresholds = - mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(); + mConfigParameterProvider.getHighAmbientBrightnessThresholds(); if (highDisplayBrightnessThresholds != null && highAmbientBrightnessThresholds != null && highDisplayBrightnessThresholds.length @@ -1738,14 +1740,12 @@ public class DisplayModeDirector { mHighAmbientBrightnessThresholds = highAmbientBrightnessThresholds; } - final int refreshRateInLowZone = mDeviceConfigDisplaySettings - .getRefreshRateInLowZone(); + final int refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone(); if (refreshRateInLowZone != -1) { mRefreshRateInLowZone = refreshRateInLowZone; } - final int refreshRateInHighZone = mDeviceConfigDisplaySettings - .getRefreshRateInHighZone(); + final int refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone(); if (refreshRateInHighZone != -1) { mRefreshRateInHighZone = refreshRateInHighZone; } @@ -1799,15 +1799,15 @@ public class DisplayModeDirector { displayDeviceConfig = mDefaultDisplayDeviceConfig; } mLowDisplayBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getLowDisplayBrightnessThresholds(), + () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(), () -> displayDeviceConfig.getLowDisplayBrightnessThresholds(), R.array.config_brightnessThresholdsOfPeakRefreshRate, - displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); + displayDeviceConfig, /* attemptReadFromFeatureParams= */ false); mLowAmbientBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getLowAmbientBrightnessThresholds(), + () -> mConfigParameterProvider.getLowAmbientBrightnessThresholds(), () -> displayDeviceConfig.getLowAmbientBrightnessThresholds(), R.array.config_ambientThresholdsOfPeakRefreshRate, - displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); + displayDeviceConfig, /* attemptReadFromFeatureParams= */ false); } restartObserver(); } @@ -1822,7 +1822,7 @@ public class DisplayModeDirector { // from there. synchronized (mLock) { loadRefreshRateInLowZone(mDefaultDisplayDeviceConfig, - /* attemptLoadingFromDeviceConfig= */ false); + /* attemptReadFromFeatureParams= */ false); } restartObserver(); } else if (refreshRate != mRefreshRateInLowZone) { @@ -1843,15 +1843,15 @@ public class DisplayModeDirector { displayDeviceConfig = mDefaultDisplayDeviceConfig; } mHighDisplayBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getHighDisplayBrightnessThresholds(), + () -> mConfigParameterProvider.getLowDisplayBrightnessThresholds(), () -> displayDeviceConfig.getHighDisplayBrightnessThresholds(), R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate, - displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); + displayDeviceConfig, /* attemptReadFromFeatureParams= */ false); mHighAmbientBrightnessThresholds = loadBrightnessThresholds( - () -> mDeviceConfigDisplaySettings.getHighAmbientBrightnessThresholds(), + () -> mConfigParameterProvider.getHighAmbientBrightnessThresholds(), () -> displayDeviceConfig.getHighAmbientBrightnessThresholds(), R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate, - displayDeviceConfig, /* attemptLoadingFromDeviceConfig= */ false); + displayDeviceConfig, /* attemptReadFromFeatureParams= */ false); } restartObserver(); } @@ -1866,7 +1866,7 @@ public class DisplayModeDirector { // from there. synchronized (mLock) { loadRefreshRateInHighZone(mDefaultDisplayDeviceConfig, - /* attemptLoadingFromDeviceConfig= */ false); + /* attemptReadFromFeatureParams= */ false); } restartObserver(); } else if (refreshRate != mRefreshRateInHighZone) { @@ -2675,113 +2675,55 @@ public class DisplayModeDirector { private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener { public void startListening() { - mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + mConfigParameterProvider.addOnPropertiesChangedListener( BackgroundThread.getExecutor(), this); } - /* - * Return null if no such property or wrong format (not comma separated integers). - */ - public int[] getLowDisplayBrightnessThresholds() { - return getIntArrayProperty( - DisplayManager.DeviceConfig - .KEY_FIXED_REFRESH_RATE_LOW_DISPLAY_BRIGHTNESS_THRESHOLDS); - } - - /* - * Return null if no such property or wrong format (not comma separated integers). - */ - public int[] getLowAmbientBrightnessThresholds() { - return getIntArrayProperty( - DisplayManager.DeviceConfig - .KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS); - } - - public int getRefreshRateInLowZone() { - return mDeviceConfig.getInt( - DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_LOW_ZONE, -1); - - } - - /* - * Return null if no such property or wrong format (not comma separated integers). - */ - public int[] getHighDisplayBrightnessThresholds() { - return getIntArrayProperty( - DisplayManager.DeviceConfig - .KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS); - } - - /* - * Return null if no such property or wrong format (not comma separated integers). - */ - public int[] getHighAmbientBrightnessThresholds() { - return getIntArrayProperty( - DisplayManager.DeviceConfig - .KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS); + private int getRefreshRateInHbmHdr(DisplayDeviceConfig displayDeviceConfig) { + return getRefreshRate( + () -> mConfigParameterProvider.getRefreshRateInHbmHdr(), + () -> displayDeviceConfig.getDefaultRefreshRateInHbmHdr(), + R.integer.config_defaultRefreshRateInHbmHdr, + displayDeviceConfig + ); } - public int getRefreshRateInHighZone() { - return mDeviceConfig.getInt( - DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HIGH_ZONE, - -1); + private int getRefreshRateInHbmSunlight(DisplayDeviceConfig displayDeviceConfig) { + return getRefreshRate( + () -> mConfigParameterProvider.getRefreshRateInHbmSunlight(), + () -> displayDeviceConfig.getDefaultRefreshRateInHbmSunlight(), + R.integer.config_defaultRefreshRateInHbmSunlight, + displayDeviceConfig + ); } - public int getRefreshRateInHbmHdr(DisplayDeviceConfig displayDeviceConfig) { - int refreshRate = - (displayDeviceConfig == null) ? mContext.getResources().getInteger( - R.integer.config_defaultRefreshRateInHbmHdr) - : displayDeviceConfig.getDefaultRefreshRateInHbmHdr(); + private int getRefreshRate(IntSupplier fromConfigPram, IntSupplier fromDisplayDeviceConfig, + @IntegerRes int configKey, DisplayDeviceConfig displayDeviceConfig) { + int refreshRate = -1; try { - refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_HDR, - refreshRate); - } catch (NullPointerException e) { + refreshRate = fromConfigPram.getAsInt(); + } catch (NullPointerException npe) { // Do Nothing } - return refreshRate; - } - - public int getRefreshRateInHbmSunlight(DisplayDeviceConfig displayDeviceConfig) { - int refreshRate = - (displayDeviceConfig == null) ? mContext.getResources() - .getInteger(R.integer.config_defaultRefreshRateInHbmSunlight) - : displayDeviceConfig.getDefaultRefreshRateInHbmSunlight(); - try { - refreshRate = mDeviceConfig.getInt(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_HBM_SUNLIGHT, - refreshRate); - } catch (NullPointerException e) { - // Do Nothing + if (refreshRate == -1) { + refreshRate = (displayDeviceConfig == null) + ? mContext.getResources().getInteger(configKey) + : fromDisplayDeviceConfig.getAsInt(); } return refreshRate; } - /* - * Return null if no such property - */ - public Float getDefaultPeakRefreshRate() { - float defaultPeakRefreshRate = mDeviceConfig.getFloat( - DeviceConfig.NAMESPACE_DISPLAY_MANAGER, - DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, -1); - - if (defaultPeakRefreshRate == -1) { - return null; - } - return defaultPeakRefreshRate; - } - @Override public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) { - Float defaultPeakRefreshRate = getDefaultPeakRefreshRate(); + float defaultPeakRefreshRate = mConfigParameterProvider.getPeakRefreshRateDefault(); mHandler.obtainMessage(MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED, - defaultPeakRefreshRate).sendToTarget(); + defaultPeakRefreshRate == -1 ? null : defaultPeakRefreshRate).sendToTarget(); - int[] lowDisplayBrightnessThresholds = getLowDisplayBrightnessThresholds(); - int[] lowAmbientBrightnessThresholds = getLowAmbientBrightnessThresholds(); - final int refreshRateInLowZone = getRefreshRateInLowZone(); + int[] lowDisplayBrightnessThresholds = + mConfigParameterProvider.getLowDisplayBrightnessThresholds(); + int[] lowAmbientBrightnessThresholds = + mConfigParameterProvider.getLowAmbientBrightnessThresholds(); + final int refreshRateInLowZone = mConfigParameterProvider.getRefreshRateInLowZone(); mHandler.obtainMessage(MSG_LOW_BRIGHTNESS_THRESHOLDS_CHANGED, new Pair<>(lowDisplayBrightnessThresholds, lowAmbientBrightnessThresholds)) @@ -2790,9 +2732,11 @@ public class DisplayModeDirector { mHandler.obtainMessage(MSG_REFRESH_RATE_IN_LOW_ZONE_CHANGED, refreshRateInLowZone, 0).sendToTarget(); - int[] highDisplayBrightnessThresholds = getHighDisplayBrightnessThresholds(); - int[] highAmbientBrightnessThresholds = getHighAmbientBrightnessThresholds(); - final int refreshRateInHighZone = getRefreshRateInHighZone(); + int[] highDisplayBrightnessThresholds = + mConfigParameterProvider.getHighDisplayBrightnessThresholds(); + int[] highAmbientBrightnessThresholds = + mConfigParameterProvider.getHighAmbientBrightnessThresholds(); + final int refreshRateInHighZone = mConfigParameterProvider.getRefreshRateInHighZone(); mHandler.obtainMessage(MSG_HIGH_BRIGHTNESS_THRESHOLDS_CHANGED, new Pair<>(highDisplayBrightnessThresholds, highAmbientBrightnessThresholds)) @@ -2814,33 +2758,6 @@ public class DisplayModeDirector { .sendToTarget(); } } - - private int[] getIntArrayProperty(String prop) { - String strArray = mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop, - null); - - if (strArray != null) { - return parseIntArray(strArray); - } - - return null; - } - - private int[] parseIntArray(@NonNull String strArray) { - String[] items = strArray.split(","); - int[] array = new int[items.length]; - - try { - for (int i = 0; i < array.length; i++) { - array[i] = Integer.parseInt(items[i]); - } - } catch (NumberFormatException e) { - Slog.e(TAG, "Incorrect format for array: '" + strArray + "'", e); - array = null; - } - - return array; - } } interface Injector { diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java index 6d70d21e3b84..633bf73120e1 100644 --- a/services/core/java/com/android/server/dreams/DreamController.java +++ b/services/core/java/com/android/server/dreams/DreamController.java @@ -16,11 +16,11 @@ package com.android.server.dreams; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.content.Intent.FLAG_RECEIVER_FOREGROUND; import android.app.ActivityTaskManager; import android.app.BroadcastOptions; +import android.app.IAppTask; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -214,6 +214,27 @@ final class DreamController { } /** + * Provides an appTask for the dream with token {@code dreamToken}, so that the dream controller + * can stop the dream task when necessary. + */ + void setDreamAppTask(Binder dreamToken, IAppTask appTask) { + if (mCurrentDream == null || mCurrentDream.mToken != dreamToken + || mCurrentDream.mAppTask != null) { + Slog.e(TAG, "Illegal dream activity start. mCurrentDream.mToken = " + + mCurrentDream.mToken + ", illegal dreamToken = " + dreamToken + + ". Ending this dream activity."); + try { + appTask.finishAndRemoveTask(); + } catch (RemoteException | RuntimeException e) { + Slog.e(TAG, "Unable to stop illegal dream activity."); + } + return; + } + + mCurrentDream.mAppTask = appTask; + } + + /** * Stops dreaming. * * The current dream, if any, and any unstopped previous dreams are stopped. The device stops @@ -303,8 +324,14 @@ final class DreamController { mSentStartBroadcast = false; } - mActivityTaskManager.removeRootTasksWithActivityTypes( - new int[] {ACTIVITY_TYPE_DREAM}); + if (mCurrentDream != null && mCurrentDream.mAppTask != null) { + // Finish the dream task in case it hasn't finished by itself already. + try { + mCurrentDream.mAppTask.finishAndRemoveTask(); + } catch (RemoteException | RuntimeException e) { + Slog.e(TAG, "Unable to stop dream activity."); + } + } mListener.onDreamStopped(dream.mToken); } @@ -364,6 +391,7 @@ final class DreamController { public final boolean mIsPreviewMode; public final boolean mCanDoze; public final int mUserId; + public IAppTask mAppTask; public PowerManager.WakeLock mWakeLock; public boolean mBound; diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 68cf59f0ae1f..d88fe8a6c201 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -27,6 +27,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.IAppTask; import android.app.TaskInfo; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -37,6 +38,7 @@ import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageManagerInternal; import android.content.pm.ServiceInfo; import android.database.ContentObserver; import android.hardware.display.AmbientDisplayConfiguration; @@ -116,6 +118,7 @@ public final class DreamManagerService extends SystemService { private final PowerManagerInternal mPowerManagerInternal; private final PowerManager.WakeLock mDozeWakeLock; private final ActivityTaskManagerInternal mAtmInternal; + private final PackageManagerInternal mPmInternal; private final UserManager mUserManager; private final UiEventLogger mUiEventLogger; private final DreamUiEventLogger mDreamUiEventLogger; @@ -216,6 +219,7 @@ public final class DreamManagerService extends SystemService { mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mPowerManagerInternal = getLocalService(PowerManagerInternal.class); mAtmInternal = getLocalService(ActivityTaskManagerInternal.class); + mPmInternal = getLocalService(PackageManagerInternal.class); mUserManager = context.getSystemService(UserManager.class); mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, DOZE_WAKE_LOCK_TAG); mDozeConfig = new AmbientDisplayConfiguration(mContext); @@ -1064,6 +1068,64 @@ public final class DreamManagerService extends SystemService { Binder.restoreCallingIdentity(ident); } } + + @Override // Binder call + public void startDreamActivity(@NonNull Intent intent) { + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + // We post here, because startDreamActivity and setDreamAppTask have to run + // synchronously and DreamController#setDreamAppTask has to run on mHandler. + mHandler.post(() -> { + final Binder dreamToken; + final String dreamPackageName; + synchronized (mLock) { + if (mCurrentDream == null) { + Slog.e(TAG, "Attempt to start DreamActivity, but the device is not " + + "dreaming. Aborting without starting the DreamActivity."); + return; + } + dreamToken = mCurrentDream.token; + dreamPackageName = mCurrentDream.name.getPackageName(); + } + + if (!canLaunchDreamActivity(dreamPackageName, intent.getPackage(), + callingUid)) { + Slog.e(TAG, "The dream activity can be started only when the device is dreaming" + + " and only by the active dream package."); + return; + } + + final IAppTask appTask = mAtmInternal.startDreamActivity(intent, callingUid, + callingPid); + if (appTask == null) { + Slog.e(TAG, "Could not start dream activity."); + stopDreamInternal(true, "DreamActivity not started"); + return; + } + mController.setDreamAppTask(dreamToken, appTask); + }); + } + + boolean canLaunchDreamActivity(String dreamPackageName, String packageName, + int callingUid) { + if (dreamPackageName == null || packageName == null) { + Slog.e(TAG, "Cannot launch dream activity due to invalid state. dream component= " + + dreamPackageName + ", packageName=" + packageName); + return false; + } + if (!mPmInternal.isSameApp(packageName, callingUid, UserHandle.getUserId(callingUid))) { + Slog.e(TAG, "Cannot launch dream activity because package=" + + packageName + " does not match callingUid=" + callingUid); + return false; + } + if (packageName.equals(dreamPackageName)) { + return true; + } + Slog.e(TAG, "Dream packageName does not match active dream. Package " + packageName + + " does not match " + dreamPackageName); + return false; + } + } private final class LocalService extends DreamManagerInternal { diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java index 8cdef8952333..d3371b187074 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java @@ -269,6 +269,9 @@ public final class FontManagerService extends IFontManager.Stub { synchronized (mUpdatableFontDirLock) { mUpdatableFontDir = createUpdatableFontDir(); if (mUpdatableFontDir == null) { + // If fs-verity is not supported, load preinstalled system font map and use it for + // all apps. + Typeface.loadPreinstalledSystemFontMap(); setSerializedFontMap(serializeSystemServerFontMap()); return; } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java index 7d0d5a73f093..b5e819511ed4 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java @@ -16,6 +16,8 @@ package com.android.server.hdmi; +import static com.android.server.hdmi.HdmiControlService.DEVICE_CLEANUP_TIMEOUT; + import android.annotation.CallSuper; import android.hardware.hdmi.DeviceFeatures; import android.hardware.hdmi.HdmiControlManager; @@ -61,9 +63,6 @@ abstract class HdmiCecLocalDevice extends HdmiLocalDevice { private static final int MAX_HDMI_ACTIVE_SOURCE_HISTORY = 10; private static final int MSG_DISABLE_DEVICE_TIMEOUT = 1; private static final int MSG_USER_CONTROL_RELEASE_TIMEOUT = 2; - // Timeout in millisecond for device clean up (5s). - // Normal actions timeout is 2s but some of them would have several sequence of timeout. - private static final int DEVICE_CLEANUP_TIMEOUT = 5000; // Within the timer, a received <User Control Pressed> will start "Press and Hold" behavior. // When it expires, we can assume <User Control Release> is received. private static final int FOLLOWER_SAFETY_TIMEOUT = 550; @@ -175,6 +174,14 @@ abstract class HdmiCecLocalDevice extends HdmiLocalDevice { }; /** + * A callback interface used by local devices use to indicate that they have finished their part + * of the standby process. + */ + interface StandbyCompletedCallback { + void onStandbyCompleted(); + } + + /** * A callback interface to get notified when all pending action is cleared. It can be called * when timeout happened. */ @@ -1260,8 +1267,14 @@ abstract class HdmiCecLocalDevice extends HdmiLocalDevice { * messages like <Standby> * @param standbyAction Intent action that drives the standby process, either {@link * HdmiControlService#STANDBY_SCREEN_OFF} or {@link HdmiControlService#STANDBY_SHUTDOWN} + * @param callback callback invoked after the standby process for the local device is completed. */ - protected void onStandby(boolean initiatedByCec, int standbyAction) {} + protected void onStandby(boolean initiatedByCec, int standbyAction, + StandbyCompletedCallback callback) {} + + protected void onStandby(boolean initiatedByCec, int standbyAction) { + onStandby(initiatedByCec, standbyAction, null); + } /** * Called when the initialization of local devices is complete. @@ -1422,6 +1435,16 @@ abstract class HdmiCecLocalDevice extends HdmiLocalDevice { } } + @ServiceThreadOnly + @VisibleForTesting + public void invokeStandbyCompletedCallback(StandbyCompletedCallback callback) { + assertRunOnServiceThread(); + if (callback == null) { + return; + } + callback.onStandbyCompleted(); + } + void sendUserControlPressedAndReleased(int targetAddress, int cecKeycode) { mService.sendCecCommand( HdmiCecMessageBuilder.buildUserControlPressed( diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java index 184bdd778300..b3aa351d69d7 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java @@ -21,6 +21,7 @@ import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED; import static com.android.server.hdmi.Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON; import static com.android.server.hdmi.Constants.PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON; import static com.android.server.hdmi.Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON; +import static com.android.server.hdmi.HdmiControlService.SendMessageCallback; import android.annotation.Nullable; import android.content.ActivityNotFoundException; @@ -243,7 +244,8 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { @Override @ServiceThreadOnly - protected void onStandby(boolean initiatedByCec, int standbyAction) { + protected void onStandby(boolean initiatedByCec, int standbyAction, + StandbyCompletedCallback callback) { assertRunOnServiceThread(); // Invalidate the internal active source record when goes to standby // This set will also update mIsActiveSource @@ -256,7 +258,7 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, isSystemAudioActivated() ? "true" : "false"); } - terminateSystemAudioMode(); + terminateSystemAudioMode(callback); } @Override @@ -1092,9 +1094,16 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { } protected void terminateSystemAudioMode() { + terminateSystemAudioMode(null); + } + + // Since this method is not just called during the standby process, the callback should be + // generalized in the future. + protected void terminateSystemAudioMode(StandbyCompletedCallback callback) { // remove pending initiation actions removeAction(SystemAudioInitiationActionFromAvr.class); if (!isSystemAudioActivated()) { + invokeStandbyCompletedCallback(callback); return; } @@ -1102,7 +1111,13 @@ public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { // send <Set System Audio Mode> [“Off”] mService.sendCecCommand( HdmiCecMessageBuilder.buildSetSystemAudioMode( - getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST, false)); + getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST, false), + new SendMessageCallback() { + @Override + public void onSendCompleted(int error) { + invokeStandbyCompletedCallback(callback); + } + }); } } diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java index c73a07d98f6a..dc416b24f3f3 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java @@ -28,7 +28,6 @@ import android.hardware.tv.cec.V1_0.SendMessageResult; import android.os.Binder; import android.os.Handler; import android.os.PowerManager; -import android.os.PowerManager.WakeLock; import android.os.SystemProperties; import android.sysprop.HdmiProperties; import android.util.Slog; @@ -238,9 +237,11 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { @Override @ServiceThreadOnly - protected void onStandby(boolean initiatedByCec, int standbyAction) { + protected void onStandby(boolean initiatedByCec, int standbyAction, + StandbyCompletedCallback callback) { assertRunOnServiceThread(); if (!mService.isCecControlEnabled()) { + invokeStandbyCompletedCallback(callback); return; } boolean wasActiveSource = isActiveSource(); @@ -248,12 +249,20 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { mService.setActiveSource(Constants.ADDR_INVALID, Constants.INVALID_PHYSICAL_ADDRESS, "HdmiCecLocalDevicePlayback#onStandby()"); if (!wasActiveSource) { + invokeStandbyCompletedCallback(callback); return; } + SendMessageCallback sendMessageCallback = new SendMessageCallback() { + @Override + public void onSendCompleted(int error) { + invokeStandbyCompletedCallback(callback); + } + }; if (initiatedByCec) { mService.sendCecCommand( HdmiCecMessageBuilder.buildInactiveSource( - getDeviceInfo().getLogicalAddress(), mService.getPhysicalAddress())); + getDeviceInfo().getLogicalAddress(), mService.getPhysicalAddress()), + sendMessageCallback); return; } switch (standbyAction) { @@ -266,7 +275,8 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { case HdmiControlManager.POWER_CONTROL_MODE_TV: mService.sendCecCommand( HdmiCecMessageBuilder.buildStandby( - getDeviceInfo().getLogicalAddress(), Constants.ADDR_TV)); + getDeviceInfo().getLogicalAddress(), Constants.ADDR_TV), + sendMessageCallback); break; case HdmiControlManager.POWER_CONTROL_MODE_TV_AND_AUDIO_SYSTEM: mService.sendCecCommand( @@ -275,19 +285,19 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { mService.sendCecCommand( HdmiCecMessageBuilder.buildStandby( getDeviceInfo().getLogicalAddress(), - Constants.ADDR_AUDIO_SYSTEM)); + Constants.ADDR_AUDIO_SYSTEM), sendMessageCallback); break; case HdmiControlManager.POWER_CONTROL_MODE_BROADCAST: mService.sendCecCommand( HdmiCecMessageBuilder.buildStandby( getDeviceInfo().getLogicalAddress(), - Constants.ADDR_BROADCAST)); + Constants.ADDR_BROADCAST), sendMessageCallback); break; case HdmiControlManager.POWER_CONTROL_MODE_NONE: mService.sendCecCommand( HdmiCecMessageBuilder.buildInactiveSource( getDeviceInfo().getLogicalAddress(), - mService.getPhysicalAddress())); + mService.getPhysicalAddress()), sendMessageCallback); break; } break; @@ -295,7 +305,8 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { // ACTION_SHUTDOWN is taken as a signal to power off all the devices. mService.sendCecCommand( HdmiCecMessageBuilder.buildStandby( - getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST)); + getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST), + sendMessageCallback); break; } } @@ -590,7 +601,7 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource { } private class SystemWakeLock implements ActiveWakeLock { - private final WakeLock mWakeLock; + private final WakeLockWrapper mWakeLock; public SystemWakeLock() { mWakeLock = mService.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mWakeLock.setReferenceCounted(false); diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 70870627b8c7..6cca1bd823d4 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -377,6 +377,7 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { return; } int oldPath = getActivePortId() != Constants.INVALID_PORT_ID + && getActivePortId() != Constants.CEC_SWITCH_HOME ? mService.portIdToPath(getActivePortId()) : getDeviceInfo().getPhysicalAddress(); setActivePath(oldPath); if (mSkipRoutingControl) { @@ -1399,10 +1400,12 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @Override @ServiceThreadOnly - protected void onStandby(boolean initiatedByCec, int standbyAction) { + protected void onStandby(boolean initiatedByCec, int standbyAction, + StandbyCompletedCallback callback) { assertRunOnServiceThread(); // Seq #11 if (!mService.isCecControlEnabled()) { + invokeStandbyCompletedCallback(callback); return; } boolean sendStandbyOnSleep = @@ -1412,7 +1415,15 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (!initiatedByCec && sendStandbyOnSleep) { mService.sendCecCommand( HdmiCecMessageBuilder.buildStandby( - getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST)); + getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST), + new SendMessageCallback() { + @Override + public void onSendCompleted(int error) { + invokeStandbyCompletedCallback(callback); + } + }); + } else { + invokeStandbyCompletedCallback(callback); } } diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java index 3e3835b3fe4e..91cf50319bca 100644 --- a/services/core/java/com/android/server/hdmi/HdmiControlService.java +++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java @@ -21,8 +21,10 @@ import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVIC import static android.hardware.hdmi.HdmiControlManager.EARC_FEATURE_DISABLED; import static android.hardware.hdmi.HdmiControlManager.EARC_FEATURE_ENABLED; import static android.hardware.hdmi.HdmiControlManager.HDMI_CEC_CONTROL_ENABLED; +import static android.hardware.hdmi.HdmiControlManager.POWER_CONTROL_MODE_NONE; import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_DISABLED; import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_ENABLED; +import static android.hardware.hdmi.HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED; import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED; import static com.android.server.hdmi.Constants.DISABLED; @@ -110,6 +112,7 @@ import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource; import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback; +import com.android.server.hdmi.HdmiCecLocalDevice.StandbyCompletedCallback; import libcore.util.EmptyArray; @@ -224,6 +227,10 @@ public class HdmiControlService extends SystemService { public @interface WakeReason { } + // Timeout in millisecond for device clean up (5s). + // Normal actions timeout is 2s but some of them would have several sequences of timeout. + static final int DEVICE_CLEANUP_TIMEOUT = 5000; + @VisibleForTesting static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI = new AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI, ""); @@ -492,6 +499,9 @@ public class HdmiControlService extends SystemService { private DeviceConfigWrapper mDeviceConfig; @Nullable + private WakeLockWrapper mWakeLock; + + @Nullable private PowerManagerWrapper mPowerManager; @Nullable @@ -3040,7 +3050,7 @@ public class HdmiControlService extends SystemService { } String powerControlMode = getHdmiCecConfig().getStringValue( HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE); - if (powerControlMode.equals(HdmiControlManager.POWER_CONTROL_MODE_NONE)) { + if (powerControlMode.equals(POWER_CONTROL_MODE_NONE)) { return false; } int hdmiCecEnabled = getHdmiCecConfig().getIntValue( @@ -3687,6 +3697,9 @@ public class HdmiControlService extends SystemService { @ServiceThreadOnly @VisibleForTesting protected void onStandby(final int standbyAction) { + if (shouldAcquireWakeLockOnStandby()) { + acquireWakeLock(); + } mWakeUpMessageReceived = false; assertRunOnServiceThread(); mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY, @@ -3752,7 +3765,8 @@ public class HdmiControlService extends SystemService { return mMenuLanguage; } - private void disableCecLocalDevices(PendingActionClearedCallback callback) { + @VisibleForTesting + protected void disableCecLocalDevices(PendingActionClearedCallback callback) { if (mCecController != null) { for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { device.disableDevice(mStandbyMessageReceived, callback); @@ -3780,25 +3794,81 @@ public class HdmiControlService extends SystemService { * cleared during standby. In this case, it does not execute the standby flow. */ @ServiceThreadOnly - private void onPendingActionsCleared(int standbyAction) { + @VisibleForTesting + protected void onPendingActionsCleared(int standbyAction) { assertRunOnServiceThread(); Slog.v(TAG, "onPendingActionsCleared"); + int localDevicesCount = getAllCecLocalDevices().size(); + final int[] countStandbyCompletedDevices = new int[1]; + StandbyCompletedCallback callback = new StandbyCompletedCallback() { + @Override + public void onStandbyCompleted() { + if (localDevicesCount < ++countStandbyCompletedDevices[0]) { + return; + } + + releaseWakeLock(); + if (isAudioSystemDevice() || !isPowerStandby()) { + return; + } + mCecController.enableSystemCecControl(false); + mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED); + } + }; if (mPowerStatusController.isPowerStatusTransientToStandby()) { mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY); for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { - device.onStandby(mStandbyMessageReceived, standbyAction); - } - if (!isAudioSystemDevice()) { - mCecController.enableSystemCecControl(false); - mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED); + device.onStandby(mStandbyMessageReceived, standbyAction, callback); } } - // Always reset this flag to set up for the next standby mStandbyMessageReceived = false; } + private boolean shouldAcquireWakeLockOnStandby() { + boolean sendStandbyOnSleep = false; + if (tv() != null) { + sendStandbyOnSleep = mHdmiCecConfig.getIntValue( + HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP) + == TV_SEND_STANDBY_ON_SLEEP_ENABLED; + } else if (playback() != null) { + sendStandbyOnSleep = !mHdmiCecConfig.getStringValue( + HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE) + .equals(POWER_CONTROL_MODE_NONE); + } + + return isCecControlEnabled() && isPowerOnOrTransient() && sendStandbyOnSleep; + } + + /** + * Acquire the wake lock used to hold the system awake until the standby process is finished. + */ + @VisibleForTesting + protected void acquireWakeLock() { + releaseWakeLock(); + mWakeLock = mPowerManager.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, TAG); + mWakeLock.acquire(DEVICE_CLEANUP_TIMEOUT); + } + + /** + * Release the wake lock acquired when the standby process started. + */ + @VisibleForTesting + protected void releaseWakeLock() { + if (mWakeLock != null) { + try { + if (mWakeLock.isHeld()) { + mWakeLock.release(); + } + } catch (RuntimeException e) { + Slog.w(TAG, "Exception when releasing wake lock."); + } + mWakeLock = null; + } + } + @VisibleForTesting void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId) { VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, vendorId); @@ -3819,6 +3889,7 @@ public class HdmiControlService extends SystemService { if (mVendorCommandListenerRecords.isEmpty()) { return false; } + boolean notifiedVendorCommandToListeners = false; for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) { if (hasVendorId) { int vendorId = @@ -3831,12 +3902,12 @@ public class HdmiControlService extends SystemService { } try { record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId); - return true; + notifiedVendorCommandToListeners = true; } catch (RemoteException e) { Slog.e(TAG, "Failed to notify vendor command reception", e); } } - return false; + return notifiedVendorCommandToListeners; } } diff --git a/services/core/java/com/android/server/hdmi/PowerManagerWrapper.java b/services/core/java/com/android/server/hdmi/PowerManagerWrapper.java index f0810687290e..7530b3b239b4 100644 --- a/services/core/java/com/android/server/hdmi/PowerManagerWrapper.java +++ b/services/core/java/com/android/server/hdmi/PowerManagerWrapper.java @@ -18,7 +18,6 @@ package com.android.server.hdmi; import android.content.Context; import android.os.PowerManager; -import android.os.PowerManager.WakeLock; /** * Abstraction around {@link PowerManager} to allow faking PowerManager in tests. @@ -44,7 +43,54 @@ public class PowerManagerWrapper { mPowerManager.goToSleep(time, reason, flags); } - WakeLock newWakeLock(int levelAndFlags, String tag) { - return mPowerManager.newWakeLock(levelAndFlags, tag); + WakeLockWrapper newWakeLock(int levelAndFlags, String tag) { + return new DefaultWakeLockWrapper(mPowerManager.newWakeLock(levelAndFlags, tag)); + } + + /** + * "Default" wrapper for {@link PowerManager.WakeLock}, as opposed to a "Fake" wrapper for + * testing - see {@link FakePowerManagerWrapper.FakeWakeLockWrapper}. + * + * Stores an instance of {@link PowerManager.WakeLock} and directly passes method calls to that + * instance. + */ + public static class DefaultWakeLockWrapper implements WakeLockWrapper { + + private static final String TAG = "DefaultWakeLockWrapper"; + + private final PowerManager.WakeLock mWakeLock; + + private DefaultWakeLockWrapper(PowerManager.WakeLock wakeLock) { + mWakeLock = wakeLock; + } + + @Override + public void acquire(long timeout) { + mWakeLock.acquire(timeout); + } + + @Override + public void acquire() { + mWakeLock.acquire(); + } + + /** + * @throws RuntimeException WakeLock can throw this exception if it is not released + * successfully. + */ + @Override + public void release() throws RuntimeException { + mWakeLock.release(); + } + + @Override + public boolean isHeld() { + return mWakeLock.isHeld(); + } + + @Override + public void setReferenceCounted(boolean value) { + mWakeLock.setReferenceCounted(value); + } } } diff --git a/services/core/java/com/android/server/hdmi/WakeLockWrapper.java b/services/core/java/com/android/server/hdmi/WakeLockWrapper.java new file mode 100644 index 000000000000..f6899106b589 --- /dev/null +++ b/services/core/java/com/android/server/hdmi/WakeLockWrapper.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.hdmi; + +/** + * Interface with the methods from {@link PowerManager.WakeLock} used by the HDMI control framework. + * Allows the class to be faked for the tests. + * + * See implementations {@link DefaultWakeLockWrapper} and + * {@link FakePowerManagerWrapper.FakeWakeLockWrapper}. + */ +public interface WakeLockWrapper { + + /** + * Wraps {@link PowerManager.WakeLock#acquire(long)}; + */ + void acquire(long timeout); + + /** + * Wraps {@link PowerManager.WakeLock#acquire()}; + */ + void acquire(); + + /** + * Wraps {@link PowerManager.WakeLock#release()}; + */ + void release(); + + /** + * Wraps {@link PowerManager.WakeLock#isHeld()}; + */ + boolean isHeld(); + + /** + * Wraps {@link PowerManager.WakeLock#setReferenceCounted(boolean)}; + */ + void setReferenceCounted(boolean value); +} diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java index 232979037d79..3716e1f5cc33 100644 --- a/services/core/java/com/android/server/input/InputSettingsObserver.java +++ b/services/core/java/com/android/server/input/InputSettingsObserver.java @@ -47,6 +47,9 @@ class InputSettingsObserver extends ContentObserver { private final NativeInputManagerService mNative; private final Map<Uri, Consumer<String /* reason*/>> mObservers; + // Cache prevent notifying same KeyRepeatInfo data to native code multiple times. + private KeyRepeatInfo mLastKeyRepeatInfoSettingsUpdate; + InputSettingsObserver(Context context, Handler handler, InputManagerService service, NativeInputManagerService nativeIms) { super(handler); @@ -195,7 +198,11 @@ class InputSettingsObserver extends ContentObserver { final int delayMs = Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.KEY_REPEAT_DELAY_MS, ViewConfiguration.getKeyRepeatDelay(), UserHandle.USER_CURRENT); - mNative.setKeyRepeatConfiguration(timeoutMs, delayMs); + if (mLastKeyRepeatInfoSettingsUpdate == null || !mLastKeyRepeatInfoSettingsUpdate.isEqualTo( + timeoutMs, delayMs)) { + mNative.setKeyRepeatConfiguration(timeoutMs, delayMs); + mLastKeyRepeatInfoSettingsUpdate = new KeyRepeatInfo(timeoutMs, delayMs); + } } // Not using ViewConfiguration.getLongPressTimeout here because it may return a stale value. @@ -214,4 +221,19 @@ class InputSettingsObserver extends ContentObserver { } mNative.setMaximumObscuringOpacityForTouch(opacity); } + + private static class KeyRepeatInfo { + private final int mKeyRepeatTimeoutMs; + private final int mKeyRepeatDelayMs; + + private KeyRepeatInfo(int keyRepeatTimeoutMs, int keyRepeatDelayMs) { + this.mKeyRepeatTimeoutMs = keyRepeatTimeoutMs; + this.mKeyRepeatDelayMs = keyRepeatDelayMs; + } + + public boolean isEqualTo(int keyRepeatTimeoutMs, int keyRepeatDelayMs) { + return mKeyRepeatTimeoutMs == keyRepeatTimeoutMs + && mKeyRepeatDelayMs == keyRepeatDelayMs; + } + } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index b0b1d676bc4b..ba9e280be49d 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -352,7 +352,6 @@ final class InputMethodBindingController { clearCurMethodAndSessions(); mService.clearInputShownLocked(); mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME); - mService.resetSystemUiLocked(); } } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 4dbd82065a66..1ab83f7c5fe5 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -48,6 +48,7 @@ import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState; import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult; +import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME; import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT; import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed; @@ -633,9 +634,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private InputMethodSubtype mCurrentSubtype; /** - * {@code true} if the IME has not been mostly hidden via {@link android.view.InsetsController}. + * {@code true} if the IME has not been mostly hidden via {@link android.view.InsetsController} */ - @GuardedBy("ImfLock.class") private boolean mCurPerceptible; /** @@ -749,26 +749,33 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub SparseArray<AccessibilitySessionState> mEnabledAccessibilitySessions = new SparseArray<>(); /** - * {@code true} if the device is currently interactive with the user, initially true. - * - * @see #handleSetInteractive + * True if the device is currently interactive with user. The value is true initially. */ - @GuardedBy("ImfLock.class") boolean mIsInteractive = true; - @GuardedBy("ImfLock.class") - @InputMethodService.BackDispositionMode int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT; /** - * The {@link InputMethodService.ImeWindowVisibility} of the currently bound IME, - * or {@code 0} if no IME is bound. + * A set of status bits regarding the active IME. * - * <p><em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and + * <p>This value is a combination of following two bits:</p> + * <dl> + * <dt>{@link InputMethodService#IME_ACTIVE}</dt> + * <dd> + * If this bit is ON, connected IME is ready to accept touch/key events. + * </dd> + * <dt>{@link InputMethodService#IME_VISIBLE}</dt> + * <dd> + * If this bit is ON, some of IME view, e.g. software input, candidate view, is visible. + * </dd> + * <dt>{@link InputMethodService#IME_INVISIBLE}</dt> + * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is + * currently invisible. + * </dd> + * </dl> + * <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and * {@link InputMethodBindingController#unbindCurrentMethod()}.</em> */ - @GuardedBy("ImfLock.class") - @InputMethodService.ImeWindowVisibility int mImeWindowVis; private LocaleList mLastSystemLocales; @@ -1529,6 +1536,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // Uh oh, current input method is no longer around! // Pick another one... Slog.i(TAG, "Current input method removed: " + curInputMethodId); + updateSystemUiLocked(0 /* vis */, mBackDisposition); if (!chooseNewDefaultIMELocked()) { changed = true; curIm = null; @@ -2360,6 +2368,28 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } } + /** + * Called when {@link #resetCurrentMethodAndClientLocked(int)} invoked for clean-up states + * before unbinding the current method. + */ + @GuardedBy("ImfLock.class") + void onUnbindCurrentMethodByReset() { + final ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull( + mCurFocusedWindow); + if (winState != null && !winState.isRequestedImeVisible() + && !mVisibilityStateComputer.isInputShown()) { + // Normally, the focus window will apply the IME visibility state to + // WindowManager when the IME has applied it. But it would be too late when + // switching IMEs in between different users. (Since the focused IME will + // first unbind the service to switch to bind the next user of the IME + // service, that wouldn't make the attached IME token validity check in time) + // As a result, we have to notify WM to apply IME visibility before clearing the + // binding states in the first place. + mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, mCurStatsToken, + STATE_HIDE_IME); + } + } + /** {@code true} when a {@link ClientState} has attached from starting the input connection. */ @GuardedBy("ImfLock.class") boolean hasAttachedClient() { @@ -2831,6 +2861,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) { setSelectedMethodIdLocked(null); + // Callback before clean-up binding states. + onUnbindCurrentMethodByReset(); mBindingController.unbindCurrentMethod(); unbindCurrentClientLocked(unbindClientReason); } @@ -2931,6 +2963,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub sessionState.mSession.finishSession(); } catch (RemoteException e) { Slog.w(TAG, "Session failed to close due to remote exception", e); + updateSystemUiLocked(0 /* vis */, mBackDisposition); } sessionState.mSession = null; } @@ -3040,8 +3073,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") - private boolean shouldShowImeSwitcherLocked( - @InputMethodService.ImeWindowVisibility int visibility) { + private boolean shouldShowImeSwitcherLocked(int visibility) { if (!mShowOngoingImeSwitcherForPhones) return false; // When the IME switcher dialog is shown, the IME switcher button should be hidden. if (mMenuController.getSwitchingDialogLocked() != null) return false; @@ -3053,7 +3085,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub && mWindowManagerInternal.isKeyguardSecure(mSettings.getCurrentUserId())) { return false; } - if ((visibility & InputMethodService.IME_ACTIVE) == 0) { + if ((visibility & InputMethodService.IME_ACTIVE) == 0 + || (visibility & InputMethodService.IME_INVISIBLE) != 0) { return false; } if (mWindowManagerInternal.isHardKeyboardAvailable()) { @@ -3112,9 +3145,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @SuppressWarnings("deprecation") - private void setImeWindowStatus(@NonNull IBinder token, - @InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition) { + private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition) { final int topFocusedDisplayId = mWindowManagerInternal.getTopFocusedDisplayId(); synchronized (ImfLock.class) { @@ -3131,7 +3162,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } mImeWindowVis = vis; mBackDisposition = backDisposition; - updateSystemUiLocked(mImeWindowVis, mBackDisposition); + updateSystemUiLocked(vis, backDisposition); } final boolean dismissImeOnBackKeyPressed; @@ -3166,46 +3197,37 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private void updateImeWindowStatus(boolean disableImeIcon) { synchronized (ImfLock.class) { - // TODO(b/285109020): disableImeIcon should be stored in a property like - // mIsSwitcherIconDisabled, but it is currently not reliably cleared. - updateSystemUiLocked(disableImeIcon ? 0 : mImeWindowVis, mBackDisposition); + if (disableImeIcon) { + updateSystemUiLocked(0, mBackDisposition); + } else { + updateSystemUiLocked(); + } } } @GuardedBy("ImfLock.class") void updateSystemUiLocked() { - // This is only used by InputMethodMenuController to trigger the IME switcher icon - // visibility, by having {@code shouldShowImeSwitcherLocked} called, which depends on the - // visibility of the IME switcher dialog. updateSystemUiLocked(mImeWindowVis, mBackDisposition); } // Caution! This method is called in this class. Handle multi-user carefully @GuardedBy("ImfLock.class") - private void updateSystemUiLocked(@InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition) { + private void updateSystemUiLocked(int vis, int backDisposition) { if (getCurTokenLocked() == null) { return; } if (DEBUG) { Slog.d(TAG, "IME window vis: " + vis - + " active: " + ((vis & InputMethodService.IME_ACTIVE) != 0) - + " visible: " + ((vis & InputMethodService.IME_VISIBLE) != 0) - + " backDisposition: " + backDisposition - + " isInteractive: " + mIsInteractive - + " curPerceptible: " + mCurPerceptible + + " active: " + (vis & InputMethodService.IME_ACTIVE) + + " inv: " + (vis & InputMethodService.IME_INVISIBLE) + " displayId: " + mCurTokenDisplayId); } // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure - // all updateSystemUi happens on system privilege. + // all updateSystemUi happens on system privilege. final long ident = Binder.clearCallingIdentity(); try { - if (!mIsInteractive) { - // When we are not interactive, - // the visibility should be 0 (no IME icons should be shown). - vis = 0; - } else if (!mCurPerceptible) { + if (!mCurPerceptible) { if ((vis & InputMethodService.IME_VISIBLE) != 0) { vis &= ~InputMethodService.IME_VISIBLE; vis |= InputMethodService.IME_VISIBLE_IMPERCEPTIBLE; @@ -3540,7 +3562,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return; } mCurPerceptible = perceptible; - updateSystemUiLocked(mImeWindowVis, mBackDisposition); + updateSystemUiLocked(); } }); } @@ -5102,11 +5124,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private void handleSetInteractive(final boolean interactive) { synchronized (ImfLock.class) { - if (mIsInteractive == interactive) { - return; - } mIsInteractive = interactive; - updateSystemUiLocked(mImeWindowVis, mBackDisposition); + updateSystemUiLocked(interactive ? mImeWindowVis : 0, mBackDisposition); // Inform the current client of the change in active status if (mCurClient == null || mCurClient.mClient == null) { @@ -6741,8 +6760,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override - public void setImeWindowStatusAsync(@InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition) { + public void setImeWindowStatusAsync(int vis, int backDisposition) { mImms.setImeWindowStatus(mToken, vis, backDisposition); } diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java index 6ba75850f95d..d700c6adfebb 100644 --- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java +++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java @@ -36,6 +36,7 @@ import android.hardware.weaver.IWeaver; import android.hardware.weaver.WeaverConfig; import android.hardware.weaver.WeaverReadResponse; import android.hardware.weaver.WeaverReadStatus; +import android.os.IBinder; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; @@ -505,7 +506,7 @@ class SyntheticPasswordManager { private final Context mContext; private LockSettingsStorage mStorage; - private IWeaver mWeaver; + private volatile IWeaver mWeaver; private WeaverConfig mWeaverConfig; private PasswordSlotManager mPasswordSlotManager; @@ -536,13 +537,33 @@ class SyntheticPasswordManager { } } - private IWeaver getWeaverService() { + private class WeaverDiedRecipient implements IBinder.DeathRecipient { + // Not synchronized on the outer class, since setting the pointer to null is atomic, and we + // don't want to have to worry about any sort of deadlock here. + @Override + public void binderDied() { + // Weaver died. Try to recover by setting mWeaver to null, which makes + // getWeaverService() look up the service again. This is done only as a simple + // robustness measure; it should not be relied on. If this triggers, the root cause is + // almost certainly a bug in the device's Weaver implementation, which must be fixed. + Slog.wtf(TAG, "Weaver service has died"); + mWeaver.asBinder().unlinkToDeath(this, 0); + mWeaver = null; + } + } + + private @Nullable IWeaver getWeaverServiceInternal() { // Try to get the AIDL service first try { IWeaver aidlWeaver = IWeaver.Stub.asInterface( ServiceManager.waitForDeclaredService(IWeaver.DESCRIPTOR + "/default")); if (aidlWeaver != null) { Slog.i(TAG, "Using AIDL weaver service"); + try { + aidlWeaver.asBinder().linkToDeath(new WeaverDiedRecipient(), 0); + } catch (RemoteException e) { + Slog.w(TAG, "Unable to register Weaver death recipient", e); + } return aidlWeaver; } } catch (SecurityException e) { @@ -568,15 +589,20 @@ class SyntheticPasswordManager { return LockPatternUtils.isAutoPinConfirmFeatureAvailable(); } - private synchronized boolean isWeaverAvailable() { - if (mWeaver != null) { - return true; + /** + * Returns a handle to the Weaver service, or null if Weaver is unavailable. Note that not all + * devices support Weaver. + */ + private synchronized @Nullable IWeaver getWeaverService() { + IWeaver weaver = mWeaver; + if (weaver != null) { + return weaver; } // Re-initialize weaver in case there was a transient error preventing access to it. - IWeaver weaver = getWeaverService(); + weaver = getWeaverServiceInternal(); if (weaver == null) { - return false; + return null; } final WeaverConfig weaverConfig; @@ -584,19 +610,18 @@ class SyntheticPasswordManager { weaverConfig = weaver.getConfig(); } catch (RemoteException | ServiceSpecificException e) { Slog.e(TAG, "Failed to get weaver config", e); - return false; + return null; } if (weaverConfig == null || weaverConfig.slots <= 0) { Slog.e(TAG, "Invalid weaver config"); - return false; + return null; } mWeaver = weaver; mWeaverConfig = weaverConfig; mPasswordSlotManager.refreshActiveSlots(getUsedWeaverSlots()); Slog.i(TAG, "Weaver service initialized"); - - return true; + return weaver; } /** @@ -606,7 +631,7 @@ class SyntheticPasswordManager { * * @return the value stored in the weaver slot, or null if the operation fails */ - private byte[] weaverEnroll(int slot, byte[] key, @Nullable byte[] value) { + private byte[] weaverEnroll(IWeaver weaver, int slot, byte[] key, @Nullable byte[] value) { if (slot == INVALID_WEAVER_SLOT || slot >= mWeaverConfig.slots) { throw new IllegalArgumentException("Invalid slot for weaver"); } @@ -619,7 +644,7 @@ class SyntheticPasswordManager { value = SecureRandomUtils.randomBytes(mWeaverConfig.valueSize); } try { - mWeaver.write(slot, key, value); + weaver.write(slot, key, value); } catch (RemoteException e) { Slog.e(TAG, "weaver write binder call failed, slot: " + slot, e); return null; @@ -648,7 +673,7 @@ class SyntheticPasswordManager { * the verification is successful, throttled or failed. If successful, the bound secret * is also returned. */ - private VerifyCredentialResponse weaverVerify(int slot, byte[] key) { + private VerifyCredentialResponse weaverVerify(IWeaver weaver, int slot, byte[] key) { if (slot == INVALID_WEAVER_SLOT || slot >= mWeaverConfig.slots) { throw new IllegalArgumentException("Invalid slot for weaver"); } @@ -659,7 +684,7 @@ class SyntheticPasswordManager { } final WeaverReadResponse readResponse; try { - readResponse = mWeaver.read(slot, key); + readResponse = weaver.read(slot, key); } catch (RemoteException e) { Slog.e(TAG, "weaver read failed, slot: " + slot, e); return VerifyCredentialResponse.ERROR; @@ -870,14 +895,15 @@ class SyntheticPasswordManager { int slot = loadWeaverSlot(protectorId, userId); destroyState(WEAVER_SLOT_NAME, protectorId, userId); if (slot != INVALID_WEAVER_SLOT) { - if (!isWeaverAvailable()) { + final IWeaver weaver = getWeaverService(); + if (weaver == null) { Slog.e(TAG, "Cannot erase Weaver slot because Weaver is unavailable"); return; } Set<Integer> usedSlots = getUsedWeaverSlots(); if (!usedSlots.contains(slot)) { Slogf.i(TAG, "Erasing Weaver slot %d", slot); - weaverEnroll(slot, null, null); + weaverEnroll(weaver, slot, null, null); mPasswordSlotManager.markSlotDeleted(slot); } else { Slogf.i(TAG, "Weaver slot %d was already reused; not erasing it", slot); @@ -955,13 +981,14 @@ class SyntheticPasswordManager { Slogf.i(TAG, "Creating LSKF-based protector %016x for user %d", protectorId, userId); - if (isWeaverAvailable()) { + final IWeaver weaver = getWeaverService(); + if (weaver != null) { // Weaver is available, so make the protector use it to verify the LSKF. Do this even // if the LSKF is empty, as that gives us support for securely deleting the protector. int weaverSlot = getNextAvailableWeaverSlot(); Slogf.i(TAG, "Enrolling LSKF for user %d into Weaver slot %d", userId, weaverSlot); - byte[] weaverSecret = weaverEnroll(weaverSlot, stretchedLskfToWeaverKey(stretchedLskf), - null); + byte[] weaverSecret = weaverEnroll(weaver, weaverSlot, + stretchedLskfToWeaverKey(stretchedLskf), null); if (weaverSecret == null) { throw new IllegalStateException( "Fail to enroll user password under weaver " + userId); @@ -1048,7 +1075,8 @@ class SyntheticPasswordManager { } return VerifyCredentialResponse.fromGateKeeperResponse(response); } else if (persistentData.type == PersistentData.TYPE_SP_WEAVER) { - if (!isWeaverAvailable()) { + final IWeaver weaver = getWeaverService(); + if (weaver == null) { Slog.e(TAG, "No weaver service to verify SP-based persistent data credential"); return VerifyCredentialResponse.ERROR; } @@ -1056,7 +1084,8 @@ class SyntheticPasswordManager { byte[] stretchedLskf = stretchLskf(userCredential, pwd); int weaverSlot = persistentData.userId; - return weaverVerify(weaverSlot, stretchedLskfToWeaverKey(stretchedLskf)).stripPayload(); + return weaverVerify(weaver, weaverSlot, + stretchedLskfToWeaverKey(stretchedLskf)).stripPayload(); } else { Slog.e(TAG, "persistentData.type must be TYPE_SP_GATEKEEPER or TYPE_SP_WEAVER, but is " + persistentData.type); @@ -1209,7 +1238,7 @@ class SyntheticPasswordManager { TokenData tokenData = new TokenData(); tokenData.mType = type; final byte[] secdiscardable = SecureRandomUtils.randomBytes(SECDISCARDABLE_LENGTH); - if (isWeaverAvailable()) { + if (getWeaverService() != null) { tokenData.weaverSecret = SecureRandomUtils.randomBytes(mWeaverConfig.valueSize); tokenData.secdiscardableOnDisk = SyntheticPasswordCrypto.encrypt(tokenData.weaverSecret, PERSONALIZATION_WEAVER_TOKEN, secdiscardable); @@ -1252,10 +1281,11 @@ class SyntheticPasswordManager { return false; } Slogf.i(TAG, "Creating token-based protector %016x for user %d", tokenHandle, userId); - if (isWeaverAvailable()) { + final IWeaver weaver = getWeaverService(); + if (weaver != null) { int slot = getNextAvailableWeaverSlot(); Slogf.i(TAG, "Using Weaver slot %d for new token-based protector", slot); - if (weaverEnroll(slot, null, tokenData.weaverSecret) == null) { + if (weaverEnroll(weaver, slot, null, tokenData.weaverSecret) == null) { Slog.e(TAG, "Failed to enroll weaver secret when activating token"); return false; } @@ -1344,12 +1374,14 @@ class SyntheticPasswordManager { int weaverSlot = loadWeaverSlot(protectorId, userId); if (weaverSlot != INVALID_WEAVER_SLOT) { // Protector uses Weaver to verify the LSKF - if (!isWeaverAvailable()) { + final IWeaver weaver = getWeaverService(); + if (weaver == null) { Slog.e(TAG, "Protector uses Weaver, but Weaver is unavailable"); result.gkResponse = VerifyCredentialResponse.ERROR; return result; } - result.gkResponse = weaverVerify(weaverSlot, stretchedLskfToWeaverKey(stretchedLskf)); + result.gkResponse = weaverVerify(weaver, weaverSlot, + stretchedLskfToWeaverKey(stretchedLskf)); if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) { return result; } @@ -1517,12 +1549,13 @@ class SyntheticPasswordManager { } int slotId = loadWeaverSlot(protectorId, userId); if (slotId != INVALID_WEAVER_SLOT) { - if (!isWeaverAvailable()) { + final IWeaver weaver = getWeaverService(); + if (weaver == null) { Slog.e(TAG, "Protector uses Weaver, but Weaver is unavailable"); result.gkResponse = VerifyCredentialResponse.ERROR; return result; } - VerifyCredentialResponse response = weaverVerify(slotId, null); + VerifyCredentialResponse response = weaverVerify(weaver, slotId, null); if (response.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK || response.getGatekeeperHAT() == null) { Slog.e(TAG, diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index 82cc53dd3535..63dc59c125a3 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -482,20 +482,20 @@ class MediaRouter2ServiceImpl { } public void registerManager(@NonNull IMediaRouter2Manager manager, - @NonNull String packageName) { + @NonNull String callerPackageName) { Objects.requireNonNull(manager, "manager must not be null"); - if (TextUtils.isEmpty(packageName)) { - throw new IllegalArgumentException("packageName must not be empty"); + if (TextUtils.isEmpty(callerPackageName)) { + throw new IllegalArgumentException("callerPackageName must not be empty"); } - final int uid = Binder.getCallingUid(); - final int pid = Binder.getCallingPid(); - final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier(); + final int callerUid = Binder.getCallingUid(); + final int callerPid = Binder.getCallingPid(); + final int userId = UserHandle.getUserHandleForUid(callerUid).getIdentifier(); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - registerManagerLocked(manager, uid, pid, packageName, userId); + registerManagerLocked(manager, callerUid, callerPid, callerPackageName, userId); } } finally { Binder.restoreCallingIdentity(token); @@ -673,30 +673,35 @@ class MediaRouter2ServiceImpl { final long token = Binder.clearCallingIdentity(); try { - RoutingSessionInfo systemSessionInfo = null; synchronized (mLock) { UserRecord userRecord = getOrCreateUserRecordLocked(userId); List<RoutingSessionInfo> sessionInfos; if (hasModifyAudioRoutingPermission) { if (setDeviceRouteSelected) { - systemSessionInfo = userRecord.mHandler.mSystemProvider + // Return a fake system session that shows the device route as selected and + // available bluetooth routes as transferable. + return userRecord.mHandler.mSystemProvider .generateDeviceRouteSelectedSessionInfo(packageName); } else { sessionInfos = userRecord.mHandler.mSystemProvider.getSessionInfos(); if (sessionInfos != null && !sessionInfos.isEmpty()) { - systemSessionInfo = new RoutingSessionInfo.Builder(sessionInfos.get(0)) - .setClientPackageName(packageName).build(); + // Return a copy of the current system session with no modification, + // except setting the client package name. + return new RoutingSessionInfo.Builder(sessionInfos.get(0)) + .setClientPackageName(packageName) + .build(); } else { Slog.w(TAG, "System provider does not have any session info."); } } } else { - systemSessionInfo = new RoutingSessionInfo.Builder( - userRecord.mHandler.mSystemProvider.getDefaultSessionInfo()) - .setClientPackageName(packageName).build(); + return new RoutingSessionInfo.Builder( + userRecord.mHandler.mSystemProvider.getDefaultSessionInfo()) + .setClientPackageName(packageName) + .build(); } } - return systemSessionInfo; + return null; } finally { Binder.restoreCallingIdentity(token); } @@ -920,6 +925,17 @@ class MediaRouter2ServiceImpl { return; } + Slog.i( + TAG, + TextUtils.formatSimple( + "requestCreateSessionWithRouter2 | router: %s(id: %d), old session id: %s," + + " new session's route id: %s, request id: %d", + routerRecord.mPackageName, + routerRecord.mRouterId, + oldSession.getId(), + route.getId(), + requestId)); + if (managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE) { ManagerRecord manager = routerRecord.mUserRecord.mHandler.findManagerWithId( toRequesterId(managerRequestId)); @@ -1125,25 +1141,26 @@ class MediaRouter2ServiceImpl { @GuardedBy("mLock") private void registerManagerLocked(@NonNull IMediaRouter2Manager manager, - int uid, int pid, @NonNull String packageName, int userId) { + int callerUid, int callerPid, @NonNull String callerPackageName, int userId) { final IBinder binder = manager.asBinder(); ManagerRecord managerRecord = mAllManagerRecords.get(binder); if (managerRecord != null) { - Slog.w(TAG, "registerManagerLocked: Same manager already exists. packageName=" - + packageName); + Slog.w(TAG, "registerManagerLocked: Same manager already exists. callerPackageName=" + + callerPackageName); return; } Slog.i(TAG, TextUtils.formatSimple( - "registerManager | uid: %d, pid: %d, package: %s, user: %d", - uid, pid, packageName, userId)); + "registerManager | callerUid: %d, callerPid: %d, package: %s, user: %d", + callerUid, callerPid, callerPackageName, userId)); - mContext.enforcePermission(Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid, + mContext.enforcePermission(Manifest.permission.MEDIA_CONTENT_CONTROL, callerPid, callerUid, "Must hold MEDIA_CONTENT_CONTROL permission."); UserRecord userRecord = getOrCreateUserRecordLocked(userId); - managerRecord = new ManagerRecord(userRecord, manager, uid, pid, packageName); + managerRecord = new ManagerRecord( + userRecord, manager, callerUid, callerPid, callerPackageName); try { binder.linkToDeath(managerRecord, 0); } catch (RemoteException ex) { @@ -1190,7 +1207,7 @@ class MediaRouter2ServiceImpl { Slog.i(TAG, TextUtils.formatSimple( "unregisterManager | package: %s, user: %d, manager: %d", - managerRecord.mPackageName, + managerRecord.mOwnerPackageName, userRecord.mUserId, managerRecord.mManagerId)); @@ -1708,20 +1725,20 @@ class MediaRouter2ServiceImpl { final class ManagerRecord implements IBinder.DeathRecipient { public final UserRecord mUserRecord; public final IMediaRouter2Manager mManager; - public final int mUid; - public final int mPid; - public final String mPackageName; + public final int mOwnerUid; + public final int mOwnerPid; + public final String mOwnerPackageName; public final int mManagerId; public SessionCreationRequest mLastSessionCreationRequest; public boolean mIsScanning; ManagerRecord(UserRecord userRecord, IMediaRouter2Manager manager, - int uid, int pid, String packageName) { + int ownerUid, int ownerPid, String ownerPackageName) { mUserRecord = userRecord; mManager = manager; - mUid = uid; - mPid = pid; - mPackageName = packageName; + mOwnerUid = ownerUid; + mOwnerPid = ownerPid; + mOwnerPackageName = ownerPackageName; mManagerId = mNextRouterOrManagerId.getAndIncrement(); } @@ -1739,10 +1756,10 @@ class MediaRouter2ServiceImpl { String indent = prefix + " "; - pw.println(indent + "mPackageName=" + mPackageName); + pw.println(indent + "mOwnerPackageName=" + mOwnerPackageName); pw.println(indent + "mManagerId=" + mManagerId); - pw.println(indent + "mUid=" + mUid); - pw.println(indent + "mPid=" + mPid); + pw.println(indent + "mOwnerUid=" + mOwnerUid); + pw.println(indent + "mOwnerPid=" + mOwnerPid); pw.println(indent + "mIsScanning=" + mIsScanning); if (mLastSessionCreationRequest != null) { @@ -1770,7 +1787,7 @@ class MediaRouter2ServiceImpl { @Override public String toString() { - return "Manager " + mPackageName + " (pid " + mPid + ")"; + return "Manager " + mOwnerPackageName + " (pid " + mOwnerPid + ")"; } } @@ -1928,7 +1945,7 @@ class MediaRouter2ServiceImpl { isUidRelevant = mUserRecord.mRouterRecords.stream().anyMatch(router -> router.mUid == uid) | mUserRecord.mManagerRecords.stream() - .anyMatch(manager -> manager.mUid == uid); + .anyMatch(manager -> manager.mOwnerUid == uid); } if (isUidRelevant) { sendMessage(PooledLambda.obtainMessage( @@ -2741,7 +2758,7 @@ class MediaRouter2ServiceImpl { if (service.mPowerManager.isInteractive()) { isManagerScanning = managerRecords.stream().anyMatch(manager -> manager.mIsScanning && service.mActivityManager - .getPackageImportance(manager.mPackageName) + .getPackageImportance(manager.mOwnerPackageName) <= sPackageImportanceForScanning); if (isManagerScanning) { diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index b440e8815c16..cc261a4b9797 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -195,7 +195,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override - public void registerClientAsUser(IMediaRouterClient client, String packageName, int userId) { + public void registerClientAsUser( + IMediaRouterClient client, @NonNull String packageName, int userId) { final int uid = Binder.getCallingUid(); if (!validatePackageName(uid, packageName)) { throw new SecurityException("packageName must match the calling uid"); @@ -537,12 +538,12 @@ public final class MediaRouterService extends IMediaRouterService.Stub // Binder call @Override - public void registerManager(IMediaRouter2Manager manager, String packageName) { + public void registerManager(IMediaRouter2Manager manager, String callerPackageName) { final int uid = Binder.getCallingUid(); - if (!validatePackageName(uid, packageName)) { - throw new SecurityException("packageName must match the calling uid"); + if (!validatePackageName(uid, callerPackageName)) { + throw new SecurityException("callerPackageName must match the calling uid"); } - mService2.registerManager(manager, packageName); + mService2.registerManager(manager, callerPackageName); } // Binder call @@ -693,8 +694,13 @@ public final class MediaRouterService extends IMediaRouterService.Stub } @GuardedBy("mLock") - private void registerClientLocked(IMediaRouterClient client, - int uid, int pid, String packageName, int userId, boolean trusted) { + private void registerClientLocked( + IMediaRouterClient client, + int uid, + int pid, + @NonNull String packageName, + int userId, + boolean trusted) { final IBinder binder = client.asBinder(); ClientRecord clientRecord = mAllClientRecords.get(binder); if (clientRecord == null) { @@ -926,6 +932,10 @@ public final class MediaRouterService extends IMediaRouterService.Stub clientRecord.dispose(); } + /** + * Validates whether the provided package name matches a given uid. Returns false if the package + * name is null. + */ private boolean validatePackageName(int uid, String packageName) { if (packageName != null) { String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); @@ -974,8 +984,13 @@ public final class MediaRouterService extends IMediaRouterService.Stub public String mSelectedRouteId; public String mGroupId; - public ClientRecord(UserRecord userRecord, IMediaRouterClient client, - int uid, int pid, String packageName, boolean trusted) { + ClientRecord( + UserRecord userRecord, + IMediaRouterClient client, + int uid, + int pid, + @NonNull String packageName, + boolean trusted) { mUserRecord = userRecord; mClient = client; mUid = uid; diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 9185a00da570..95ca08cc7fe9 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -106,6 +106,16 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) static final long THROW_FOR_INVALID_BROADCAST_RECEIVER = 270049379L; + /** + * {@link MediaSession#setMediaButtonReceiver(PendingIntent)} throws an {@link + * IllegalArgumentException} if the provided {@link PendingIntent} targets an {@link + * android.app.Activity activity} for apps targeting Android V and above. For apps targeting + * Android U and below, the request will be ignored. + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + static final long THROW_FOR_ACTIVITY_MEDIA_BUTTON_RECEIVER = 272737196L; + private static final String TAG = "MediaSessionRecord"; private static final String[] ART_URIS = new String[] { MediaMetadata.METADATA_KEY_ALBUM_ART_URI, @@ -1055,13 +1065,26 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR } @Override - public void setMediaButtonReceiver(PendingIntent pi) throws RemoteException { + public void setMediaButtonReceiver(@Nullable PendingIntent pi) throws RemoteException { + final int uid = Binder.getCallingUid(); final long token = Binder.clearCallingIdentity(); try { if ((mPolicies & MediaSessionPolicyProvider.SESSION_POLICY_IGNORE_BUTTON_RECEIVER) != 0) { return; } + + if (pi != null && pi.isActivity()) { + if (CompatChanges.isChangeEnabled( + THROW_FOR_ACTIVITY_MEDIA_BUTTON_RECEIVER, uid)) { + throw new IllegalArgumentException( + "The media button receiver cannot be set to an activity."); + } else { + Log.w(TAG, "Ignoring invalid media button receiver targeting an activity."); + return; + } + } + mMediaButtonReceiverHolder = MediaButtonReceiverHolder.create(mUserId, pi, mPackageName); mService.onMediaButtonReceiverChanged(MediaSessionRecord.this); diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index f4e6abd47b44..6c9aa4b0d849 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -17,6 +17,7 @@ package com.android.server.media; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -253,6 +254,16 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { return mDefaultSessionInfo; } + /** + * Builds a system {@link RoutingSessionInfo} with the selected route set to the currently + * selected <b>device</b> route (wired or built-in, but not bluetooth) and transferable routes + * set to the currently available (connected) bluetooth routes. + * + * <p>The session's client package name is set to the provided package name. + * + * <p>Returns {@code null} if there are no registered system sessions. + */ + @Nullable public RoutingSessionInfo generateDeviceRouteSelectedSessionInfo(String packageName) { synchronized (mLock) { if (mSessionInfos.isEmpty()) { diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index fc506b606633..c649164562d3 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -147,7 +147,7 @@ public final class MediaProjectionManagerService extends SystemService mInjector = injector; mClock = injector.createClock(); mDeathEaters = new ArrayMap<IBinder, IBinder.DeathRecipient>(); - mCallbackDelegate = new CallbackDelegate(); + mCallbackDelegate = new CallbackDelegate(injector.createCallbackLooper()); mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); mPackageManager = mContext.getPackageManager(); @@ -182,6 +182,11 @@ public final class MediaProjectionManagerService extends SystemService Clock createClock() { return SystemClock::uptimeMillis; } + + /** Creates the {@link Looper} to be used when notifying callbacks. */ + Looper createCallbackLooper() { + return Looper.getMainLooper(); + } } @Override @@ -268,7 +273,8 @@ public final class MediaProjectionManagerService extends SystemService dispatchStop(projection); } - private void addCallback(final IMediaProjectionWatcherCallback callback) { + @VisibleForTesting + void addCallback(final IMediaProjectionWatcherCallback callback) { IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { @@ -315,6 +321,12 @@ public final class MediaProjectionManagerService extends SystemService mCallbackDelegate.dispatchStop(projection); } + private void dispatchSessionSet( + @NonNull MediaProjectionInfo projectionInfo, + @Nullable ContentRecordingSession session) { + mCallbackDelegate.dispatchSession(projectionInfo, session); + } + /** * Returns {@code true} when updating the current mirroring session on WM succeeded, and * {@code false} otherwise. @@ -335,6 +347,7 @@ public final class MediaProjectionManagerService extends SystemService if (mProjectionGrant != null) { // Cache the session details. mProjectionGrant.mSession = incomingSession; + dispatchSessionSet(mProjectionGrant.getProjectionInfo(), incomingSession); } return true; } @@ -1122,8 +1135,8 @@ public final class MediaProjectionManagerService extends SystemService private Handler mHandler; private final Object mLock = new Object(); - public CallbackDelegate() { - mHandler = new Handler(Looper.getMainLooper(), null, true /*async*/); + CallbackDelegate(Looper callbackLooper) { + mHandler = new Handler(callbackLooper, null, true /*async*/); mClientCallbacks = new ArrayMap<IBinder, IMediaProjectionCallback>(); mWatcherCallbacks = new ArrayMap<IBinder, IMediaProjectionWatcherCallback>(); } @@ -1186,6 +1199,16 @@ public final class MediaProjectionManagerService extends SystemService } } + public void dispatchSession( + @NonNull MediaProjectionInfo projectionInfo, + @Nullable ContentRecordingSession session) { + synchronized (mLock) { + for (IMediaProjectionWatcherCallback callback : mWatcherCallbacks.values()) { + mHandler.post(new WatcherSessionCallback(callback, projectionInfo, session)); + } + } + } + public void dispatchResize(MediaProjection projection, int width, int height) { if (projection == null) { Slog.e(TAG, @@ -1302,6 +1325,29 @@ public final class MediaProjectionManagerService extends SystemService } } + private static final class WatcherSessionCallback implements Runnable { + private final IMediaProjectionWatcherCallback mCallback; + private final MediaProjectionInfo mProjectionInfo; + private final ContentRecordingSession mSession; + + WatcherSessionCallback( + @NonNull IMediaProjectionWatcherCallback callback, + @NonNull MediaProjectionInfo projectionInfo, + @Nullable ContentRecordingSession session) { + mCallback = callback; + mProjectionInfo = projectionInfo; + mSession = session; + } + + @Override + public void run() { + try { + mCallback.onRecordingSessionSet(mProjectionInfo, mSession); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to notify content recording session changed", e); + } + } + } private static String typeToString(int type) { switch (type) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index f571c9d7cc7f..cdc63473cacb 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -5592,6 +5592,11 @@ public class NotificationManagerService extends SystemService { boolean granted, boolean userSet) { Objects.requireNonNull(listener); checkNotificationListenerAccess(); + if (granted && listener.flattenToString().length() + > NotificationManager.MAX_SERVICE_COMPONENT_NAME_LENGTH) { + throw new IllegalArgumentException( + "Component name too long: " + listener.flattenToString()); + } if (!userSet && isNotificationListenerAccessUserSet(listener)) { // Don't override user's choice return; diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java index a5fa81da3c07..6505e8b91c06 100644 --- a/services/core/java/com/android/server/pm/AppsFilterImpl.java +++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java @@ -834,12 +834,6 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, } updateEntireShouldFilterCacheInner(snapshot, settings, usersRef[0], USER_ALL); - synchronized (mImplicitlyQueryableLock) { - if (mNeedToUpdateCacheForImplicitAccess) { - updateShouldFilterCacheForImplicitAccess(); - mNeedToUpdateCacheForImplicitAccess = false; - } - } logCacheRebuilt(reason, SystemClock.currentTimeMicro() - currentTimeUs, users.length, settings.size()); @@ -850,7 +844,14 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, return; } - mCacheReady = true; + synchronized (mImplicitlyQueryableLock) { + if (mNeedToUpdateCacheForImplicitAccess) { + updateShouldFilterCacheForImplicitAccess(); + mNeedToUpdateCacheForImplicitAccess = false; + } + mCacheReady = true; + } + onChanged(); }, delayMs); } diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 9f3ee1cfd979..4963dd4ecb6a 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -16,6 +16,7 @@ package com.android.server.pm; +import static android.Manifest.permission.CONTROL_KEYGUARD; import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; @@ -364,6 +365,12 @@ final class DeletePackageHelper { synchronized (mPm.mLock) { final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName); final PackageSetting disabledPs = mPm.mSettings.getDisabledSystemPkgLPr(ps); + if (PackageManagerServiceUtils.isSystemApp(ps) + && mPm.checkPermission(CONTROL_KEYGUARD, packageName, UserHandle.USER_SYSTEM) + == PERMISSION_GRANTED) { + Slog.w(TAG, "Attempt to delete keyguard system package " + packageName); + return false; + } action = mayDeletePackageLocked(outInfo, ps, disabledPs, flags, user); } if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user); @@ -625,7 +632,8 @@ final class DeletePackageHelper { PackageManager.UNINSTALL_REASON_UNKNOWN, null /*harmfulAppWarning*/, null /*splashScreenTheme*/, - 0 /*firstInstallTime*/); + 0 /*firstInstallTime*/, + PackageManager.USER_MIN_ASPECT_RATIO_UNSET); } mPm.mSettings.writeKernelMappingLPr(ps); } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index e01e1d3f3edd..7d4776285db0 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -1077,6 +1077,8 @@ final class InstallPackageHelper { final boolean isApex = ((installFlags & PackageManager.INSTALL_APEX) != 0); final boolean isRollback = request.getInstallReason() == PackageManager.INSTALL_REASON_ROLLBACK; + final boolean extractProfile = + ((installFlags & PackageManager.INSTALL_DONT_EXTRACT_BASELINE_PROFILES) == 0); @PackageManagerService.ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE; if (request.isInstallMove()) { // moving a complete application; perform an initial scan on the new install location @@ -1112,7 +1114,9 @@ final class InstallPackageHelper { @ParsingPackageUtils.ParseFlags final int parseFlags = mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_CHATTY | ParsingPackageUtils.PARSE_ENFORCE_CODE - | (onExternal ? ParsingPackageUtils.PARSE_EXTERNAL_STORAGE : 0); + | (onExternal ? ParsingPackageUtils.PARSE_EXTERNAL_STORAGE : 0) + | (extractProfile + ? ParsingPackageUtils.PARSE_EXTRACT_BASELINE_PROFILES_FROM_APK : 0); Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage"); final ParsedPackage parsedPackage; @@ -2790,6 +2794,8 @@ final class InstallPackageHelper { final String[] pkgNames = new String[]{ request.getRemovedInfo().mRemovedPackage}; final int[] uids = new int[]{request.getRemovedInfo().mUid}; + mPm.notifyResourcesChanged(false /* mediaStatus */, + true /* replacing */, pkgNames, uids); mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer, false /* mediaStatus */, true /* replacing */, pkgNames, uids); } @@ -2995,6 +3001,8 @@ final class InstallPackageHelper { final int[] uids = new int[]{request.getPkg().getUid()}; mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer, true /* mediaStatus */, true /* replacing */, pkgNames, uids); + mPm.notifyResourcesChanged(true /* mediaStatus */, true /* replacing */, + pkgNames, uids); } } else if (!ArrayUtils.isEmpty(request.getLibraryConsumers())) { // if static shared lib // No need to kill consumers if it's installation of new version static shared lib. diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 3377f0090027..af6c1a25967c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -29,6 +29,7 @@ import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS; import static android.content.pm.PackageManager.MATCH_FACTORY_ONLY; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET; import static android.os.Process.INVALID_UID; import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER; import static android.os.storage.StorageManager.FLAG_STORAGE_CE; @@ -128,6 +129,7 @@ import android.os.FileUtils; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; +import android.os.IRemoteCallback; import android.os.Message; import android.os.Parcel; import android.os.ParcelFileDescriptor; @@ -710,6 +712,9 @@ public class PackageManagerService implements PackageSender, TestUtilityService @NonNull private final PackageObserverHelper mPackageObserverHelper = new PackageObserverHelper(); + @NonNull + private final PackageMonitorCallbackHelper mPackageMonitorCallbackHelper; + private final ModuleInfoProvider mModuleInfoProvider; final ApexManager mApexManager; @@ -1832,6 +1837,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper); mStorageEventHelper = testParams.storageEventHelper; + mPackageMonitorCallbackHelper = testParams.packageMonitorCallbackHelper; registerObservers(false); invalidatePackageInfoCache(); @@ -1972,6 +1978,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService mDomainVerificationManager.setConnection(mDomainVerificationConnection); mBroadcastHelper = new BroadcastHelper(mInjector); + mPackageMonitorCallbackHelper = new PackageMonitorCallbackHelper(mInjector); mAppDataHelper = new AppDataHelper(this); mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper); mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper); @@ -2493,13 +2500,22 @@ public class PackageManagerService implements PackageSender, TestUtilityService @NonNull private String getRequiredServicesExtensionPackageLPr(@NonNull Computer computer) { - String servicesExtensionPackage = - ensureSystemPackageName(computer, - mContext.getString(R.string.config_servicesExtensionPackage)); + String configServicesExtensionPackage = mContext.getString( + R.string.config_servicesExtensionPackage); + if (TextUtils.isEmpty(configServicesExtensionPackage)) { + throw new RuntimeException( + "Required services extension package failed due to " + + "config_servicesExtensionPackage is empty."); + } + String servicesExtensionPackage = ensureSystemPackageName(computer, + configServicesExtensionPackage); if (TextUtils.isEmpty(servicesExtensionPackage)) { throw new RuntimeException( - "Required services extension package is missing, check " - + "config_servicesExtensionPackage."); + "Required services extension package is missing, " + + "config_servicesExtensionPackage had defined with " + + configServicesExtensionPackage + + ", but can not find the package info on the system image, check if " + + "the package has a problem."); } return servicesExtensionPackage; } @@ -2975,6 +2991,25 @@ public class PackageManagerService implements PackageSender, TestUtilityService mHandler.post(() -> mBroadcastHelper.sendPackageBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver, userIds, instantUserIds, broadcastAllowList, null /* filterExtrasForReceiver */, bOptions)); + if (targetPkg == null) { + // For some broadcast action, e.g. ACTION_PACKAGE_ADDED, this method will be called + // many times to different targets, e.g. installer app, permission controller, other + // registered apps. We should filter it to avoid calling back many times for the same + // action. When the targetPkg is set, it sends the broadcast to specific app, e.g. + // installer app or null for registered apps. The callback only need to send back to the + // registered apps so we check the null condition here. + notifyPackageMonitor(action, pkg, extras, userIds); + } + } + + void notifyPackageMonitor(String action, String pkg, Bundle extras, int[] userIds) { + mPackageMonitorCallbackHelper.notifyPackageMonitor(action, pkg, extras, userIds); + } + + void notifyResourcesChanged(boolean mediaStatus, boolean replacing, + @NonNull String[] pkgNames, @NonNull int[] uids) { + mPackageMonitorCallbackHelper.notifyResourcesChanged(mediaStatus, replacing, pkgNames, + uids); } @Override @@ -3023,6 +3058,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService userIds, snapshot.getPackageStates()); mHandler.post(() -> mBroadcastHelper.sendPackageAddedForNewUsers( packageName, appId, userIds, instantUserIds, dataLoaderType, broadcastAllowList)); + mPackageMonitorCallbackHelper.notifyPackageAddedForNewUsers(packageName, appId, userIds, + instantUserIds, dataLoaderType); if (sendBootCompleted && !ArrayUtils.isEmpty(userIds)) { mHandler.post(() -> { for (int userId : userIds) { @@ -4015,6 +4052,8 @@ public class PackageManagerService implements PackageSender, TestUtilityService mHandler.post(() -> mBroadcastHelper.sendPackageChangedBroadcast( packageName, dontKillApp, componentNames, packageUid, reason, userIds, instantUserIds, broadcastAllowList)); + mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames, + packageUid, reason, userIds); } /** @@ -5100,6 +5139,20 @@ public class PackageManagerService implements PackageSender, TestUtilityService } @Override + @PackageManager.UserMinAspectRatio + public int getUserMinAspectRatio(@NonNull String packageName, int userId) { + final Computer snapshot = snapshotComputer(); + final int callingUid = Binder.getCallingUid(); + snapshot.enforceCrossUserPermission( + callingUid, userId, false /* requireFullPermission */, + false /* checkShell */, "getUserMinAspectRatio"); + final PackageStateInternal packageState = snapshot + .getPackageStateForInstalledAndFiltered(packageName, callingUid, userId); + return packageState == null ? USER_MIN_ASPECT_RATIO_UNSET + : packageState.getUserStateOrDefault(userId).getMinAspectRatio(); + } + + @Override public Bundle getSuspendedPackageAppExtras(String packageName, int userId) { final int callingUid = Binder.getCallingUid(); final Computer snapshot = snapshot(); @@ -6064,6 +6117,32 @@ public class PackageManagerService implements PackageSender, TestUtilityService return true; } + @android.annotation.EnforcePermission(android.Manifest.permission.INSTALL_PACKAGES) + @Override + public void setUserMinAspectRatio(@NonNull String packageName, int userId, + @PackageManager.UserMinAspectRatio int aspectRatio) { + setUserMinAspectRatio_enforcePermission(); + final int callingUid = Binder.getCallingUid(); + final Computer snapshot = snapshotComputer(); + snapshot.enforceCrossUserPermission(callingUid, userId, + false /* requireFullPermission */, false /* checkShell */, + "setUserMinAspectRatio"); + enforceOwnerRights(snapshot, packageName, callingUid); + + final PackageStateInternal packageState = snapshot + .getPackageStateForInstalledAndFiltered(packageName, callingUid, userId); + if (packageState == null) { + return; + } + + if (packageState.getUserStateOrDefault(userId).getMinAspectRatio() == aspectRatio) { + return; + } + + commitPackageStateMutation(null, packageName, state -> + state.userState(userId).setMinAspectRatio(aspectRatio)); + } + @Override @SuppressWarnings("GuardedBy") public void setRuntimePermissionsVersion(int version, @UserIdInt int userId) { @@ -6142,6 +6221,16 @@ public class PackageManagerService implements PackageSender, TestUtilityService } @Override + public void registerPackageMonitorCallback(@NonNull IRemoteCallback callback, int userId) { + mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, userId); + } + + @Override + public void unregisterPackageMonitorCallback(@NonNull IRemoteCallback callback) { + mPackageMonitorCallbackHelper.unregisterPackageMonitorCallback(callback); + } + + @Override public void requestPackageChecksums(@NonNull String packageName, boolean includeSplits, @Checksum.TypeMask int optional, @Checksum.TypeMask int required, @Nullable List trustedInstallers, diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java index 50711deafa58..ca572091486e 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java @@ -123,4 +123,5 @@ public final class PackageManagerServiceTestParams { public Set<String> initialNonStoppedSystemPackages = new ArraySet<>(); public boolean shouldStopSystemPackagesByDefault; public FreeStorageHelper freeStorageHelper; + public PackageMonitorCallbackHelper packageMonitorCallbackHelper; } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index e4692f19ada9..5cb480c0278f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -3436,6 +3436,10 @@ class PackageManagerShellCommand extends ShellCommand { sessionParams.installFlags |= PackageManager.INSTALL_BYPASS_LOW_TARGET_SDK_BLOCK; break; + case "--no-profile": + sessionParams.installFlags |= + PackageManager.INSTALL_DONT_EXTRACT_BASELINE_PROFILES; + break; default: throw new IllegalArgumentException("Unknown option " + opt); } @@ -4324,7 +4328,7 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" [--install-reason 0/1/2/3/4] [--originating-uri URI]"); pw.println(" [--referrer URI] [--abi ABI_NAME] [--force-sdk]"); pw.println(" [--preload] [--instant] [--full] [--dont-kill]"); - pw.println(" [--enable-rollback]"); + pw.println(" [--enable-rollback] [--no-profile]"); pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]"); pw.println(" [--apex] [--force-non-staged] [--staged-ready-timeout TIMEOUT]"); pw.println(" [PATH [SPLIT...]|-]"); @@ -4357,6 +4361,7 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" --apex: install an .apex file, not an .apk"); pw.println(" --force-non-staged: force the installation to run under a non-staged"); pw.println(" session, which may complete without requiring a reboot"); + pw.println(" --no-profile: don't extract the profiles from the apk"); pw.println(" --staged-ready-timeout: By default, staged sessions wait " + DEFAULT_STAGED_READY_TIMEOUT_MS); pw.println(" milliseconds for pre-reboot verification to complete when"); @@ -4378,7 +4383,7 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" [--referrer URI] [--abi ABI_NAME] [--force-sdk]"); pw.println(" [--preload] [--instant] [--full] [--dont-kill]"); pw.println(" [--force-uuid internal|UUID] [--pkg PACKAGE] [--apex] [-S BYTES]"); - pw.println(" [--multi-package] [--staged] [--update-ownership]"); + pw.println(" [--multi-package] [--staged] [--no-profile] [--update-ownership]"); pw.println(" Like \"install\", but starts an install session. Use \"install-write\""); pw.println(" to push data into the session, and \"install-commit\" to finish."); pw.println(""); diff --git a/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java new file mode 100644 index 000000000000..c582321da1ec --- /dev/null +++ b/services/core/java/com/android/server/pm/PackageMonitorCallbackHelper.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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 static com.android.server.pm.PackageManagerService.PACKAGE_SCHEME; + +import android.annotation.AppIdInt; +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.app.IActivityManager; +import android.content.Intent; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.IRemoteCallback; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.os.UserHandle; +import android.text.TextUtils; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.ArrayUtils; + +import java.util.ArrayList; + +/** Helper class to handle PackageMonitorCallback and notify the registered client. This is mainly + * used by PackageMonitor to improve the broadcast latency. */ +class PackageMonitorCallbackHelper { + @NonNull + private final Object mLock = new Object(); + final IActivityManager mActivityManager = ActivityManager.getService(); + + final Handler mHandler; + + PackageMonitorCallbackHelper(PackageManagerServiceInjector injector) { + mHandler = injector.getHandler(); + } + + @NonNull + @GuardedBy("mLock") + private final RemoteCallbackList<IRemoteCallback> mCallbacks = new RemoteCallbackList<>(); + + public void registerPackageMonitorCallback(IRemoteCallback callback, int userId) { + synchronized (mLock) { + mCallbacks.register(callback, userId); + } + } + + public void unregisterPackageMonitorCallback(IRemoteCallback callback) { + synchronized (mLock) { + mCallbacks.unregister(callback); + } + } + + public void notifyPackageAddedForNewUsers(String packageName, + @AppIdInt int appId, @NonNull int[] userIds, @NonNull int[] instantUserIds, + int dataLoaderType) { + Bundle extras = new Bundle(2); + // Set to UID of the first user, EXTRA_UID is automatically updated in sendPackageBroadcast + final int uid = UserHandle.getUid( + (ArrayUtils.isEmpty(userIds) ? instantUserIds[0] : userIds[0]), appId); + extras.putInt(Intent.EXTRA_UID, uid); + extras.putInt(PackageInstaller.EXTRA_DATA_LOADER_TYPE, dataLoaderType); + notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, packageName, extras , + userIds /* userIds */); + } + + public void notifyResourcesChanged(boolean mediaStatus, boolean replacing, + @NonNull String[] pkgNames, @NonNull int[] uids) { + Bundle extras = new Bundle(); + extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST, pkgNames); + extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uids); + if (replacing) { + extras.putBoolean(Intent.EXTRA_REPLACING, replacing); + } + String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE + : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE; + notifyPackageMonitor(action, null /* pkg */, extras, null /* userIds */); + } + + public void notifyPackageChanged(String packageName, boolean dontKillApp, + ArrayList<String> componentNames, int packageUid, String reason, int[] userIds) { + Bundle extras = new Bundle(4); + extras.putString(Intent.EXTRA_CHANGED_COMPONENT_NAME, componentNames.get(0)); + String[] nameList = new String[componentNames.size()]; + componentNames.toArray(nameList); + extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList); + extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, dontKillApp); + extras.putInt(Intent.EXTRA_UID, packageUid); + if (reason != null) { + extras.putString(Intent.EXTRA_REASON, reason); + } + notifyPackageMonitor(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, userIds); + } + + public void notifyPackageMonitor(String action, String pkg, Bundle extras, + int[] userIds) { + if (!isAllowedCallbackAction(action)) { + return; + } + try { + final int[] resolvedUserIds; + if (userIds == null) { + if (mActivityManager == null) return; + resolvedUserIds = mActivityManager.getRunningUserIds(); + } else { + resolvedUserIds = userIds; + } + doNotifyCallbacks(action, pkg, extras, resolvedUserIds); + } catch (RemoteException e) { + // do nothing + } + } + + private static boolean isAllowedCallbackAction(String action) { + return TextUtils.equals(action, Intent.ACTION_PACKAGE_ADDED) + || TextUtils.equals(action, Intent.ACTION_PACKAGE_REMOVED) + || TextUtils.equals(action, Intent.ACTION_PACKAGE_CHANGED) + || TextUtils.equals(action, Intent.ACTION_UID_REMOVED) + || TextUtils.equals(action, Intent.ACTION_PACKAGES_SUSPENDED) + || TextUtils.equals(action, Intent.ACTION_PACKAGES_UNSUSPENDED) + || TextUtils.equals(action, Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE) + || TextUtils.equals(action, Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); + } + + private void doNotifyCallbacks(String action, String pkg, Bundle extras, int[] userIds) { + RemoteCallbackList<IRemoteCallback> callbacks; + synchronized (mLock) { + callbacks = mCallbacks; + } + for (int userId : userIds) { + final Intent intent = new Intent(action, + pkg != null ? Uri.fromParts(PACKAGE_SCHEME, pkg, null) : null); + if (extras != null) { + intent.putExtras(extras); + } + int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); + if (uid >= 0 && UserHandle.getUserId(uid) != userId) { + uid = UserHandle.getUid(userId, UserHandle.getAppId(uid)); + intent.putExtra(Intent.EXTRA_UID, uid); + } + intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + + mHandler.post(() -> callbacks.broadcast((callback, user) -> { + int registerUserId = (int) user; + if ((registerUserId != UserHandle.USER_ALL) && (registerUserId != userId)) { + return; + } + invokeCallback(callback, intent); + })); + } + } + + private void invokeCallback(IRemoteCallback callback, Intent intent) { + try { + Bundle bundle = new Bundle(); + bundle.putParcelable( + PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, intent); + callback.sendResult(bundle); + } catch (RemoteException e) { + // do nothing + } + } +} diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 24118203bb56..3e9ccac2993a 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -875,7 +875,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal ArraySet<String> enabledComponents, ArraySet<String> disabledComponents, int installReason, int uninstallReason, String harmfulAppWarning, String splashScreenTheme, - long firstInstallTime) { + long firstInstallTime, int aspectRatio) { modifyUserState(userId) .setSuspendParams(suspendParams) .setCeDataInode(ceDataInode) @@ -894,7 +894,8 @@ public class PackageSetting extends SettingBase implements PackageStateInternal .setVirtualPreload(virtualPreload) .setHarmfulAppWarning(harmfulAppWarning) .setSplashScreenTheme(splashScreenTheme) - .setFirstInstallTimeMillis(firstInstallTime); + .setFirstInstallTimeMillis(firstInstallTime) + .setMinAspectRatio(aspectRatio); onChanged(); } @@ -912,7 +913,7 @@ public class PackageSetting extends SettingBase implements PackageStateInternal ? null : otherState.getDisabledComponentsNoCopy().untrackedStorage(), otherState.getInstallReason(), otherState.getUninstallReason(), otherState.getHarmfulAppWarning(), otherState.getSplashScreenTheme(), - otherState.getFirstInstallTimeMillis()); + otherState.getFirstInstallTimeMillis(), otherState.getMinAspectRatio()); } WatchedArraySet<String> getEnabledComponents(int userId) { diff --git a/services/core/java/com/android/server/pm/PreferredActivityHelper.java b/services/core/java/com/android/server/pm/PreferredActivityHelper.java index 9ff83929a609..6e273cf8e5d5 100644 --- a/services/core/java/com/android/server/pm/PreferredActivityHelper.java +++ b/services/core/java/com/android/server/pm/PreferredActivityHelper.java @@ -655,6 +655,7 @@ final class PreferredActivityHelper { final Iterator<PreferredActivity> it = pir.filterIterator(); while (it.hasNext()) { final PreferredActivity pa = it.next(); + if (pa == null) continue; final String prefPackageName = pa.mPref.mComponent.getPackageName(); if (packageName == null || (prefPackageName.equals(packageName) && pa.mPref.mAlways)) { diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index be031250bc76..87f912653c60 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -352,6 +352,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile private static final String ATTR_VIRTUAL_PRELOAD = "virtual-preload"; private static final String ATTR_HARMFUL_APP_WARNING = "harmful-app-warning"; private static final String ATTR_SPLASH_SCREEN_THEME = "splash-screen-theme"; + private static final String ATTR_MIN_ASPECT_RATIO = "min-aspect-ratio"; private static final String ATTR_PACKAGE_NAME = "packageName"; private static final String ATTR_BUILD_FINGERPRINT = "buildFingerprint"; @@ -1127,7 +1128,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile PackageManager.UNINSTALL_REASON_UNKNOWN, null /*harmfulAppWarning*/, null /*splashscreenTheme*/, - 0 /*firstInstallTime*/ + 0 /*firstInstallTime*/, + PackageManager.USER_MIN_ASPECT_RATIO_UNSET ); } } @@ -1794,7 +1796,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile PackageManager.UNINSTALL_REASON_UNKNOWN, null /*harmfulAppWarning*/, null /* splashScreenTheme*/, - 0 /*firstInstallTime*/ + 0 /*firstInstallTime*/, + PackageManager.USER_MIN_ASPECT_RATIO_UNSET ); } return; @@ -1889,6 +1892,9 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile ATTR_SPLASH_SCREEN_THEME); final long firstInstallTime = parser.getAttributeLongHex(null, ATTR_FIRST_INSTALL_TIME, 0); + final int minAspectRatio = parser.getAttributeInt(null, + ATTR_MIN_ASPECT_RATIO, + PackageManager.USER_MIN_ASPECT_RATIO_UNSET); ArraySet<String> enabledComponents = null; ArraySet<String> disabledComponents = null; @@ -1965,7 +1971,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile enabledCaller, enabledComponents, disabledComponents, installReason, uninstallReason, harmfulAppWarning, splashScreenTheme, firstInstallTime != 0 ? firstInstallTime : - origFirstInstallTimes.getOrDefault(name, 0L)); + origFirstInstallTimes.getOrDefault(name, 0L), + minAspectRatio); mDomainVerificationManager.setLegacyUserState(name, userId, verifState); } else if (tagName.equals("preferred-activities")) { @@ -2265,6 +2272,11 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile serializer.attribute(null, ATTR_SPLASH_SCREEN_THEME, ustate.getSplashScreenTheme()); } + if (ustate.getMinAspectRatio() + != PackageManager.USER_MIN_ASPECT_RATIO_UNSET) { + serializer.attributeInt(null, ATTR_MIN_ASPECT_RATIO, + ustate.getMinAspectRatio()); + } if (ustate.isSuspended()) { for (int i = 0; i < ustate.getSuspendParams().size(); i++) { final String suspendingPackage = ustate.getSuspendParams().keyAt(i); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 5b3514c01f9f..710e0b72ecfb 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -15,6 +15,7 @@ */ package com.android.server.pm; +import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED; import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI; import android.Manifest.permission; @@ -24,6 +25,7 @@ import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.ActivityOptions; import android.app.AppGlobals; import android.app.IUidObserver; import android.app.IUriGrantsManager; @@ -4407,8 +4409,11 @@ public class ShortcutService extends IShortcutService.Stub { return; } try { + ActivityOptions options = ActivityOptions.makeBasic() + .setPendingIntentBackgroundActivityStartMode( + MODE_BACKGROUND_ACTIVITY_START_DENIED); intentSender.sendIntent(mContext, /* code= */ 0, extras, - /* onFinished=*/ null, /* handler= */ null); + /* onFinished=*/ null, /* handler= */ null, null, options.toBundle()); } catch (SendIntentException e) { Slog.w(TAG, "sendIntent failed().", e); } diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java index 8f8f4376d2cc..6f0fe63cdaa8 100644 --- a/services/core/java/com/android/server/pm/StorageEventHelper.java +++ b/services/core/java/com/android/server/pm/StorageEventHelper.java @@ -306,6 +306,7 @@ public final class StorageEventHelper extends StorageEventListener { } mBroadcastHelper.sendResourcesChangedBroadcast(mPm::snapshotComputer, mediaStatus, replacing, packageNames, packageUids); + mPm.notifyResourcesChanged(mediaStatus, replacing, packageNames, packageUids); } /** diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java index 08934c69e099..89aff9eec4cf 100644 --- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java +++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java @@ -633,6 +633,7 @@ public final class SuspendPackageHelper { (callingUid, intentExtras) -> BroadcastHelper.filterExtrasChangedPackageList( mPm.snapshotComputer(), callingUid, intentExtras), options)); + mPm.notifyPackageMonitor(intent, null /* pkg */, extras, new int[]{userId}); } /** diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 4b8be5bce4c6..e9c6511aee9f 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1037,7 +1037,7 @@ public class UserManagerService extends IUserManager.Stub { final UserData userData = mUsers.valueAt(i); final int userId = userData.info.id; if (userId != currentUser && userData.info.isFull() && !userData.info.partial - && !mRemovingUserIds.get(userId)) { + && userData.info.isEnabled() && !mRemovingUserIds.get(userId)) { final long userEnteredTime = userData.mLastEnteredForegroundTimeMillis; if (userEnteredTime > latestEnteredTime) { latestEnteredTime = userEnteredTime; @@ -2987,14 +2987,14 @@ public class UserManagerService extends IUserManager.Stub { Preconditions.checkState(mCachedEffectiveUserRestrictions.getRestrictions(userId) != newBaseRestrictions); - if (mBaseUserRestrictions.updateRestrictions(userId, newBaseRestrictions)) { + if (mBaseUserRestrictions.updateRestrictions(userId, new Bundle(newBaseRestrictions))) { scheduleWriteUser(userId); } } final Bundle effective = computeEffectiveUserRestrictionsLR(userId); - mCachedEffectiveUserRestrictions.updateRestrictions(userId, effective); + mCachedEffectiveUserRestrictions.updateRestrictions(userId, new Bundle(effective)); // Apply the new restrictions. if (DBG) { @@ -5636,8 +5636,14 @@ public class UserManagerService extends IUserManager.Stub { } } - @GuardedBy("mUsersLock") @VisibleForTesting + void addRemovingUserId(@UserIdInt int userId) { + synchronized (mUsersLock) { + addRemovingUserIdLocked(userId); + } + } + + @GuardedBy("mUsersLock") void addRemovingUserIdLocked(@UserIdInt int userId) { // We remember deleted user IDs to prevent them from being // reused during the current boot; they can still be reused diff --git a/services/core/java/com/android/server/pm/UserTypeDetails.java b/services/core/java/com/android/server/pm/UserTypeDetails.java index 9ebb8d6ac4be..7bdcd685a2e9 100644 --- a/services/core/java/com/android/server/pm/UserTypeDetails.java +++ b/services/core/java/com/android/server/pm/UserTypeDetails.java @@ -633,4 +633,12 @@ public final class UserTypeDetails { public boolean isCommunalProfile() { return UserManager.isUserTypeCommunalProfile(mName); } + + /** + * Returns whether the user type is a private profile + * (i.e. {@link UserManager#USER_TYPE_PROFILE_PRIVATE}). + */ + public boolean isPrivateProfile() { + return UserManager.isUserTypePrivateProfile(mName); + } } diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java index 1569e06bd06d..f7967c0a60d9 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -35,6 +35,7 @@ import static android.os.UserManager.USER_TYPE_FULL_SYSTEM; import static android.os.UserManager.USER_TYPE_PROFILE_CLONE; import static android.os.UserManager.USER_TYPE_PROFILE_COMMUNAL; import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED; +import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE; import static android.os.UserManager.USER_TYPE_PROFILE_TEST; import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS; @@ -108,6 +109,7 @@ public final class UserTypeFactory { builders.put(USER_TYPE_SYSTEM_HEADLESS, getDefaultTypeSystemHeadless()); builders.put(USER_TYPE_PROFILE_CLONE, getDefaultTypeProfileClone()); builders.put(USER_TYPE_PROFILE_COMMUNAL, getDefaultTypeProfileCommunal()); + builders.put(USER_TYPE_PROFILE_PRIVATE, getDefaultTypeProfilePrivate()); if (Build.IS_DEBUGGABLE) { builders.put(USER_TYPE_PROFILE_TEST, getDefaultTypeProfileTest()); } @@ -264,6 +266,39 @@ public final class UserTypeFactory { } /** + * Returns the Builder for the default {@link UserManager#USER_TYPE_PROFILE_PRIVATE} + * configuration. + */ + private static UserTypeDetails.Builder getDefaultTypeProfilePrivate() { + return new UserTypeDetails.Builder() + .setName(USER_TYPE_PROFILE_PRIVATE) + .setBaseType(FLAG_PROFILE) + .setMaxAllowedPerParent(1) + .setLabel(0) + .setIconBadge(com.android.internal.R.drawable.ic_test_icon_badge_experiment) + .setBadgePlain(com.android.internal.R.drawable.ic_test_badge_experiment) + .setBadgeNoBackground(com.android.internal.R.drawable.ic_test_badge_no_background) + .setStatusBarIcon(com.android.internal.R.drawable.ic_test_badge_experiment) + .setBadgeLabels( + com.android.internal.R.string.managed_profile_label_badge, + com.android.internal.R.string.managed_profile_label_badge_2, + com.android.internal.R.string.managed_profile_label_badge_3) + .setBadgeColors( + com.android.internal.R.color.profile_badge_2) + .setDarkThemeBadgeColors( + com.android.internal.R.color.profile_badge_2_dark) + .setDefaultRestrictions(getDefaultProfileRestrictions()) + .setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings()) + .setDefaultUserProperties(new UserProperties.Builder() + .setStartWithParent(true) + .setCredentialShareableWithParent(false) + .setMediaSharedWithParent(false) + .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE) + .setCrossProfileIntentFilterAccessControl( + UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)); + } + + /** * Returns the Builder for the default {@link UserManager#USER_TYPE_FULL_SECONDARY} * configuration. */ diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java index 9d7354ee2f87..056aae49ae82 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java @@ -664,29 +664,39 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, @Override public PackageImpl addUsesLibrary(String libraryName) { - this.usesLibraries = CollectionUtils.add(this.usesLibraries, - TextUtils.safeIntern(libraryName)); + libraryName = TextUtils.safeIntern(libraryName); + if (!ArrayUtils.contains(this.usesLibraries, libraryName)) { + this.usesLibraries = CollectionUtils.add(this.usesLibraries, libraryName); + } return this; } @Override public final PackageImpl addUsesNativeLibrary(String libraryName) { - this.usesNativeLibraries = CollectionUtils.add(this.usesNativeLibraries, - TextUtils.safeIntern(libraryName)); + libraryName = TextUtils.safeIntern(libraryName); + if (!ArrayUtils.contains(this.usesNativeLibraries, libraryName)) { + this.usesNativeLibraries = CollectionUtils.add(this.usesNativeLibraries, libraryName); + } return this; } @Override public PackageImpl addUsesOptionalLibrary(String libraryName) { - this.usesOptionalLibraries = CollectionUtils.add(this.usesOptionalLibraries, - TextUtils.safeIntern(libraryName)); + libraryName = TextUtils.safeIntern(libraryName); + if (!ArrayUtils.contains(this.usesOptionalLibraries, libraryName)) { + this.usesOptionalLibraries = CollectionUtils.add(this.usesOptionalLibraries, + libraryName); + } return this; } @Override public final PackageImpl addUsesOptionalNativeLibrary(String libraryName) { - this.usesOptionalNativeLibraries = CollectionUtils.add(this.usesOptionalNativeLibraries, - TextUtils.safeIntern(libraryName)); + libraryName = TextUtils.safeIntern(libraryName); + if (!ArrayUtils.contains(this.usesOptionalNativeLibraries, libraryName)) { + this.usesOptionalNativeLibraries = CollectionUtils.add(this.usesOptionalNativeLibraries, + libraryName); + } return this; } diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java index 91a25db3710e..3d056e89ba78 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java +++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java @@ -377,6 +377,8 @@ public class PackageStateImpl implements PackageState { private final int mUninstallReason; @Nullable private final String mSplashScreenTheme; + @PackageManager.UserMinAspectRatio + private final int mMinAspectRatio; private final long mFirstInstallTimeMillis; private UserStateImpl(@NonNull PackageUserState userState) { @@ -392,6 +394,7 @@ public class PackageStateImpl implements PackageState { mSharedLibraryOverlayPaths = userState.getSharedLibraryOverlayPaths(); mUninstallReason = userState.getUninstallReason(); mSplashScreenTheme = userState.getSplashScreenTheme(); + mMinAspectRatio = userState.getMinAspectRatio(); setBoolean(Booleans.HIDDEN, userState.isHidden()); setBoolean(Booleans.INSTALLED, userState.isInstalled()); setBoolean(Booleans.INSTANT_APP, userState.isInstantApp()); @@ -543,6 +546,11 @@ public class PackageStateImpl implements PackageState { } @DataClass.Generated.Member + public @PackageManager.UserMinAspectRatio int getMinAspectRatio() { + return mMinAspectRatio; + } + + @DataClass.Generated.Member public long getFirstInstallTimeMillis() { return mFirstInstallTimeMillis; } @@ -554,10 +562,10 @@ public class PackageStateImpl implements PackageState { } @DataClass.Generated( - time = 1671671043891L, + time = 1687938966108L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java", - inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final long mFirstInstallTimeMillis\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)") + inputSignatures = "private int mBooleans\nprivate final long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate final long mFirstInstallTimeMillis\npublic static com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate void setBoolean(int,boolean)\nprivate boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final int HIDDEN\nprivate static final int INSTALLED\nprivate static final int INSTANT_APP\nprivate static final int NOT_LAUNCHED\nprivate static final int STOPPED\nprivate static final int SUSPENDED\nprivate static final int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserState.java b/services/core/java/com/android/server/pm/pkg/PackageUserState.java index 2048d651e11e..f75d214c5128 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserState.java @@ -217,4 +217,12 @@ public interface PackageUserState { */ @Nullable String getSplashScreenTheme(); + + /** + * @return the min aspect ratio setting of the package which by default is unset + * unless it has been set by the user + * @hide + */ + @PackageManager.UserMinAspectRatio + int getMinAspectRatio(); } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java index 73fb67298aec..1fb12a8ce218 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateDefault.java @@ -136,6 +136,11 @@ class PackageUserStateDefault implements PackageUserStateInternal { } @Override + public @PackageManager.UserMinAspectRatio int getMinAspectRatio() { + return PackageManager.USER_MIN_ASPECT_RATIO_UNSET; + } + + @Override public long getFirstInstallTimeMillis() { return 0; } diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java index e8e2d4179326..d911ac1cfcd8 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java +++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java @@ -83,6 +83,9 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt @Nullable private String mSplashScreenTheme; + @PackageManager.UserMinAspectRatio + private int mMinAspectRatio = PackageManager.USER_MIN_ASPECT_RATIO_UNSET; + /** * Suspending package to suspend params */ @@ -146,6 +149,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt mHarmfulAppWarning = other.mHarmfulAppWarning; mLastDisableAppCaller = other.mLastDisableAppCaller; mSplashScreenTheme = other.mSplashScreenTheme; + mMinAspectRatio = other.mMinAspectRatio; mSuspendParams = other.mSuspendParams == null ? null : other.mSuspendParams.snapshot(); mComponentLabelIconOverrideMap = other.mComponentLabelIconOverrideMap == null ? null : other.mComponentLabelIconOverrideMap.snapshot(); @@ -508,6 +512,19 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt } /** + * Sets user min aspect ratio override value + * @see PackageManager.UserMinAspectRatio + */ + public @NonNull PackageUserStateImpl setMinAspectRatio( + @PackageManager.UserMinAspectRatio int value) { + mMinAspectRatio = value; + com.android.internal.util.AnnotationValidations.validate( + PackageManager.UserMinAspectRatio.class, null, mMinAspectRatio); + onChanged(); + return this; + } + + /** * Suspending package to suspend params */ public @NonNull PackageUserStateImpl setSuspendParams( @@ -679,6 +696,11 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt return mSplashScreenTheme; } + @DataClass.Generated.Member + public @PackageManager.UserMinAspectRatio int getMinAspectRatio() { + return mMinAspectRatio; + } + /** * Suspending package to suspend params */ @@ -766,6 +788,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt && Objects.equals(mOverlayPaths, that.mOverlayPaths) && Objects.equals(mSharedLibraryOverlayPaths, that.mSharedLibraryOverlayPaths) && Objects.equals(mSplashScreenTheme, that.mSplashScreenTheme) + && mMinAspectRatio == that.mMinAspectRatio && Objects.equals(mSuspendParams, that.mSuspendParams) && Objects.equals(mComponentLabelIconOverrideMap, that.mComponentLabelIconOverrideMap) && mFirstInstallTimeMillis == that.mFirstInstallTimeMillis @@ -798,6 +821,7 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt _hash = 31 * _hash + Objects.hashCode(mOverlayPaths); _hash = 31 * _hash + Objects.hashCode(mSharedLibraryOverlayPaths); _hash = 31 * _hash + Objects.hashCode(mSplashScreenTheme); + _hash = 31 * _hash + mMinAspectRatio; _hash = 31 * _hash + Objects.hashCode(mSuspendParams); _hash = 31 * _hash + Objects.hashCode(mComponentLabelIconOverrideMap); _hash = 31 * _hash + Long.hashCode(mFirstInstallTimeMillis); @@ -807,10 +831,10 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt } @DataClass.Generated( - time = 1686952839807L, + time = 1687938397579L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java", - inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") + inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate long mCeDataInode\nprivate boolean mInstalled\nprivate boolean mStopped\nprivate boolean mNotLaunched\nprivate boolean mHidden\nprivate int mDistractionFlags\nprivate boolean mInstantApp\nprivate boolean mVirtualPreload\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate boolean watchableEquals(com.android.server.utils.Watchable)\nprivate int watchableHashCode()\nprivate boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java index 8125b0f662aa..8430cf7a0d11 100644 --- a/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java +++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageStateMutator.java @@ -439,6 +439,16 @@ public class PackageStateMutator { } return null; } + + @NonNull + @Override + public PackageUserStateWrite setMinAspectRatio( + @PackageManager.UserMinAspectRatio int aspectRatio) { + if (mUserState != null) { + mUserState.setMinAspectRatio(aspectRatio); + } + return this; + } } } } diff --git a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java index 11d6d97d3920..0c6c6723b79b 100644 --- a/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java +++ b/services/core/java/com/android/server/pm/pkg/mutate/PackageUserStateWrite.java @@ -22,6 +22,7 @@ import android.content.ComponentName; import android.content.pm.PackageManager; import android.content.pm.overlay.OverlayPaths; +import com.android.server.pm.pkg.PackageUserStateImpl; import com.android.server.pm.pkg.SuspendParams; public interface PackageUserStateWrite { @@ -68,4 +69,8 @@ public interface PackageUserStateWrite { @NonNull PackageUserStateWrite setComponentLabelIcon(@NonNull ComponentName componentName, @Nullable String nonLocalizedLabel, @Nullable Integer icon); + + /** @see PackageUserStateImpl#setMinAspectRatio(int) */ + @NonNull + PackageUserStateWrite setMinAspectRatio(@PackageManager.UserMinAspectRatio int aspectRatio); } diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index e2cb87e72c7a..d737b1c6bfa6 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -50,6 +50,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.Property; import android.content.pm.Signature; import android.content.pm.SigningDetails; +import android.content.pm.dex.DexMetadataHelper; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.FrameworkParsingPackageUtils; import android.content.pm.parsing.PackageLite; @@ -73,6 +74,7 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; import android.os.ext.SdkExtensions; +import android.os.incremental.IncrementalManager; import android.permission.PermissionManager; import android.text.TextUtils; import android.util.ArrayMap; @@ -240,6 +242,11 @@ public class ParsingPackageUtils { public static final int PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY = 1 << 7; public static final int PARSE_APK_IN_APEX = 1 << 9; + /** + * This flag is to determine whether to extract the baseline profiles from the apk or not. + */ + public static final int PARSE_EXTRACT_BASELINE_PROFILES_FROM_APK = 1 << 10; + public static final int PARSE_CHATTY = 1 << 31; /** The total maximum number of activities, services, providers and activity-aliases */ @@ -251,14 +258,16 @@ public class ParsingPackageUtils { private static final int MAX_PERMISSION_NAME_LENGTH = 512; @IntDef(flag = true, prefix = { "PARSE_" }, value = { + PARSE_APK_IN_APEX, PARSE_CHATTY, PARSE_COLLECT_CERTIFICATES, PARSE_ENFORCE_CODE, PARSE_EXTERNAL_STORAGE, + PARSE_EXTRACT_BASELINE_PROFILES_FROM_APK, + PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY, PARSE_IGNORE_PROCESSES, PARSE_IS_SYSTEM_DIR, PARSE_MUST_BE_APK, - PARSE_IGNORE_OVERLAY_REQUIRED_SYSTEM_PROPERTY, }) @Retention(RetentionPolicy.SOURCE) public @interface ParseFlags {} @@ -559,6 +568,26 @@ public class ParsingPackageUtils { pkg.setSigningDetails(SigningDetails.UNKNOWN); } + // 1. The apkFile is an apk file + // 2. The flags include PARSE_EXTRACT_PROFILE_FROM_APK + // 3. The apk patch is NOT an incremental path + // 4. If the .dm file exists in the current apk directory, it means the caller + // prepares the .dm file. Don't extract the profiles from the apk again. + if (ApkLiteParseUtils.isApkFile(apkFile) + && (flags & PARSE_EXTRACT_BASELINE_PROFILES_FROM_APK) != 0 + && !IncrementalManager.isIncrementalPath(apkPath) + && DexMetadataHelper.findDexMetadataForFile(apkFile) == null) { + // Extract the baseline profiles from the apk if the profiles exist in the assets + // directory in the apk. + boolean extractedResult = + DexMetadataHelper.extractBaselineProfilesToDexMetadataFileFromApk(assets, + apkPath); + + if (DEBUG_JAR) { + Slog.d(TAG, "Extract profiles " + (extractedResult ? "success" : "fail")); + } + } + return input.success(pkg); } catch (Exception e) { return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION, diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 4957eaf256a9..fc88776b2208 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -724,8 +724,11 @@ public class PhoneWindowManager implements WindowManagerPolicy { finishKeyguardDrawn(); break; case MSG_WINDOW_MANAGER_DRAWN_COMPLETE: - if (DEBUG_WAKEUP) Slog.w(TAG, "Setting mWindowManagerDrawComplete"); - finishWindowsDrawn(msg.arg1); + final int displayId = msg.arg1; + if (DEBUG_WAKEUP) Slog.w(TAG, "All windows drawn on display " + displayId); + Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, + TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, displayId /* cookie */); + finishWindowsDrawn(displayId); break; case MSG_HIDE_BOOT_MESSAGE: handleHideBootMessage(); @@ -3711,19 +3714,17 @@ public class PhoneWindowManager implements WindowManagerPolicy { } @Override - public void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition) { - if (mKeyguardDelegate != null && waitAppTransition) { + public void onKeyguardOccludedChangedLw(boolean occluded) { + if (mKeyguardDelegate != null) { mPendingKeyguardOccluded = occluded; mKeyguardOccludedChanged = true; - } else { - setKeyguardOccludedLw(occluded); } } @Override public int applyKeyguardOcclusionChange() { if (DEBUG_KEYGUARD) Slog.d(TAG, "transition/occluded commit occluded=" - + mPendingKeyguardOccluded); + + mPendingKeyguardOccluded + " changed=" + mKeyguardOccludedChanged); // TODO(b/276433230): Explicitly save before/after for occlude state in each // Transition so we don't need to update SysUI every time. @@ -5137,15 +5138,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { // ... eventually calls finishWindowsDrawn which will finalize our screen turn on // as well as enabling the orientation change logic/sensor. Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, - TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0); - mWindowManagerInternal.waitForAllWindowsDrawn(() -> { - if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for every display"); - mHandler.sendMessage(mHandler.obtainMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE, - INVALID_DISPLAY, 0)); - - Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, - TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0); - }, WAITING_FOR_DRAWN_TIMEOUT, INVALID_DISPLAY); + TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, INVALID_DISPLAY /* cookie */); + mWindowManagerInternal.waitForAllWindowsDrawn(mHandler.obtainMessage( + MSG_WINDOW_MANAGER_DRAWN_COMPLETE, INVALID_DISPLAY, 0), + WAITING_FOR_DRAWN_TIMEOUT, INVALID_DISPLAY); } // Called on the DisplayManager's DisplayPowerController thread. @@ -5225,15 +5221,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { mScreenOnListeners.put(displayId, screenOnListener); Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, - TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0); - mWindowManagerInternal.waitForAllWindowsDrawn(() -> { - if (DEBUG_WAKEUP) Slog.i(TAG, "All windows ready for display: " + displayId); - mHandler.sendMessage(mHandler.obtainMessage(MSG_WINDOW_MANAGER_DRAWN_COMPLETE, - displayId, 0)); - - Trace.asyncTraceEnd(Trace.TRACE_TAG_WINDOW_MANAGER, - TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, /* cookie= */ 0); - }, WAITING_FOR_DRAWN_TIMEOUT, displayId); + TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD, displayId /* cookie */); + mWindowManagerInternal.waitForAllWindowsDrawn(mHandler.obtainMessage( + MSG_WINDOW_MANAGER_DRAWN_COMPLETE, displayId, 0), + WAITING_FOR_DRAWN_TIMEOUT, displayId); } } diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 887f9461bdce..03a7bd3b68b3 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -169,7 +169,7 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { * * @param occluded Whether Keyguard is currently occluded or not. */ - void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition); + void onKeyguardOccludedChangedLw(boolean occluded); /** * Commit any queued changes to keyguard occlude status that had been deferred during the diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index a694e315ac29..3ecc9853be91 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -222,7 +222,7 @@ public class Notifier { mShowWirelessChargingAnimationConfig = context.getResources().getBoolean( com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim); - mWakeLockLog = new WakeLockLog(); + mWakeLockLog = new WakeLockLog(context); // Initialize interactive state for battery stats. try { diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING index cf1bfc3b555f..fbfe291b1659 100644 --- a/services/core/java/com/android/server/power/TEST_MAPPING +++ b/services/core/java/com/android/server/power/TEST_MAPPING @@ -20,6 +20,7 @@ "name": "FrameworksServicesTests", "options": [ {"include-filter": "com.android.server.power"}, + {"exclude-filter": "com.android.server.power.BatteryStatsTests"}, {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, {"exclude-annotation": "androidx.test.filters.FlakyTest"} ] @@ -38,7 +39,8 @@ { "name": "FrameworksServicesTests", "options": [ - {"include-filter": "com.android.server.power"} + {"include-filter": "com.android.server.power"}, + {"exclude-filter": "com.android.server.power.BatteryStatsTests"} ] } ] diff --git a/services/core/java/com/android/server/power/WakeLockLog.java b/services/core/java/com/android/server/power/WakeLockLog.java index d20c7f1fe9c6..d3486a4a1cf8 100644 --- a/services/core/java/com/android/server/power/WakeLockLog.java +++ b/services/core/java/com/android/server/power/WakeLockLog.java @@ -16,20 +16,26 @@ package com.android.server.power; +import android.content.Context; +import android.content.pm.PackageManager; import android.os.PowerManager; import android.text.TextUtils; import android.util.Slog; +import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.BackgroundThread; import java.io.PrintWriter; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Arrays; import java.util.ConcurrentModificationException; import java.util.Date; import java.util.Iterator; +import java.util.List; import java.util.NoSuchElementException; +import java.util.Objects; /** * Simple Log for wake lock events. Optimized to reduce memory usage. @@ -117,7 +123,7 @@ final class WakeLockLog { private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); /** - * Lock protects WakeLockLock.dump (binder thread) from conflicting with changes to the log + * Lock protects WakeLockLog.dump (binder thread) from conflicting with changes to the log * happening on the background thread. */ private final Object mLock = new Object(); @@ -126,18 +132,20 @@ final class WakeLockLog { private final TheLog mLog; private final TagDatabase mTagDatabase; private final SimpleDateFormat mDumpsysDateFormat; + private final Context mContext; - WakeLockLog() { - this(new Injector()); + WakeLockLog(Context context) { + this(new Injector(), context); } @VisibleForTesting - WakeLockLog(Injector injector) { + WakeLockLog(Injector injector, Context context) { mInjector = injector; mTagDatabase = new TagDatabase(injector); EntryByteTranslator translator = new EntryByteTranslator(mTagDatabase); mLog = new TheLog(injector, translator, mTagDatabase); mDumpsysDateFormat = injector.getDateFormat(); + mContext = context; } /** @@ -176,10 +184,24 @@ final class WakeLockLog { try { synchronized (mLock) { pw.println("Wake Lock Log"); - LogEntry tempEntry = new LogEntry(); // Temporary entry for the iterator to reuse. - final Iterator<LogEntry> iterator = mLog.getAllItems(tempEntry); int numEvents = 0; int numResets = 0; + SparseArray<String[]> uidToPackagesCache = new SparseArray(); + + for (int i = 0; i < mLog.mSavedAcquisitions.size(); i++) { + numEvents++; + LogEntry entry = mLog.mSavedAcquisitions.get(i); + + entry.updatePackageName(uidToPackagesCache, mContext.getPackageManager()); + + if (DEBUG) { + pw.print("Saved acquisition no. " + i); + } + entry.dump(pw, mDumpsysDateFormat); + } + + LogEntry tempEntry = new LogEntry(); // Temporary entry for the iterator to reuse. + final Iterator<LogEntry> iterator = mLog.getAllItems(tempEntry); while (iterator.hasNext()) { String address = null; if (DEBUG) { @@ -192,6 +214,8 @@ final class WakeLockLog { numResets++; } else { numEvents++; + entry.updatePackageName(uidToPackagesCache, + mContext.getPackageManager()); if (DEBUG) { pw.print(address); } @@ -381,6 +405,11 @@ final class WakeLockLog { */ public int flags; + /** + * The name of the package that acquired the wake lock + */ + public String packageName; + LogEntry() {} LogEntry(long time, int type, TagData tag, int flags) { @@ -438,8 +467,13 @@ final class WakeLockLog { } sb.append(dateFormat.format(new Date(time))) .append(" - ") - .append(tag == null ? "---" : tag.ownerUid) - .append(" - ") + .append(tag == null ? "---" : tag.ownerUid); + if (packageName != null) { + sb.append(" ("); + sb.append(packageName); + sb.append(")"); + } + sb.append(" - ") .append(type == TYPE_ACQUIRE ? "ACQ" : "REL") .append(" ") .append(tag == null ? "UNKNOWN" : tag.tag); @@ -463,6 +497,36 @@ final class WakeLockLog { sb.append(",system-wakelock"); } } + + /** + * Update the package name using the cache if available or the package manager. + * @param uidToPackagesCache The cache of package names + * @param packageManager The package manager + */ + public void updatePackageName(SparseArray<String[]> uidToPackagesCache, + PackageManager packageManager) { + if (tag == null) { + return; + } + + String[] packages; + if (uidToPackagesCache.contains(tag.ownerUid)) { + packages = uidToPackagesCache.get(tag.ownerUid); + } else { + packages = packageManager.getPackagesForUid(tag.ownerUid); + uidToPackagesCache.put(tag.ownerUid, packages); + } + + if (packages != null && packages.length > 0) { + packageName = packages[0]; + if (packages.length > 1) { + StringBuilder sb = new StringBuilder(); + sb.append(packageName) + .append(",..."); + packageName = sb.toString(); + } + } + } } /** @@ -744,6 +808,12 @@ final class WakeLockLog { private final TagDatabase mTagDatabase; + /** + * Wake lock acquisition events should continue to be printed until their corresponding + * release event is removed from the log. + */ + private final List<LogEntry> mSavedAcquisitions; + TheLog(Injector injector, EntryByteTranslator translator, TagDatabase tagDatabase) { final int logSize = Math.max(injector.getLogSize(), LOG_SIZE_MIN); mBuffer = new byte[logSize]; @@ -758,6 +828,8 @@ final class WakeLockLog { removeTagIndex(index); } }); + + mSavedAcquisitions = new ArrayList(); } /** @@ -976,6 +1048,19 @@ final class WakeLockLog { // Copy the contents of the start of the buffer to our temporary buffer. LogEntry entry = readEntryAt(mStart, mStartTime, null); + if (entry.type == TYPE_ACQUIRE) { + // We'll continue to print the event until the corresponding release event is also + // removed from the log. + mSavedAcquisitions.add(entry); + } else if (entry.type == TYPE_RELEASE) { + // We no longer need to print the corresponding acquire event. + for (int i = 0; i < mSavedAcquisitions.size(); i++) { + if (Objects.equals(mSavedAcquisitions.get(i).tag, entry.tag)) { + mSavedAcquisitions.remove(i); + break; + } + } + } if (DEBUG) { Slog.d(TAG, "Removing oldest item at @ " + mStart + ", found: " + entry); } diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java index 1fed7129d488..32104f68c091 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -113,6 +113,7 @@ import com.android.internal.os.BatteryStatsHistoryIterator; import com.android.internal.os.BinderCallsStats; import com.android.internal.os.BinderTransactionNameResolver; import com.android.internal.os.Clock; +import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.KernelCpuSpeedReader; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader; @@ -251,6 +252,9 @@ public class BatteryStatsImpl extends BatteryStats { private static final LongCounter[] ZERO_LONG_COUNTER_ARRAY = new LongCounter[]{ZERO_LONG_COUNTER}; + @VisibleForTesting + protected CpuScalingPolicies mCpuScalingPolicies; + private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats(); @VisibleForTesting @@ -324,6 +328,7 @@ public class BatteryStatsImpl extends BatteryStats { * ModemActivityInfo must be available. */ public static final int PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX = 2; + @IntDef(flag = true, prefix = "PER_UID_MODEM_MODEL_", value = { PER_UID_MODEM_POWER_MODEL_MOBILE_RADIO_ACTIVE_TIME, PER_UID_MODEM_POWER_MODEL_MODEM_ACTIVITY_INFO_RX_TX, @@ -578,9 +583,7 @@ public class BatteryStatsImpl extends BatteryStats { @SuppressWarnings("GuardedBy") // errorprone false positive on getProcStateTimeCounter @VisibleForTesting public void updateProcStateCpuTimesLocked(int uid, long elapsedRealtimeMs, long uptimeMs) { - if (!initKernelSingleUidTimeReaderLocked()) { - return; - } + ensureKernelSingleUidTimeReaderLocked(); final Uid u = getUidStatsLocked(uid); @@ -657,9 +660,7 @@ public class BatteryStatsImpl extends BatteryStats { return; } - if(!initKernelSingleUidTimeReaderLocked()) { - return; - } + ensureKernelSingleUidTimeReaderLocked(); // TODO(b/197162116): just get a list of UIDs final SparseArray<long[]> allUidCpuFreqTimesMs = @@ -719,24 +720,15 @@ public class BatteryStatsImpl extends BatteryStats { } @GuardedBy("this") - private boolean initKernelSingleUidTimeReaderLocked() { - if (mKernelSingleUidTimeReader == null) { - if (mPowerProfile == null) { - return false; - } - if (mCpuFreqs == null) { - mCpuFreqs = mCpuUidFreqTimeReader.readFreqs(mPowerProfile); - } - if (mCpuFreqs != null) { - mKernelSingleUidTimeReader = new KernelSingleUidTimeReader(mCpuFreqs.length); - } else { - mPerProcStateCpuTimesAvailable = mCpuUidFreqTimeReader.allUidTimesAvailable(); - return false; - } + private void ensureKernelSingleUidTimeReaderLocked() { + if (mKernelSingleUidTimeReader != null) { + return; } - mPerProcStateCpuTimesAvailable = mCpuUidFreqTimeReader.allUidTimesAvailable() + + mKernelSingleUidTimeReader = new KernelSingleUidTimeReader( + mCpuScalingPolicies.getScalingStepCount()); + mPerProcStateCpuTimesAvailable = mCpuUidFreqTimeReader.perClusterTimesAvailable() && mKernelSingleUidTimeReader.singleUidCpuTimesAvailable(); - return true; } public interface ExternalStatsSync { @@ -1544,8 +1536,6 @@ public class BatteryStatsImpl extends BatteryStats { private long mBatteryTimeToFullSeconds = -1; - private boolean mCpuFreqsInitialized; - private long[] mCpuFreqs; private LongArrayMultiStateCounter.LongArrayContainer mTmpCpuTimeInFreq; /** @@ -8224,11 +8214,13 @@ public class BatteryStatsImpl extends BatteryStats { mProcStateTimeMs = new TimeInFreqMultiStateCounter(mBsi.mOnBatteryTimeBase, - PROC_STATE_TIME_COUNTER_STATE_COUNT, mBsi.getCpuFreqCount(), + PROC_STATE_TIME_COUNTER_STATE_COUNT, + mBsi.mCpuScalingPolicies.getScalingStepCount(), timestampMs); mProcStateScreenOffTimeMs = new TimeInFreqMultiStateCounter(mBsi.mOnBatteryScreenOffTimeBase, - PROC_STATE_TIME_COUNTER_STATE_COUNT, mBsi.getCpuFreqCount(), + PROC_STATE_TIME_COUNTER_STATE_COUNT, + mBsi.mCpuScalingPolicies.getScalingStepCount(), timestampMs); } @@ -9229,6 +9221,7 @@ public class BatteryStatsImpl extends BatteryStats { } @Override + @Deprecated public long getTimeAtCpuSpeed(int cluster, int step, int which) { if (mCpuClusterSpeedTimesUs != null) { if (cluster >= 0 && cluster < mCpuClusterSpeedTimesUs.length) { @@ -10460,7 +10453,7 @@ public class BatteryStatsImpl extends BatteryStats { cpuActiveCounter.setState(0, timestampMs); if (mBsi.trackPerProcStateCpuTimes()) { - final int cpuFreqCount = mBsi.getCpuFreqCount(); + final int cpuFreqCount = mBsi.mCpuScalingPolicies.getScalingStepCount(); cpuTimeInFreqCounter = new LongArrayMultiStateCounter(1, cpuFreqCount); @@ -10863,44 +10856,43 @@ public class BatteryStatsImpl extends BatteryStats { } @GuardedBy("this") - @Override - public long[] getCpuFreqs() { - if (!mCpuFreqsInitialized) { - mCpuFreqs = mCpuUidFreqTimeReader.readFreqs(mPowerProfile); - mCpuFreqsInitialized = true; - } - return mCpuFreqs; - } - - @GuardedBy("this") - @Override - public int getCpuFreqCount() { - final long[] cpuFreqs = getCpuFreqs(); - return cpuFreqs != null ? cpuFreqs.length : 0; + public CpuScalingPolicies getCpuScalingPolicies() { + return mCpuScalingPolicies; } @GuardedBy("this") private LongArrayMultiStateCounter.LongArrayContainer getCpuTimeInFreqContainer() { if (mTmpCpuTimeInFreq == null) { mTmpCpuTimeInFreq = - new LongArrayMultiStateCounter.LongArrayContainer(getCpuFreqCount()); + new LongArrayMultiStateCounter.LongArrayContainer( + mCpuScalingPolicies.getScalingStepCount()); } return mTmpCpuTimeInFreq; } - public BatteryStatsImpl(File systemDir, Handler handler, PlatformIdleStateCallback cb, - EnergyStatsRetriever energyStatsCb, UserInfoProvider userInfoProvider) { - this(Clock.SYSTEM_CLOCK, systemDir, handler, cb, energyStatsCb, userInfoProvider); + public BatteryStatsImpl(@Nullable File systemDir, @NonNull Handler handler, + @Nullable PlatformIdleStateCallback cb, @Nullable EnergyStatsRetriever energyStatsCb, + @NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile, + @NonNull CpuScalingPolicies cpuScalingPolicies) { + this(Clock.SYSTEM_CLOCK, systemDir, handler, cb, energyStatsCb, userInfoProvider, + powerProfile, cpuScalingPolicies); } - private BatteryStatsImpl(Clock clock, File systemDir, Handler handler, - PlatformIdleStateCallback cb, EnergyStatsRetriever energyStatsCb, - UserInfoProvider userInfoProvider) { + private BatteryStatsImpl(@NonNull Clock clock, @Nullable File systemDir, + @NonNull Handler handler, @Nullable PlatformIdleStateCallback cb, + @Nullable EnergyStatsRetriever energyStatsCb, + @NonNull UserInfoProvider userInfoProvider, @NonNull PowerProfile powerProfile, + @NonNull CpuScalingPolicies cpuScalingPolicies) { init(clock); mHandler = new MyHandler(handler.getLooper()); mConstants = new Constants(mHandler); + mPowerProfile = powerProfile; + mCpuScalingPolicies = cpuScalingPolicies; + + initPowerProfile(); + if (systemDir == null) { mStatsFile = null; mCheckinFile = null; @@ -11016,39 +11008,27 @@ public class BatteryStatsImpl extends BatteryStats { mBatteryLevel = 0; } - /** - * Injects a power profile. - */ - @GuardedBy("this") - public void setPowerProfileLocked(PowerProfile profile) { - mPowerProfile = profile; - - int totalSpeedStepCount = 0; - - // We need to initialize the KernelCpuSpeedReaders to read from - // the first cpu of each core. Once we have the PowerProfile, we have access to this - // information. - final int numClusters = mPowerProfile.getNumCpuClusters(); - mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters]; - int firstCpuOfCluster = 0; - for (int i = 0; i < numClusters; i++) { - final int numSpeedSteps = mPowerProfile.getNumSpeedStepsInCpuCluster(i); - mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster, - numSpeedSteps); - firstCpuOfCluster += mPowerProfile.getNumCoresInCpuCluster(i); - totalSpeedStepCount += numSpeedSteps; + private void initPowerProfile() { + int[] policies = mCpuScalingPolicies.getPolicies(); + mKernelCpuSpeedReaders = new KernelCpuSpeedReader[policies.length]; + for (int i = 0; i < policies.length; i++) { + int[] cpus = mCpuScalingPolicies.getRelatedCpus(policies[i]); + int[] freqs = mCpuScalingPolicies.getFrequencies(policies[i]); + // We need to initialize the KernelCpuSpeedReaders to read from + // the first cpu of each core. Once we have the CpuScalingPolicy, we have access to this + // information. + mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(cpus[0], freqs.length); } // Initialize CPU power bracket map, which combines CPU states (cluster/freq pairs) // into a small number of brackets - mCpuPowerBracketMap = new int[totalSpeedStepCount]; + mCpuPowerBracketMap = new int[mCpuScalingPolicies.getScalingStepCount()]; int index = 0; - int numCpuClusters = mPowerProfile.getNumCpuClusters(); - for (int cluster = 0; cluster < numCpuClusters; cluster++) { - int steps = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster); + for (int policy : policies) { + int steps = mCpuScalingPolicies.getFrequencies(policy).length; for (int step = 0; step < steps; step++) { mCpuPowerBracketMap[index++] = - mPowerProfile.getPowerBracketForCpuCore(cluster, step); + mPowerProfile.getCpuPowerBracketForScalingStep(policy, step); } } @@ -11057,7 +11037,7 @@ public class BatteryStatsImpl extends BatteryStats { mCpuUsageDetails.cpuUsageMs = new long[cpuPowerBracketCount]; for (int i = 0; i < cpuPowerBracketCount; i++) { mCpuUsageDetails.cpuBracketDescriptions[i] = - mPowerProfile.getCpuPowerBracketDescription(i); + mPowerProfile.getCpuPowerBracketDescription(mCpuScalingPolicies, i); } if (mEstimatedBatteryCapacityMah == -1) { @@ -12179,7 +12159,8 @@ public class BatteryStatsImpl extends BatteryStats { if (DEBUG_ENERGY) { Slog.d(TAG, "Updating mobile radio stats with " + activityInfo); } - ModemActivityInfo deltaInfo = mLastModemActivityInfo == null ? activityInfo + ModemActivityInfo deltaInfo = mLastModemActivityInfo == null + ? (activityInfo == null ? null : activityInfo.getDelta(activityInfo)) : mLastModemActivityInfo.getDelta(activityInfo); mLastModemActivityInfo = activityInfo; @@ -13573,18 +13554,10 @@ public class BatteryStatsImpl extends BatteryStats { @GuardedBy("this") public void updateCpuTimeLocked(boolean onBattery, boolean onBatteryScreenOff, long[] cpuClusterChargeUC) { - if (mPowerProfile == null) { - return; - } - if (DEBUG_ENERGY_CPU) { Slog.d(TAG, "!Cpu updating!"); } - if (mCpuFreqs == null) { - mCpuFreqs = mCpuUidFreqTimeReader.readFreqs(mPowerProfile); - } - // Calculate the wakelocks we have to distribute amongst. The system is excluded as it is // usually holding the wakelock on behalf of an app. // And Only distribute cpu power to wakelocks if the screen is off and we're on battery. @@ -13616,31 +13589,37 @@ public class BatteryStatsImpl extends BatteryStats { mCpuUidClusterTimeReader.readDelta(false, null); mNumAllUidCpuTimeReads += 2; } - for (int cluster = mKernelCpuSpeedReaders.length - 1; cluster >= 0; --cluster) { - mKernelCpuSpeedReaders[cluster].readDelta(); + for (int i = mKernelCpuSpeedReaders.length - 1; i >= 0; --i) { + if (mKernelCpuSpeedReaders[i] != null) { + mKernelCpuSpeedReaders[i].readDelta(); + } } mSystemServerCpuThreadReader.readDelta(); return; } mUserInfoProvider.refreshUserIds(); - final SparseLongArray updatedUids = mCpuUidFreqTimeReader.perClusterTimesAvailable() + final SparseLongArray updatedUids = mCpuUidFreqTimeReader.allUidTimesAvailable() ? null : new SparseLongArray(); final CpuDeltaPowerAccumulator powerAccumulator; if (mGlobalEnergyConsumerStats != null && mGlobalEnergyConsumerStats.isStandardBucketSupported( - EnergyConsumerStats.POWER_BUCKET_CPU) && mCpuPowerCalculator != null) { + EnergyConsumerStats.POWER_BUCKET_CPU)) { if (cpuClusterChargeUC == null) { Slog.wtf(TAG, "POWER_BUCKET_CPU supported but no EnergyConsumer Cpu Cluster charge " + "reported on updateCpuTimeLocked!"); powerAccumulator = null; } else { + if (mCpuPowerCalculator == null) { + mCpuPowerCalculator = new CpuPowerCalculator(mCpuScalingPolicies, + mPowerProfile); + } // Cpu EnergyConsumer is supported, create an object to accumulate the estimated // charge consumption since the last cpu update - final int numClusters = mPowerProfile.getNumCpuClusters(); - powerAccumulator = new CpuDeltaPowerAccumulator(mCpuPowerCalculator, numClusters); + powerAccumulator = new CpuDeltaPowerAccumulator(mCpuPowerCalculator, + mCpuScalingPolicies.getPolicies().length); } } else { powerAccumulator = null; @@ -13698,15 +13677,14 @@ public class BatteryStatsImpl extends BatteryStats { if (DEBUG_BINDER_STATS) { Slog.d(TAG, "System server threads per CPU cluster (incoming binder threads)"); long binderThreadTimeMs = 0; - int cpuIndex = 0; final long[] binderThreadCpuTimesUs = mBinderThreadCpuTimesUs.getCountsLocked( BatteryStats.STATS_SINCE_CHARGED); int index = 0; - int numCpuClusters = mPowerProfile.getNumCpuClusters(); - for (int cluster = 0; cluster < numCpuClusters; cluster++) { + int[] policies = mCpuScalingPolicies.getPolicies(); + for (int policy : policies) { StringBuilder sb = new StringBuilder(); - sb.append("cpu").append(cpuIndex).append(": ["); - int numSpeeds = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster); + sb.append("policy").append(policy).append(": ["); + int numSpeeds = mCpuScalingPolicies.getFrequencies(policy).length; for (int speed = 0; speed < numSpeeds; speed++) { if (speed != 0) { sb.append(", "); @@ -13717,7 +13695,6 @@ public class BatteryStatsImpl extends BatteryStats { binderThreadTimeMs += binderCountMs; index++; } - cpuIndex += mPowerProfile.getNumCoresInCpuCluster(cluster); Slog.d(TAG, sb.toString()); } } @@ -13768,10 +13745,12 @@ public class BatteryStatsImpl extends BatteryStats { // Read the time spent for each cluster at various cpu frequencies. final long[][] clusterSpeedTimesMs = new long[mKernelCpuSpeedReaders.length][]; for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) { - clusterSpeedTimesMs[cluster] = mKernelCpuSpeedReaders[cluster].readDelta(); - if (clusterSpeedTimesMs[cluster] != null) { - for (int speed = clusterSpeedTimesMs[cluster].length - 1; speed >= 0; --speed) { - totalCpuClustersTimeMs += clusterSpeedTimesMs[cluster][speed]; + if (mKernelCpuSpeedReaders[cluster] != null) { + clusterSpeedTimesMs[cluster] = mKernelCpuSpeedReaders[cluster].readDelta(); + if (clusterSpeedTimesMs[cluster] != null) { + for (int speed = clusterSpeedTimesMs[cluster].length - 1; speed >= 0; --speed) { + totalCpuClustersTimeMs += clusterSpeedTimesMs[cluster][speed]; + } } } } @@ -13786,13 +13765,13 @@ public class BatteryStatsImpl extends BatteryStats { final Uid u = getUidStatsLocked(updatedUids.keyAt(i), elapsedRealtimeMs, uptimeMs); final long appCpuTimeUs = updatedUids.valueAt(i); // Add the cpu speeds to this UID. - final int numClusters = mPowerProfile.getNumCpuClusters(); + int[] policies = mCpuScalingPolicies.getPolicies(); if (u.mCpuClusterSpeedTimesUs == null || - u.mCpuClusterSpeedTimesUs.length != numClusters) { - u.mCpuClusterSpeedTimesUs = new LongSamplingCounter[numClusters][]; + u.mCpuClusterSpeedTimesUs.length != policies.length) { + u.mCpuClusterSpeedTimesUs = new LongSamplingCounter[policies.length][]; } - for (int cluster = 0; cluster < clusterSpeedTimesMs.length; cluster++) { + for (int cluster = 0; cluster < policies.length; cluster++) { final int speedsInCluster = clusterSpeedTimesMs[cluster].length; if (u.mCpuClusterSpeedTimesUs[cluster] == null || speedsInCluster != u.mCpuClusterSpeedTimesUs[cluster].length) { @@ -13948,7 +13927,8 @@ public class BatteryStatsImpl extends BatteryStats { final boolean perClusterTimesAvailable = mCpuUidFreqTimeReader.perClusterTimesAvailable(); final int numWakelocks = partialTimers == null ? 0 : partialTimers.size(); - final int numClusters = mPowerProfile.getNumCpuClusters(); + final int[] policies = mCpuScalingPolicies.getPolicies(); + final int numClusters = policies.length; mWakeLockAllocationsUs = null; final long startTimeMs = mClock.uptimeMillis(); final long elapsedRealtimeMs = mClock.elapsedRealtime(); @@ -13989,19 +13969,18 @@ public class BatteryStatsImpl extends BatteryStats { } int freqIndex = 0; - for (int cluster = 0; cluster < numClusters; ++cluster) { - final int speedsInCluster = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster); + for (int cluster = 0; cluster < numClusters; cluster++) { + final int[] freqs = mCpuScalingPolicies.getFrequencies(policies[cluster]); if (u.mCpuClusterSpeedTimesUs[cluster] == null || - u.mCpuClusterSpeedTimesUs[cluster].length != speedsInCluster) { + u.mCpuClusterSpeedTimesUs[cluster].length != freqs.length) { detachIfNotNull(u.mCpuClusterSpeedTimesUs[cluster]); - u.mCpuClusterSpeedTimesUs[cluster] - = new LongSamplingCounter[speedsInCluster]; + u.mCpuClusterSpeedTimesUs[cluster] = new LongSamplingCounter[freqs.length]; } if (numWakelocks > 0 && mWakeLockAllocationsUs[cluster] == null) { - mWakeLockAllocationsUs[cluster] = new long[speedsInCluster]; + mWakeLockAllocationsUs[cluster] = new long[freqs.length]; } final LongSamplingCounter[] cpuTimesUs = u.mCpuClusterSpeedTimesUs[cluster]; - for (int speed = 0; speed < speedsInCluster; ++speed) { + for (int speed = 0; speed < freqs.length; ++speed) { if (cpuTimesUs[speed] == null) { cpuTimesUs[speed] = new LongSamplingCounter(mOnBatteryTimeBase); } @@ -14041,7 +14020,8 @@ public class BatteryStatsImpl extends BatteryStats { } for (int cluster = 0; cluster < numClusters; ++cluster) { - final int speedsInCluster = mPowerProfile.getNumSpeedStepsInCpuCluster(cluster); + final int speedsInCluster = + mCpuScalingPolicies.getFrequencies(policies[cluster]).length; if (u.mCpuClusterSpeedTimesUs[cluster] == null || u.mCpuClusterSpeedTimesUs[cluster].length != speedsInCluster) { detachIfNotNull(u.mCpuClusterSpeedTimesUs[cluster]); @@ -14171,6 +14151,9 @@ public class BatteryStatsImpl extends BatteryStats { * Notifies BatteryStatsImpl that the system server is ready. */ public void onSystemReady() { + if (mCpuUidFreqTimeReader != null) { + mCpuUidFreqTimeReader.onSystemReady(); + } mSystemReady = true; } @@ -14632,17 +14615,13 @@ public class BatteryStatsImpl extends BatteryStats { // Inform StatsLog of setBatteryState changes. private void reportChangesToStatsLog(final int status, final int plugType, final int level) { - if (!mHaveBatteryLevel) { - return; - } - - if (mBatteryStatus != status) { + if (!mHaveBatteryLevel || mBatteryStatus != status) { FrameworkStatsLog.write(FrameworkStatsLog.CHARGING_STATE_CHANGED, status); } - if (mBatteryPlugType != plugType) { + if (!mHaveBatteryLevel || mBatteryPlugType != plugType) { FrameworkStatsLog.write(FrameworkStatsLog.PLUGGED_STATE_CHANGED, plugType); } - if (mBatteryLevel != level) { + if (!mHaveBatteryLevel || mBatteryLevel != level) { FrameworkStatsLog.write(FrameworkStatsLog.BATTERY_LEVEL_CHANGED, level); } } @@ -15245,9 +15224,6 @@ public class BatteryStatsImpl extends BatteryStats { if (supportedStandardBuckets[EnergyConsumerStats.POWER_BUCKET_BLUETOOTH]) { mBluetoothPowerCalculator = new BluetoothPowerCalculator(mPowerProfile); } - if (supportedStandardBuckets[EnergyConsumerStats.POWER_BUCKET_CPU]) { - mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile); - } if (supportedStandardBuckets[EnergyConsumerStats.POWER_BUCKET_MOBILE_RADIO]) { mMobileRadioPowerCalculator = new MobileRadioPowerCalculator(mPowerProfile); } @@ -15635,7 +15611,7 @@ public class BatteryStatsImpl extends BatteryStats { pw.print(" "); pw.print(bracket); pw.print(": "); - pw.println(mPowerProfile.getCpuPowerBracketDescription(bracket)); + pw.println(mPowerProfile.getCpuPowerBracketDescription(mCpuScalingPolicies, bracket)); } } @@ -16126,7 +16102,9 @@ public class BatteryStatsImpl extends BatteryStats { if (in.readInt() != 0) { final int numClusters = in.readInt(); - if (mPowerProfile != null && mPowerProfile.getNumCpuClusters() != numClusters) { + int[] policies = + mCpuScalingPolicies != null ? mCpuScalingPolicies.getPolicies() : null; + if (policies != null && policies.length != numClusters) { throw new ParcelFormatException("Incompatible cpu cluster arrangement"); } detachIfNotNull(u.mCpuClusterSpeedTimesUs); @@ -16134,8 +16112,9 @@ public class BatteryStatsImpl extends BatteryStats { for (int cluster = 0; cluster < numClusters; cluster++) { if (in.readInt() != 0) { final int NSB = in.readInt(); - if (mPowerProfile != null && - mPowerProfile.getNumSpeedStepsInCpuCluster(cluster) != NSB) { + if (policies != null + && mCpuScalingPolicies.getFrequencies(policies[cluster]).length + != NSB) { throw new ParcelFormatException("File corrupt: too many speed bins " + NSB); } @@ -16180,7 +16159,7 @@ public class BatteryStatsImpl extends BatteryStats { detachIfNotNull(u.mProcStateTimeMs); u.mProcStateTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in, mOnBatteryTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT, - getCpuFreqCount(), mClock.elapsedRealtime()); + mCpuScalingPolicies.getScalingStepCount(), mClock.elapsedRealtime()); } detachIfNotNull(u.mProcStateScreenOffTimeMs); @@ -16191,7 +16170,7 @@ public class BatteryStatsImpl extends BatteryStats { detachIfNotNull(u.mProcStateScreenOffTimeMs); u.mProcStateScreenOffTimeMs = TimeInFreqMultiStateCounter.readFromParcel(in, mOnBatteryScreenOffTimeBase, PROC_STATE_TIME_COUNTER_STATE_COUNT, - getCpuFreqCount(), mClock.elapsedRealtime()); + mCpuScalingPolicies.getScalingStepCount(), mClock.elapsedRealtime()); } if (in.readInt() != 0) { diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java index ebd4aec3aef9..3a3273328a7a 100644 --- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java +++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java @@ -31,6 +31,7 @@ import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.PowerProfile; import java.util.ArrayList; @@ -47,6 +48,7 @@ public class BatteryUsageStatsProvider { private final BatteryStats mStats; private final BatteryUsageStatsStore mBatteryUsageStatsStore; private final PowerProfile mPowerProfile; + private final CpuScalingPolicies mCpuScalingPolicies; private final Object mLock = new Object(); private List<PowerCalculator> mPowerCalculators; @@ -63,6 +65,7 @@ public class BatteryUsageStatsProvider { mPowerProfile = stats instanceof BatteryStatsImpl ? ((BatteryStatsImpl) stats).getPowerProfile() : new PowerProfile(context); + mCpuScalingPolicies = stats.getCpuScalingPolicies(); } private List<PowerCalculator> getPowerCalculators() { @@ -72,7 +75,7 @@ public class BatteryUsageStatsProvider { // Power calculators are applied in the order of registration mPowerCalculators.add(new BatteryChargeCalculator()); - mPowerCalculators.add(new CpuPowerCalculator(mPowerProfile)); + mPowerCalculators.add(new CpuPowerCalculator(mCpuScalingPolicies, mPowerProfile)); mPowerCalculators.add(new MemoryPowerCalculator(mPowerProfile)); mPowerCalculators.add(new WakelockPowerCalculator(mPowerProfile)); if (!BatteryStats.checkWifiOnly(mContext)) { @@ -97,7 +100,8 @@ public class BatteryUsageStatsProvider { // It is important that SystemServicePowerCalculator be applied last, // because it re-attributes some of the power estimated by the other // calculators. - mPowerCalculators.add(new SystemServicePowerCalculator(mPowerProfile)); + mPowerCalculators.add( + new SystemServicePowerCalculator(mCpuScalingPolicies, mPowerProfile)); } } return mPowerCalculators; diff --git a/services/core/java/com/android/server/power/stats/CpuPowerCalculator.java b/services/core/java/com/android/server/power/stats/CpuPowerCalculator.java index 5074838c037f..ce8680f6cefd 100644 --- a/services/core/java/com/android/server/power/stats/CpuPowerCalculator.java +++ b/services/core/java/com/android/server/power/stats/CpuPowerCalculator.java @@ -24,6 +24,7 @@ import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; +import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.PowerProfile; import java.util.Arrays; @@ -32,6 +33,8 @@ public class CpuPowerCalculator extends PowerCalculator { private static final String TAG = "CpuPowerCalculator"; private static final boolean DEBUG = PowerCalculator.DEBUG; private static final BatteryConsumer.Key[] UNINITIALIZED_KEYS = new BatteryConsumer.Key[0]; + + private final CpuScalingPolicies mCpuScalingPolicies; private final int mNumCpuClusters; // Time-in-state based CPU power estimation model computes the estimated power @@ -59,34 +62,33 @@ public class CpuPowerCalculator extends PowerCalculator { public long[] cpuFreqTimes; } - public CpuPowerCalculator(PowerProfile profile) { - mNumCpuClusters = profile.getNumCpuClusters(); + public CpuPowerCalculator(CpuScalingPolicies cpuScalingPolicies, PowerProfile profile) { + mCpuScalingPolicies = cpuScalingPolicies; + int[] policies = mCpuScalingPolicies.getPolicies(); + mNumCpuClusters = policies.length; mCpuActivePowerEstimator = new UsageBasedPowerEstimator( profile.getAveragePower(PowerProfile.POWER_CPU_ACTIVE)); - mPerClusterPowerEstimators = new UsageBasedPowerEstimator[mNumCpuClusters]; - for (int cluster = 0; cluster < mNumCpuClusters; cluster++) { - mPerClusterPowerEstimators[cluster] = new UsageBasedPowerEstimator( - profile.getAveragePowerForCpuCluster(cluster)); - } - - int freqCount = 0; - for (int cluster = 0; cluster < mNumCpuClusters; cluster++) { - freqCount += profile.getNumSpeedStepsInCpuCluster(cluster); + mPerClusterPowerEstimators = new UsageBasedPowerEstimator[policies.length]; + for (int i = 0; i < policies.length; i++) { + mPerClusterPowerEstimators[i] = new UsageBasedPowerEstimator( + profile.getAveragePowerForCpuScalingPolicy(policies[i])); } + mPerCpuFreqPowerEstimators = + new UsageBasedPowerEstimator[cpuScalingPolicies.getScalingStepCount()]; mPerCpuFreqPowerEstimatorsByCluster = new UsageBasedPowerEstimator[mNumCpuClusters][]; - mPerCpuFreqPowerEstimators = new UsageBasedPowerEstimator[freqCount]; int index = 0; - for (int cluster = 0; cluster < mNumCpuClusters; cluster++) { - final int speedsForCluster = profile.getNumSpeedStepsInCpuCluster(cluster); + for (int cluster = 0; cluster < policies.length; cluster++) { + int policy = policies[cluster]; + int[] freqs = cpuScalingPolicies.getFrequencies(policy); mPerCpuFreqPowerEstimatorsByCluster[cluster] = - new UsageBasedPowerEstimator[speedsForCluster]; - for (int speed = 0; speed < speedsForCluster; speed++) { + new UsageBasedPowerEstimator[freqs.length]; + for (int step = 0; step < freqs.length; step++) { final UsageBasedPowerEstimator estimator = new UsageBasedPowerEstimator( - profile.getAveragePowerForCpuCore(cluster, speed)); - mPerCpuFreqPowerEstimatorsByCluster[cluster][speed] = estimator; + profile.getAveragePowerForCpuScalingStep(policy, step)); + mPerCpuFreqPowerEstimatorsByCluster[cluster][step] = estimator; mPerCpuFreqPowerEstimators[index++] = estimator; } } @@ -105,7 +107,7 @@ public class CpuPowerCalculator extends PowerCalculator { BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS; Result result = new Result(); if (query.isProcessStateDataNeeded()) { - result.cpuFreqTimes = new long[batteryStats.getCpuFreqCount()]; + result.cpuFreqTimes = new long[mCpuScalingPolicies.getScalingStepCount()]; } final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders = builder.getUidBatteryConsumerBuilders(); @@ -333,7 +335,7 @@ public class CpuPowerCalculator extends PowerCalculator { * @param durationsMs duration of CPU usage. * @return a double in milliamp-hours of estimated active CPU power consumption. */ - public double calculateActiveCpuPowerMah(long durationsMs) { + private double calculateActiveCpuPowerMah(long durationsMs) { return mCpuActivePowerEstimator.calculatePower(durationsMs); } diff --git a/services/core/java/com/android/server/power/stats/SystemServicePowerCalculator.java b/services/core/java/com/android/server/power/stats/SystemServicePowerCalculator.java index aa17d4fb69bf..1d5d93eb411a 100644 --- a/services/core/java/com/android/server/power/stats/SystemServicePowerCalculator.java +++ b/services/core/java/com/android/server/power/stats/SystemServicePowerCalculator.java @@ -25,6 +25,7 @@ import android.os.UidBatteryConsumer; import android.util.Log; import android.util.SparseArray; +import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.PowerProfile; /** @@ -39,23 +40,19 @@ public class SystemServicePowerCalculator extends PowerCalculator { // to this layout: // {cluster1-speed1, cluster1-speed2, ..., cluster2-speed1, cluster2-speed2, ...} private final UsageBasedPowerEstimator[] mPowerEstimators; - private final com.android.server.power.stats.CpuPowerCalculator mCpuPowerCalculator; - - public SystemServicePowerCalculator(PowerProfile powerProfile) { - mCpuPowerCalculator = new CpuPowerCalculator(powerProfile); - int numFreqs = 0; - final int numCpuClusters = powerProfile.getNumCpuClusters(); - for (int cluster = 0; cluster < numCpuClusters; cluster++) { - numFreqs += powerProfile.getNumSpeedStepsInCpuCluster(cluster); - } + private final CpuPowerCalculator mCpuPowerCalculator; - mPowerEstimators = new UsageBasedPowerEstimator[numFreqs]; + public SystemServicePowerCalculator(CpuScalingPolicies cpuScalingPolicies, + PowerProfile powerProfile) { + mCpuPowerCalculator = new CpuPowerCalculator(cpuScalingPolicies, powerProfile); + mPowerEstimators = new UsageBasedPowerEstimator[cpuScalingPolicies.getScalingStepCount()]; int index = 0; - for (int cluster = 0; cluster < numCpuClusters; cluster++) { - final int numSpeeds = powerProfile.getNumSpeedStepsInCpuCluster(cluster); + int[] policies = cpuScalingPolicies.getPolicies(); + for (int policy : policies) { + final int numSpeeds = cpuScalingPolicies.getFrequencies(policy).length; for (int speed = 0; speed < numSpeeds; speed++) { mPowerEstimators[index++] = new UsageBasedPowerEstimator( - powerProfile.getAveragePowerForCpuCore(cluster, speed)); + powerProfile.getAveragePowerForCpuScalingStep(policy, speed)); } } } diff --git a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java index 712a6964e82d..f047f564538d 100644 --- a/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java +++ b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java @@ -32,12 +32,12 @@ import android.os.Trace; import android.os.UserHandle; import android.provider.DeviceConfig; import android.util.IndentingPrintWriter; +import android.util.LongSparseArray; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.SparseLongArray; -import android.util.TimeSparseArray; import android.util.TimeUtils; import com.android.internal.annotations.VisibleForTesting; @@ -74,12 +74,12 @@ public class CpuWakeupStats { private final WakingActivityHistory mRecentWakingActivity; @VisibleForTesting - final TimeSparseArray<Wakeup> mWakeupEvents = new TimeSparseArray<>(); + final LongSparseArray<Wakeup> mWakeupEvents = new LongSparseArray<>(); /* Maps timestamp -> {subsystem -> {uid -> procState}} */ @VisibleForTesting - final TimeSparseArray<SparseArray<SparseIntArray>> mWakeupAttribution = - new TimeSparseArray<>(); + final LongSparseArray<SparseArray<SparseIntArray>> mWakeupAttribution = + new LongSparseArray<>(); final SparseIntArray mUidProcStates = new SparseIntArray(); private final SparseIntArray mReusableUidProcStates = new SparseIntArray(4); @@ -218,11 +218,11 @@ public class CpuWakeupStats { // the last wakeup and its attribution (if computed) is always stored, even if that wakeup // had occurred before retentionDuration. final long retentionDuration = mConfig.WAKEUP_STATS_RETENTION_MS; - int lastIdx = mWakeupEvents.closestIndexOnOrBefore(elapsedRealtime - retentionDuration); + int lastIdx = mWakeupEvents.lastIndexOnOrBefore(elapsedRealtime - retentionDuration); for (int i = lastIdx; i >= 0; i--) { mWakeupEvents.removeAt(i); } - lastIdx = mWakeupAttribution.closestIndexOnOrBefore(elapsedRealtime - retentionDuration); + lastIdx = mWakeupAttribution.lastIndexOnOrBefore(elapsedRealtime - retentionDuration); for (int i = lastIdx; i >= 0; i--) { mWakeupAttribution.removeAt(i); } @@ -273,9 +273,9 @@ public class CpuWakeupStats { SparseIntArray uidProcStates) { final long matchingWindowMillis = mConfig.WAKEUP_MATCHING_WINDOW_MS; - final int startIdx = mWakeupEvents.closestIndexOnOrAfter( + final int startIdx = mWakeupEvents.firstIndexOnOrAfter( activityElapsed - matchingWindowMillis); - final int endIdx = mWakeupEvents.closestIndexOnOrBefore( + final int endIdx = mWakeupEvents.lastIndexOnOrBefore( activityElapsed + matchingWindowMillis); for (int wakeupIdx = startIdx; wakeupIdx <= endIdx; wakeupIdx++) { @@ -404,7 +404,7 @@ public class CpuWakeupStats { static final class WakingActivityHistory { private LongSupplier mRetentionSupplier; @VisibleForTesting - final SparseArray<TimeSparseArray<SparseIntArray>> mWakingActivity = new SparseArray<>(); + final SparseArray<LongSparseArray<SparseIntArray>> mWakingActivity = new SparseArray<>(); WakingActivityHistory(LongSupplier retentionSupplier) { mRetentionSupplier = retentionSupplier; @@ -414,9 +414,9 @@ public class CpuWakeupStats { if (uidProcStates == null) { return; } - TimeSparseArray<SparseIntArray> wakingActivity = mWakingActivity.get(subsystem); + LongSparseArray<SparseIntArray> wakingActivity = mWakingActivity.get(subsystem); if (wakingActivity == null) { - wakingActivity = new TimeSparseArray<>(); + wakingActivity = new LongSparseArray<>(); mWakingActivity.put(subsystem, wakingActivity); } final SparseIntArray uidsToBlame = wakingActivity.get(elapsedRealtime); @@ -435,7 +435,7 @@ public class CpuWakeupStats { // Limit activity history per subsystem to the last retention period as supplied by // mRetentionSupplier. Note that the last activity is always present, even if it // occurred before the retention period. - final int endIdx = wakingActivity.closestIndexOnOrBefore( + final int endIdx = wakingActivity.lastIndexOnOrBefore( elapsedRealtime - mRetentionSupplier.getAsLong()); for (int i = endIdx; i >= 0; i--) { wakingActivity.removeAt(i); @@ -445,11 +445,11 @@ public class CpuWakeupStats { SparseIntArray removeBetween(int subsystem, long startElapsed, long endElapsed) { final SparseIntArray uidsToReturn = new SparseIntArray(); - final TimeSparseArray<SparseIntArray> activityForSubsystem = + final LongSparseArray<SparseIntArray> activityForSubsystem = mWakingActivity.get(subsystem); if (activityForSubsystem != null) { - final int startIdx = activityForSubsystem.closestIndexOnOrAfter(startElapsed); - final int endIdx = activityForSubsystem.closestIndexOnOrBefore(endElapsed); + final int startIdx = activityForSubsystem.firstIndexOnOrAfter(startElapsed); + final int endIdx = activityForSubsystem.lastIndexOnOrBefore(endElapsed); for (int i = endIdx; i >= startIdx; i--) { final SparseIntArray uidsForTime = activityForSubsystem.valueAt(i); for (int j = 0; j < uidsForTime.size(); j++) { @@ -463,8 +463,8 @@ public class CpuWakeupStats { activityForSubsystem.removeAt(i); } // Generally waking activity is a high frequency occurrence for any subsystem, so we - // don't delete the TimeSparseArray even if it is now empty, to avoid object churn. - // This will leave one TimeSparseArray per subsystem, which are few right now. + // don't delete the LongSparseArray even if it is now empty, to avoid object churn. + // This will leave one LongSparseArray per subsystem, which are few right now. } return uidsToReturn.size() > 0 ? uidsToReturn : null; } @@ -474,7 +474,7 @@ public class CpuWakeupStats { pw.increaseIndent(); for (int i = 0; i < mWakingActivity.size(); i++) { pw.println("Subsystem " + subsystemToString(mWakingActivity.keyAt(i)) + ":"); - final TimeSparseArray<SparseIntArray> wakingActivity = mWakingActivity.valueAt(i); + final LongSparseArray<SparseIntArray> wakingActivity = mWakingActivity.valueAt(i); if (wakingActivity == null) { continue; } diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java index ecd5bd22cd03..76126df14bfd 100644 --- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java +++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java @@ -1487,6 +1487,7 @@ public final class SensorPrivacyService extends SystemService { @Override public void binderDied() { mSensorPrivacyServiceImpl.removeSensorPrivacyListener(mListener); + mSensorPrivacyServiceImpl.removeToggleSensorPrivacyListener(mListener); } public void destroy() { diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index 6e9a22c7872b..efd8b6d9a943 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -19,7 +19,6 @@ package com.android.server.statusbar; import android.annotation.Nullable; import android.app.ITransientNotificationCallback; import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; -import android.inputmethodservice.InputMethodService; import android.os.Bundle; import android.os.IBinder; import android.view.WindowInsets.Type.InsetsType; @@ -55,13 +54,13 @@ public interface StatusBarManagerInternal { * @param displayId The display to which the IME is bound to. * @param token The IME token. * @param vis Bit flags about the IME visibility. + * (e.g. {@link android.inputmethodservice.InputMethodService#IME_ACTIVE}) * @param backDisposition Bit flags about the IME back disposition. + * (e.g. {@link android.inputmethodservice.InputMethodService#BACK_DISPOSITION_DEFAULT}) * @param showImeSwitcher {@code true} when the IME switcher button should be shown. */ - void setImeWindowStatus(int displayId, IBinder token, - @InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition, - boolean showImeSwitcher); + void setImeWindowStatus(int displayId, IBinder token, int vis, + int backDisposition, boolean showImeSwitcher); /** * See {@link android.app.StatusBarManager#setIcon(String, int, int, String)}. diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 2c381ca3bd41..cc849b6fbf91 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -59,7 +59,6 @@ import android.hardware.biometrics.PromptInfo; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManager.DisplayListener; import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; -import android.inputmethodservice.InputMethodService; import android.media.INearbyMediaDevicesProvider; import android.media.MediaRoute2Info; import android.net.Uri; @@ -533,9 +532,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void setImeWindowStatus(int displayId, IBinder token, - @InputMethodService.ImeWindowVisibility int vis, - @InputMethodService.BackDispositionMode int backDisposition, + public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, boolean showImeSwitcher) { StatusBarManagerService.this.setImeWindowStatus(displayId, token, vis, backDisposition, showImeSwitcher); @@ -1267,14 +1264,12 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void setImeWindowStatus(int displayId, final IBinder token, - @InputMethodService.ImeWindowVisibility final int vis, - @InputMethodService.BackDispositionMode final int backDisposition, - final boolean showImeSwitcher) { + public void setImeWindowStatus(int displayId, final IBinder token, final int vis, + final int backDisposition, final boolean showImeSwitcher) { enforceStatusBar(); if (SPEW) { - Slog.d(TAG, "setImeWindowStatus vis=" + vis + " backDisposition=" + backDisposition); + Slog.d(TAG, "swetImeWindowStatus vis=" + vis + " backDisposition=" + backDisposition); } synchronized(mLock) { @@ -1337,9 +1332,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D private String mPackageName = "none"; private int mDisabled1 = 0; private int mDisabled2 = 0; - @InputMethodService.ImeWindowVisibility private int mImeWindowVis = 0; - @InputMethodService.BackDispositionMode private int mImeBackDisposition = 0; private boolean mShowImeSwitcher = false; private IBinder mImeToken = null; @@ -1384,8 +1377,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D return mDisabled1 == disabled1 && mDisabled2 == disabled2; } - private void setImeWindowState(@InputMethodService.ImeWindowVisibility final int vis, - @InputMethodService.BackDispositionMode final int backDisposition, + private void setImeWindowState(final int vis, final int backDisposition, final boolean showImeSwitcher, final IBinder token) { mImeWindowVis = vis; mImeBackDisposition = backDisposition; diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java index d63a908517fb..0ab6d5769b5e 100755 --- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java +++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java @@ -396,6 +396,16 @@ class TvInputHardwareManager implements TvInputHal.Callback { } } + public void updateInputInfo(TvInputInfo info) { + synchronized (mLock) { + if (!mInputMap.containsKey(info.getId())) { + return; + } + Slog.w(TAG, "update inputInfo for input id " + info.getId()); + mInputMap.put(info.getId(), info); + } + } + /** * Create a TvInputHardware object with a specific deviceId. One service at a time can access * the object, and if more than one process attempts to create hardware with the same deviceId, diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index a05bd2d008da..19ee554fd973 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -1067,6 +1067,11 @@ public final class TvInputManagerService extends SystemService { } inputState.info = inputInfo; inputState.uid = getInputUid(inputInfo); + ServiceState serviceState = userState.serviceStateMap.get(inputInfo.getComponent()); + if (serviceState != null && serviceState.isHardware) { + serviceState.hardwareInputMap.put(inputInfo.getId(), inputInfo); + mTvInputHardwareManager.updateInputInfo(inputInfo); + } int n = userState.mCallbacks.beginBroadcast(); for (int i = 0; i < n; ++i) { diff --git a/services/core/java/com/android/server/utils/AlarmQueue.java b/services/core/java/com/android/server/utils/AlarmQueue.java index 09ba19508476..83605ae09e97 100644 --- a/services/core/java/com/android/server/utils/AlarmQueue.java +++ b/services/core/java/com/android/server/utils/AlarmQueue.java @@ -151,6 +151,10 @@ public abstract class AlarmQueue<K> implements AlarmManager.OnAlarmListener { @GuardedBy("mLock") @ElapsedRealtimeLong private long mTriggerTimeElapsed = NOT_SCHEDULED; + /** The last time an alarm went off (ie. the last time {@link #onAlarm()} was called}). */ + @GuardedBy("mLock") + @ElapsedRealtimeLong + private long mLastFireTimeElapsed; /** * @param alarmTag The tag to use when scheduling the alarm with AlarmManager. @@ -284,7 +288,7 @@ public abstract class AlarmQueue<K> implements AlarmManager.OnAlarmListener { /** Sets an alarm with {@link AlarmManager} for the earliest alarm in the queue after now. */ @GuardedBy("mLock") private void setNextAlarmLocked() { - setNextAlarmLocked(mInjector.getElapsedRealtime()); + setNextAlarmLocked(mLastFireTimeElapsed + mMinTimeBetweenAlarmsMs); } /** @@ -334,6 +338,7 @@ public abstract class AlarmQueue<K> implements AlarmManager.OnAlarmListener { final ArraySet<K> expired = new ArraySet<>(); synchronized (mLock) { final long nowElapsed = mInjector.getElapsedRealtime(); + mLastFireTimeElapsed = nowElapsed; while (mAlarmPriorityQueue.size() > 0) { final Pair<K, Long> alarm = mAlarmPriorityQueue.peek(); if (alarm.second <= nowElapsed) { diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java index b296ef2a1443..1ff01a6c70bf 100644 --- a/services/core/java/com/android/server/vr/VrManagerService.java +++ b/services/core/java/com/android/server/vr/VrManagerService.java @@ -1049,7 +1049,11 @@ public class VrManagerService extends SystemService for (ComponentName c : possibleServices) { if (Objects.equals(c.getPackageName(), pkg)) { - nm.setNotificationListenerAccessGrantedForUser(c, userId, true); + try { + nm.setNotificationListenerAccessGrantedForUser(c, userId, true); + } catch (Exception e) { + Slog.w(TAG, "Could not grant NLS access to package " + pkg, e); + } } } } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 11d1126c9ae4..25a0a527cd0c 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -955,7 +955,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub try { connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false, wpdData.mWidth, wpdData.mHeight, - wpdData.mPadding, mDisplayId, wallpaper.mWhich); + wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo); } catch (RemoteException e) { Slog.w(TAG, "Failed attaching wallpaper on display", e); if (wallpaper != null && !wallpaper.wallpaperUpdating @@ -3158,7 +3158,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (which == FLAG_SYSTEM && systemIsStatic && systemIsBoth) { Slog.i(TAG, "Migrating current wallpaper to be lock-only before" + " updating system wallpaper"); - migrateStaticSystemToLockWallpaperLocked(userId); + if (!migrateStaticSystemToLockWallpaperLocked(userId) + && !isLockscreenLiveWallpaperEnabled()) { + which |= FLAG_LOCK; + } } wallpaper = getWallpaperSafeLocked(userId, which); @@ -3186,13 +3189,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } - private void migrateStaticSystemToLockWallpaperLocked(int userId) { + private boolean migrateStaticSystemToLockWallpaperLocked(int userId) { WallpaperData sysWP = mWallpaperMap.get(userId); if (sysWP == null) { if (DEBUG) { Slog.i(TAG, "No system wallpaper? Not tracking for lock-only"); } - return; + return true; } // We know a-priori that there is no lock-only wallpaper currently @@ -3219,9 +3222,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub SELinux.restorecon(lockWP.getWallpaperFile()); mLastLockWallpaper = lockWP; } + return true; } catch (ErrnoException e) { - Slog.e(TAG, "Can't migrate system wallpaper: " + e.getMessage()); + // can happen when migrating default wallpaper (which is not stored in wallpaperFile) + Slog.w(TAG, "Couldn't migrate system wallpaper: " + e.getMessage()); clearWallpaperBitmaps(lockWP); + return false; } } @@ -3433,7 +3439,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // therefore it's a shared system+lock image that we need to migrate. Slog.i(TAG, "Migrating current wallpaper to be lock-only before" + "updating system wallpaper"); - migrateStaticSystemToLockWallpaperLocked(userId); + if (!migrateStaticSystemToLockWallpaperLocked(userId)) { + which |= FLAG_LOCK; + } } } @@ -3588,7 +3596,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final int hasPrivilege = mIPackageManager.checkPermission( android.Manifest.permission.AMBIENT_WALLPAPER, wi.getPackageName(), serviceUserId); - if (hasPrivilege != PERMISSION_GRANTED) { + // All watch wallpapers support ambient mode by default. + if (hasPrivilege != PERMISSION_GRANTED + && !mIPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH, 0)) { String msg = "Selected service does not have " + android.Manifest.permission.AMBIENT_WALLPAPER + ": " + componentName; diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index f3001133338a..b3ae2ee30f22 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -1280,45 +1280,53 @@ final class AccessibilityController { } void drawIfNeeded(SurfaceControl.Transaction t) { + // Drawing variables (alpha, dirty rect, and bounds) access is synchronized + // using WindowManagerGlobalLock. Grab copies of these values before + // drawing on the canvas so that drawing can be performed outside of the lock. + int alpha; + Rect drawingRect = null; + Region drawingBounds = null; synchronized (mService.mGlobalLock) { if (!mInvalidated) { return; } mInvalidated = false; - if (mAlpha > 0) { - Canvas canvas = null; - try { - // Empty dirty rectangle means unspecified. - if (mDirtyRect.isEmpty()) { - mBounds.getBounds(mDirtyRect); - } - mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth); - canvas = mSurface.lockCanvas(mDirtyRect); - if (DEBUG_VIEWPORT_WINDOW) { - Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect); - } - } catch (IllegalArgumentException iae) { - /* ignore */ - } catch (Surface.OutOfResourcesException oore) { - /* ignore */ - } - if (canvas == null) { - return; + + alpha = mAlpha; + if (alpha > 0) { + drawingBounds = new Region(mBounds); + // Empty dirty rectangle means unspecified. + if (mDirtyRect.isEmpty()) { + mBounds.getBounds(mDirtyRect); } + mDirtyRect.inset(-mHalfBorderWidth, -mHalfBorderWidth); + drawingRect = new Rect(mDirtyRect); if (DEBUG_VIEWPORT_WINDOW) { - Slog.i(LOG_TAG, "Bounds: " + mBounds); + Slog.i(LOG_TAG, "ViewportWindow bounds: " + mBounds); + Slog.i(LOG_TAG, "ViewportWindow dirty rect: " + mDirtyRect); } - canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); - mPaint.setAlpha(mAlpha); - Path path = mBounds.getBoundaryPath(); - canvas.drawPath(path, mPaint); - - mSurface.unlockCanvasAndPost(canvas); - t.show(mSurfaceControl); - } else { - t.hide(mSurfaceControl); } } + + // Draw without holding WindowManagerGlobalLock. + if (alpha > 0) { + Canvas canvas = null; + try { + canvas = mSurface.lockCanvas(drawingRect); + } catch (IllegalArgumentException | OutOfResourcesException e) { + /* ignore */ + } + if (canvas == null) { + return; + } + canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); + mPaint.setAlpha(alpha); + canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint); + mSurface.unlockCanvasAndPost(canvas); + t.show(mSurfaceControl); + } else { + t.hide(mSurfaceControl); + } } void releaseSurface() { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 1636e466a503..5165e86bcf02 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -4574,6 +4574,26 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } task.forAllActivities(fromActivity -> { if (fromActivity == this) return true; + // The snapshot starting window could remove itself when receive resized request without + // redraw, so transfer it to a different size activity could only cause flicker. + // By schedule remove snapshot starting window, the remove process will happen when + // transition ready, transition ready means the app window is drawn. + final StartingData tmpStartingData = fromActivity.mStartingData; + if (tmpStartingData != null && tmpStartingData.mAssociatedTask == null + && mTransitionController.isCollecting(fromActivity) + && tmpStartingData instanceof SnapshotStartingData) { + final Rect fromBounds = fromActivity.getBounds(); + final Rect myBounds = getBounds(); + if (!fromBounds.equals(myBounds)) { + // Mark as no animation, so these changes won't merge into playing transition. + if (mTransitionController.inPlayingTransition(fromActivity)) { + mTransitionController.setNoAnimation(this); + mTransitionController.setNoAnimation(fromActivity); + } + fromActivity.removeStartingWindow(); + return true; + } + } return !fromActivity.isVisibleRequested() && transferStartingWindow(fromActivity); }); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 32f1f42aacb9..a2547fd437d1 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -23,6 +23,7 @@ import android.app.ActivityManager; import android.app.AppProtoEnums; import android.app.BackgroundStartPrivileges; import android.app.IActivityManager; +import android.app.IAppTask; import android.app.IApplicationThread; import android.app.ITaskStackListener; import android.app.ProfilerInfo; @@ -308,6 +309,12 @@ public abstract class ActivityTaskManagerInternal { public abstract void notifyActiveDreamChanged(@Nullable ComponentName activeDreamComponent); /** + * Starts a dream activity in the DreamService's process. + */ + public abstract IAppTask startDreamActivity(@NonNull Intent intent, int callingUid, + int callingPid); + + /** * Set a uid that is allowed to bypass stopped app switches, launching an app * whenever it wants. * diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 41b3aad2415d..aeaf78327d57 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -546,6 +546,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { */ private volatile long mLastStopAppSwitchesTime; + @GuardedBy("itself") private final List<AnrController> mAnrController = new ArrayList<>(); IActivityController mController = null; boolean mControllerIsAMonkey = false; @@ -733,7 +734,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private boolean mShowDialogs = true; /** Set if we are shutting down the system, similar to sleeping. */ - boolean mShuttingDown = false; + volatile boolean mShuttingDown; /** * We want to hold a wake lock while running a voice interaction session, since @@ -1466,23 +1467,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return false; } - private void enforceCallerIsDream(String callerPackageName) { - final long origId = Binder.clearCallingIdentity(); - try { - if (!canLaunchDreamActivity(callerPackageName)) { - throw new SecurityException("The dream activity can be started only when the device" - + " is dreaming and only by the active dream package."); - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - - @Override - public boolean startDreamActivity(@NonNull Intent intent) { - assertPackageMatchesCallingUid(intent.getPackage()); - enforceCallerIsDream(intent.getPackage()); - + private IAppTask startDreamActivityInternal(@NonNull Intent intent, int callingUid, + int callingPid) { final ActivityInfo a = new ActivityInfo(); a.theme = com.android.internal.R.style.Theme_Dream; a.exported = true; @@ -1500,7 +1486,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { options.setLaunchActivityType(ACTIVITY_TYPE_DREAM); synchronized (mGlobalLock) { - final WindowProcessController process = mProcessMap.getProcess(Binder.getCallingPid()); + final WindowProcessController process = mProcessMap.getProcess(callingPid); a.packageName = process.mInfo.packageName; a.applicationInfo = process.mInfo; @@ -1508,26 +1494,25 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { a.uiOptions = process.mInfo.uiOptions; a.taskAffinity = "android:" + a.packageName + "/dream"; - final int callingUid = Binder.getCallingUid(); - final int callingPid = Binder.getCallingPid(); - final long origId = Binder.clearCallingIdentity(); - try { - getActivityStartController().obtainStarter(intent, "dream") - .setCallingUid(callingUid) - .setCallingPid(callingPid) - .setCallingPackage(intent.getPackage()) - .setActivityInfo(a) - .setActivityOptions(createSafeActivityOptionsWithBalAllowed(options)) - // To start the dream from background, we need to start it from a persistent - // system process. Here we set the real calling uid to the system server uid - .setRealCallingUid(Binder.getCallingUid()) - .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL) - .execute(); - return true; - } finally { - Binder.restoreCallingIdentity(origId); - } + final ActivityRecord[] outActivity = new ActivityRecord[1]; + getActivityStartController().obtainStarter(intent, "dream") + .setCallingUid(callingUid) + .setCallingPid(callingPid) + .setCallingPackage(intent.getPackage()) + .setActivityInfo(a) + .setActivityOptions(createSafeActivityOptionsWithBalAllowed(options)) + .setOutActivity(outActivity) + // To start the dream from background, we need to start it from a persistent + // system process. Here we set the real calling uid to the system server uid + .setRealCallingUid(Binder.getCallingUid()) + .setBackgroundStartPrivileges(BackgroundStartPrivileges.ALLOW_BAL) + .execute(); + + final ActivityRecord started = outActivity[0]; + final IAppTask appTask = started == null ? null : + new AppTaskImpl(this, started.getTask().mTaskId, callingUid); + return appTask; } } @@ -2298,14 +2283,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /** Register an {@link AnrController} to control the ANR dialog behavior */ public void registerAnrController(AnrController controller) { - synchronized (mGlobalLock) { + synchronized (mAnrController) { mAnrController.add(controller); } } /** Unregister an {@link AnrController} */ public void unregisterAnrController(AnrController controller) { - synchronized (mGlobalLock) { + synchronized (mAnrController) { mAnrController.remove(controller); } } @@ -2321,7 +2306,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } final ArrayList<AnrController> controllers; - synchronized (mGlobalLock) { + synchronized (mAnrController) { controllers = new ArrayList<>(mAnrController); } @@ -5925,6 +5910,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override + public IAppTask startDreamActivity(@NonNull Intent intent, int callingUid, int callingPid) { + return startDreamActivityInternal(intent, callingUid, callingPid); + } + + @Override public void setAllowAppSwitches(@NonNull String type, int uid, int userId) { if (!mAmInternal.isUserRunning(userId, ActivityManager.FLAG_OR_STOPPED)) { return; @@ -6034,15 +6024,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public boolean isShuttingDown() { - synchronized (mGlobalLock) { - return mShuttingDown; - } + return mShuttingDown; } @Override public boolean shuttingDown(boolean booted, int timeout) { + mShuttingDown = true; synchronized (mGlobalLock) { - mShuttingDown = true; mRootWindowContainer.prepareForShutdown(); updateEventDispatchingLocked(booted); notifyTaskPersisterLocked(null, true); diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java index f7ccc0d91969..0273a30e157c 100644 --- a/services/core/java/com/android/server/wm/AppWarnings.java +++ b/services/core/java/com/android/server/wm/AppWarnings.java @@ -16,7 +16,9 @@ package com.android.server.wm; +import android.annotation.NonNull; import android.annotation.UiThread; +import android.annotation.WorkerThread; import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -30,14 +32,18 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemProperties; +import android.util.ArrayMap; +import android.util.ArraySet; import android.util.AtomicFile; import android.util.DisplayMetrics; import android.util.Slog; import android.util.Xml; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.ArrayUtils; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; +import com.android.server.IoThread; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -45,9 +51,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; /** * Manages warning dialogs shown during application lifecycle. @@ -61,11 +65,12 @@ class AppWarnings { public static final int FLAG_HIDE_DEPRECATED_SDK = 0x04; public static final int FLAG_HIDE_DEPRECATED_ABI = 0x08; - private final HashMap<String, Integer> mPackageFlags = new HashMap<>(); + @GuardedBy("mPackageFlags") + private final ArrayMap<String, Integer> mPackageFlags = new ArrayMap<>(); private final ActivityTaskManagerService mAtm; private final Context mUiContext; - private final ConfigHandler mHandler; + private final WriteConfigTask mWriteConfigTask; private final UiHandler mUiHandler; private final AtomicFile mConfigFile; @@ -75,30 +80,20 @@ class AppWarnings { private DeprecatedAbiDialog mDeprecatedAbiDialog; /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */ - private HashSet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities = - new HashSet<>(); + private final ArraySet<ComponentName> mAlwaysShowUnsupportedCompileSdkWarningActivities = + new ArraySet<>(); /** @see android.app.ActivityManager#alwaysShowUnsupportedCompileSdkWarning */ void alwaysShowUnsupportedCompileSdkWarning(ComponentName activity) { mAlwaysShowUnsupportedCompileSdkWarningActivities.add(activity); } - /** - * Creates a new warning dialog manager. - * <p> - * <strong>Note:</strong> Must be called from the ActivityManagerService thread. - * - * @param atm - * @param uiContext - * @param handler - * @param uiHandler - * @param systemDir - */ + /** Creates a new warning dialog manager. */ public AppWarnings(ActivityTaskManagerService atm, Context uiContext, Handler handler, Handler uiHandler, File systemDir) { mAtm = atm; mUiContext = uiContext; - mHandler = new ConfigHandler(handler.getLooper()); + mWriteConfigTask = new WriteConfigTask(); mUiHandler = new UiHandler(uiHandler.getLooper()); mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME), "warnings-config"); @@ -256,8 +251,9 @@ class AppWarnings { mUiHandler.hideDialogsForPackage(name); synchronized (mPackageFlags) { - mPackageFlags.remove(name); - mHandler.scheduleWrite(); + if (mPackageFlags.remove(name) != null) { + mWriteConfigTask.schedule(); + } } } @@ -425,7 +421,7 @@ class AppWarnings { } else { mPackageFlags.remove(name); } - mHandler.scheduleWrite(); + mWriteConfigTask.schedule(); } } } @@ -556,46 +552,30 @@ class AppWarnings { } } - /** - * Handles messages on the ActivityTaskManagerService thread. - */ - private final class ConfigHandler extends Handler { - private static final int MSG_WRITE = 1; - - private static final int DELAY_MSG_WRITE = 10000; - - public ConfigHandler(Looper looper) { - super(looper, null, true); - } + private final class WriteConfigTask implements Runnable { + private static final long WRITE_CONFIG_DELAY_MS = 10000; + final AtomicReference<ArrayMap<String, Integer>> mPendingPackageFlags = + new AtomicReference<>(); @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_WRITE: - writeConfigToFileAmsThread(); - break; + public void run() { + final ArrayMap<String, Integer> packageFlags = mPendingPackageFlags.getAndSet(null); + if (packageFlags != null) { + writeConfigToFile(packageFlags); } } - public void scheduleWrite() { - removeMessages(MSG_WRITE); - sendEmptyMessageDelayed(MSG_WRITE, DELAY_MSG_WRITE); + @GuardedBy("mPackageFlags") + void schedule() { + if (mPendingPackageFlags.getAndSet(new ArrayMap<>(mPackageFlags)) == null) { + IoThread.getHandler().postDelayed(this, WRITE_CONFIG_DELAY_MS); + } } } - /** - * Writes the configuration file. - * <p> - * <strong>Note:</strong> Should be called from the ActivityManagerService thread unless you - * don't care where you're doing I/O operations. But you <i>do</i> care, don't you? - */ - private void writeConfigToFileAmsThread() { - // Create a shallow copy so that we don't have to synchronize on config. - final HashMap<String, Integer> packageFlags; - synchronized (mPackageFlags) { - packageFlags = new HashMap<>(mPackageFlags); - } - + /** Writes the configuration file. */ + @WorkerThread + private void writeConfigToFile(@NonNull ArrayMap<String, Integer> packageFlags) { FileOutputStream fos = null; try { fos = mConfigFile.startWrite(); @@ -605,9 +585,9 @@ class AppWarnings { out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); out.startTag(null, "packages"); - for (Map.Entry<String, Integer> entry : packageFlags.entrySet()) { - String pkg = entry.getKey(); - int mode = entry.getValue(); + for (int i = 0; i < packageFlags.size(); i++) { + final String pkg = packageFlags.keyAt(i); + final int mode = packageFlags.valueAt(i); if (mode == 0) { continue; } diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index b216578262b4..188f4d0b8ced 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -280,7 +280,7 @@ public class BackgroundActivityStartController { // visible window. if (Process.isSdkSandboxUid(realCallingUid)) { int realCallingSdkSandboxUidToAppUid = - Process.getAppUidForSdkSandboxUid(UserHandle.getAppId(realCallingUid)); + Process.getAppUidForSdkSandboxUid(realCallingUid); if (mService.hasActiveVisibleWindow(realCallingSdkSandboxUidToAppUid)) { return logStartAllowedAndReturnCode(BAL_ALLOW_SDK_SANDBOX, diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 12b5f5f774de..bfd2a10a8882 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3701,6 +3701,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mInputMonitor.dump(pw, " "); pw.println(); mInsetsStateController.dump(prefix, pw); + mInsetsPolicy.dump(prefix, pw); mDwpcHelper.dump(prefix, pw); pw.println(); } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index a61be9ea556d..ab89b8748e2f 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -327,8 +327,6 @@ public class DisplayPolicy { private WindowState mTopFullscreenOpaqueWindowState; private boolean mTopIsFullscreen; private int mNavBarOpacityMode = NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED; - private boolean mForceConsumeSystemBars; - private boolean mForceShowSystemBars; /** * Windows that provides gesture insets. If multiple windows provide gesture insets at the same @@ -1286,18 +1284,10 @@ public class DisplayPolicy { return ANIMATION_STYLEABLE; } - /** - * @return true if the system bars are forced to be consumed - */ + // TODO (b/277891341): Remove this and related usages. This has been replaced by + // InsetsSource#FLAG_FORCE_CONSUMING. public boolean areSystemBarsForcedConsumedLw() { - return mForceConsumeSystemBars; - } - - /** - * @return true if the system bars are forced to stay visible - */ - public boolean areSystemBarsForcedShownLw() { - return mForceShowSystemBars; + return false; } /** @@ -1694,7 +1684,8 @@ public class DisplayPolicy { * @return Whether the top fullscreen app hides the given type of system bar. */ boolean topAppHidesSystemBar(@InsetsType int type) { - if (mTopFullscreenOpaqueWindowState == null || mForceShowSystemBars) { + if (mTopFullscreenOpaqueWindowState == null + || getInsetsPolicy().areTypesForciblyShowing(type)) { return false; } return !mTopFullscreenOpaqueWindowState.isRequestedVisible(type); @@ -2371,14 +2362,7 @@ public class DisplayPolicy { final boolean freeformRootTaskVisible = defaultTaskDisplayArea.isRootTaskVisible(WINDOWING_MODE_FREEFORM); - // We need to force showing system bars when adjacent tasks or freeform roots visible. - mForceShowSystemBars = adjacentTasksVisible || freeformRootTaskVisible; - // We need to force the consumption of the system bars if they are force shown or if they - // are controlled by a remote insets controller. - mForceConsumeSystemBars = mForceShowSystemBars - || getInsetsPolicy().remoteInsetsControllerControlsSystemBars(win) - || getInsetsPolicy().forcesShowingNavigationBars(win); - mDisplayContent.getInsetsPolicy().updateBarControlTarget(win); + getInsetsPolicy().updateSystemBars(win, adjacentTasksVisible, freeformRootTaskVisible); final boolean topAppHidesStatusBar = topAppHidesSystemBar(Type.statusBars()); if (getStatusBar() != null) { diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index b681c198538f..99cbdde306a2 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -977,6 +977,8 @@ public class DisplayRotation { return false; case IWindowManager.FIXED_TO_USER_ROTATION_ENABLED: return true; + case IWindowManager.FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION: + return false; default: return mDefaultFixedToUserRotation; } @@ -1290,9 +1292,13 @@ public class DisplayRotation { // Application just wants to remain locked in the last rotation. preferredRotation = lastRotation; } else if (!mSupportAutoRotation) { - // If we don't support auto-rotation then bail out here and ignore - // the sensor and any rotation lock settings. - preferredRotation = -1; + if (mFixedToUserRotation == IWindowManager.FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION) { + preferredRotation = mUserRotation; + } else { + // If we don't support auto-rotation then bail out here and ignore + // the sensor and any rotation lock settings. + preferredRotation = -1; + } } else if (((mUserRotationMode == WindowManagerPolicy.USER_ROTATION_FREE || isTabletopAutoRotateOverrideEnabled()) && (orientation == ActivityInfo.SCREEN_ORIENTATION_USER diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 798dc85ec11b..835c92d0a30d 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -21,12 +21,7 @@ import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; -import static android.view.InsetsController.ANIMATION_TYPE_HIDE; -import static android.view.InsetsController.ANIMATION_TYPE_SHOW; -import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN; -import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN; import static android.view.InsetsSource.ID_IME; -import static android.view.SyncRtSurfaceTransactionApplier.applyParams; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION; @@ -39,16 +34,13 @@ import android.app.StatusBarManager; import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.res.Resources; +import android.os.Handler; +import android.os.IBinder; import android.util.SparseArray; -import android.view.InsetsAnimationControlCallbacks; -import android.view.InsetsAnimationControlImpl; -import android.view.InsetsAnimationControlRunner; import android.view.InsetsController; import android.view.InsetsFrameProvider; import android.view.InsetsSource; -import android.view.InsetsSourceControl; import android.view.InsetsState; -import android.view.InternalInsetsAnimationController; import android.view.SurfaceControl; import android.view.SyncRtSurfaceTransactionApplier; import android.view.WindowInsets; @@ -56,14 +48,16 @@ import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation; import android.view.WindowInsetsAnimation.Bounds; -import android.view.WindowInsetsAnimationControlListener; import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.server.DisplayThread; import com.android.server.statusbar.StatusBarManagerInternal; +import java.io.PrintWriter; +import java.util.List; + /** * Policy that implements who gets control over the windows generating insets. */ @@ -77,47 +71,19 @@ class InsetsPolicy { private final DisplayContent mDisplayContent; private final DisplayPolicy mPolicy; - /** For resetting visibilities of insets sources. */ - private final InsetsControlTarget mDummyControlTarget = new InsetsControlTarget() { + /** Used to show system bars transiently. This won't affect the layout. */ + private final InsetsControlTarget mTransientControlTarget; - @Override - public void notifyInsetsControlChanged() { - boolean hasLeash = false; - final InsetsSourceControl[] controls = - mStateController.getControlsForDispatch(this); - if (controls == null) { - return; - } - for (InsetsSourceControl control : controls) { - if (isTransient(control.getType())) { - // The visibilities of transient bars will be handled with animations. - continue; - } - final SurfaceControl leash = control.getLeash(); - if (leash != null) { - hasLeash = true; - - // We use alpha to control the visibility here which aligns the logic at - // SurfaceAnimator.createAnimationLeash - final boolean visible = - (control.getType() & WindowInsets.Type.defaultVisible()) != 0; - mDisplayContent.getPendingTransaction().setAlpha(leash, visible ? 1f : 0f); - } - } - if (hasLeash) { - mDisplayContent.scheduleAnimation(); - } - } - }; + /** Used to show system bars permanently. This will affect the layout. */ + private final InsetsControlTarget mPermanentControlTarget; private WindowState mFocusedWin; private final BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR); private final BarWindow mNavBar = new BarWindow(StatusBarManager.WINDOW_NAVIGATION_BAR); private @InsetsType int mShowingTransientTypes; - private boolean mAnimatingShown; + private @InsetsType int mForcedShowingTypes; private final boolean mHideNavBarForKeyboard; - private final float[] mTmpFloat9 = new float[9]; InsetsPolicy(InsetsStateController stateController, DisplayContent displayContent) { mStateController = stateController; @@ -125,9 +91,12 @@ class InsetsPolicy { mPolicy = displayContent.getDisplayPolicy(); final Resources r = mPolicy.getContext().getResources(); mHideNavBarForKeyboard = r.getBoolean(R.bool.config_hideNavBarForKeyboard); + mTransientControlTarget = new ControlTarget( + stateController, displayContent.mWmService.mH, "TransientControlTarget"); + mPermanentControlTarget = new ControlTarget( + stateController, displayContent.mWmService.mH, "PermanentControlTarget"); } - /** Updates the target which can control system bars. */ void updateBarControlTarget(@Nullable WindowState focusedWin) { if (mFocusedWin != focusedWin) { @@ -142,13 +111,13 @@ class InsetsPolicy { final WindowState topApp = mPolicy.getTopFullscreenOpaqueWindow(); mStateController.onBarControlTargetChanged( statusControlTarget, - statusControlTarget == mDummyControlTarget + statusControlTarget == mTransientControlTarget ? getStatusControlTarget(focusedWin, true /* fake */) : statusControlTarget == notificationShade ? getStatusControlTarget(topApp, true /* fake */) : null, navControlTarget, - navControlTarget == mDummyControlTarget + navControlTarget == mTransientControlTarget ? getNavControlTarget(focusedWin, true /* fake */) : navControlTarget == notificationShade ? getNavControlTarget(topApp, true /* fake */) @@ -198,19 +167,19 @@ class InsetsPolicy { mFocusedWin, (showingTransientTypes & (Type.statusBars() | Type.navigationBars())) != 0, isGestureOnSystemBar); - - // The leashes can be created while updating bar control target. The surface transaction - // of the new leashes might not be applied yet. The callback posted here ensures we can - // get the valid leashes because the surface transaction will be applied in the next - // animation frame which will be triggered if a new leash is created. - mDisplayContent.mWmService.mAnimator.getChoreographer().postFrameCallback(time -> { - synchronized (mDisplayContent.mWmService.mGlobalLock) { - startAnimation(true /* show */, null /* callback */); - } - }); } } + @VisibleForTesting + InsetsControlTarget getTransientControlTarget() { + return mTransientControlTarget; + } + + @VisibleForTesting + InsetsControlTarget getPermanentControlTarget() { + return mPermanentControlTarget; + } + void hideTransient() { if (mShowingTransientTypes == 0) { return; @@ -221,23 +190,8 @@ class InsetsPolicy { /* areVisible= */ false, /* wereRevealedFromSwipeOnSystemBar= */ false); - startAnimation(false /* show */, () -> { - synchronized (mDisplayContent.mWmService.mGlobalLock) { - final SparseArray<InsetsSourceProvider> providers = - mStateController.getSourceProviders(); - for (int i = providers.size() - 1; i >= 0; i--) { - final InsetsSourceProvider provider = providers.valueAt(i); - if (!isTransient(provider.getSource().getType())) { - continue; - } - // We are about to clear mShowingTransientTypes, we don't want the transient bar - // can cause insets on the client. Restore the client visibility. - provider.setClientVisible(false); - } - mShowingTransientTypes = 0; - updateBarControlTarget(mFocusedWin); - } - }); + mShowingTransientTypes = 0; + updateBarControlTarget(mFocusedWin); } boolean isTransient(@InsetsType int type) { @@ -500,7 +454,7 @@ class InsetsPolicy { private @Nullable InsetsControlTarget getStatusControlTarget(@Nullable WindowState focusedWin, boolean fake) { if (!fake && isTransient(Type.statusBars())) { - return mDummyControlTarget; + return mTransientControlTarget; } final WindowState notificationShade = mPolicy.getNotificationShade(); if (focusedWin == notificationShade) { @@ -514,16 +468,16 @@ class InsetsPolicy { component, focusedWin.getRequestedVisibleTypes()); return mDisplayContent.mRemoteInsetsControlTarget; } - if (mPolicy.areSystemBarsForcedShownLw()) { + if (areTypesForciblyShowing(Type.statusBars())) { // Status bar is forcibly shown. We don't want the client to control the status bar, and // we will dispatch the real visibility of status bar to the client. - return null; + return mPermanentControlTarget; } if (forceShowsStatusBarTransiently() && !fake) { // Status bar is forcibly shown transiently, and its new visibility won't be // dispatched to the client so that we can keep the layout stable. We will dispatch the // fake control to the client, so that it can re-show the bar during this scenario. - return mDummyControlTarget; + return mTransientControlTarget; } if (!canBeTopFullscreenOpaqueWindow(focusedWin) && mPolicy.topAppHidesSystemBar(Type.statusBars()) @@ -554,7 +508,7 @@ class InsetsPolicy { return null; } if (!fake && isTransient(Type.navigationBars())) { - return mDummyControlTarget; + return mTransientControlTarget; } if (focusedWin == mPolicy.getNotificationShade()) { // Notification shade has control anyways, no reason to force anything. @@ -567,13 +521,6 @@ class InsetsPolicy { return focusedWin; } } - if (forcesShowingNavigationBars(focusedWin)) { - // When "force show navigation bar" is enabled, it means both force visible is true, and - // we are in 3-button navigation. In this mode, the navigation bar is forcibly shown - // when activity type is ACTIVITY_TYPE_STANDARD which means Launcher or Recent could - // still control the navigation bar in this mode. - return null; - } if (remoteInsetsControllerControlsSystemBars(focusedWin)) { ComponentName component = focusedWin.mActivityRecord != null ? focusedWin.mActivityRecord.mActivityComponent : null; @@ -581,16 +528,16 @@ class InsetsPolicy { component, focusedWin.getRequestedVisibleTypes()); return mDisplayContent.mRemoteInsetsControlTarget; } - if (mPolicy.areSystemBarsForcedShownLw()) { + if (areTypesForciblyShowing(Type.navigationBars())) { // Navigation bar is forcibly shown. We don't want the client to control the navigation // bar, and we will dispatch the real visibility of navigation bar to the client. - return null; + return mPermanentControlTarget; } if (forceShowsNavigationBarTransiently() && !fake) { // Navigation bar is forcibly shown transiently, and its new visibility won't be // dispatched to the client so that we can keep the layout stable. We will dispatch the // fake control to the client, so that it can re-show the bar during this scenario. - return mDummyControlTarget; + return mTransientControlTarget; } final WindowState notificationShade = mPolicy.getNotificationShade(); if (!canBeTopFullscreenOpaqueWindow(focusedWin) @@ -603,7 +550,32 @@ class InsetsPolicy { return focusedWin; } - boolean forcesShowingNavigationBars(WindowState win) { + boolean areTypesForciblyShowing(@InsetsType int types) { + return (mForcedShowingTypes & types) == types; + } + + void updateSystemBars(WindowState win, boolean inSplitScreenMode, boolean inFreeformMode) { + mForcedShowingTypes = (inSplitScreenMode || inFreeformMode) + ? (Type.statusBars() | Type.navigationBars()) + : forceShowingNavigationBars(win) + ? Type.navigationBars() + : 0; + + // The client app won't be able to control these types of system bars. Here makes the client + // forcibly consume these types to prevent the app content from getting obscured. + mStateController.setForcedConsumingTypes( + mForcedShowingTypes | (remoteInsetsControllerControlsSystemBars(win) + ? (Type.statusBars() | Type.navigationBars()) + : 0)); + + updateBarControlTarget(win); + } + + private boolean forceShowingNavigationBars(WindowState win) { + // When "force show navigation bar" is enabled, it means both force visible is true, and + // we are in 3-button navigation. In this mode, the navigation bar is forcibly shown + // when activity type is ACTIVITY_TYPE_STANDARD which means Launcher or Recent could + // still control the navigation bar in this mode. return mPolicy.isForceShowNavigationBarEnabled() && win != null && win.getActivityType() == ACTIVITY_TYPE_STANDARD; } @@ -642,34 +614,6 @@ class InsetsPolicy { && (win.mAttrs.privateFlags & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0; } - @VisibleForTesting - void startAnimation(boolean show, Runnable callback) { - @InsetsType int typesReady = 0; - final SparseArray<InsetsSourceControl> controlsReady = new SparseArray<>(); - final InsetsSourceControl[] controls = - mStateController.getControlsForDispatch(mDummyControlTarget); - if (controls == null) { - if (callback != null) { - DisplayThread.getHandler().post(callback); - } - return; - } - for (InsetsSourceControl control : controls) { - if (isTransient(control.getType()) && control.getLeash() != null) { - typesReady |= control.getType(); - controlsReady.put(control.getId(), new InsetsSourceControl(control)); - } - } - controlAnimationUnchecked(typesReady, controlsReady, show, callback); - } - - private void controlAnimationUnchecked(int typesReady, - SparseArray<InsetsSourceControl> controls, boolean show, Runnable callback) { - InsetsPolicyAnimationControlListener listener = - new InsetsPolicyAnimationControlListener(show, callback, typesReady); - listener.mControlCallbacks.controlAnimationUnchecked(typesReady, controls, show); - } - private void dispatchTransientSystemBarsVisibilityChanged( @Nullable WindowState focusedWindow, boolean areVisible, @@ -696,6 +640,21 @@ class InsetsPolicy { wereRevealedFromSwipeOnSystemBar); } + void dump(String prefix, PrintWriter pw) { + pw.println(prefix + "InsetsPolicy"); + prefix = prefix + " "; + pw.println(prefix + "status: " + StatusBarManager.windowStateToString(mStatusBar.mState)); + pw.println(prefix + "nav: " + StatusBarManager.windowStateToString(mNavBar.mState)); + if (mShowingTransientTypes != 0) { + pw.println(prefix + "mShowingTransientTypes=" + + WindowInsets.Type.toString(mShowingTransientTypes)); + } + if (mForcedShowingTypes != 0) { + pw.println(prefix + "mForcedShowingTypes=" + + WindowInsets.Type.toString(mForcedShowingTypes)); + } + } + private class BarWindow { private final int mId; @@ -725,105 +684,138 @@ class InsetsPolicy { } } - private class InsetsPolicyAnimationControlListener extends - InsetsController.InternalAnimationControlListener { - Runnable mFinishCallback; - InsetsPolicyAnimationControlCallbacks mControlCallbacks; + private static class ControlTarget implements InsetsControlTarget { + + private final InsetsState mState = new InsetsState(); + private final InsetsController mInsetsController; + private final InsetsStateController mStateController; + private final String mName; - InsetsPolicyAnimationControlListener(boolean show, Runnable finishCallback, int types) { - super(show, false /* hasCallbacks */, types, BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE, - false /* disable */, 0 /* floatingImeBottomInsets */, - null /* loggingListener */, null /* jankContext */); - mFinishCallback = finishCallback; - mControlCallbacks = new InsetsPolicyAnimationControlCallbacks(this); + ControlTarget(InsetsStateController stateController, Handler handler, String name) { + mStateController = stateController; + mInsetsController = new InsetsController(new Host(handler, name)); + mName = name; } @Override - protected void onAnimationFinish() { - super.onAnimationFinish(); - if (mFinishCallback != null) { - DisplayThread.getHandler().post(mFinishCallback); - } + public void notifyInsetsControlChanged() { + mState.set(mStateController.getRawInsetsState(), true /* copySources */); + mInsetsController.onStateChanged(mState); + mInsetsController.onControlsChanged(mStateController.getControlsForDispatch(this)); } - private class InsetsPolicyAnimationControlCallbacks implements - InsetsAnimationControlCallbacks { - private InsetsAnimationControlImpl mAnimationControl = null; - private InsetsPolicyAnimationControlListener mListener; + @Override + public String toString() { + return mName; + } + } - InsetsPolicyAnimationControlCallbacks(InsetsPolicyAnimationControlListener listener) { - mListener = listener; - } + private static class Host implements InsetsController.Host { - private void controlAnimationUnchecked(int typesReady, - SparseArray<InsetsSourceControl> controls, boolean show) { - if (typesReady == 0) { - // nothing to animate. - return; - } - mAnimatingShown = show; - - final InsetsState state = mFocusedWin.getInsetsState(); - - // We are about to playing the default animation. Passing a null frame indicates - // the controlled types should be animated regardless of the frame. - mAnimationControl = new InsetsAnimationControlImpl(controls, - null /* frame */, state, mListener, typesReady, this, - mListener.getDurationMs(), getInsetsInterpolator(), - show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, show - ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN - : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN, - null /* translator */, null /* statsToken */); - SurfaceAnimationThread.getHandler().post( - () -> mListener.onReady(mAnimationControl, typesReady)); - } + private final float[] mTmpFloat9 = new float[9]; + private final Handler mHandler; + private final String mName; - /** Called on SurfaceAnimationThread without global WM lock held. */ - @Override - public void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner) { - if (mAnimationControl.applyChangeInsets(null /* outState */)) { - mAnimationControl.finish(mAnimatingShown); - } - } + Host(Handler handler, String name) { + mHandler = handler; + mName = name; + } - @Override - public void notifyFinished(InsetsAnimationControlRunner runner, boolean shown) { - // Nothing's needed here. Finish steps is handled in the listener - // onAnimationFinished callback. - } + @Override + public Handler getHandler() { + return mHandler; + } - /** Called on SurfaceAnimationThread without global WM lock held. */ - @Override - public void applySurfaceParams( - final SyncRtSurfaceTransactionApplier.SurfaceParams... params) { - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - for (int i = params.length - 1; i >= 0; i--) { - SyncRtSurfaceTransactionApplier.SurfaceParams surfaceParams = params[i]; - applyParams(t, surfaceParams, mTmpFloat9); - } - t.apply(); - t.close(); - } + @Override + public void notifyInsetsChanged() { } - // Since we don't push applySurfaceParams to a Handler-queue we don't need - // to push release in this case. - @Override - public void releaseSurfaceControlFromRt(SurfaceControl sc) { - sc.release(); - } + @Override + public void dispatchWindowInsetsAnimationPrepare( + @NonNull WindowInsetsAnimation animation) { } - @Override - public <T extends InsetsAnimationControlRunner & InternalInsetsAnimationController> - void startAnimation(T runner, WindowInsetsAnimationControlListener listener, int types, - WindowInsetsAnimation animation, - Bounds bounds) { - } + @Override + public Bounds dispatchWindowInsetsAnimationStart( + @NonNull WindowInsetsAnimation animation, + @NonNull Bounds bounds) { + return bounds; + } + + @Override + public WindowInsets dispatchWindowInsetsAnimationProgress( + @NonNull WindowInsets insets, + @NonNull List<WindowInsetsAnimation> runningAnimations) { + return insets; + } + + @Override + public void dispatchWindowInsetsAnimationEnd( + @NonNull WindowInsetsAnimation animation) { } - @Override - public void reportPerceptible(int types, boolean perceptible) { - // No-op for now - only client windows report perceptibility for now, with policy - // controllers assumed to always be perceptible. + @Override + public void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... p) { + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); + for (int i = p.length - 1; i >= 0; i--) { + SyncRtSurfaceTransactionApplier.applyParams(t, p[i], mTmpFloat9); } + t.apply(); + t.close(); + } + + @Override + public void updateRequestedVisibleTypes(int types) { } + + @Override + public boolean hasAnimationCallbacks() { + return false; + } + + @Override + public void setSystemBarsAppearance(int appearance, int mask) { } + + @Override + public int getSystemBarsAppearance() { + return 0; + } + + @Override + public void setSystemBarsBehavior(int behavior) { } + + @Override + public int getSystemBarsBehavior() { + return BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; + } + + @Override + public void releaseSurfaceControlFromRt(SurfaceControl surfaceControl) { + surfaceControl.release(); + } + + @Override + public void addOnPreDrawRunnable(Runnable r) { } + + @Override + public void postInsetsAnimationCallback(Runnable r) { } + + @Override + public InputMethodManager getInputMethodManager() { + return null; + } + + @Nullable + @Override + public String getRootViewTitle() { + return mName; + } + + @Override + public int dipToPx(int dips) { + return 0; + } + + @Nullable + @Override + public IBinder getWindowToken() { + return null; } } } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index e1c865bb85be..2b8312c3ea60 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -40,6 +40,7 @@ import android.graphics.Rect; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import android.view.InsetsSource; +import android.view.InsetsSource.Flags; import android.view.InsetsSourceControl; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; @@ -81,6 +82,8 @@ class InsetsSourceProvider { private final Rect mSourceFrame = new Rect(); private final Rect mLastSourceFrame = new Rect(); private @NonNull Insets mInsetsHint = Insets.NONE; + private @Flags int mFlagsFromFrameProvider; + private @Flags int mFlagsFromServer; private final Consumer<Transaction> mSetLeashPositionConsumer = t -> { if (mControl != null) { @@ -189,6 +192,16 @@ class InsetsSourceProvider { } } + boolean setFlags(@Flags int flags, @Flags int mask) { + mFlagsFromServer = (mFlagsFromServer & ~mask) | (flags & mask); + final @Flags int mergedFlags = mFlagsFromFrameProvider | mFlagsFromServer; + if (mSource.getFlags() != mergedFlags) { + mSource.setFlags(mergedFlags); + return true; + } + return false; + } + /** * The source frame can affect the layout of other windows, so this should be called once the * window container gets laid out. @@ -217,11 +230,11 @@ class InsetsSourceProvider { mSourceFrame.set(frame); if (mFrameProvider != null) { - final int flags = mFrameProvider.apply( + mFlagsFromFrameProvider = mFrameProvider.apply( mWindowContainer.getDisplayContent().mDisplayFrames, mWindowContainer, mSourceFrame); - mSource.setFlags(flags); + mSource.setFlags(mFlagsFromFrameProvider | mFlagsFromServer); } updateSourceFrameForServerVisibility(); diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index addb5219c663..081ebe0e7cbd 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; +import static android.view.InsetsSource.FLAG_FORCE_CONSUMING; import static android.view.InsetsSource.ID_IME; import static android.view.WindowInsets.Type.displayCutout; import static android.view.WindowInsets.Type.ime; @@ -85,6 +86,8 @@ class InsetsStateController { } }; + private @InsetsType int mForcedConsumingTypes; + InsetsStateController(DisplayContent displayContent) { mDisplayContent = displayContent; } @@ -122,6 +125,11 @@ class InsetsStateController { provider = id == ID_IME ? new ImeInsetsSourceProvider(source, this, mDisplayContent) : new InsetsSourceProvider(source, this, mDisplayContent); + provider.setFlags( + (mForcedConsumingTypes & type) != 0 + ? FLAG_FORCE_CONSUMING + : 0, + FLAG_FORCE_CONSUMING); mProviders.put(id, provider); return provider; } @@ -137,6 +145,24 @@ class InsetsStateController { } } + void setForcedConsumingTypes(@InsetsType int types) { + if (mForcedConsumingTypes != types) { + mForcedConsumingTypes = types; + boolean changed = false; + for (int i = mProviders.size() - 1; i >= 0; i--) { + final InsetsSourceProvider provider = mProviders.valueAt(i); + changed |= provider.setFlags( + (types & provider.getSource().getType()) != 0 + ? FLAG_FORCE_CONSUMING + : 0, + FLAG_FORCE_CONSUMING); + } + if (changed) { + notifyInsetsChanged(); + } + } + } + /** * Called when a layout pass has occurred. */ @@ -391,6 +417,10 @@ class InsetsStateController { for (int i = mProviders.size() - 1; i >= 0; i--) { mProviders.valueAt(i).dump(pw, prefix + " "); } + if (mForcedConsumingTypes != 0) { + pw.println(prefix + "mForcedConsumingTypes=" + + WindowInsets.Type.toString(mForcedConsumingTypes)); + } } void dumpDebug(ProtoOutputStream proto, @WindowTraceLogLevel int logLevel) { diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index 671400cc92a7..d03388a8455e 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -204,7 +204,8 @@ class KeyguardController { // handle the snapshot. // - The display state is ON. Because if AOD is not on or pulsing, the display state will // be OFF or DOZE (the path of screen off may have handled it). - if (((aodShowing ^ keyguardShowing) || (aodShowing && aodChanged && keyguardChanged)) + if (displayId == DEFAULT_DISPLAY + && ((aodShowing ^ keyguardShowing) || (aodShowing && aodChanged && keyguardChanged)) && !state.mKeyguardGoingAway && Display.isOnState( mRootWindowContainer.getDefaultDisplay().getDisplayInfo().state)) { mWindowManager.mTaskSnapshotController.snapshotForSleeping(DEFAULT_DISPLAY); @@ -418,13 +419,17 @@ class KeyguardController { return; } - final boolean waitAppTransition = isKeyguardLocked(displayId); - mWindowManager.mPolicy.onKeyguardOccludedChangedLw(isDisplayOccluded(DEFAULT_DISPLAY), - waitAppTransition); - if (waitAppTransition) { - mService.deferWindowLayout(); - try { - if (isDisplayOccluded(DEFAULT_DISPLAY)) { + final TransitionController tc = mRootWindowContainer.mTransitionController; + + final boolean occluded = isDisplayOccluded(displayId); + final boolean performTransition = isKeyguardLocked(displayId); + final boolean executeTransition = performTransition && !tc.isCollecting(); + + mWindowManager.mPolicy.onKeyguardOccludedChangedLw(occluded); + mService.deferWindowLayout(); + try { + if (isKeyguardLocked(displayId)) { + if (occluded) { mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare( TRANSIT_KEYGUARD_OCCLUDE, TRANSIT_FLAG_KEYGUARD_OCCLUDING, @@ -434,11 +439,19 @@ class KeyguardController { TRANSIT_KEYGUARD_UNOCCLUDE, TRANSIT_FLAG_KEYGUARD_UNOCCLUDING); } - updateKeyguardSleepToken(DEFAULT_DISPLAY); + } else { + if (tc.inTransition()) { + tc.mStateValidators.add(mWindowManager.mPolicy::applyKeyguardOcclusionChange); + } else { + mWindowManager.mPolicy.applyKeyguardOcclusionChange(); + } + } + updateKeyguardSleepToken(displayId); + if (performTransition && executeTransition) { mWindowManager.executeAppTransition(); - } finally { - mService.continueWindowLayout(); } + } finally { + mService.continueWindowLayout(); } } @@ -485,6 +498,9 @@ class KeyguardController { } } + /** + * @return true if Keyguard is occluded or the device is dreaming. + */ boolean isDisplayOccluded(int displayId) { return getDisplayState(displayId).mOccluded; } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index adaecc2e6aa3..48420d22512e 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1025,31 +1025,28 @@ class RootWindowContainer extends WindowContainer<DisplayContent> * manager may choose to mirror or blank the display. */ boolean handleNotObscuredLocked(WindowState w, boolean obscured, boolean syswin) { - final WindowManager.LayoutParams attrs = w.mAttrs; - final int attrFlags = attrs.flags; final boolean onScreen = w.isOnScreen(); - final boolean canBeSeen = w.isDisplayed(); - final int privateflags = attrs.privateFlags; boolean displayHasContent = false; ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON, "handleNotObscuredLocked w: %s, w.mHasSurface: %b, w.isOnScreen(): %b, w" + ".isDisplayedLw(): %b, w.mAttrs.userActivityTimeout: %d", w, w.mHasSurface, onScreen, w.isDisplayed(), w.mAttrs.userActivityTimeout); - if (w.mHasSurface && onScreen) { - if (!syswin && w.mAttrs.userActivityTimeout >= 0 && mUserActivityTimeout < 0) { - mUserActivityTimeout = w.mAttrs.userActivityTimeout; - ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON, "mUserActivityTimeout set to %d", - mUserActivityTimeout); - } + if (!onScreen) { + return false; + } + if (!syswin && w.mAttrs.userActivityTimeout >= 0 && mUserActivityTimeout < 0) { + mUserActivityTimeout = w.mAttrs.userActivityTimeout; + ProtoLog.d(WM_DEBUG_KEEP_SCREEN_ON, "mUserActivityTimeout set to %d", + mUserActivityTimeout); } - if (w.mHasSurface && canBeSeen) { + if (w.isDrawn() || (w.mActivityRecord != null && w.mActivityRecord.firstWindowDrawn + && w.mActivityRecord.isVisibleRequested())) { if (!syswin && w.mAttrs.screenBrightness >= 0 && Float.isNaN(mScreenBrightnessOverride)) { mScreenBrightnessOverride = w.mAttrs.screenBrightness; } - final int type = attrs.type; // This function assumes that the contents of the default display are processed first // before secondary displays. final DisplayContent displayContent = w.getDisplayContent(); @@ -1063,11 +1060,13 @@ class RootWindowContainer extends WindowContainer<DisplayContent> displayHasContent = true; } else if (displayContent != null && (!mObscureApplicationContentOnSecondaryDisplays - || (obscured && type == TYPE_KEYGUARD_DIALOG))) { - // Allow full screen keyguard presentation dialogs to be seen. + || displayContent.isKeyguardAlwaysUnlocked() + || (obscured && w.mAttrs.type == TYPE_KEYGUARD_DIALOG))) { + // Allow full screen keyguard presentation dialogs to be seen, or simply ignore the + // keyguard if this display is always unlocked. displayHasContent = true; } - if ((privateflags & PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE) != 0) { + if ((w.mAttrs.privateFlags & PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE) != 0) { mSustainedPerformanceModeCurrent = true; } } diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java index 586077658a26..c914fa10687f 100644 --- a/services/core/java/com/android/server/wm/SafeActivityOptions.java +++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java @@ -48,6 +48,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.Slog; import android.view.RemoteAnimationAdapter; +import android.window.RemoteTransition; import android.window.WindowContainerToken; import com.android.internal.annotations.VisibleForTesting; @@ -385,6 +386,18 @@ public class SafeActivityOptions { throw new SecurityException(msg); } + // Check permission for remote transitions + final RemoteTransition transition = options.getRemoteTransition(); + if (transition != null && supervisor.mService.checkPermission( + CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, callingPid, callingUid) + != PERMISSION_GRANTED) { + final String msg = "Permission Denial: starting " + getIntentString(intent) + + " from " + callerApp + " (pid=" + callingPid + + ", uid=" + callingUid + ") with remoteTransition"; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + // If launched from bubble is specified, then ensure that the caller is system or sysui. if (options.getLaunchedFromBubble() && !isSystemOrSystemUI(callingPid, callingUid)) { final String msg = "Permission Denial: starting " + getIntentString(intent) diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 39772dda4792..9c23beb21a92 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3473,6 +3473,11 @@ class Task extends TaskFragment { top.mLetterboxUiController.getLetterboxPositionForVerticalReachability(); } } + // User Aspect Ratio Settings is enabled if the app is not in SCM + info.topActivityEligibleForUserAspectRatioButton = + mWmService.mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled() + && top != null && !info.topActivityInSizeCompat; + info.topActivityBoundsLetterboxed = top != null && top.areBoundsLetterboxed(); } /** diff --git a/services/core/java/com/android/server/wm/TaskPersister.java b/services/core/java/com/android/server/wm/TaskPersister.java index 29c192cc7c48..7e0e5a4cc599 100644 --- a/services/core/java/com/android/server/wm/TaskPersister.java +++ b/services/core/java/com/android/server/wm/TaskPersister.java @@ -549,8 +549,8 @@ public class TaskPersister implements PersisterQueue.Listener { // Write out one task. byte[] data = null; Task task = mTask; - if (DEBUG) Slog.d(TAG, "Writing task=" + task); synchronized (mService.mGlobalLock) { + if (DEBUG) Slog.d(TAG, "Writing task=" + task); if (task.inRecents) { // Still there. try { diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 79a54c3cfb32..1b27bb17f599 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -27,6 +27,7 @@ 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.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; +import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; @@ -77,6 +78,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; +import android.os.Looper; import android.os.RemoteException; import android.os.SystemClock; import android.os.Trace; @@ -613,12 +615,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } if (mParticipants.contains(wc)) return; - // Wallpaper is like in a static drawn state unless display may have changes, so exclude - // the case to reduce transition latency waiting for the unchanged wallpaper to redraw. - final boolean needSync = (!isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent)) - // Transient-hide may be hidden later, so no need to request redraw. - && !isInTransientHide(wc); - if (needSync) { + // Transient-hide may be hidden later, so no need to request redraw. + if (!isInTransientHide(wc)) { mSyncEngine.addToSyncSet(mSyncId, wc); } ChangeInfo info = mChanges.get(wc); @@ -1101,6 +1099,16 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final Task task = ar.getTask(); if (task == null) continue; boolean visibleAtTransitionEnd = mVisibleAtTransitionEndTokens.contains(ar); + // visibleAtTransitionEnd is used to guard against pre-maturely committing + // invisible on a window which is actually hidden by a later transition and not this + // one. However, for a transient launch, we can't use this mechanism because the + // visibility is determined at finish. Instead, use a different heuristic: don't + // commit invisible if the window is already in a later transition. That later + // transition will then handle the commit. + if (isTransientLaunch(ar) && !ar.isVisibleRequested() + && mController.inCollectingTransition(ar)) { + visibleAtTransitionEnd = true; + } // We need both the expected visibility AND current requested-visibility to be // false. If it is expected-visible but not currently visible, it means that // another animation is queued-up to animate this to invisibility, so we can't @@ -2262,7 +2270,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // transitions anyways). return wc.getParent().asDisplayContent().getWindowingLayer(); } - return wc.getParent().getSurfaceControl(); + return wc.getParentSurfaceControl(); } /** @@ -2657,7 +2665,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } private void validateKeyguardOcclusion() { - if ((mFlags & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) { + if ((mFlags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) { mController.mStateValidators.add( mController.mAtm.mWindowManager.mPolicy::applyKeyguardOcclusionChange); } @@ -2686,6 +2694,26 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { }); } + /** + * Returns {@code true} if the transition and the corresponding transaction should be applied + * on display thread. Currently, this only checks for display rotation change because the order + * of dispatching the new display info will be after requesting the windows to sync drawing. + * That avoids potential flickering of screen overlays (e.g. cutout, rounded corner). Also, + * because the display thread has a higher priority, it is faster to perform the configuration + * changes and window hierarchy traversal. + */ + boolean shouldApplyOnDisplayThread() { + for (int i = mParticipants.size() - 1; i >= 0; --i) { + final DisplayContent dc = mParticipants.valueAt(i).asDisplayContent(); + if (dc == null) continue; + final ChangeInfo changeInfo = mChanges.get(dc); + if (changeInfo != null && changeInfo.mRotation != dc.getRotation()) { + return Looper.myLooper() != mController.mAtm.mWindowManager.mH.getLooper(); + } + } + return false; + } + /** Applies the new configuration for the changed displays. */ void applyDisplayChangeIfNeeded() { for (int i = mParticipants.size() - 1; i >= 0; --i) { diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index a539a4893d4f..79cb61be5948 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -588,15 +588,6 @@ class TransitionController { /** Sets the sync method for the display change. */ private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange, @NonNull Transition displayTransition, @NonNull DisplayContent displayContent) { - final int startRotation = displayChange.getStartRotation(); - final int endRotation = displayChange.getEndRotation(); - if (startRotation != endRotation && (startRotation + endRotation) % 2 == 0) { - // 180 degrees rotation change may not change screen size. So the clients may draw - // some frames before and after the display projection transaction is applied by the - // remote player. That may cause some buffers to show in different rotation. So use - // sync method to pause clients drawing until the projection transaction is applied. - mSyncEngine.setSyncMethod(displayTransition.getSyncId(), BLASTSyncEngine.METHOD_BLAST); - } final Rect startBounds = displayChange.getStartAbsBounds(); final Rect endBounds = displayChange.getEndAbsBounds(); if (startBounds == null || endBounds == null) return; diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index 9e7df004806b..805e7ffe7d76 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -30,6 +30,7 @@ import android.graphics.Region; import android.hardware.display.DisplayManagerInternal; import android.os.Bundle; import android.os.IBinder; +import android.os.Message; import android.util.Pair; import android.view.ContentRecordingSession; import android.view.Display; @@ -515,12 +516,13 @@ public abstract class WindowManagerInternal { * Invalidate all visible windows on a given display, and report back on the callback when all * windows have redrawn. * - * @param callback reporting callback to be called when all windows have redrawn. + * @param message The message will be sent when all windows have redrawn. Note that the message + * must be obtained from handler, otherwise it will throw NPE. * @param timeout calls the callback anyway after the timeout. * @param displayId waits for the windows on the given display, INVALID_DISPLAY to wait for all * windows on all displays. */ - public abstract void waitForAllWindowsDrawn(Runnable callback, long timeout, int displayId); + public abstract void waitForAllWindowsDrawn(Message message, long timeout, int displayId); /** * Overrides the display size. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 6f4f075f1428..cb1c08645458 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -601,7 +601,7 @@ public class WindowManagerService extends IWindowManager.Stub * The callbacks to make when the windows all have been drawn for a given * {@link WindowContainer}. */ - final HashMap<WindowContainer, Runnable> mWaitingForDrawnCallbacks = new HashMap<>(); + final ArrayMap<WindowContainer<?>, Message> mWaitingForDrawnCallbacks = new ArrayMap<>(); /** List of window currently causing non-system overlay windows to be hidden. */ private ArrayList<WindowState> mHidingNonSystemOverlayWindows = new ArrayList<>(); @@ -5359,8 +5359,6 @@ public class WindowManagerService extends IWindowManager.Stub public static final int CLIENT_FREEZE_TIMEOUT = 30; public static final int NOTIFY_ACTIVITY_DRAWN = 32; - public static final int ALL_WINDOWS_DRAWN = 33; - public static final int NEW_ANIMATOR_SCALE = 34; public static final int SHOW_EMULATOR_DISPLAY_OVERLAY = 36; @@ -5482,7 +5480,7 @@ public class WindowManagerService extends IWindowManager.Stub } case WAITING_FOR_DRAWN_TIMEOUT: { - Runnable callback = null; + final Message callback; final WindowContainer<?> container = (WindowContainer<?>) msg.obj; synchronized (mGlobalLock) { ProtoLog.w(WM_ERROR, "Timeout waiting for drawn: undrawn=%s", @@ -5496,7 +5494,7 @@ public class WindowManagerService extends IWindowManager.Stub callback = mWaitingForDrawnCallbacks.remove(container); } if (callback != null) { - callback.run(); + callback.sendToTarget(); } break; } @@ -5520,17 +5518,6 @@ public class WindowManagerService extends IWindowManager.Stub } break; } - case ALL_WINDOWS_DRAWN: { - Runnable callback; - final WindowContainer container = (WindowContainer) msg.obj; - synchronized (mGlobalLock) { - callback = mWaitingForDrawnCallbacks.remove(container); - } - if (callback != null) { - callback.run(); - } - break; - } case NEW_ANIMATOR_SCALE: { float scale = getCurrentAnimatorScale(); ValueAnimator.setDurationScale(scale); @@ -6078,7 +6065,8 @@ public class WindowManagerService extends IWindowManager.Stub if (mWaitingForDrawnCallbacks.isEmpty()) { return; } - mWaitingForDrawnCallbacks.forEach((container, callback) -> { + for (int i = mWaitingForDrawnCallbacks.size() - 1; i >= 0; i--) { + final WindowContainer<?> container = mWaitingForDrawnCallbacks.keyAt(i); for (int j = container.mWaitingForDrawn.size() - 1; j >= 0; j--) { final WindowState win = (WindowState) container.mWaitingForDrawn.get(j); ProtoLog.i(WM_DEBUG_SCREEN_ON, @@ -6104,9 +6092,9 @@ public class WindowManagerService extends IWindowManager.Stub if (container.mWaitingForDrawn.isEmpty()) { ProtoLog.d(WM_DEBUG_SCREEN_ON, "All windows drawn!"); mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, container); - mH.sendMessage(mH.obtainMessage(H.ALL_WINDOWS_DRAWN, container)); + mWaitingForDrawnCallbacks.removeAt(i).sendToTarget(); } - }); + } } private void traceStartWaitingForWindowDrawn(WindowState window) { @@ -7818,13 +7806,14 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void waitForAllWindowsDrawn(Runnable callback, long timeout, int displayId) { + public void waitForAllWindowsDrawn(Message message, long timeout, int displayId) { + Objects.requireNonNull(message.getTarget()); final WindowContainer<?> container = displayId == INVALID_DISPLAY ? mRoot : mRoot.getDisplayContent(displayId); if (container == null) { // The waiting container doesn't exist, no need to wait to run the callback. Run and // return; - callback.run(); + message.sendToTarget(); return; } boolean allWindowsDrawn = false; @@ -7841,13 +7830,13 @@ public class WindowManagerService extends IWindowManager.Stub } } - mWaitingForDrawnCallbacks.put(container, callback); + mWaitingForDrawnCallbacks.put(container, message); mH.sendNewMessageDelayed(H.WAITING_FOR_DRAWN_TIMEOUT, container, timeout); checkDrawnWindowsLocked(); } } if (allWindowsDrawn) { - callback.run(); + message.sendToTarget(); } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 7b64fed3e116..32d574a0fa10 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -321,9 +321,18 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub applyTransaction(wct, -1 /*syncId*/, null /*transition*/, caller); return transition.getToken(); } - transition.start(); transition.mLogger.mStartWCT = wct; - applyTransaction(wct, -1 /*syncId*/, transition, caller); + if (transition.shouldApplyOnDisplayThread()) { + mService.mH.post(() -> { + synchronized (mService.mGlobalLock) { + transition.start(); + applyTransaction(wct, -1 /* syncId */, transition, caller); + } + }); + } else { + transition.start(); + applyTransaction(wct, -1 /* syncId */, transition, caller); + } // Since the transition is already provided, it means WMCore is determining the // "readiness lifecycle" outside the provided transaction, so don't set ready here. return transition.getToken(); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index cf1e51fb4e94..0d4c2d631b2c 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5665,7 +5665,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } if (mIsWallpaper) { // TODO(b/233286785): Add sync support to wallpaper. - return false; + return true; } // In the WindowContainer implementation we immediately mark ready // since a generic WindowContainer only needs to wait for its diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 31afcbf26220..9806be85467b 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -388,12 +388,23 @@ class WindowToken extends WindowContainer<WindowState> { @Override SurfaceControl.Builder makeSurface() { final SurfaceControl.Builder builder = super.makeSurface(); + // The overlay may use COLOR_MODE_A8 that needs to be at the top of the display to avoid + // additional memory usage, see b/235601833. Note that getParentSurfaceControl() must use + // the same parent. if (mRoundedCornerOverlay) { builder.setParent(null); } return builder; } + @Override + public SurfaceControl getParentSurfaceControl() { + if (mRoundedCornerOverlay) { + return null; + } + return super.getParentSurfaceControl(); + } + boolean isClientVisible() { return mClientVisible; } diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index cb0b9c9ace4f..c74c727d8cdf 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -968,7 +968,7 @@ void NativeInputManager::notifyDropWindow(const sp<IBinder>& token, float x, flo void NativeInputManager::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp, const std::set<gui::Uid>& uids) { static const bool ENABLE_INPUT_DEVICE_USAGE_METRICS = - sysprop::InputProperties::enable_input_device_usage_metrics().value_or(true); + sysprop::InputProperties::enable_input_device_usage_metrics().value_or(false); if (!ENABLE_INPUT_DEVICE_USAGE_METRICS) return; mInputManager->getMetricsCollector().notifyDeviceInteraction(deviceId, timestamp, uids); diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java index 46c90b4bc731..bafa4a56b28a 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java @@ -126,6 +126,7 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption mElementKeys = new HashSet<>(requestOption .getCredentialRetrievalData() .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS)); + mStatus = Status.PENDING; } protected ProviderRegistryGetSession(@NonNull Context context, @@ -143,6 +144,7 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption mElementKeys = new HashSet<>(requestOption .getCredentialRetrievalData() .getStringArrayList(CredentialOption.SUPPORTED_ELEMENT_KEYS)); + mStatus = Status.PENDING; } private List<Entry> prepareUiCredentialEntries( diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 6ea71e382a71..2f1d67d34c43 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -3469,8 +3469,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } revertTransferOwnershipIfNecessaryLocked(); + if (!isPolicyEngineForFinanceFlagEnabled()) { + updateUsbDataSignal(mContext, isUsbDataSignalingEnabledInternalLocked()); + } } - updateUsbDataSignal(); // In case flag value has changed, we apply it during boot to avoid doing it concurrently // with user toggling quiet mode. @@ -16224,6 +16226,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override + public ComponentName getDeviceOwnerComponent(boolean callingUserOnly) { + return DevicePolicyManagerService.this.getDeviceOwnerComponent(callingUserOnly); + } + + @Override public int getDeviceOwnerUserId() { return DevicePolicyManagerService.this.getDeviceOwnerUserId(); } @@ -19500,8 +19507,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public boolean isCurrentInputMethodSetByOwner() { final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) - || isProfileOwner(caller) || isSystemUid(caller), - "Only profile owner, device owner and system may call this method."); + || isProfileOwner(caller) || canQueryAdminPolicy(caller) || isSystemUid(caller), + "Only profile owner, device owner, a caller with QUERY_ADMIN_POLICY " + + "permission or system may call this method."); return getUserData(caller.getUserId()).mCurrentInputMethodSet; } @@ -22355,7 +22363,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public void setUsbDataSignalingEnabled(String packageName, boolean enabled) { Objects.requireNonNull(packageName, "Admin package name must be provided"); final CallerIdentity caller = getCallerIdentity(packageName); - if (!isPermissionCheckFlagEnabled()) { + if (!isPolicyEngineForFinanceFlagEnabled()) { Preconditions.checkCallAuthorization( isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller), "USB data signaling can only be controlled by a device owner or " @@ -22364,22 +22372,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { "USB data signaling cannot be disabled."); } - synchronized (getLockObject()) { - ActiveAdmin admin; - if (isPermissionCheckFlagEnabled()) { - admin = enforcePermissionAndGetEnforcingAdmin( + if (isPolicyEngineForFinanceFlagEnabled()) { + EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( /* admin= */ null, MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING, caller.getPackageName(), - caller.getUserId()).getActiveAdmin(); + caller.getUserId()); + Preconditions.checkState(canUsbDataSignalingBeDisabled(), + "USB data signaling cannot be disabled."); + mDevicePolicyEngine.setGlobalPolicy( + PolicyDefinition.USB_DATA_SIGNALING, + enforcingAdmin, + new BooleanPolicyValue(enabled)); } else { - admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); - } - - if (admin.mUsbDataSignalingEnabled != enabled) { - admin.mUsbDataSignalingEnabled = enabled; - saveSettingsLocked(caller.getUserId()); - updateUsbDataSignal(); + ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()); + if (admin.mUsbDataSignalingEnabled != enabled) { + admin.mUsbDataSignalingEnabled = enabled; + saveSettingsLocked(caller.getUserId()); + updateUsbDataSignal(mContext, isUsbDataSignalingEnabledInternalLocked()); + } } } DevicePolicyEventLogger @@ -22389,16 +22400,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { .write(); } - private void updateUsbDataSignal() { - if (!canUsbDataSignalingBeDisabled()) { + static void updateUsbDataSignal(Context context, boolean value) { + if (!canUsbDataSignalingBeDisabledInternal(context)) { return; } - final boolean usbEnabled; - synchronized (getLockObject()) { - usbEnabled = isUsbDataSignalingEnabledInternalLocked(); - } - if (!mInjector.binderWithCleanCallingIdentity( - () -> mInjector.getUsbManager().enableUsbDataSignal(usbEnabled))) { + if (!Binder.withCleanCallingIdentity( + () -> context.getSystemService(UsbManager.class).enableUsbDataSignal(value))) { Slogf.w(LOG_TAG, "Failed to set usb data signaling state"); } } @@ -22406,28 +22413,26 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean isUsbDataSignalingEnabled(String packageName) { final CallerIdentity caller = getCallerIdentity(packageName); - synchronized (getLockObject()) { - // If the caller is an admin, return the policy set by itself. Otherwise - // return the device-wide policy. - if (isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller)) { - return getProfileOwnerOrDeviceOwnerLocked( - caller.getUserId()).mUsbDataSignalingEnabled; - } else { - return isUsbDataSignalingEnabledInternalLocked(); + if (isPolicyEngineForFinanceFlagEnabled()) { + Boolean enabled = mDevicePolicyEngine.getResolvedPolicy( + PolicyDefinition.USB_DATA_SIGNALING, + caller.getUserId()); + return enabled == null || enabled; + } else { + synchronized (getLockObject()) { + // If the caller is an admin, return the policy set by itself. Otherwise + // return the device-wide policy. + if (isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice( + caller)) { + return getProfileOwnerOrDeviceOwnerLocked( + caller.getUserId()).mUsbDataSignalingEnabled; + } else { + return isUsbDataSignalingEnabledInternalLocked(); + } } } } - @Override - public boolean isUsbDataSignalingEnabledForUser(int userId) { - final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isSystemUid(caller)); - - synchronized (getLockObject()) { - return isUsbDataSignalingEnabledInternalLocked(); - } - } - private boolean isUsbDataSignalingEnabledInternalLocked() { // TODO(b/261999445): remove ActiveAdmin admin; @@ -22442,9 +22447,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean canUsbDataSignalingBeDisabled() { - return mInjector.binderWithCleanCallingIdentity(() -> - mInjector.getUsbManager() != null - && mInjector.getUsbManager().getUsbHalVersion() >= UsbManager.USB_HAL_V1_3 + return canUsbDataSignalingBeDisabledInternal(mContext); + } + + private static boolean canUsbDataSignalingBeDisabledInternal(Context context) { + return Binder.withCleanCallingIdentity(() -> + context.getSystemService(UsbManager.class) != null + && context.getSystemService(UsbManager.class).getUsbHalVersion() + >= UsbManager.USB_HAL_V1_3 ); } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index 7e48407fc911..7a877b9afdad 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -332,6 +332,14 @@ final class PolicyDefinition<V> { PolicyEnforcerCallbacks::setPersonalAppsSuspended, new BooleanPolicySerializer()); + static PolicyDefinition<Boolean> USB_DATA_SIGNALING = new PolicyDefinition<>( + new NoArgsPolicyKey(DevicePolicyIdentifiers.USB_DATA_SIGNALING_POLICY), + // usb data signaling is enabled by default, hence disabling it is more restrictive. + FALSE_MORE_RESTRICTIVE, + POLICY_FLAG_GLOBAL_ONLY_POLICY, + (Boolean value, Context context, Integer userId, PolicyKey policyKey) -> + PolicyEnforcerCallbacks.setUsbDataSignalingEnabled(value, context), + new BooleanPolicySerializer()); private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>(); private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>(); @@ -364,6 +372,8 @@ final class PolicyDefinition<V> { SCREEN_CAPTURE_DISABLED); POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PERSONAL_APPS_SUSPENDED_POLICY, PERSONAL_APPS_SUSPENDED); + POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.USB_DATA_SIGNALING_POLICY, + USB_DATA_SIGNALING); // User Restriction Policies USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MODIFY_ACCOUNTS, /* flags= */ 0); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java index 3b048b250075..6570ce1cd500 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java @@ -302,4 +302,14 @@ final class PolicyEnforcerCallbacks { Slogf.wtf(LOG_TAG, "Failed to suspend apps: " + String.join(",", failedApps)); } } + + static boolean setUsbDataSignalingEnabled(@Nullable Boolean value, @NonNull Context context) { + return Binder.withCleanCallingIdentity(() -> { + Objects.requireNonNull(context); + + boolean enabled = value == null || value; + DevicePolicyManagerService.updateUsbDataSignal(context, enabled); + return true; + }); + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index d1bf4fc2e6bc..2f2e1cbaef5e 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -337,6 +337,8 @@ public final class SystemServer implements Dumpable { "com.android.clockwork.time.WearTimeService"; private static final String WEAR_SETTINGS_SERVICE_CLASS = "com.android.clockwork.settings.WearSettingsService"; + private static final String WRIST_ORIENTATION_SERVICE_CLASS = + "com.android.clockwork.wristorientation.WristOrientationService"; private static final String ACCOUNT_SERVICE_CLASS = "com.android.server.accounts.AccountManagerService$Lifecycle"; private static final String CONTENT_SERVICE_CLASS = @@ -1557,9 +1559,11 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(ROLE_SERVICE_CLASS); t.traceEnd(); - t.traceBegin("StartVibratorManagerService"); - mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class); - t.traceEnd(); + if (!isTv) { + t.traceBegin("StartVibratorManagerService"); + mSystemServiceManager.startService(VibratorManagerService.Lifecycle.class); + t.traceEnd(); + } t.traceBegin("StartDynamicSystemService"); dynamicSystem = new DynamicSystemService(context); @@ -2582,7 +2586,7 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(MediaProjectionManagerService.class); t.traceEnd(); - if (isWatch) { + if (isWatch) { // Must be started before services that depend it, e.g. WearConnectivityService t.traceBegin("StartWearPowerService"); mSystemServiceManager.startService(WEAR_POWER_SERVICE_CLASS); @@ -2615,6 +2619,14 @@ public final class SystemServer implements Dumpable { t.traceBegin("StartWearModeService"); mSystemServiceManager.startService(WEAR_MODE_SERVICE_CLASS); t.traceEnd(); + + boolean enableWristOrientationService = SystemProperties.getBoolean( + "config.enable_wristorientation", false); + if (enableWristOrientationService) { + t.traceBegin("StartWristOrientationService"); + mSystemServiceManager.startService(WRIST_ORIENTATION_SERVICE_CLASS); + t.traceEnd(); + } } if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_SLICES_DISABLED)) { @@ -2714,10 +2726,12 @@ public final class SystemServer implements Dumpable { Slog.d(TAG, "TranslationService not defined by OEM"); } - // Selection toolbar service - t.traceBegin("StartSelectionToolbarManagerService"); - mSystemServiceManager.startService(SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS); - t.traceEnd(); + if (!isTv) { + // Selection toolbar service + t.traceBegin("StartSelectionToolbarManagerService"); + mSystemServiceManager.startService(SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS); + t.traceEnd(); + } // NOTE: ClipboardService depends on ContentCapture and Autofill t.traceBegin("StartClipboardService"); diff --git a/services/tests/InputMethodSystemServerTests/OWNERS b/services/tests/InputMethodSystemServerTests/OWNERS index 1f2c036773a4..eb4fe3c12036 100644 --- a/services/tests/InputMethodSystemServerTests/OWNERS +++ b/services/tests/InputMethodSystemServerTests/OWNERS @@ -1 +1,2 @@ +# Bug component: 34867 include /services/core/java/com/android/server/inputmethod/OWNERS diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java index a6ada4d77253..869497c28def 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java @@ -20,6 +20,7 @@ import static android.inputmethodservice.InputMethodService.IME_ACTIVE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE; import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT; +import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SWITCH_USER; import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT; import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME; import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT; @@ -43,6 +44,7 @@ import android.view.inputmethod.InputMethodManager; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; @@ -165,6 +167,29 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe verify(mMockImeTargetVisibilityPolicy).removeImeScreenshot(eq(Display.DEFAULT_DISPLAY)); } + @Test + public void testApplyImeVisibility_hideImeWhenUnbinding() { + mInputMethodManagerService.setAttachedClientForTesting(null); + startInputOrWindowGainedFocus(mWindowToken, SOFT_INPUT_STATE_ALWAYS_VISIBLE); + ExtendedMockito.spyOn(mVisibilityApplier); + + synchronized (ImfLock.class) { + // Simulate the system hides the IME when switching IME services in different users. + // (e.g. unbinding the IME from the current user to the profile user) + final int displayIdToShowIme = mInputMethodManagerService.getDisplayIdToShowImeLocked(); + mInputMethodManagerService.hideCurrentInputLocked(mWindowToken, null, 0, null, + HIDE_SWITCH_USER); + mInputMethodManagerService.onUnbindCurrentMethodByReset(); + + // Expects applyImeVisibility() -> hideIme() will be called to notify WM for syncing + // the IME hidden state. + verify(mVisibilityApplier).applyImeVisibility(eq(mWindowToken), any(), + eq(STATE_HIDE_IME)); + verify(mInputMethodManagerService.mWindowManagerInternal).hideIme( + eq(mWindowToken), eq(displayIdToShowIme), eq(null)); + } + } + private InputBindResult startInputOrWindowGainedFocus(IBinder windowToken, int softInputMode) { return mInputMethodManagerService.startInputOrWindowGainedFocus( StartInputReason.WINDOW_FOCUS_GAIN /* startInputReason */, diff --git a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java index e33ca7775e22..b7a0cf389396 100644 --- a/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java +++ b/services/tests/PackageManagerServiceTests/appenumeration/src/com/android/server/pm/test/appenumeration/CrossUserPackageVisibilityTests.java @@ -138,6 +138,14 @@ public class CrossUserPackageVisibilityTests { } @Test + public void testGetUserMinAspectRatio_withCrossUserId() { + final int crossUserId = UserHandle.myUserId() + 1; + assertThrows(SecurityException.class, + () -> mIPackageManager.getUserMinAspectRatio( + mInstrumentation.getContext().getPackageName(), crossUserId)); + } + + @Test public void testIsPackageSignedByKeySet_cannotDetectCrossUserPkg() throws Exception { final KeySet keySet = mIPackageManager.getSigningKeySet(mContext.getPackageName()); assertThrows(IllegalArgumentException.class, diff --git a/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6 b/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6 Binary files differindex 30bb6478d18d..6feebb8c833c 100644 --- a/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6 +++ b/services/tests/PackageManagerServiceTests/server/res/raw/install_app2_cert5_rotated_cert6 diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java index 435f0d7f5f15..a805e5c9f87e 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java @@ -20,6 +20,7 @@ import static com.android.compatibility.common.util.ShellUtils.runShellCommand; import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import static java.lang.reflect.Modifier.isFinal; @@ -138,7 +139,7 @@ public class PackageManagerServiceTest { .setSecondaryCpuAbiString("secondaryCpuAbiString") .setCpuAbiOverrideString("cpuAbiOverrideString") .build(); - pri.populateUsers(new int[] { + pri.populateUsers(new int[]{ 1, 2, 3, 4, 5 }, setting); Assert.assertNotNull(pri.mBroadcastUsers); @@ -150,7 +151,7 @@ public class PackageManagerServiceTest { pri.mBroadcastUsers = null; final int EXCLUDED_USER_ID = 4; setting.setInstantApp(true, EXCLUDED_USER_ID); - pri.populateUsers(new int[] { + pri.populateUsers(new int[]{ 1, 2, 3, EXCLUDED_USER_ID, 5 }, setting); Assert.assertNotNull(pri.mBroadcastUsers); @@ -164,8 +165,8 @@ public class PackageManagerServiceTest { @Test public void testPartitions() { - String[] partitions = { "system", "vendor", "odm", "oem", "product", "system_ext" }; - String[] appdir = { "app", "priv-app" }; + String[] partitions = {"system", "vendor", "odm", "oem", "product", "system_ext"}; + String[] appdir = {"app", "priv-app"}; for (int i = 0; i < partitions.length; i++) { final ScanPartition scanPartition = PackageManagerService.SYSTEM_PARTITIONS.get(i); @@ -425,10 +426,10 @@ public class PackageManagerServiceTest { private String displayName(Method m) { String r = m.getName(); String p = Arrays.toString(m.getGenericParameterTypes()) - .replaceAll("([a-zA-Z0-9]+\\.)+", "") - .replace("class ", "") - .replaceAll("^\\[", "(") - .replaceAll("\\]$", ")"); + .replaceAll("([a-zA-Z0-9]+\\.)+", "") + .replace("class ", "") + .replaceAll("^\\[", "(") + .replaceAll("\\]$", ")"); return r + p; } @@ -612,4 +613,22 @@ public class PackageManagerServiceTest { runShellCommand("pm uninstall " + TEST_PKG_NAME); } } + + @Test + public void testSetUserMinAspectRatio_samePackage_succeeds() throws Exception { + mIPackageManager.setUserMinAspectRatio(PACKAGE_NAME, UserHandle.myUserId(), + PackageManager.USER_MIN_ASPECT_RATIO_UNSET); + // Invoking setUserMinAspectRatio on the same package shouldn't get any exception. + } + + @Test + public void testSetUserMinAspectRatio_differentPackage_fails() { + final File testApk = new File(TEST_DATA_PATH, TEST_APP_APK); + runShellCommand("pm install " + testApk); + assertThrows(SecurityException.class, () -> { + mIPackageManager.setUserMinAspectRatio(TEST_PKG_NAME, UserHandle.myUserId(), + PackageManager.USER_MIN_ASPECT_RATIO_UNSET); + }); + runShellCommand("pm uninstall " + TEST_PKG_NAME); + } } diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java index 836f8581e8eb..16fb012b2f40 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -971,7 +971,7 @@ public class PackageManagerSettingsTests { origPkgSetting01.setUserState(0, 100, 1, true, false, false, false, 0, null, false, false, "lastDisabledCaller", new ArraySet<>(new String[]{"enabledComponent1"}), new ArraySet<>(new String[]{"disabledComponent1"}), 0, 0, "harmfulAppWarning", - "splashScreenTheme", 1000L); + "splashScreenTheme", 1000L, PackageManager.USER_MIN_ASPECT_RATIO_UNSET); final PersistableBundle appExtras1 = createPersistableBundle( PACKAGE_NAME_1, 1L, 0.01, true, "appString1"); final PersistableBundle launcherExtras1 = createPersistableBundle( @@ -1638,7 +1638,8 @@ public class PackageManagerSettingsTests { : oldUserState.getSharedLibraryOverlayPaths() == null) && userState.getSplashScreenTheme().equals( oldUserState.getSplashScreenTheme()) - && userState.getUninstallReason() == oldUserState.getUninstallReason(); + && userState.getUninstallReason() == oldUserState.getUninstallReason() + && userState.getMinAspectRatio() == oldUserState.getMinAspectRatio(); } private SharedUserSetting createSharedUserSetting(Settings settings, String userName, diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp index b242ec2db420..f1ff33809184 100644 --- a/services/tests/displayservicetests/Android.bp +++ b/services/tests/displayservicetests/Android.bp @@ -22,11 +22,20 @@ android_test { "src/**/*.java", ], + libs: [ + "android.test.mock", + ], + static_libs: [ - "services.core", - "androidx.test.runner", - "androidx.test.rules", + "androidx.test.ext.junit", + "display-core-libs", + "frameworks-base-testutils", + "junit", + "junit-params", + "platform-compat-test-rules", "platform-test-annotations", + "services.core", + "servicestests-utils", ], defaults: [ @@ -47,3 +56,10 @@ android_test { enabled: false, }, } + +java_library { + name: "display-core-libs", + srcs: [ + "src/com/android/server/display/TestUtils.java", + ], +} diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml index c2e417429eac..d2bd10dd18dc 100644 --- a/services/tests/displayservicetests/AndroidManifest.xml +++ b/services/tests/displayservicetests/AndroidManifest.xml @@ -21,6 +21,16 @@ Insert permissions here. eg: <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> --> + <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS" /> + <uses-permission android:name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" /> + <uses-permission android:name="android.permission.DEVICE_POWER" /> + <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> + <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" /> + <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS" /> + <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" /> + <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> + <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <application android:debuggable="true" android:testOnly="true"> diff --git a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java index 2c4fe536b75c..7333bc75fe9d 100644 --- a/services/tests/servicestests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/AmbientBrightnessStatsTrackerTest.java @@ -11,7 +11,7 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT 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.display; diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index a6acd60f3bd7..a6acd60f3bd7 100644 --- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java index 5f81869903c3..ee7826f13578 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessMappingStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java @@ -11,7 +11,7 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT 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.display; @@ -206,11 +206,11 @@ public class BrightnessMappingStrategyTest { BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc, mMockDwbc); strategy.setBrightnessConfiguration(null); - final int N = DISPLAY_LEVELS_BACKLIGHT.length; + final int n = DISPLAY_LEVELS_BACKLIGHT.length; final float expectedBrightness = - (float) DISPLAY_LEVELS_BACKLIGHT[N - 1] / PowerManager.BRIGHTNESS_ON; + (float) DISPLAY_LEVELS_BACKLIGHT[n - 1] / PowerManager.BRIGHTNESS_ON; assertEquals(expectedBrightness, - strategy.getBrightness(LUX_LEVELS[N - 1]), 0.0001f /*tolerance*/); + strategy.getBrightness(LUX_LEVELS[n - 1]), 0.0001f /*tolerance*/); } @Test @@ -270,10 +270,10 @@ public class BrightnessMappingStrategyTest { // Check that null returns us to the default configuration. strategy.setBrightnessConfiguration(null); - final int N = DISPLAY_LEVELS_NITS.length; - final float expectedBrightness = DISPLAY_LEVELS_NITS[N - 1] / DISPLAY_RANGE_NITS[1]; + final int n = DISPLAY_LEVELS_NITS.length; + final float expectedBrightness = DISPLAY_LEVELS_NITS[n - 1] / DISPLAY_RANGE_NITS[1]; assertEquals(expectedBrightness, - strategy.getBrightness(LUX_LEVELS[N - 1]), 0.0001f /*tolerance*/); + strategy.getBrightness(LUX_LEVELS[n - 1]), 0.0001f /*tolerance*/); } @Test diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java index 46956d74cc5c..8faaf5998d13 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessThrottlerTest.java @@ -159,7 +159,7 @@ public class BrightnessThrottlerTest { @Test public void testThermalThrottlingSingleLevel() throws Exception { final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, - 0.25f); + 0.25f); List<ThrottlingLevel> levels = new ArrayList<>(); levels.add(level); @@ -184,7 +184,7 @@ public class BrightnessThrottlerTest { assertEquals(level.brightness, throttler.getBrightnessCap(), 0f); assertTrue(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); // Set status more than high enough to trigger throttling listener.notifyThrottling(getSkinTemp(level.thermalStatus + 1)); @@ -192,7 +192,7 @@ public class BrightnessThrottlerTest { assertEquals(level.brightness, throttler.getBrightnessCap(), 0f); assertTrue(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); // Return to the lower throttling level listener.notifyThrottling(getSkinTemp(level.thermalStatus)); @@ -200,7 +200,7 @@ public class BrightnessThrottlerTest { assertEquals(level.brightness, throttler.getBrightnessCap(), 0f); assertTrue(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); // Cool down listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1)); @@ -208,15 +208,15 @@ public class BrightnessThrottlerTest { assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f); assertFalse(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); } @Test public void testThermalThrottlingMultiLevel() throws Exception { final ThrottlingLevel levelLo = new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, - 0.62f); + 0.62f); final ThrottlingLevel levelHi = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, - 0.25f); + 0.25f); List<ThrottlingLevel> levels = new ArrayList<>(); levels.add(levelLo); @@ -242,7 +242,7 @@ public class BrightnessThrottlerTest { assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f); assertTrue(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); // Set status to an intermediate throttling level listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1)); @@ -250,7 +250,7 @@ public class BrightnessThrottlerTest { assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f); assertTrue(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); // Set status to the highest configured throttling level listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus)); @@ -258,7 +258,7 @@ public class BrightnessThrottlerTest { assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f); assertTrue(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); // Set status to exceed the highest configured throttling level listener.notifyThrottling(getSkinTemp(levelHi.thermalStatus + 1)); @@ -266,7 +266,7 @@ public class BrightnessThrottlerTest { assertEquals(levelHi.brightness, throttler.getBrightnessCap(), 0f); assertTrue(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); // Return to an intermediate throttling level listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus + 1)); @@ -274,7 +274,7 @@ public class BrightnessThrottlerTest { assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f); assertTrue(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); // Return to the lowest configured throttling level listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus)); @@ -282,7 +282,7 @@ public class BrightnessThrottlerTest { assertEquals(levelLo.brightness, throttler.getBrightnessCap(), 0f); assertTrue(throttler.isThrottled()); assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL, - throttler.getBrightnessMaxReason()); + throttler.getBrightnessMaxReason()); // Cool down listener.notifyThrottling(getSkinTemp(levelLo.thermalStatus - 1)); diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java index 021f2d1df835..44c7dec7633e 100644 --- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessTrackerTest.java @@ -11,7 +11,7 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT 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.display; @@ -395,8 +395,8 @@ public class BrightnessTrackerTest { final long currentTime = mInjector.currentTimeMillis(); notifyBrightnessChanged(mTracker, brightness, displayId, new float[] {1000.0f}, new long[] {TimeUnit.NANOSECONDS.toMillis(mInjector.elapsedRealtimeNanos())}); - List<BrightnessChangeEvent> eventsNoPackage - = mTracker.getEvents(0, false).getList(); + List<BrightnessChangeEvent> eventsNoPackage = + mTracker.getEvents(0, false).getList(); List<BrightnessChangeEvent> events = mTracker.getEvents(0, true).getList(); mTracker.stop(); @@ -1037,9 +1037,9 @@ public class BrightnessTrackerTest { } void setBrightnessMode(boolean isBrightnessModeAutomatic) { - mIsBrightnessModeAutomatic = isBrightnessModeAutomatic; - mContentObserver.dispatchChange(false, null); - waitForHandler(); + mIsBrightnessModeAutomatic = isBrightnessModeAutomatic; + mContentObserver.dispatchChange(false, null); + waitForHandler(); } void sendScreenChange(boolean screenOn) { @@ -1184,8 +1184,8 @@ public class BrightnessTrackerTest { @Override public int getNightDisplayColorTemperature(Context context) { - return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, - mDefaultNightModeColorTemperature); + return mSecureIntSettings.getOrDefault(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, + mDefaultNightModeColorTemperature); } @Override diff --git a/services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java b/services/tests/displayservicetests/src/com/android/server/display/ColorFadeTest.java index 53d8de0c2bbb..53d8de0c2bbb 100644 --- a/services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/ColorFadeTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java index 130e6ad91b49..130e6ad91b49 100644 --- a/services/tests/servicestests/src/com/android/server/display/DeviceStateToLayoutMapTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index 8b04eca69132..8b04eca69132 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 5db9d1f6f5bd..d16c9c59bb1b 100644 --- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -142,7 +142,7 @@ public class DisplayManagerServiceTest { private static final float FLOAT_TOLERANCE = 0.01f; private static final String VIRTUAL_DISPLAY_NAME = "Test Virtual Display"; - private static final String PACKAGE_NAME = "com.android.frameworks.servicestests"; + private static final String PACKAGE_NAME = "com.android.frameworks.displayservicetests"; private static final long STANDARD_DISPLAY_EVENTS = DisplayManager.EVENT_FLAG_DISPLAY_ADDED | DisplayManager.EVENT_FLAG_DISPLAY_CHANGED | DisplayManager.EVENT_FLAG_DISPLAY_REMOVED; @@ -238,7 +238,7 @@ public class DisplayManagerServiceTest { boolean getHdrOutputConversionSupport() { return true; } - } + } private final DisplayManagerService.Injector mBasicInjector = new BasicInjector(); diff --git a/services/tests/servicestests/src/com/android/server/display/HbmEventTest.java b/services/tests/displayservicetests/src/com/android/server/display/HbmEventTest.java index 24fc34849829..24fc34849829 100644 --- a/services/tests/servicestests/src/com/android/server/display/HbmEventTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/HbmEventTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java index e2a66f03f5ca..76e6ec7f6780 100644 --- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeControllerTest.java @@ -483,8 +483,10 @@ public class HighBrightnessModeControllerTest { // Verify Stats HBM_ON_HDR verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 0 /*numberOfHdrLayers*/, 0, 0, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/); @@ -492,8 +494,10 @@ public class HighBrightnessModeControllerTest { // Verify Stats HBM_OFF verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_ENABLED); hbmc.onAmbientLuxChange(MINIMUM_LUX + 1); @@ -501,16 +505,20 @@ public class HighBrightnessModeControllerTest { // Verify Stats HBM_ON_SUNLIGHT verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); hbmc.onAmbientLuxChange(1); advanceTime(TIME_ALLOWED_IN_WINDOW_MILLIS / 2 + 1); // Verify Stats HBM_OFF verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP)); } @Test @@ -527,8 +535,8 @@ public class HighBrightnessModeControllerTest { // Verify Stats HBM_ON_HDR not report verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR), - anyInt()); + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR), + anyInt()); } @Test @@ -545,8 +553,8 @@ public class HighBrightnessModeControllerTest { // Verify Stats HBM_ON_SUNLIGHT not report verify(mInjectorMock, never()).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), - anyInt()); + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + anyInt()); } // Test reporting of thermal throttling when triggered externally through @@ -565,8 +573,10 @@ public class HighBrightnessModeControllerTest { BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE); advanceTime(1); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); // Brightness is thermally throttled, HBM brightness denied (NBM brightness granted) hbmc.onBrightnessChanged(nbmBrightness, hbmBrightness, @@ -578,8 +588,8 @@ public class HighBrightnessModeControllerTest { // the HBM transition point. assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode()); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT)); + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT)); } @Test @@ -592,15 +602,17 @@ public class HighBrightnessModeControllerTest { hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(0); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); // Use up all the time in the window. advanceTime(TIME_WINDOW_MILLIS + 1); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT)); + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT)); } @Test @@ -613,13 +625,17 @@ public class HighBrightnessModeControllerTest { hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(0); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); hbmc.setAutoBrightnessEnabled(AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF)); } @Test @@ -632,16 +648,18 @@ public class HighBrightnessModeControllerTest { hbmcOnBrightnessChanged(hbmc, TRANSITION_POINT + 0.01f); advanceTime(0); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); hbmc.getHdrListener().onHdrInfoChanged(null /*displayToken*/, 1 /*numberOfHdrLayers*/, DISPLAY_WIDTH, DISPLAY_HEIGHT, 0 /*flags*/, 1.0f /*maxDesiredHdrSdrRatio*/); advanceTime(0); verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING)); + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR), + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING)); } @Test @@ -657,15 +675,17 @@ public class HighBrightnessModeControllerTest { assertEquals(HIGH_BRIGHTNESS_MODE_SUNLIGHT, hbmc.getHighBrightnessMode()); // verify HBM_ON_SUNLIGHT verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN)); hbmcOnBrightnessChanged(hbmc, DEFAULT_MIN); // verify HBM_SV_OFF due to LOW_REQUESTED_BRIGHTNESS verify(mInjectorMock).reportHbmStateChange(eq(displayStatsId), - eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), - eq(FrameworkStatsLog - .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS)); + eq(FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF), + eq(FrameworkStatsLog + .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS)); } private void assertState(HighBrightnessModeController hbmc, diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataTest.java index ede54e096ad0..ede54e096ad0 100644 --- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeMetadataTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/HighBrightnessModeMetadataTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java index 1eec70da3d20..1eec70da3d20 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java index 30ff8ba6e331..20654797a5d2 100644 --- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java @@ -40,6 +40,7 @@ import androidx.test.filters.SmallTest; import com.android.server.display.layout.Layout; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import java.io.InputStream; @@ -121,7 +122,9 @@ public class LogicalDisplayTest { assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition()); } + // TODO: b/288880734 - fix test after display tests migration @Test + @Ignore public void testDisplayInputFlags() { SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false); diff --git a/services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java index c379d6b79ee7..c379d6b79ee7 100644 --- a/services/tests/servicestests/src/com/android/server/display/NormalBrightnessModeControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java index 642f54c25a46..9f91916a4046 100644 --- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/PersistentDataStoreTest.java @@ -11,7 +11,7 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT 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.display; diff --git a/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java index 5b10dc4e0bab..5b10dc4e0bab 100644 --- a/services/tests/servicestests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/ScreenOffBrightnessSensorControllerTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/utils/SensorUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/SensorUtilsTest.java index 4494b0c412dc..4494b0c412dc 100644 --- a/services/tests/servicestests/src/com/android/server/display/utils/SensorUtilsTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/SensorUtilsTest.java diff --git a/services/tests/displayservicetests/src/com/android/server/display/TestUtils.java b/services/tests/displayservicetests/src/com/android/server/display/TestUtils.java new file mode 100644 index 000000000000..8b45145b160f --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/TestUtils.java @@ -0,0 +1,94 @@ +/* + * 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.display; + +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.input.InputSensorInfo; +import android.os.Parcel; +import android.os.SystemClock; +import android.view.DisplayAddress; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public final class TestUtils { + + public static SensorEvent createSensorEvent(Sensor sensor, int value) throws Exception { + final Constructor<SensorEvent> constructor = + SensorEvent.class.getDeclaredConstructor(int.class); + constructor.setAccessible(true); + final SensorEvent event = constructor.newInstance(1); + event.sensor = sensor; + event.values[0] = value; + event.timestamp = SystemClock.elapsedRealtimeNanos(); + return event; + } + + + public static void setSensorType(Sensor sensor, int type, String strType) throws Exception { + Method setter = Sensor.class.getDeclaredMethod("setType", Integer.TYPE); + setter.setAccessible(true); + setter.invoke(sensor, type); + if (strType != null) { + Field f = sensor.getClass().getDeclaredField("mStringType"); + f.setAccessible(true); + f.set(sensor, strType); + } + } + + public static void setMaximumRange(Sensor sensor, float maximumRange) throws Exception { + Method setter = Sensor.class.getDeclaredMethod("setRange", Float.TYPE, Float.TYPE); + setter.setAccessible(true); + setter.invoke(sensor, maximumRange, 1); + } + + public static Sensor createSensor(int type, String strType) throws Exception { + Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor(); + constr.setAccessible(true); + Sensor sensor = constr.newInstance(); + setSensorType(sensor, type, strType); + return sensor; + } + + public static Sensor createSensor(int type, String strType, float maximumRange) + throws Exception { + Constructor<Sensor> constr = Sensor.class.getDeclaredConstructor(); + constr.setAccessible(true); + Sensor sensor = constr.newInstance(); + setSensorType(sensor, type, strType); + setMaximumRange(sensor, maximumRange); + return sensor; + } + + public static Sensor createSensor(String type, String name) { + return new Sensor(new InputSensorInfo( + name, "vendor", 0, 0, 0, 1f, 1f, 1, 1, 1, 1, + type, "", 0, 0, 0)); + } + + /** + * Create a custom {@link DisplayAddress} to ensure we're not relying on any specific + * display-address implementation in our code. Intentionally uses default object (reference) + * equality rules. + */ + public static class TestDisplayAddress extends DisplayAddress { + @Override + public void writeToParcel(Parcel out, int flags) { } + } +} diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java index c0c63c69add8..c0c63c69add8 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessEventTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java index e58b3e891b70..e58b3e891b70 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java index c4f483810478..c4f483810478 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java index a9e616d766c6..a9e616d766c6 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java index b652576a75c8..b652576a75c8 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java index c4346317a6ef..c4346317a6ef 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java index d60caf6efb7a..d60caf6efb7a 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java index 081f19d19f75..081f19d19f75 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java index 530245dacd8b..530245dacd8b 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java index 7147aa8d3701..7147aa8d3701 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java index 9830edbea645..9830edbea645 100644 --- a/services/tests/servicestests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/AppSaturationControllerTest.java index a525814435ea..a525814435ea 100644 --- a/services/tests/servicestests/src/com/android/server/display/color/AppSaturationControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/color/AppSaturationControllerTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/CctEvaluatorTest.java index b96666ae40a3..b96666ae40a3 100644 --- a/services/tests/servicestests/src/com/android/server/display/color/CctEvaluatorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/color/CctEvaluatorTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java index 618ab1b75587..c7c09b5deb35 100644 --- a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/color/ColorDisplayServiceTest.java @@ -1093,15 +1093,15 @@ public class ColorDisplayServiceTest { @Test public void compositionColorSpaces_invalidResources() { when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes)) - .thenReturn(new int[] { - ColorDisplayManager.COLOR_MODE_NATURAL, - // Missing second color mode - }); + .thenReturn(new int[] { + ColorDisplayManager.COLOR_MODE_NATURAL, + // Missing second color mode + }); when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces)) - .thenReturn(new int[] { - Display.COLOR_MODE_SRGB, - Display.COLOR_MODE_DISPLAY_P3 - }); + .thenReturn(new int[] { + Display.COLOR_MODE_SRGB, + Display.COLOR_MODE_DISPLAY_P3 + }); setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); startService(); verify(mDisplayTransformManager).setColorMode( @@ -1111,13 +1111,13 @@ public class ColorDisplayServiceTest { @Test public void compositionColorSpaces_validResources_validColorMode() { when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes)) - .thenReturn(new int[] { - ColorDisplayManager.COLOR_MODE_NATURAL - }); + .thenReturn(new int[] { + ColorDisplayManager.COLOR_MODE_NATURAL + }); when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces)) - .thenReturn(new int[] { - Display.COLOR_MODE_SRGB, - }); + .thenReturn(new int[] { + Display.COLOR_MODE_SRGB, + }); setColorMode(ColorDisplayManager.COLOR_MODE_NATURAL); startService(); verify(mDisplayTransformManager).setColorMode( @@ -1127,13 +1127,13 @@ public class ColorDisplayServiceTest { @Test public void compositionColorSpaces_validResources_invalidColorMode() { when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorModes)) - .thenReturn(new int[] { - ColorDisplayManager.COLOR_MODE_NATURAL - }); + .thenReturn(new int[] { + ColorDisplayManager.COLOR_MODE_NATURAL + }); when(mResourcesSpy.getIntArray(R.array.config_displayCompositionColorSpaces)) - .thenReturn(new int[] { - Display.COLOR_MODE_SRGB, - }); + .thenReturn(new int[] { + Display.COLOR_MODE_SRGB, + }); setColorMode(ColorDisplayManager.COLOR_MODE_BOOSTED); startService(); verify(mDisplayTransformManager).setColorMode( @@ -1143,7 +1143,7 @@ public class ColorDisplayServiceTest { @Test public void getColorMode_noAvailableModes_returnsNotSet() { when(mResourcesSpy.getIntArray(R.array.config_availableColorModes)) - .thenReturn(new int[] {}); + .thenReturn(new int[] {}); startService(); verify(mDisplayTransformManager, never()).setColorMode(anyInt(), any(), anyInt()); assertThat(mBinderService.getColorMode()).isEqualTo(-1); diff --git a/services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java index e0bef1a83821..e0bef1a83821 100644 --- a/services/tests/servicestests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/color/DisplayWhiteBalanceTintControllerTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java index 4f0cb324f17f..4f0cb324f17f 100644 --- a/services/tests/servicestests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/color/GlobalSaturationTintControllerTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java index 35014dcb7492..35014dcb7492 100644 --- a/services/tests/servicestests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/color/ReduceBrightColorsTintControllerTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java index 04273d6f4ed6..04273d6f4ed6 100644 --- a/services/tests/servicestests/src/com/android/server/display/mode/DisplayModeDirectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java index 9ab6ee5bd230..9ab6ee5bd230 100644 --- a/services/tests/servicestests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/SkinThermalStatusObserverTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/mode/VotesStorageTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java index 287fdd5c344b..287fdd5c344b 100644 --- a/services/tests/servicestests/src/com/android/server/display/mode/VotesStorageTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VotesStorageTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/utils/AmbientFilterTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/AmbientFilterTest.java index 9b76b13d2ede..9b76b13d2ede 100644 --- a/services/tests/servicestests/src/com/android/server/display/utils/AmbientFilterTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/utils/AmbientFilterTest.java diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java index 4d2551087c59..4d2551087c59 100644 --- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java +++ b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientFilterStubber.java diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java index ac97911027bf..f975b6fd1d6f 100644 --- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientLuxTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientLuxTest.java @@ -268,9 +268,9 @@ public final class AmbientLuxTest { controller.mBrightnessFilter = spy(new AmbientFilterStubber()); for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) { - setEstimatedBrightnessAndUpdate(controller, luxOverride); - assertEquals(controller.mPendingAmbientColorTemperature, - ambientColorTemperature, 0.001); + setEstimatedBrightnessAndUpdate(controller, luxOverride); + assertEquals(controller.mPendingAmbientColorTemperature, + ambientColorTemperature, 0.001); } } @@ -286,9 +286,9 @@ public final class AmbientLuxTest { controller.mBrightnessFilter = spy(new AmbientFilterStubber()); for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) { - setEstimatedBrightnessAndUpdate(controller, luxOverride); - assertEquals(controller.mPendingAmbientColorTemperature, - ambientColorTemperature, 0.001); + setEstimatedBrightnessAndUpdate(controller, luxOverride); + assertEquals(controller.mPendingAmbientColorTemperature, + ambientColorTemperature, 0.001); } } @@ -366,22 +366,22 @@ public final class AmbientLuxTest { @Test public void testSpline_InvalidCombinations() throws Exception { - setBrightnesses(100.0f, 200.0f); - setBiases(0.0f, 1.0f); - setHighLightBrightnesses(150.0f, 250.0f); - setHighLightBiases(0.0f, 1.0f); - - DisplayWhiteBalanceController controller = - DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy); - final float ambientColorTemperature = 8000.0f; - setEstimatedColorTemperature(controller, ambientColorTemperature); - controller.mBrightnessFilter = spy(new AmbientFilterStubber()); - - for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) { - setEstimatedBrightnessAndUpdate(controller, luxOverride); - assertEquals(controller.mPendingAmbientColorTemperature, - ambientColorTemperature, 0.001); - } + setBrightnesses(100.0f, 200.0f); + setBiases(0.0f, 1.0f); + setHighLightBrightnesses(150.0f, 250.0f); + setHighLightBiases(0.0f, 1.0f); + + DisplayWhiteBalanceController controller = + DisplayWhiteBalanceFactory.create(mHandler, mSensorManagerMock, mResourcesSpy); + final float ambientColorTemperature = 8000.0f; + setEstimatedColorTemperature(controller, ambientColorTemperature); + controller.mBrightnessFilter = spy(new AmbientFilterStubber()); + + for (float luxOverride = 0.1f; luxOverride <= 10000; luxOverride *= 10) { + setEstimatedBrightnessAndUpdate(controller, luxOverride); + assertEquals(controller.mPendingAmbientColorTemperature, + ambientColorTemperature, 0.001); + } } @Test @@ -486,7 +486,7 @@ public final class AmbientLuxTest { private void mockResourcesFloat(int id, float floatValue) { doAnswer(new Answer<Void>() { public Void answer(InvocationOnMock invocation) { - TypedValue value = (TypedValue)invocation.getArgument(1); + TypedValue value = (TypedValue) invocation.getArgument(1); value.type = TypedValue.TYPE_FLOAT; value.data = Float.floatToIntBits(floatValue); return null; diff --git a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientSensorTest.java index 3e3e535df986..3e3e535df986 100644 --- a/services/tests/servicestests/src/com/android/server/display/whitebalance/AmbientSensorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/whitebalance/AmbientSensorTest.java diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java index 32243f04f6e8..212a243c6a9e 100644 --- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -2221,7 +2221,7 @@ public class GameManagerServiceTests { String[] packages = {mPackageName}; when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true); } @@ -2238,12 +2238,12 @@ public class GameManagerServiceTests { doAnswer(inv -> powerState.put(inv.getArgument(0), inv.getArgument(1))) .when(mMockPowerManager).setPowerMode(anyInt(), anyBoolean()); gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); assertTrue(powerState.get(Mode.GAME)); gameManagerService.mUidObserver.onUidStateChanged( DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); gameManagerService.mUidObserver.onUidStateChanged( - somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0); assertTrue(powerState.get(Mode.GAME)); gameManagerService.mUidObserver.onUidStateChanged( somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); @@ -2260,13 +2260,13 @@ public class GameManagerServiceTests { int somePackageId = DEFAULT_PACKAGE_UID + 1; when(mMockPackageManager.getPackagesForUid(somePackageId)).thenReturn(packages2); gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); gameManagerService.mUidObserver.onUidStateChanged( - somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + somePackageId, ActivityManager.PROCESS_STATE_TOP, 0, 0); gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); gameManagerService.mUidObserver.onUidStateChanged( - somePackageId, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + somePackageId, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, true); verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false); } @@ -2277,9 +2277,9 @@ public class GameManagerServiceTests { String[] packages = {mPackageName}; when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages); gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0); gameManagerService.mUidObserver.onUidStateChanged( - DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0); + DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0, 0); verify(mMockPowerManager, times(1)).setPowerMode(Mode.GAME, false); } diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java index 434a75f0e218..b5f03bab291b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java @@ -1086,6 +1086,16 @@ public final class DisplayPowerController2Test { .getThermalBrightnessThrottlingDataMapByThrottlingId(); } + @Test + public void testSetBrightness_BrightnessShouldBeClamped() { + float clampedBrightness = 0.6f; + when(mHolder.hbmController.getCurrentBrightnessMax()).thenReturn(clampedBrightness); + + mHolder.dpc.setBrightness(PowerManager.BRIGHTNESS_MAX); + + verify(mHolder.brightnessSetting).setBrightness(clampedBrightness); + } + /** * Creates a mock and registers it to {@link LocalServices}. */ diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java index db786bd00658..f3e3d342776b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -1092,6 +1092,16 @@ public final class DisplayPowerControllerTest { .getThermalBrightnessThrottlingDataMapByThrottlingId(); } + @Test + public void testSetBrightness_BrightnessShouldBeClamped() { + float clampedBrightness = 0.6f; + when(mHolder.hbmController.getCurrentBrightnessMax()).thenReturn(clampedBrightness); + + mHolder.dpc.setBrightness(PowerManager.BRIGHTNESS_MAX); + + verify(mHolder.brightnessSetting).setBrightness(clampedBrightness); + } + /** * Creates a mock and registers it to {@link LocalServices}. */ diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java index f89f73c98cfd..aa0a2fea1a5a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java @@ -1257,6 +1257,17 @@ public class LocalDisplayAdapterTest { public LocalDisplayAdapter.SurfaceControlProxy getSurfaceControlProxy() { return mSurfaceControlProxy; } + + // Instead of using DisplayDeviceConfig.create(context, physicalDisplayId, isFirstDisplay) + // we should use DisplayDeviceConfig.create(context, isFirstDisplay) for the test to ensure + // that real device DisplayDeviceConfig is not loaded for FakeDisplay and we are getting + // consistent behaviour. Please also note that context passed to this method, is + // mMockContext and values will be loaded from mMockResources. + @Override + public DisplayDeviceConfig createDisplayDeviceConfig(Context context, + long physicalDisplayId, boolean isFirstDisplay) { + return DisplayDeviceConfig.create(context, isFirstDisplay); + } } private class TestListener implements DisplayAdapter.Listener { diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt index c197b3419211..d6a4d40c763c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/DeletePackageHelperTest.kt @@ -16,9 +16,13 @@ package com.android.server.pm +import android.Manifest.permission.CONTROL_KEYGUARD import android.content.pm.PackageManager +import android.content.pm.PackageManager.PERMISSION_DENIED +import android.content.pm.PackageManager.PERMISSION_GRANTED import android.content.pm.UserInfo import android.os.Build +import android.os.UserHandle.USER_SYSTEM import android.util.Log import com.android.server.testutils.any import com.android.server.testutils.spy @@ -105,6 +109,8 @@ class DeletePackageHelperTest { whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true) whenever(mUserManagerInternal.getUserInfo(1)).thenReturn( UserInfo(1, "test", UserInfo.FLAG_ADMIN)) + whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM)) + .thenReturn(PERMISSION_DENIED) val dph = DeletePackageHelper(mPms) val result = dph.deletePackageX("a.data.package", 1L, 1, @@ -124,6 +130,8 @@ class DeletePackageHelperTest { whenever(mUserManagerInternal.getProfileParentId(userId)).thenReturn(parentId) whenever(mUserManagerInternal.getUserInfo(parentId)).thenReturn( UserInfo(userId, "testparent", UserInfo.FLAG_ADMIN)) + whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM)) + .thenReturn(PERMISSION_DENIED) val dph = DeletePackageHelper(mPms) val result = dph.deletePackageX("a.data.package", 1L, userId, @@ -138,6 +146,9 @@ class DeletePackageHelperTest { whenever(PackageManagerServiceUtils.isUpdatedSystemApp(ps)).thenReturn(true) whenever(mUserManagerInternal.getUserInfo(1)).thenReturn(UserInfo(1, "test", 0)) whenever(mUserManagerInternal.getProfileParentId(1)).thenReturn(1) + whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true) + whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM)) + .thenReturn(PERMISSION_DENIED) val dph = DeletePackageHelper(mPms) val result = dph.deletePackageX("a.data.package", 1L, 1, @@ -145,4 +156,18 @@ class DeletePackageHelperTest { assertThat(result).isEqualTo(PackageManager.DELETE_SUCCEEDED) } + + @Test + fun deleteSystemPackageWithKeyguard_fails() { + val ps = mPms.mSettings.getPackageLPr("a.data.package") + whenever(PackageManagerServiceUtils.isSystemApp(ps)).thenReturn(true) + whenever(mPms.checkPermission(CONTROL_KEYGUARD, "a.data.package", USER_SYSTEM)) + .thenReturn(PERMISSION_GRANTED) + + val dph = DeletePackageHelper(mPms) + val result = dph.deletePackageX("a.data.package", 1L, 1, + PackageManager.DELETE_SYSTEM_APP, false) + + assertThat(result).isEqualTo(PackageManager.DELETE_FAILED_INTERNAL_ERROR) + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java new file mode 100644 index 000000000000..96985521049f --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageMonitorCallbackHelperTest.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Intent; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.IRemoteCallback; +import android.os.Looper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentCaptor; + +import java.util.ArrayList; + +/** + * A unit test for PackageMonitorCallbackHelper implementation. + */ +@RunWith(JUnit4.class) +public class PackageMonitorCallbackHelperTest { + + private static final String FAKE_PACKAGE_NAME = "com.android.server.pm.fakeapp"; + private static final int FAKE_PACKAGE_UID = 123; + + private final Handler mHandler = new Handler(Looper.getMainLooper()); + PackageMonitorCallbackHelper mPackageMonitorCallbackHelper; + + @Rule + public final MockSystemRule mMockSystem = new MockSystemRule(); + + @Before + public void setup() { + when(mMockSystem.mocks().getInjector().getHandler()).thenReturn(mHandler); + mPackageMonitorCallbackHelper = new PackageMonitorCallbackHelper( + mMockSystem.mocks().getInjector()); + } + + + @After + public void teardown() { + mPackageMonitorCallbackHelper = null; + } + + @Test + public void testWithoutRegisterPackageMonitorCallback_callbackNotCalled() throws Exception { + IRemoteCallback callback = createMockPackageMonitorCallback(); + + mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, + FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */); + Thread.sleep(300); + + verify(callback, never()).sendResult(any()); + } + + @Test + public void testUnregisterPackageMonitorCallback_callbackShouldNotCalled() throws Exception { + IRemoteCallback callback = createMockPackageMonitorCallback(); + + mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */); + mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, + FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0}); + Thread.sleep(300); + + verify(callback, times(1)).sendResult(any()); + + reset(callback); + mPackageMonitorCallbackHelper.unregisterPackageMonitorCallback(callback); + mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, + FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */); + Thread.sleep(300); + + verify(callback, never()).sendResult(any()); + } + + @Test + public void testRegisterPackageMonitorCallback_callbackCalled() throws Exception { + IRemoteCallback callback = createMockPackageMonitorCallback(); + + mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */); + mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, + FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{0} /* userIds */); + Thread.sleep(300); + + ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(callback, times(1)).sendResult(bundleCaptor.capture()); + Bundle bundle = bundleCaptor.getValue(); + Intent intent = bundle.getParcelable( + PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, Intent.class); + assertThat(intent).isNotNull(); + assertThat(intent.getAction()).isEqualTo(Intent.ACTION_PACKAGE_ADDED); + assertThat(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)).isEqualTo(0); + } + + @Test + public void testRegisterPackageMonitorCallbackUserNotMatch_callbackShouldNotCalled() + throws Exception { + IRemoteCallback callback = createMockPackageMonitorCallback(); + + // Register for user 0 + mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */); + // Notify for user 10 + mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_ADDED, + FAKE_PACKAGE_NAME, createFakeBundle(), new int[]{10} /* userIds */); + Thread.sleep(300); + + verify(callback, never()).sendResult(any()); + } + + @Test + public void testNotifyPackageChanged_callbackCalled() throws Exception { + IRemoteCallback callback = createMockPackageMonitorCallback(); + + ArrayList<String> components = new ArrayList<>(); + String component1 = FAKE_PACKAGE_NAME + "/.Component1"; + components.add(component1); + mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */); + mPackageMonitorCallbackHelper.notifyPackageChanged(FAKE_PACKAGE_NAME, + false /* dontKillApp */, components, FAKE_PACKAGE_UID, null /* reason */, + new int[]{0} /* userIds */); + Thread.sleep(300); + + ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(callback, times(1)).sendResult(bundleCaptor.capture()); + Bundle bundle = bundleCaptor.getValue(); + Intent intent = bundle.getParcelable( + PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, Intent.class); + assertThat(intent).isNotNull(); + assertThat(intent.getAction()).isEqualTo(Intent.ACTION_PACKAGE_CHANGED); + assertThat(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)).isEqualTo(0); + assertThat(intent.getStringExtra(Intent.EXTRA_REASON)).isNull(); + assertThat(intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, true)).isFalse(); + assertThat(intent.getIntExtra(Intent.EXTRA_UID, -1)).isEqualTo(FAKE_PACKAGE_UID); + String[] result = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST); + assertThat(result).isNotNull(); + assertThat(result.length).isEqualTo(1); + assertThat(result[0]).isEqualTo(component1); + } + + @Test + public void testNotifyPackageAddedForNewUsers_callbackCalled() throws Exception { + IRemoteCallback callback = createMockPackageMonitorCallback(); + + mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */); + mPackageMonitorCallbackHelper.notifyPackageAddedForNewUsers(FAKE_PACKAGE_NAME, + FAKE_PACKAGE_UID, new int[]{0} /* userIds */, new int[0], + PackageInstaller.DATA_LOADER_TYPE_STREAMING); + Thread.sleep(300); + + ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(callback, times(1)).sendResult(bundleCaptor.capture()); + Bundle bundle = bundleCaptor.getValue(); + Intent intent = bundle.getParcelable( + PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, Intent.class); + assertThat(intent).isNotNull(); + assertThat(intent.getAction()).isEqualTo(Intent.ACTION_PACKAGE_ADDED); + assertThat(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)).isEqualTo(0); + assertThat(intent.getIntExtra(PackageInstaller.EXTRA_DATA_LOADER_TYPE, -1)).isEqualTo( + PackageInstaller.DATA_LOADER_TYPE_STREAMING); + assertThat(intent.getIntExtra(Intent.EXTRA_UID, -1)).isEqualTo(FAKE_PACKAGE_UID); + } + + @Test + public void testNotifyResourcesChanged_callbackCalled() throws Exception { + IRemoteCallback callback = createMockPackageMonitorCallback(); + + mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, 0 /* userId */); + mPackageMonitorCallbackHelper.notifyResourcesChanged(true /* mediaStatus */, + true /* replacing */, new String[]{FAKE_PACKAGE_NAME}, + new int[]{FAKE_PACKAGE_UID} /* uids */); + Thread.sleep(300); + + ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + verify(callback, times(1)).sendResult(bundleCaptor.capture()); + Bundle bundle = bundleCaptor.getValue(); + Intent intent = bundle.getParcelable( + PackageManager.EXTRA_PACKAGE_MONITOR_CALLBACK_RESULT, Intent.class); + assertThat(intent).isNotNull(); + assertThat(intent.getAction()).isEqualTo(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); + assertThat(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1)).isEqualTo(0); + assertThat(intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)).isTrue(); + + int[] uids = intent.getIntArrayExtra(Intent.EXTRA_CHANGED_UID_LIST); + assertThat(uids).isNotNull(); + assertThat(uids.length).isEqualTo(1); + assertThat(uids[0]).isEqualTo(FAKE_PACKAGE_UID); + + String[] pkgNames = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); + assertThat(pkgNames).isNotNull(); + assertThat(pkgNames.length).isEqualTo(1); + assertThat(pkgNames[0]).isEqualTo(FAKE_PACKAGE_NAME); + } + + private IRemoteCallback createMockPackageMonitorCallback() { + return spy(new IRemoteCallback.Stub() { + @Override + public void sendResult(Bundle data) { + // no op + } + }); + } + + private Bundle createFakeBundle() { + Bundle bundle = new Bundle(); + bundle.putInt(Intent.EXTRA_UID, FAKE_PACKAGE_UID); + return bundle; + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java index 7cd88196bf1b..60c15fe635ff 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -325,6 +325,83 @@ public final class UserManagerServiceTest { () -> mUmi.getBootUser(/* waitUntilSet= */ false)); } + @Test + public void testGetPreviousFullUserToEnterForeground() throws Exception { + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + + assertWithMessage("getPreviousFullUserToEnterForeground") + .that(mUms.getPreviousFullUserToEnterForeground()) + .isEqualTo(OTHER_USER_ID); + } + + @Test + public void testGetPreviousFullUserToEnterForeground_SkipsCurrentUser() throws Exception { + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + + mockCurrentUser(OTHER_USER_ID); + assertWithMessage("getPreviousFullUserToEnterForeground should skip current user") + .that(mUms.getPreviousFullUserToEnterForeground()) + .isEqualTo(USER_ID); + } + + @Test + public void testGetPreviousFullUserToEnterForeground_SkipsNonFullUsers() throws Exception { + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + + mUsers.get(OTHER_USER_ID).info.flags &= ~UserInfo.FLAG_FULL; + assertWithMessage("getPreviousFullUserToEnterForeground should skip non-full users") + .that(mUms.getPreviousFullUserToEnterForeground()) + .isEqualTo(USER_ID); + } + + @Test + public void testGetPreviousFullUserToEnterForeground_SkipsPartialUsers() throws Exception { + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + + mUsers.get(OTHER_USER_ID).info.partial = true; + assertWithMessage("getPreviousFullUserToEnterForeground should skip partial users") + .that(mUms.getPreviousFullUserToEnterForeground()) + .isEqualTo(USER_ID); + } + + @Test + public void testGetPreviousFullUserToEnterForeground_SkipsDisabledUsers() throws Exception { + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + + mUsers.get(OTHER_USER_ID).info.flags |= UserInfo.FLAG_DISABLED; + assertWithMessage("getPreviousFullUserToEnterForeground should skip disabled users") + .that(mUms.getPreviousFullUserToEnterForeground()) + .isEqualTo(USER_ID); + } + + @Test + public void testGetPreviousFullUserToEnterForeground_SkipsRemovingUsers() throws Exception { + addUser(USER_ID); + setLastForegroundTime(USER_ID, 1_000_000L); + addUser(OTHER_USER_ID); + setLastForegroundTime(OTHER_USER_ID, 2_000_000L); + + mUms.addRemovingUserId(OTHER_USER_ID); + assertWithMessage("getPreviousFullUserToEnterForeground should skip removing users") + .that(mUms.getPreviousFullUserToEnterForeground()) + .isEqualTo(USER_ID); + } + private void mockCurrentUser(@UserIdInt int userId) { mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal); diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java index a3a49d7035d9..f3aa4274b3cd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java @@ -259,6 +259,29 @@ public class AlarmQueueTest { } @Test + public void testMinTimeBetweenAlarms_freshAlarm() { + final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 5 * MINUTE_IN_MILLIS); + final long fixedTimeElapsed = mInjector.getElapsedRealtime(); + + InOrder inOrder = inOrder(mAlarmManager); + + final String pkg1 = "com.android.test.1"; + final String pkg2 = "com.android.test.2"; + alarmQueue.addAlarm(pkg1, fixedTimeElapsed + MINUTE_IN_MILLIS); + inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( + anyInt(), eq(fixedTimeElapsed + MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any()); + + advanceElapsedClock(MINUTE_IN_MILLIS); + + alarmQueue.onAlarm(); + // Minimum of 5 minutes between alarms, so the next alarm should be 5 minutes after the + // first. + alarmQueue.addAlarm(pkg2, fixedTimeElapsed + 2 * MINUTE_IN_MILLIS); + inOrder.verify(mAlarmManager, timeout(1000).times(1)).setExact( + anyInt(), eq(fixedTimeElapsed + 6 * MINUTE_IN_MILLIS), eq(ALARM_TAG), any(), any()); + } + + @Test public void testOnAlarm() { final AlarmQueue<String> alarmQueue = createAlarmQueue(true, 0); final long nowElapsed = mInjector.getElapsedRealtime(); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java index e6ef044d4791..5b5c8d415f84 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationControllerTest.java @@ -29,6 +29,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -202,6 +203,7 @@ public class FullScreenMagnificationControllerTest { assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_0)); assertFalse(mFullScreenMagnificationController.isRegistered(DISPLAY_1)); + // Once for each display on unregister verify(mMockThumbnail, times(2)).hideThumbnail(); } @@ -543,7 +545,11 @@ public class FullScreenMagnificationControllerTest { // The first time is triggered when the thumbnail is just created. // The second time is triggered when the magnification region changed. verify(mMockThumbnail, times(2)).setThumbnailBounds( - any(), anyFloat(), anyFloat(), anyFloat()); + /* currentBounds= */ any(), + /* scale= */ anyFloat(), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); } @Test @@ -681,6 +687,9 @@ public class FullScreenMagnificationControllerTest { checkActivatedAndMagnifying(/* activated= */ true, /* magnifying= */ true, displayId); assertTrue(mFullScreenMagnificationController.resetIfNeeded(displayId, SERVICE_ID_2)); checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, displayId); + + // Once on init before it's activated and once for reset + verify(mMockThumbnail, times(2)).hideThumbnail(); } @Test @@ -783,6 +792,9 @@ public class FullScreenMagnificationControllerTest { mMessageCapturingHandler.sendAllMessages(); checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, DISPLAY_0); checkActivatedAndMagnifying(/* activated= */ false, /* magnifying= */ false, DISPLAY_1); + + // Twice for each display: once on init before it's activated and once for screen off + verify(mMockThumbnail, times(4)).hideThumbnail(); } @Test @@ -847,6 +859,15 @@ public class FullScreenMagnificationControllerTest { mMessageCapturingHandler.sendAllMessages(); checkActivatedAndMagnifying( /* activated= */ expectedActivated, /* magnifying= */ false, displayId); + + if (expectedActivated) { + verify(mMockThumbnail, times(2)).setThumbnailBounds( + /* currentBounds= */ any(), + /* scale= */ anyFloat(), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); + } } @Test @@ -950,6 +971,13 @@ public class FullScreenMagnificationControllerTest { INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale); assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets))); verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec))); + + verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds( + /* currentBounds= */ any(), + eq(scale), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); } @Test @@ -984,6 +1012,13 @@ public class FullScreenMagnificationControllerTest { INITIAL_BOUNDS_LOWER_RIGHT_2X_CENTER, scale); assertThat(endSpec, closeTo(getMagnificationSpec(scale, expectedOffsets))); verify(mMockWindowManager).setMagnificationSpec(eq(displayId), argThat(closeTo(endSpec))); + + verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds( + /* currentBounds= */ any(), + eq(scale), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); } @Test @@ -1246,6 +1281,13 @@ public class FullScreenMagnificationControllerTest { callbacks.onImeWindowVisibilityChanged(true); mMessageCapturingHandler.sendAllMessages(); verify(mRequestObserver).onImeWindowVisibilityChanged(eq(DISPLAY_0), eq(true)); + + verify(mMockThumbnail, atLeastOnce()).setThumbnailBounds( + /* currentBounds= */ any(), + /* scale= */ anyFloat(), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); } @Test @@ -1270,6 +1312,15 @@ public class FullScreenMagnificationControllerTest { mFullScreenMagnificationController.onUserContextChanged(DISPLAY_0); verify(mRequestObserver).onFullScreenMagnificationActivationState(eq(DISPLAY_0), eq(false)); + verify(mMockThumbnail).setThumbnailBounds( + /* currentBounds= */ any(), + /* scale= */ anyFloat(), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); + + // Once on init before it's activated and once for reset + verify(mMockThumbnail, times(2)).hideThumbnail(); } @Test @@ -1281,6 +1332,12 @@ public class FullScreenMagnificationControllerTest { assertEquals(1.0f, mFullScreenMagnificationController.getScale(DISPLAY_0), 0); assertTrue(mFullScreenMagnificationController.isActivated(DISPLAY_0)); + verify(mMockThumbnail).setThumbnailBounds( + /* currentBounds= */ any(), + /* scale= */ anyFloat(), + /* centerX= */ anyFloat(), + /* centerY= */ anyFloat() + ); } private void setScaleToMagnifying() { diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java index 3baa102b882b..8faddf8ff541 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationThumbnailTest.java @@ -187,6 +187,29 @@ public class MagnificationThumbnailTest { .addView(eq(mMagnificationThumbnail.mThumbnailLayout), any()); verify(mMockWindowManager, never()) .removeView(eq(mMagnificationThumbnail.mThumbnailLayout)); + verify(mMockWindowManager, never()) + .updateViewLayout(eq(mMagnificationThumbnail.mThumbnailLayout), any()); + } + + @Test + public void whenVisible_setBoundsUpdatesLayout() throws InterruptedException { + runOnMainSync(() -> mMagnificationThumbnail.updateThumbnail( + /* scale= */ 2f, + /* centerX= */ 5, + /* centerY= */ 10 + )); + runOnMainSync(() -> mMagnificationThumbnail.setThumbnailBounds( + new Rect(), + /* scale= */ 2f, + /* centerX= */ 5, + /* centerY= */ 10 + )); + idle(); + + verify(mMockWindowManager).updateViewLayout( + eq(mMagnificationThumbnail.mThumbnailLayout), + /* params= */ any() + ); } private static void idle() { diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index dccacb4d301a..24a628eb4331 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -1342,6 +1342,10 @@ public class UserControllerTest { Message copy = new Message(); copy.copyFrom(msg); mMessages.add(copy); + if (msg.getCallback() != null) { + msg.getCallback().run(); + msg.setCallback(null); + } return super.sendMessageAtTime(msg, uptimeMillis); } } 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 99a3b808e082..ad1c60e2cc8a 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -8125,14 +8125,13 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test - public void testIsUsbDataSignalingEnabledForUser_systemUser() throws Exception { + public void testIsUsbDataSignalingEnabledForUser() throws Exception { when(getServices().usbManager.enableUsbDataSignal(false)).thenReturn(true); when(getServices().usbManager.getUsbHalVersion()).thenReturn(UsbManager.USB_HAL_V1_3); setDeviceOwner(); dpm.setUsbDataSignalingEnabled(false); - mContext.binder.callingUid = DpmMockContext.SYSTEM_UID; - assertThat(dpm.isUsbDataSignalingEnabledForUser(UserHandle.myUserId())).isFalse(); + assertThat(dpm.isUsbDataSignalingEnabled()).isFalse(); } @Test diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index 9ff600a6d0f8..e5fac7ac5e0c 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -251,6 +251,8 @@ public class DpmMockContext extends MockContext { return mMockSystemServices.roleManager; case Context.TELEPHONY_SUBSCRIPTION_SERVICE: return mMockSystemServices.subscriptionManager; + case Context.USB_SERVICE: + return mMockSystemServices.usbManager; } throw new UnsupportedOperationException(); } diff --git a/services/tests/servicestests/src/com/android/server/display/OWNERS b/services/tests/servicestests/src/com/android/server/display/OWNERS deleted file mode 100644 index 6ce1ee4d3de2..000000000000 --- a/services/tests/servicestests/src/com/android/server/display/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /services/core/java/com/android/server/display/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING deleted file mode 100644 index 92d8abd4f173..000000000000 --- a/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING +++ /dev/null @@ -1,13 +0,0 @@ -{ - "presubmit": [ - { - "name": "FrameworksServicesTests", - "options": [ - {"include-filter": "com.android.server.display"}, - {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, - {"exclude-annotation": "androidx.test.filters.FlakyTest"}, - {"exclude-annotation": "org.junit.Ignore"} - ] - } - ] -} diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java index bc09d4b10723..399655ffac55 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java @@ -137,6 +137,20 @@ public abstract class BaseAbsoluteVolumeBehaviorTest { protected void writeStringSystemProperty(String key, String value) { // do nothing } + + /** + * Override displayOsd to prevent it from broadcasting an intent, which + * can trigger a SecurityException. + */ + @Override + void displayOsd(int messageId) { + // do nothing + } + + @Override + void displayOsd(int messageId, int extra) { + // do nothing + } }; mLooper = mTestLooper.getLooper(); diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java index 3bde665e87c0..cb19029d246e 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java @@ -61,6 +61,7 @@ final class FakeNativeWrapper implements NativeWrapper { private HdmiPortInfo[] mHdmiPortInfo = null; private HdmiCecController.HdmiCecCallback mCallback = null; private int mCecVersion = HdmiControlManager.HDMI_CEC_VERSION_2_0; + private boolean mIsCecControlEnabled = true; @Override public String nativeInit() { @@ -128,7 +129,9 @@ final class FakeNativeWrapper implements NativeWrapper { public void enableCec(boolean enabled) {} @Override - public void enableSystemCecControl(boolean enabled) {} + public void enableSystemCecControl(boolean enabled) { + mIsCecControlEnabled = enabled; + } @Override public void nativeSetLanguage(String language) {} @@ -154,6 +157,10 @@ final class FakeNativeWrapper implements NativeWrapper { mPortConnectionStatus.put(port, connected); } + public boolean getIsCecControlEnabled() { + return mIsCecControlEnabled; + } + public void setCecVersion(@HdmiControlManager.HdmiCecVersion int cecVersion) { mCecVersion = cecVersion; } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakePowerManagerWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakePowerManagerWrapper.java index 7c8a11ec1cea..04f921f495a2 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/FakePowerManagerWrapper.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/FakePowerManagerWrapper.java @@ -24,10 +24,18 @@ import android.content.Context; */ final class FakePowerManagerWrapper extends PowerManagerWrapper { private boolean mInteractive; + private WakeLockWrapper mWakeLock; + private boolean mWasWakeLockInstanceCreated = false; + FakePowerManagerWrapper(@NonNull Context context) { + this(context, null); + } + + FakePowerManagerWrapper(@NonNull Context context, WakeLockWrapper wakeLock) { super(context); mInteractive = true; + mWakeLock = wakeLock; } @Override @@ -51,5 +59,48 @@ final class FakePowerManagerWrapper extends PowerManagerWrapper { return; } - // Don't stub WakeLock. + @Override + WakeLockWrapper newWakeLock(int levelAndFlags, String tag) { + if (mWakeLock == null) { + mWakeLock = new FakeWakeLockWrapper(); + } + mWasWakeLockInstanceCreated = true; + return mWakeLock; + } + + boolean wasWakeLockInstanceCreated() { + return mWasWakeLockInstanceCreated; + } + + /** + * "Fake" wrapper for {@link PowerManager.WakeLock}, as opposed to a "Default" wrapper used by + * the framework - see {@link PowerManagerWrapper.DefaultWakeLockWrapper}. + */ + public static class FakeWakeLockWrapper implements WakeLockWrapper { + private static final String TAG = "FakeWakeLockWrapper"; + private boolean mWakeLockHeld = false; + + @Override + public void acquire(long timeout) { + mWakeLockHeld = true; + } + + @Override + public void acquire() { + mWakeLockHeld = true; + } + + @Override + public void release() { + mWakeLockHeld = false; + } + + @Override + public boolean isHeld() { + return mWakeLockHeld; + } + + @Override + public void setReferenceCounted(boolean value) {} + } } diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java index 39930bc1e35a..0d172fdb2a80 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java @@ -21,6 +21,7 @@ import static android.hardware.hdmi.HdmiDeviceInfo.DEVICE_TV; import static com.android.server.SystemService.PHASE_BOOT_COMPLETED; import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; +import static com.android.server.hdmi.HdmiControlService.DEVICE_CLEANUP_TIMEOUT; import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC; import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON; @@ -37,6 +38,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -63,6 +65,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.mockito.InOrder; import org.mockito.Mockito; import java.util.ArrayList; @@ -87,6 +90,7 @@ public class HdmiControlServiceTest { private HdmiEarcController mHdmiEarcController; private FakeEarcNativeWrapper mEarcNativeWrapper; private FakePowerManagerWrapper mPowerManager; + private WakeLockWrapper mWakeLockSpy; private Looper mMyLooper; private TestLooper mTestLooper = new TestLooper(); private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>(); @@ -164,7 +168,8 @@ public class HdmiControlServiceTest { .build(); mNativeWrapper.setPortInfo(mHdmiPortInfo); mHdmiControlServiceSpy.initService(); - mPowerManager = new FakePowerManagerWrapper(mContextSpy); + mWakeLockSpy = spy(new FakePowerManagerWrapper.FakeWakeLockWrapper()); + mPowerManager = new FakePowerManagerWrapper(mContextSpy, mWakeLockSpy); mHdmiControlServiceSpy.setPowerManager(mPowerManager); mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); mHdmiControlServiceSpy.setEarcSupported(true); @@ -190,6 +195,7 @@ public class HdmiControlServiceTest { doReturn(true).when(mHdmiControlServiceSpy).isStandbyMessageReceived(); mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); + assertTrue(mPlaybackDeviceSpy.isStandby()); assertTrue(mAudioSystemDeviceSpy.isStandby()); assertTrue(mPlaybackDeviceSpy.isDisabled()); @@ -197,6 +203,75 @@ public class HdmiControlServiceTest { } @Test + public void playbackOnlyDevice_onStandbyCompleted_disableCecController() { + mLocalDevices.remove(mAudioSystemDeviceSpy); + mHdmiControlServiceSpy.clearCecLocalDevices(); + mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC); + mTestLooper.dispatchAll(); + mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY); + + assertTrue(mNativeWrapper.getIsCecControlEnabled()); + mHdmiControlServiceSpy.disableCecLocalDevices( + new HdmiCecLocalDevice.PendingActionClearedCallback() { + @Override + public void onCleared(HdmiCecLocalDevice device) { + assertTrue(mNativeWrapper.getIsCecControlEnabled()); + mHdmiControlServiceSpy.onPendingActionsCleared( + HdmiControlService.STANDBY_SCREEN_OFF); + } + }); + mTestLooper.dispatchAll(); + + verify(mPlaybackDeviceSpy, times(1)).invokeStandbyCompletedCallback(any()); + assertFalse(mNativeWrapper.getIsCecControlEnabled()); + } + + + @Test + public void playbackAndAudioDevice_onStandbyCompleted_doNotDisableCecController() { + mLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY); + + assertTrue(mNativeWrapper.getIsCecControlEnabled()); + mHdmiControlServiceSpy.disableCecLocalDevices( + new HdmiCecLocalDevice.PendingActionClearedCallback() { + @Override + public void onCleared(HdmiCecLocalDevice device) { + assertTrue(mNativeWrapper.getIsCecControlEnabled()); + mHdmiControlServiceSpy.onPendingActionsCleared( + HdmiControlService.STANDBY_SCREEN_OFF); + } + }); + mTestLooper.dispatchAll(); + + verify(mPlaybackDeviceSpy, times(1)).invokeStandbyCompletedCallback(any()); + verify(mAudioSystemDeviceSpy, times(1)).invokeStandbyCompletedCallback(any()); + assertTrue(mNativeWrapper.getIsCecControlEnabled()); + } + + @Test + public void onStandby_acquireAndReleaseWakeLockSuccessfully() { + mHdmiControlServiceSpy.getHdmiCecConfig().setStringValue( + HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE, + HdmiControlManager.POWER_CONTROL_MODE_TV_AND_AUDIO_SYSTEM); + mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON); + doReturn(true).when(mHdmiControlServiceSpy).isStandbyMessageReceived(); + mTestLooper.dispatchAll(); + + assertFalse(mPowerManager.wasWakeLockInstanceCreated()); + mHdmiControlServiceSpy.onStandby(HdmiControlService.STANDBY_SCREEN_OFF); + + InOrder inOrder = inOrder(mHdmiControlServiceSpy, mWakeLockSpy); + inOrder.verify(mWakeLockSpy, times(1)).acquire(DEVICE_CLEANUP_TIMEOUT); + inOrder.verify(mHdmiControlServiceSpy, times(1)).disableCecLocalDevices(any()); + inOrder.verify(mHdmiControlServiceSpy, times(1)) + .onPendingActionsCleared(HdmiControlService.STANDBY_SCREEN_OFF); + inOrder.verify(mWakeLockSpy, times(1)).release(); + + assertTrue(mPowerManager.wasWakeLockInstanceCreated()); + } + + @Test public void initialPowerStatus_normalBoot_isTransientToStandby() { assertThat(mHdmiControlServiceSpy.getInitialPowerStatus()).isEqualTo( HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY); @@ -903,6 +978,33 @@ public class HdmiControlServiceTest { assertThat(vendorCmdListener.mVendorCommandCallbackReceived).isFalse(); } + @Test + public void multipleVendorCommandListeners_receiveCallback() { + int destAddress = mHdmiControlServiceSpy.playback().getDeviceInfo().getLogicalAddress(); + int sourceAddress = Constants.ADDR_TV; + byte[] params = {0x00, 0x01, 0x02, 0x03}; + int vendorId = 0x123456; + mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON); + + VendorCommandListener vendorCmdListener = + new VendorCommandListener(sourceAddress, destAddress, params, vendorId); + VendorCommandListener secondVendorCmdListener = + new VendorCommandListener(sourceAddress, destAddress, params, vendorId); + mHdmiControlServiceSpy.addVendorCommandListener(vendorCmdListener, vendorId); + mHdmiControlServiceSpy.addVendorCommandListener(secondVendorCmdListener, vendorId); + mTestLooper.dispatchAll(); + + HdmiCecMessage vendorCommandNoId = + HdmiCecMessageBuilder.buildVendorCommand(sourceAddress, destAddress, params); + mNativeWrapper.onCecMessage(vendorCommandNoId); + mTestLooper.dispatchAll(); + assertThat(vendorCmdListener.mVendorCommandCallbackReceived).isTrue(); + assertThat(vendorCmdListener.mParamsCorrect).isTrue(); + + assertThat(secondVendorCmdListener.mVendorCommandCallbackReceived).isTrue(); + assertThat(secondVendorCmdListener.mParamsCorrect).isTrue(); + } + private static class VendorCommandListener extends IHdmiVendorCommandListener.Stub { boolean mVendorCommandCallbackReceived = false; boolean mParamsCorrect = false; @@ -1423,8 +1525,10 @@ public class HdmiControlServiceTest { } @Override - protected void onStandby(boolean initiatedByCec, int standbyAction) { + protected void onStandby(boolean initiatedByCec, int standbyAction, + StandbyCompletedCallback callback) { mIsStandby = true; + invokeStandbyCompletedCallback(callback); } protected boolean isStandby() { @@ -1476,8 +1580,10 @@ public class HdmiControlServiceTest { } @Override - protected void onStandby(boolean initiatedByCec, int standbyAction) { + protected void onStandby(boolean initiatedByCec, int standbyAction, + StandbyCompletedCallback callback) { mIsStandby = true; + invokeStandbyCompletedCallback(callback); } protected boolean isStandby() { diff --git a/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvbTest.java b/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvbTest.java index 079ef2e36673..024e36d62273 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvbTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/TvToAudioSystemAvbTest.java @@ -262,4 +262,63 @@ public class TvToAudioSystemAvbTest extends BaseAbsoluteVolumeBehaviorTest { assertThat(mNativeWrapper.getResultMessages()).isEmpty(); } + + @Test + public void adjustOnlyAvbEnabled_savlBecomesSupported_switchToAvb() { + enableAdjustOnlyAbsoluteVolumeBehavior(); + mNativeWrapper.clearResultMessages(); + + // When the Audio System reports support for <Set Audio Volume Level>, + // the device should start the process for adopting AVB by sending <Give Audio Status> + receiveSetAudioVolumeLevelSupport(DeviceFeatures.FEATURE_SUPPORTED); + verifyGiveAudioStatusSent(); + + // The device should use adjust-only AVB while waiting for <Report Audio Status> + assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( + AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); + + // The device should switch to AVB upon receiving <Report Audio Status> + receiveReportAudioStatus(60, false); + assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( + AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); + + } + + /** + * Tests that adjust-only AVB is not interrupted when a device's support for + * <Set Audio Volume Level> becomes unknown. + * + * This may currently occur when NewDeviceAction overwrites a device's info in HdmiCecNetwork. + * However, because replicating this scenario would be brittle and the behavior may change, + * this test does not simulate it and instead changes HdmiCecNetwork directly. + */ + @Test + public void adjustOnlyAvbEnabled_savlSupportBecomesUnknown_keepUsingAdjustOnlyAvb() { + enableAdjustOnlyAbsoluteVolumeBehavior(); + mNativeWrapper.clearResultMessages(); + + // Make sure the existing SetAudioVolumeLevelDiscoveryAction expires, + // so that we can check whether a new one is started. + mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS + 1); + mTestLooper.dispatchAll(); + + // Replace Audio System device info with one that has unknown support for all features + HdmiDeviceInfo updatedAudioSystemDeviceInfo = + mHdmiControlService.getHdmiCecNetwork().getDeviceInfo(Constants.ADDR_AUDIO_SYSTEM) + .toBuilder() + .setDeviceFeatures(DeviceFeatures.ALL_FEATURES_SUPPORT_UNKNOWN) + .build(); + mHdmiControlService.getHdmiCecNetwork().addCecDevice(updatedAudioSystemDeviceInfo); + mTestLooper.dispatchAll(); + + // The device should not switch away from adjust-only AVB + assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo( + AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); + + // The device should query support for <Set Audio Volume Level> again + assertThat(mNativeWrapper.getResultMessages()).contains( + SetAudioVolumeLevelMessage.build( + getLogicalAddress(), getSystemAudioDeviceLogicalAddress(), + Constants.AUDIO_VOLUME_STATUS_UNKNOWN)); + } } diff --git a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt index 2f1a3760512b..498776db3ac8 100644 --- a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt +++ b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt @@ -133,8 +133,7 @@ class InputManagerServiceTests { verify(native).setMotionClassifierEnabled(anyBoolean()) verify(native).setMaximumObscuringOpacityForTouch(anyFloat()) verify(native).setStylusPointerIconEnabled(anyBoolean()) - // TODO(b/286078544): There is no need to call this more than once. - verify(native, times(3)).setKeyRepeatConfiguration(anyInt(), anyInt()) + verify(native).setKeyRepeatConfiguration(anyInt(), anyInt()) } @Test diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/OWNERS b/services/tests/servicestests/src/com/android/server/inputmethod/OWNERS index 5deb2ce8f24b..cbd94ba6b467 100644 --- a/services/tests/servicestests/src/com/android/server/inputmethod/OWNERS +++ b/services/tests/servicestests/src/com/android/server/inputmethod/OWNERS @@ -1 +1,2 @@ +# Bug component: 34867 include /core/java/android/view/inputmethod/OWNERS diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java index c42928eba85f..bb8b986c6f61 100644 --- a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionManagerServiceTest.java @@ -38,6 +38,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.testng.Assert.assertThrows; import android.app.ActivityManagerInternal; @@ -49,10 +50,14 @@ import android.content.pm.PackageManager.ApplicationInfoFlags; import android.content.pm.PackageManager.NameNotFoundException; import android.media.projection.IMediaProjection; import android.media.projection.IMediaProjectionCallback; +import android.media.projection.IMediaProjectionWatcherCallback; import android.media.projection.ReviewGrantedConsentResult; +import android.os.Binder; import android.os.IBinder; +import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; +import android.os.test.TestLooper; import android.platform.test.annotations.Presubmit; import android.view.ContentRecordingSession; @@ -86,6 +91,7 @@ public class MediaProjectionManagerServiceTest { private static final int UID = 10; private static final String PACKAGE_NAME = "test.package"; private final ApplicationInfo mAppInfo = new ApplicationInfo(); + private final TestLooper mTestLooper = new TestLooper(); private static final ContentRecordingSession DISPLAY_SESSION = ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY); // Callback registered by an app on a MediaProjection instance. @@ -110,6 +116,14 @@ public class MediaProjectionManagerServiceTest { } }; + private final MediaProjectionManagerService.Injector mTestLooperInjector = + new MediaProjectionManagerService.Injector() { + @Override + Looper createCallbackLooper() { + return mTestLooper.getLooper(); + } + }; + private Context mContext; private MediaProjectionManagerService mService; private OffsettableClock mClock; @@ -122,12 +136,15 @@ public class MediaProjectionManagerServiceTest { private WindowManagerInternal mWindowManagerInternal; @Mock private PackageManager mPackageManager; + @Mock + private IMediaProjectionWatcherCallback mWatcherCallback; @Captor private ArgumentCaptor<ContentRecordingSession> mSessionCaptor; @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); + when(mWatcherCallback.asBinder()).thenReturn(new Binder()); LocalServices.removeServiceForTest(ActivityManagerInternal.class); LocalServices.addService(ActivityManagerInternal.class, mAmInternal); @@ -671,6 +688,59 @@ public class MediaProjectionManagerServiceTest { assertThat(mService.isCurrentProjection(projection)).isTrue(); } + @Test + public void setContentRecordingSession_successful_notifiesListeners() + throws Exception { + mService.addCallback(mWatcherCallback); + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + projection.start(mIMediaProjectionCallback); + + doReturn(true).when(mWindowManagerInternal).setContentRecordingSession( + any(ContentRecordingSession.class)); + mService.setContentRecordingSession(DISPLAY_SESSION); + + verify(mWatcherCallback).onRecordingSessionSet( + projection.getProjectionInfo(), + DISPLAY_SESSION + ); + } + + @Test + public void setContentRecordingSession_notifiesListenersOnCallbackLooper() + throws Exception { + mService = new MediaProjectionManagerService(mContext, mTestLooperInjector); + mService.addCallback(mWatcherCallback); + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + projection.start(mIMediaProjectionCallback); + doReturn(true).when(mWindowManagerInternal).setContentRecordingSession( + any(ContentRecordingSession.class)); + + mService.setContentRecordingSession(DISPLAY_SESSION); + // Callback not notified yet, as test looper hasn't dispatched the message yet + verify(mWatcherCallback, never()).onRecordingSessionSet(any(), any()); + + mTestLooper.dispatchAll(); + // Message dispatched on test looper. Callback should now be notified. + verify(mWatcherCallback).onRecordingSessionSet( + projection.getProjectionInfo(), + DISPLAY_SESSION + ); + } + + @Test + public void setContentRecordingSession_failure_doesNotNotifyListeners() + throws Exception { + mService.addCallback(mWatcherCallback); + MediaProjectionManagerService.MediaProjection projection = startProjectionPreconditions(); + projection.start(mIMediaProjectionCallback); + + doReturn(false).when(mWindowManagerInternal).setContentRecordingSession( + any(ContentRecordingSession.class)); + mService.setContentRecordingSession(DISPLAY_SESSION); + + verify(mWatcherCallback, never()).onRecordingSessionSet(any(), any()); + } + private void verifySetSessionWithContent(@ContentRecordingSession.RecordContent int content) { verify(mWindowManagerInternal, atLeastOnce()).setContentRecordingSession( mSessionCaptor.capture()); diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java index fdf94bec8c18..39cc6537c759 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceCreateProfileTest.java @@ -182,7 +182,7 @@ public class UserManagerServiceCreateProfileTest { UserInfo secondaryUser = addUser(); UserInfo profile = addProfile(secondaryUser); // Add the profile it to the users being removed. - mUserManagerService.addRemovingUserIdLocked(profile.id); + mUserManagerService.addRemovingUserId(profile.id); // We should reuse the badge from the profile being removed. assertEquals("Badge index not reused while removing a user", 0, mUserManagerService.getFreeProfileBadgeLU(secondaryUser.id, diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java index 1f4c9f8cd343..b6fd65e5d3b6 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceIdRecyclingTest.java @@ -111,7 +111,7 @@ public class UserManagerServiceIdRecyclingTest { private void removeUser(int userId) { mUserManagerService.removeUserInfo(userId); - mUserManagerService.addRemovingUserIdLocked(userId); + mUserManagerService.addRemovingUserId(userId); } private void assertNoNextIdAvailable(String message) { diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java index 4e1196ff60c4..d94f10dfa7ea 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -19,6 +19,7 @@ package com.android.server.pm; import static android.os.UserManager.DISALLOW_USER_SWITCH; import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; import android.app.ActivityManager; import android.app.PropertyInvalidatedCache; @@ -28,6 +29,7 @@ import android.os.Bundle; import android.os.FileUtils; import android.os.Looper; import android.os.Parcelable; +import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.Postsubmit; @@ -70,6 +72,9 @@ public class UserManagerServiceTest { LocalServices.removeServiceForTest(UserManagerInternal.class); mUserManagerService = new UserManagerService(InstrumentationRegistry.getContext()); + // Put the current user to mUsers. UMS can't find userlist.xml, and fallbackToSingleUserLP. + mUserManagerService.putUserInfo( + new UserInfo(ActivityManager.getCurrentUser(), "Current User", 0)); restrictionsFile = new File(mContext.getCacheDir(), "restrictions.xml"); restrictionsFile.delete(); @@ -304,8 +309,10 @@ public class UserManagerServiceTest { private static String setSystemProperty(String name, String value) throws Exception { final String oldValue = runShellCommand("getprop " + name); - assertThat(runShellCommand("setprop " + name + " " + value)) - .isEqualTo(""); + assertWithMessage("can not set system property") + .that(runShellCommand("setprop " + name + " " + value)).isEqualTo(""); + assertWithMessage("failed to set system property") + .that(SystemProperties.get(name)).isEqualTo(value); return oldValue; } diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index 7d0f36133a9c..dd681aa85c3f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -265,6 +265,52 @@ public final class UserManagerTest { assertWithMessage("Communal profile not visible").that(umCommunal.isUserVisible()).isTrue(); } + @Test + public void testPrivateProfile() throws Exception { + UserHandle mainUser = mUserManager.getMainUser(); + assumeTrue("Main user is null", mainUser != null); + // Get the default properties for private profile user type. + final UserTypeDetails userTypeDetails = + UserTypeFactory.getUserTypes().get(UserManager.USER_TYPE_PROFILE_PRIVATE); + assertWithMessage("No %s type on device", UserManager.USER_TYPE_PROFILE_PRIVATE) + .that(userTypeDetails).isNotNull(); + final UserProperties typeProps = userTypeDetails.getDefaultUserPropertiesReference(); + + // Test that only one private profile can be created + final int mainUserId = mainUser.getIdentifier(); + UserInfo userInfo = createProfileForUser("Private profile1", + UserManager.USER_TYPE_PROFILE_PRIVATE, + mainUserId); + assertThat(userInfo).isNotNull(); + UserInfo userInfo2 = createProfileForUser("Private profile2", + UserManager.USER_TYPE_PROFILE_PRIVATE, + mainUserId); + assertThat(userInfo2).isNull(); + + // Check that the new private profile has the expected properties (relative to the defaults) + // provided that the test caller has the necessary permissions. + UserProperties privateProfileUserProperties = + mUserManager.getUserProperties(UserHandle.of(userInfo.id)); + assertThat(typeProps.getShowInLauncher()) + .isEqualTo(privateProfileUserProperties.getShowInLauncher()); + assertThrows(SecurityException.class, privateProfileUserProperties::getStartWithParent); + assertThrows(SecurityException.class, + privateProfileUserProperties::getCrossProfileIntentFilterAccessControl); + assertThat(typeProps.isMediaSharedWithParent()) + .isEqualTo(privateProfileUserProperties.isMediaSharedWithParent()); + assertThat(typeProps.isCredentialShareableWithParent()) + .isEqualTo(privateProfileUserProperties.isCredentialShareableWithParent()); + assertThrows(SecurityException.class, privateProfileUserProperties::getDeleteAppWithParent); + + // Verify private profile parent + assertThat(mUserManager.getProfileParent(mainUserId)).isNull(); + UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id); + assertThat(parentProfileInfo).isNotNull(); + assertThat(mainUserId).isEqualTo(parentProfileInfo.id); + removeUser(userInfo.id); + assertThat(mUserManager.getProfileParent(mainUserId)).isNull(); + } + @MediumTest @Test public void testAdd2Users() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt new file mode 100644 index 000000000000..003797066b39 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt @@ -0,0 +1,685 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.parsing + +import android.content.res.Validator +import android.os.Environment +import android.platform.test.annotations.Postsubmit +import com.android.internal.R +import com.android.server.pm.PackageManagerService +import com.android.server.pm.pkg.parsing.ParsingPackageUtils +import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertEquals +import org.junit.Assert.assertThrows +import org.junit.Assert.fail +import org.junit.Test +import org.xmlpull.v1.XmlPullParser +import org.xmlpull.v1.XmlPullParserException +import org.xmlpull.v1.XmlPullParserFactory +import java.io.ByteArrayInputStream +import java.io.File + +@Postsubmit +class AndroidPackageParsingValidationTest { + companion object { + private val parser2 = PackageParser2.forParsingFileWithDefaults() + private val apks = ((PackageManagerService.SYSTEM_PARTITIONS) + .flatMap { + listOfNotNull(it.privAppFolder, it.appFolder, it.overlayFolder) + } + File(Environment.getRootDirectory(), "framework")) + .flatMap { + it.walkTopDown() + .filter { file -> file.name.endsWith(".apk") } + .toList() + } + .distinct() + private val pullParser: XmlPullParser = run { + val factory = XmlPullParserFactory.newInstance() + factory.isNamespaceAware = true + factory.newPullParser() + } + private val ns = "xmlns:android=\"http://schemas.android.com/apk/res/android\"" + } + + @Test + fun parseExistingApks_NoParseFailures() { + val failedParsePackages = mutableListOf<File>() + for (apk in apks) { + try { + parser2.parsePackage(apk, ParsingPackageUtils.PARSE_IS_SYSTEM_DIR, false) + } catch (e: Exception) { + if (e.message!!.startsWith("Failed to parse")) { + failedParsePackages.add(apk) + } else if (e.message!!.startsWith("Skipping target and overlay pair")) { + // ignore + } else { + throw e + } + } + } + assertThat(failedParsePackages).isEmpty() + } + + @Test + fun parseBadManifests() { + val tag = "manifest" + val prefix = "<manifest $ns>" + val suffix = "</manifest>" + parseTagBadAttr(tag, "package", 256, ) + parseTagBadAttr(tag, "android:sharedUserId", 256) + parseTagBadAttr(tag, "android:versionName", 4000) + parseBadApplicationTags(100, prefix, suffix, tag) + parseBadOverlayTags(100, prefix, suffix, tag) + parseBadInstrumentationTags(100, prefix, suffix, tag) + parseBadPermissionGroupTags(100, prefix, suffix, tag) + parseBadPermissionTreeTags(100, prefix, suffix, tag) + parseBadSupportsGlTextureTags(100, prefix, suffix, tag) + parseBadSupportsScreensTags(100, prefix, suffix, tag) + parseBadUsesConfigurationTags(100, prefix, suffix, tag) + parseBadUsesPermissionSdk23Tags(100, prefix, suffix, tag) + parseBadUsesSdkTags(100, prefix, suffix, tag) + parseBadCompatibleScreensTags(200, prefix, suffix, tag) + parseBadQueriesTags(200, prefix, suffix, tag) + parseBadAttributionTags(400, prefix, suffix, tag) + parseBadUsesFeatureTags(400, prefix, suffix, tag) + parseBadPermissionTags(2000, prefix, suffix, tag) + parseBadUsesPermissionTags(20000, prefix, suffix, tag) + } + + private fun parseBadApplicationTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "application" + val newPrefix = "$prefix<$tag>" + val newSuffix = "</$tag>$suffix" + + parseTagBadAttr(tag, "android:backupAgent", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:manageSpaceActivity", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:process", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:requiredAccountType", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:restrictedAccountType", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:taskAffinity", 1024, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + + parseBadProfileableTags(100, newPrefix, newSuffix, tag) + parseBadUsesNativeLibraryTags(100, newPrefix, newSuffix, tag) + parseBadReceiverTags(1000, newPrefix, newSuffix, tag) + parseBadServiceTags(1000, newPrefix, newSuffix, tag) + parseBadActivityAliasTags(4000, newPrefix, newSuffix, tag) + parseBadUsesLibraryTags(4000, newPrefix, newSuffix, tag) + parseBadProviderTags(8000, newPrefix, newSuffix, tag) + parseBadMetaDataTags(8000, newPrefix, newSuffix, tag) + parseBadActivityTags(40000, newPrefix, newSuffix, tag) + } + + private fun parseBadProfileableTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "profileable" + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadUsesNativeLibraryTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "uses-native-library" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadReceiverTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "receiver" + val newPrefix = "$prefix<$tag>" + val newSuffix = "</$tag>$suffix" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:process", 1024, prefix, suffix) + + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + parseBadMetaDataTags(8000, newPrefix, newSuffix, tag) + parseBadIntentFilterTags(20000, newPrefix, newSuffix, tag) + } + + private fun parseBadServiceTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "service" + val newPrefix = "$prefix<$tag>" + val newSuffix = "</$tag>$suffix" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:process", 1024, prefix, suffix) + + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + parseBadMetaDataTags(8000, newPrefix, newSuffix, tag) + parseBadIntentFilterTags(20000, newPrefix, newSuffix, tag) + } + + private fun parseBadActivityAliasTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "activity-alias" + val newPrefix = "$prefix<$tag>" + val newSuffix = "</$tag>$suffix" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:targetActivity", 1024, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + parseBadMetaDataTags(8000, newPrefix, newSuffix, tag) + parseBadIntentFilterTags(20000, newPrefix, newSuffix, tag) + } + + private fun parseBadUsesLibraryTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "uses-library" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadActivityTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "activity" + val newPrefix = "$prefix<$tag>" + val newSuffix = "</$tag>$suffix" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:parentActivityName", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:process", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:taskAffinity", 1024, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + parseBadLayoutTags(1000, newPrefix, newSuffix, tag) + parseBadMetaDataTags(8000, newPrefix, newSuffix, tag) + parseBadIntentFilterTags(20000, newPrefix, newSuffix, tag) + } + + private fun parseBadLayoutTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "layout" + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadOverlayTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "overlay" + parseTagBadAttr(tag, "android:category", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:requiredSystemPropertyName", 32768, prefix, suffix) + parseTagBadAttr(tag, "android:requiredSystemPropertyValue", 256, prefix, suffix) + parseTagBadAttr(tag, "android:targetPackage", 256, prefix, suffix) + parseTagBadAttr(tag, "android:targetName", 1024, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadInstrumentationTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "instrumentation" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:targetPackage", 256, prefix, suffix) + parseTagBadAttr(tag, "android:targetProcesses", 1024, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadPermissionGroupTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "permission-group" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadPermissionTreeTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "permission-tree" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadSupportsGlTextureTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "supports-gl-texture" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadSupportsScreensTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "supports-screens" + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadUsesConfigurationTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "uses-configuration" + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadUsesPermissionSdk23Tags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "uses-permission-sdk-23" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadUsesSdkTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "uses-sdk" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadCompatibleScreensTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "compatible-screens" + val newPrefix = "$prefix<$tag>" + val newSuffix = "</$tag>$suffix" + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + parseBadScreenTags(4000, newPrefix, newSuffix, tag) + } + + private fun parseBadScreenTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "screen" + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadQueriesTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "queries" + val newPrefix = "$prefix<$tag>" + val newSuffix = "</$tag>$suffix" + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + parseBadPackageTags(1000, newPrefix, newSuffix, tag) + parseBadIntentTags(2000, newPrefix, newSuffix, tag) + parseBadProviderTags(8000, newPrefix, newSuffix, tag) + } + + private fun parseBadPackageTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "package" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadIntentTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "intent" + val newPrefix = "$prefix<$tag>" + val newSuffix = "</$tag>$suffix" + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + parseBadActionTags(20000, newPrefix, newSuffix, tag) + parseBadCategoryTags(40000, newPrefix, newSuffix, tag) + parseBadDataTags(40000, newPrefix, newSuffix, tag) + } + + private fun parseBadProviderTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "provider" + val newPrefix = "$prefix<$tag>" + val newSuffix = "</$tag>$suffix" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:process", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:readPermission", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:writePermission", 1024, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + parseBadGrantUriPermissionTags(100, newPrefix, newSuffix, tag) + parseBadPathPermissionTags(100, newPrefix, newSuffix, tag) + parseBadMetaDataTags(8000, newPrefix, newSuffix, tag) + parseBadIntentFilterTags(20000, newPrefix, newSuffix, tag) + } + + private fun parseBadGrantUriPermissionTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "grant-uri-permission" + parseTagBadAttr(tag, "android:path", 4000, prefix, suffix) + parseTagBadAttr(tag, "android:pathPrefix", 4000, prefix, suffix) + parseTagBadAttr(tag, "android:pathPattern", 4000, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadPathPermissionTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "path-permission" + parseTagBadAttr(tag, "android:path", 4000, prefix, suffix) + parseTagBadAttr(tag, "android:pathPrefix", 4000, prefix, suffix) + parseTagBadAttr(tag, "android:pathPattern", 4000, prefix, suffix) + parseTagBadAttr(tag, "android:permission", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:readPermission", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:writePermission", 1024, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadMetaDataTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "meta-data" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:value", 32768, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadIntentFilterTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "intent-filter" + val newPrefix = "$prefix<$tag>" + val newSuffix = "</$tag>$suffix" + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + parseBadActionTags(20000, newPrefix, newSuffix, tag) + parseBadCategoryTags(40000, newPrefix, newSuffix, tag) + parseBadDataTags(40000, newPrefix, newSuffix, tag) + } + + private fun parseBadActionTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "action" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadCategoryTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "category" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadDataTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "data" + parseTagBadAttr(tag, "android:scheme", 256, prefix, suffix) + parseTagBadAttr(tag, "android:host", 256, prefix, suffix) + parseTagBadAttr(tag, "android:path", 4000, prefix, suffix) + parseTagBadAttr(tag, "android:pathPattern", 4000, prefix, suffix) + parseTagBadAttr(tag, "android:pathPrefix", 4000, prefix, suffix) + parseTagBadAttr(tag, "android:pathSuffix", 4000, prefix, suffix) + parseTagBadAttr(tag, "android:pathAdvancedPattern", 4000, prefix, suffix) + parseTagBadAttr(tag, "android:mimeType", 512, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadAttributionTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "attribution" + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadUsesFeatureTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "uses-feature" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadPermissionTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "permission" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseTagBadAttr(tag, "android:permissionGroup", 256, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseBadUsesPermissionTags( + maxNum: Int, + prefix: String, + suffix: String, + parentTag: String + ) { + val tag = "uses-permission" + parseTagBadAttr(tag, "android:name", 1024, prefix, suffix) + parseBadTagCount(tag, maxNum, parentTag, prefix, suffix) + } + + private fun parseTagBadAttr( + tag: String, + attrName: String, + maxLength: Int, + prefix: String = "", + suffix: String = "" + ) { + var attrValue = "x".repeat(maxLength) + var tagValue = if (tag.equals("manifest")) "$tag $ns" else tag + var manifestStr = "$prefix<$tagValue $attrName=\"$attrValue\" />$suffix" + try { + parseManifestStr(manifestStr) + } catch (e: XmlPullParserException) { + fail("Failed to parse valid <$tag> attribute $attrName with max length of $maxLength:" + + " ${e.message}") + } + attrValue = "x".repeat(maxLength + 1) + manifestStr = "$prefix<$tagValue $attrName=\"$attrValue\" />$suffix" + val e = assertThrows(XmlPullParserException::class.java) { + parseManifestStr(manifestStr) + } + assertEquals(expectedAttrLengthErrorMsg(attrName.split(":").last(), tag), e.message) + } + + private fun parseBadTagCount( + tag: String, + maxNum: Int, + parentTag: String, + prefix: String, + suffix: String + ) { + var tags = "<$tag />".repeat(maxNum) + var manifestStr = "$prefix$tags$suffix" + try { + parseManifestStr(manifestStr) + } catch (e: XmlPullParserException) { + fail("Failed to parse <$tag> with max count limit of $maxNum under" + + " <$parentTag>: ${e.message}") + } + tags = "<$tag />".repeat(maxNum + 1) + manifestStr = "$prefix$tags$suffix" + val e = assertThrows(XmlPullParserException::class.java) { + parseManifestStr(manifestStr) + } + assertEquals(expectedCountErrorMsg(tag, parentTag), e.message) + } + + @Test + fun parseUnexpectedTag_shouldSkip() { + val host = "x".repeat(256) + val dataTags = "<data android:host=\"$host\" />".repeat(2049) + val ns = "http://schemas.android.com/apk/res/android" + val manifestStr = "<manifest xmlns:android=\"$ns\" package=\"test\">$dataTags</manifest>" + parseManifestStr(manifestStr) + } + + fun parseManifestStr(manifestStr: String) { + pullParser.setInput(ByteArrayInputStream(manifestStr.toByteArray()), null) + val validator = Validator() + do { + val type = pullParser.next() + validator.validate(pullParser) + } while (type != XmlPullParser.END_DOCUMENT) + } + + fun expectedCountErrorMsg(tag: String, parentTag: String) = + "The number of child $tag elements exceeded the max allowed in $parentTag" + + fun expectedAttrLengthErrorMsg(attr: String, tag: String) = + "String length limit exceeded for attribute $attr in $tag" + + fun expectedResAttrLengthErrorMsg(tag: String) = + "String length limit exceeded for attribute in $tag" + + @Test + fun validateResAttrs() { + pullParser.setInput(ByteArrayInputStream("<manifest />".toByteArray()), null) + pullParser.next() + val validator = Validator() + validator.validate(pullParser) + validateResAttr(pullParser, validator, R.styleable.AndroidManifestData_host, + "R.styleable.AndroidManifestData_host", 255) + validateResAttr(pullParser, validator, R.styleable.AndroidManifestData_port, + "R.styleable.AndroidManifestData_port", 255) + validateResAttr(pullParser, validator, R.styleable.AndroidManifestData_scheme, + "R.styleable.AndroidManifestData_scheme", 255) + validateResAttr(pullParser, validator, R.styleable.AndroidManifestData_mimeType, + "R.styleable.AndroidManifestData_mimeType", 512) + } + + fun validateResAttr( + parser: XmlPullParser, + validator: Validator, + resId: Int, + resIdStr: String, + maxLength: Int + ) { + try { + validator.validateAttr(parser, resId, "x".repeat(maxLength)) + } catch (e: XmlPullParserException) { + fail("Failed to parse valid string resource attribute $resIdStr with max length of" + + " $maxLength: ${e.message}") + } + val e = assertThrows(XmlPullParserException::class.java) { + validator.validateAttr(parser, resId, "x".repeat(maxLength + 1)) + } + assertEquals(expectedResAttrLengthErrorMsg("manifest"), e.message) + } +}
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java b/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java index a73fcb89da27..7af4b3d87a3a 100644 --- a/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java +++ b/services/tests/servicestests/src/com/android/server/power/WakeLockLogTest.java @@ -18,11 +18,18 @@ package com.android.server.power; import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.Context; +import android.content.pm.PackageManager; import android.os.PowerManager; +import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import java.io.PrintWriter; import java.io.StringWriter; @@ -34,12 +41,27 @@ import java.util.TimeZone; */ public class WakeLockLogTest { + @Mock + private Context mContext; + + @Mock + private PackageManager mPackageManager; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + + when(mPackageManager.getPackagesForUid(101)).thenReturn(new String[]{ "some.package1" }); + when(mPackageManager.getPackagesForUid(102)).thenReturn(new String[]{ "some.package2" }); + } + @Test public void testAddTwoItems() { final int tagDatabaseSize = 128; final int logSize = 20; TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); - WakeLockLog log = new WakeLockLog(injectorSpy); + WakeLockLog log = new WakeLockLog(injectorSpy, mContext); when(injectorSpy.currentTimeMillis()).thenReturn(1000L); log.onWakeLockAcquired("TagPartial", 101, @@ -50,8 +72,10 @@ public class WakeLockLogTest { PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP); assertEquals("Wake Lock Log\n" - + " 01-01 00:00:01.000 - 101 - ACQ TagPartial (partial,on-after-release)\n" - + " 01-01 00:00:01.150 - 102 - ACQ TagFull (full,acq-causes-wake)\n" + + " 01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial " + + "(partial,on-after-release)\n" + + " 01-01 00:00:01.150 - 102 (some.package2) - ACQ TagFull " + + "(full,acq-causes-wake)\n" + " -\n" + " Events: 2, Time-Resets: 0\n" + " Buffer, Bytes used: 6\n", @@ -63,7 +87,7 @@ public class WakeLockLogTest { final int tagDatabaseSize = 128; final int logSize = 20; TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); - WakeLockLog log = new WakeLockLog(injectorSpy); + WakeLockLog log = new WakeLockLog(injectorSpy, mContext); when(injectorSpy.currentTimeMillis()).thenReturn(1000L); log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK); @@ -72,8 +96,8 @@ public class WakeLockLogTest { log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK); assertEquals("Wake Lock Log\n" - + " 01-01 00:00:01.000 - 101 - ACQ TagPartial (partial)\n" - + " 01-01 00:00:01.350 - 102 - ACQ TagFull (full)\n" + + " 01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial (partial)\n" + + " 01-01 00:00:01.350 - 102 (some.package2) - ACQ TagFull (full)\n" + " -\n" + " Events: 2, Time-Resets: 1\n" + " Buffer, Bytes used: 15\n", @@ -85,7 +109,7 @@ public class WakeLockLogTest { final int tagDatabaseSize = 2; final int logSize = 20; TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); - WakeLockLog log = new WakeLockLog(injectorSpy); + WakeLockLog log = new WakeLockLog(injectorSpy, mContext); when(injectorSpy.currentTimeMillis()).thenReturn(1000L); log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK); @@ -95,7 +119,7 @@ public class WakeLockLogTest { assertEquals("Wake Lock Log\n" + " 01-01 00:00:01.000 - --- - ACQ UNKNOWN (partial)\n" - + " 01-01 00:00:01.150 - 102 - ACQ TagFull (full)\n" + + " 01-01 00:00:01.150 - 102 (some.package2) - ACQ TagFull (full)\n" + " -\n" + " Events: 2, Time-Resets: 0\n" + " Buffer, Bytes used: 6\n", @@ -107,26 +131,55 @@ public class WakeLockLogTest { final int tagDatabaseSize = 6; final int logSize = 10; TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); - WakeLockLog log = new WakeLockLog(injectorSpy); + WakeLockLog log = new WakeLockLog(injectorSpy, mContext); - // This first item will get deleted when ring buffer loops around + // Wake lock 1 acquired - log size = 3 when(injectorSpy.currentTimeMillis()).thenReturn(1000L); log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK); + // Wake lock 2 acquired - log size = 3 + 3 = 6 when(injectorSpy.currentTimeMillis()).thenReturn(1150L); log.onWakeLockAcquired("TagFull", 102, PowerManager.FULL_WAKE_LOCK); + + // Wake lock 3 acquired - log size = 6 + 3 = 9 when(injectorSpy.currentTimeMillis()).thenReturn(1151L); log.onWakeLockAcquired("TagThree", 101, PowerManager.PARTIAL_WAKE_LOCK); + + // We need more space - wake lock 1 acquisition is removed from the log and saved in the + // list. Log size = 9 - 3 + 2 = 8 when(injectorSpy.currentTimeMillis()).thenReturn(1152L); + log.onWakeLockReleased("TagThree", 101); + + // We need more space - wake lock 2 acquisition is removed from the log and saved in the + // list. Log size = 8 - 3 + 2 = 7 + when(injectorSpy.currentTimeMillis()).thenReturn(1153L); + log.onWakeLockReleased("TagPartial", 101); + + // We need more space - wake lock 3 acquisition is removed from the log and saved in the + // list. Log size = 7 - 3 + 3 = 7 + when(injectorSpy.currentTimeMillis()).thenReturn(1154L); log.onWakeLockAcquired("TagFour", 101, PowerManager.PARTIAL_WAKE_LOCK); + // We need more space - wake lock 3 release is removed from the log and wake lock 3 + // acquisition is removed from the list. Log size = 7 - 2 + 3 = 8 + when(injectorSpy.currentTimeMillis()).thenReturn(1155L); + log.onWakeLockAcquired("TagFive", 101, PowerManager.PARTIAL_WAKE_LOCK); + + // We need more space - wake lock 1 release is removed from the log and wake lock 1 + // acquisition is removed from the list. Log size = 8 - 2 + 2 = 8 + when(injectorSpy.currentTimeMillis()).thenReturn(1156L); + log.onWakeLockReleased("TagFull", 102); + + // Wake lock 2 acquisition is still printed because its release have not rolled off the log + // yet. assertEquals("Wake Lock Log\n" - + " 01-01 00:00:01.150 - 102 - ACQ TagFull (full)\n" - + " 01-01 00:00:01.151 - 101 - ACQ TagThree (partial)\n" - + " 01-01 00:00:01.152 - 101 - ACQ TagFour (partial)\n" + + " 01-01 00:00:01.150 - 102 (some.package2) - ACQ TagFull (full)\n" + + " 01-01 00:00:01.154 - 101 (some.package1) - ACQ TagFour (partial)\n" + + " 01-01 00:00:01.155 - 101 (some.package1) - ACQ TagFive (partial)\n" + + " 01-01 00:00:01.156 - 102 (some.package2) - REL TagFull\n" + " -\n" - + " Events: 3, Time-Resets: 0\n" - + " Buffer, Bytes used: 9\n", + + " Events: 4, Time-Resets: 0\n" + + " Buffer, Bytes used: 8\n", dumpLog(log, false)); } @@ -135,7 +188,7 @@ public class WakeLockLogTest { final int tagDatabaseSize = 6; final int logSize = 10; TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); - WakeLockLog log = new WakeLockLog(injectorSpy); + WakeLockLog log = new WakeLockLog(injectorSpy, mContext); // Bad tag means it wont get written when(injectorSpy.currentTimeMillis()).thenReturn(1000L); @@ -153,14 +206,15 @@ public class WakeLockLogTest { final int tagDatabaseSize = 6; final int logSize = 10; TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); - WakeLockLog log = new WakeLockLog(injectorSpy); + WakeLockLog log = new WakeLockLog(injectorSpy, mContext); when(injectorSpy.currentTimeMillis()).thenReturn(1000L); log.onWakeLockAcquired("*job*/com.one.two.3hree/.one..Last", 101, PowerManager.PARTIAL_WAKE_LOCK); assertEquals("Wake Lock Log\n" - + " 01-01 00:00:01.000 - 101 - ACQ *job*/c.o.t.3/.o..Last (partial)\n" + + " 01-01 00:00:01.000 - 101 (some.package1) - ACQ " + + "*job*/c.o.t.3/.o..Last (partial)\n" + " -\n" + " Events: 1, Time-Resets: 0\n" + " Buffer, Bytes used: 3\n", @@ -172,7 +226,7 @@ public class WakeLockLogTest { final int tagDatabaseSize = 6; final int logSize = 10; TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); - WakeLockLog log = new WakeLockLog(injectorSpy); + WakeLockLog log = new WakeLockLog(injectorSpy, mContext); when(injectorSpy.currentTimeMillis()).thenReturn(1000L); log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK); @@ -180,8 +234,8 @@ public class WakeLockLogTest { log.onWakeLockReleased("HowdyTag", 101); assertEquals("Wake Lock Log\n" - + " 01-01 00:00:01.000 - 101 - ACQ HowdyTag (partial)\n" - + " 01-01 00:00:01.001 - 101 - REL HowdyTag\n" + + " 01-01 00:00:01.000 - 101 (some.package1) - ACQ HowdyTag (partial)\n" + + " 01-01 00:00:01.001 - 101 (some.package1) - REL HowdyTag\n" + " -\n" + " Events: 2, Time-Resets: 0\n" + " Buffer, Bytes used: 5\n" @@ -194,7 +248,7 @@ public class WakeLockLogTest { final int tagDatabaseSize = 6; final int logSize = 10; TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); - WakeLockLog log = new WakeLockLog(injectorSpy); + WakeLockLog log = new WakeLockLog(injectorSpy, mContext); when(injectorSpy.currentTimeMillis()).thenReturn(1100L); log.onWakeLockAcquired("HowdyTag", 101, PowerManager.PARTIAL_WAKE_LOCK); @@ -204,7 +258,7 @@ public class WakeLockLogTest { log.onWakeLockReleased("HowdyTag", 101); assertEquals("Wake Lock Log\n" - + " 01-01 00:00:01.100 - 101 - ACQ HowdyTag (partial)\n" + + " 01-01 00:00:01.100 - 101 (some.package1) - ACQ HowdyTag (partial)\n" + " -\n" + " Events: 1, Time-Resets: 0\n" + " Buffer, Bytes used: 3\n", @@ -216,20 +270,153 @@ public class WakeLockLogTest { final int tagDatabaseSize = 6; final int logSize = 10; TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); - WakeLockLog log = new WakeLockLog(injectorSpy); + WakeLockLog log = new WakeLockLog(injectorSpy, mContext); when(injectorSpy.currentTimeMillis()).thenReturn(1000L); log.onWakeLockAcquired("TagPartial", 101, PowerManager.PARTIAL_WAKE_LOCK | PowerManager.SYSTEM_WAKELOCK); assertEquals("Wake Lock Log\n" - + " 01-01 00:00:01.000 - 101 - ACQ TagPartial (partial,system-wakelock)\n" + + " 01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial " + + "(partial,system-wakelock)\n" + " -\n" + " Events: 1, Time-Resets: 0\n" + " Buffer, Bytes used: 3\n", dumpLog(log, false)); } + @Test + public void testAddItemWithNoPackageName() { + final int tagDatabaseSize = 128; + final int logSize = 20; + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + WakeLockLog log = new WakeLockLog(injectorSpy, mContext); + + when(mPackageManager.getPackagesForUid(101)).thenReturn(null); + when(injectorSpy.currentTimeMillis()).thenReturn(1000L); + log.onWakeLockAcquired("TagPartial", 101, + PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE); + + assertEquals("Wake Lock Log\n" + + " 01-01 00:00:01.000 - 101 - ACQ TagPartial " + + "(partial,on-after-release)\n" + + " -\n" + + " Events: 1, Time-Resets: 0\n" + + " Buffer, Bytes used: 3\n", + dumpLog(log, false)); + } + + @Test + public void testAddItemWithMultiplePackageNames() { + final int tagDatabaseSize = 128; + final int logSize = 20; + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + WakeLockLog log = new WakeLockLog(injectorSpy, mContext); + + when(mPackageManager.getPackagesForUid(101)).thenReturn( + new String[]{ "some.package1", "some.package2", "some.package3" }); + + when(injectorSpy.currentTimeMillis()).thenReturn(1000L); + log.onWakeLockAcquired("TagPartial", 101, + PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE); + + assertEquals("Wake Lock Log\n" + + " 01-01 00:00:01.000 - 101 (some.package1,...) - ACQ TagPartial " + + "(partial,on-after-release)\n" + + " -\n" + + " Events: 1, Time-Resets: 0\n" + + " Buffer, Bytes used: 3\n", + dumpLog(log, false)); + } + + @Test + public void testAddItemsWithRepeatOwnerUid_UsesCache() { + final int tagDatabaseSize = 128; + final int logSize = 20; + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + WakeLockLog log = new WakeLockLog(injectorSpy, mContext); + + when(injectorSpy.currentTimeMillis()).thenReturn(1000L); + log.onWakeLockAcquired("TagPartial", 101, + PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE); + + when(injectorSpy.currentTimeMillis()).thenReturn(1150L); + log.onWakeLockAcquired("TagFull", 101, + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP); + + when(injectorSpy.currentTimeMillis()).thenReturn(1151L); + log.onWakeLockAcquired("TagFull2", 101, + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP); + + assertEquals("Wake Lock Log\n" + + " 01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial " + + "(partial,on-after-release)\n" + + " 01-01 00:00:01.150 - 101 (some.package1) - ACQ TagFull " + + "(full,acq-causes-wake)\n" + + " 01-01 00:00:01.151 - 101 (some.package1) - ACQ TagFull2 " + + "(full,acq-causes-wake)\n" + + " -\n" + + " Events: 3, Time-Resets: 0\n" + + " Buffer, Bytes used: 9\n", + dumpLog(log, false)); + + verify(mPackageManager, times(1)).getPackagesForUid(101); + } + + @Test + public void testAddItemsWithRepeatOwnerUid_SavedAcquisitions_UsesCache() { + final int tagDatabaseSize = 128; + final int logSize = 10; + TestInjector injectorSpy = spy(new TestInjector(tagDatabaseSize, logSize)); + WakeLockLog log = new WakeLockLog(injectorSpy, mContext); + + when(injectorSpy.currentTimeMillis()).thenReturn(1000L); + log.onWakeLockAcquired("TagPartial", 101, + PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE); + + when(injectorSpy.currentTimeMillis()).thenReturn(1150L); + log.onWakeLockAcquired("TagFull", 101, + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP); + + when(injectorSpy.currentTimeMillis()).thenReturn(1151L); + log.onWakeLockAcquired("TagFull2", 101, + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP); + + when(injectorSpy.currentTimeMillis()).thenReturn(1152L); + log.onWakeLockAcquired("TagFull3", 101, + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP); + + when(injectorSpy.currentTimeMillis()).thenReturn(1153L); + log.onWakeLockAcquired("TagFull4", 101, + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP); + + when(injectorSpy.currentTimeMillis()).thenReturn(1154L); + log.onWakeLockAcquired("TagFull5", 101, + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP); + + // The first 3 events have been removed from the log and they exist in the saved + // acquisitions list. They should also use the cache when fetching the package names. + assertEquals("Wake Lock Log\n" + + " 01-01 00:00:01.000 - 101 (some.package1) - ACQ TagPartial " + + "(partial,on-after-release)\n" + + " 01-01 00:00:01.150 - 101 (some.package1) - ACQ TagFull " + + "(full,acq-causes-wake)\n" + + " 01-01 00:00:01.151 - 101 (some.package1) - ACQ TagFull2 " + + "(full,acq-causes-wake)\n" + + " 01-01 00:00:01.152 - 101 (some.package1) - ACQ TagFull3 " + + "(full,acq-causes-wake)\n" + + " 01-01 00:00:01.153 - 101 (some.package1) - ACQ TagFull4 " + + "(full,acq-causes-wake)\n" + + " 01-01 00:00:01.154 - 101 (some.package1) - ACQ TagFull5 " + + "(full,acq-causes-wake)\n" + + " -\n" + + " Events: 6, Time-Resets: 0\n" + + " Buffer, Bytes used: 9\n", + dumpLog(log, false)); + + verify(mPackageManager, times(1)).getPackagesForUid(101); + } + private String dumpLog(WakeLockLog log, boolean includeTagDb) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java index cff4cc72de52..5a2d2e3d33fd 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java @@ -42,6 +42,7 @@ import android.util.SparseArray; import androidx.test.InstrumentationRegistry; +import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.PowerProfile; import org.junit.Before; @@ -214,6 +215,15 @@ public class BatteryExternalStatsWorkerTest { public class TestBatteryStatsImpl extends BatteryStatsImpl { public TestBatteryStatsImpl(Context context) { mPowerProfile = new PowerProfile(context, true /* forTest */); + + SparseArray<int[]> cpusByPolicy = new SparseArray<>(); + cpusByPolicy.put(0, new int[]{0, 1, 2, 3}); + cpusByPolicy.put(4, new int[]{4, 5, 6, 7}); + SparseArray<int[]> freqsByPolicy = new SparseArray<>(); + freqsByPolicy.put(0, new int[]{300000, 1000000, 2000000}); + freqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000}); + mCpuScalingPolicies = new CpuScalingPolicies(freqsByPolicy, freqsByPolicy); + initTimersAndCounters(); } } diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java index f48a296358c4..55ffa1a15a6b 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java @@ -32,25 +32,25 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.os.BatteryStats; import android.os.UserHandle; +import android.util.SparseArray; import android.util.SparseLongArray; import android.view.Display; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.KernelCpuSpeedReader; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader; -import com.android.internal.os.PowerProfile; import com.android.internal.util.ArrayUtils; import org.junit.Before; @@ -95,8 +95,6 @@ public class BatteryStatsCpuTimesTest { SystemServerCpuThreadReader mSystemServerCpuThreadReader; @Mock BatteryStatsImpl.UserInfoProvider mUserInfoProvider; - @Mock - PowerProfile mPowerProfile; private MockClock mClocks; private MockBatteryStatsImpl mBatteryStatsImpl; @@ -108,6 +106,7 @@ public class BatteryStatsCpuTimesTest { mClocks = new MockClock(); mBatteryStatsImpl = new MockBatteryStatsImpl(mClocks) + .setTestCpuScalingPolicies() .setKernelCpuUidUserSysTimeReader(mCpuUidUserSysTimeReader) .setKernelCpuUidFreqTimeReader(mCpuUidFreqTimeReader) .setKernelCpuUidActiveTimeReader(mCpuUidActiveTimeReader) @@ -119,18 +118,14 @@ public class BatteryStatsCpuTimesTest { @Test public void testUpdateCpuTimeLocked() { // PRECONDITIONS - mBatteryStatsImpl.setPowerProfile(mPowerProfile); mBatteryStatsImpl.setOnBatteryInternal(false); final int numClusters = 3; initKernelCpuSpeedReaders(numClusters); - final long[] freqs = {1, 12, 123, 12, 1234}; - when(mCpuUidFreqTimeReader.readFreqs(mPowerProfile)).thenReturn(freqs); // RUN mBatteryStatsImpl.updateCpuTimeLocked(false, false, null); // VERIFY - assertArrayEquals("Unexpected cpu freqs", freqs, mBatteryStatsImpl.getCpuFreqs()); verify(mCpuUidUserSysTimeReader).readDelta(anyBoolean(), isNull()); verify(mCpuUidFreqTimeReader).readDelta(anyBoolean(), isNull()); for (int i = 0; i < numClusters; ++i) { @@ -153,9 +148,8 @@ public class BatteryStatsCpuTimesTest { verify(mUserInfoProvider).refreshUserIds(); verify(mCpuUidUserSysTimeReader).readDelta(anyBoolean(), any(KernelCpuUidUserSysTimeReader.Callback.class)); - // perClusterTimesAvailable is called twice, once in updateCpuTimeLocked() and the other - // in readKernelUidCpuFreqTimesLocked. - verify(mCpuUidFreqTimeReader, times(2)).perClusterTimesAvailable(); + verify(mCpuUidFreqTimeReader).perClusterTimesAvailable(); + verify(mCpuUidFreqTimeReader).allUidTimesAvailable(); verify(mCpuUidFreqTimeReader).readDelta(anyBoolean(), any(KernelCpuUidFreqTimeReader.Callback.class)); verify(mCpuUidActiveTimeReader).readAbsolute( @@ -210,15 +204,25 @@ public class BatteryStatsCpuTimesTest { // PRECONDITIONS updateTimeBasesLocked(true, Display.STATE_ON, 0, 0); final long[][] clusterSpeedTimesMs = {{20, 30}, {40, 50, 60}}; + + CpuScalingPolicies scalingPolicies = new CpuScalingPolicies( + new SparseArray<>() {{ + for (int i = 0; i < clusterSpeedTimesMs.length; i++) { + put(i, new int[]{i}); + } + }}, + new SparseArray<>() {{ + for (int i = 0; i < clusterSpeedTimesMs.length; i++) { + put(i, new int[clusterSpeedTimesMs[i].length]); + } + }}); + mBatteryStatsImpl.setCpuScalingPolicies(scalingPolicies); + initKernelCpuSpeedReaders(clusterSpeedTimesMs.length); for (int i = 0; i < clusterSpeedTimesMs.length; ++i) { when(mKernelCpuSpeedReaders[i].readDelta()).thenReturn(clusterSpeedTimesMs[i]); } - when(mPowerProfile.getNumCpuClusters()).thenReturn(clusterSpeedTimesMs.length); - for (int i = 0; i < clusterSpeedTimesMs.length; ++i) { - when(mPowerProfile.getNumSpeedStepsInCpuCluster(i)) - .thenReturn(clusterSpeedTimesMs[i].length); - } + final SparseLongArray updatedUids = new SparseLongArray(); final int[] testUids = {10012, 10014, 10016}; final int[] cpuTimeUs = {89, 31, 43}; @@ -626,14 +630,19 @@ public class BatteryStatsCpuTimesTest { FIRST_APPLICATION_UID + 27, FIRST_APPLICATION_UID + 33 }); - final long[] freqs = {1, 12, 123, 12, 1234}; + CpuScalingPolicies scalingPolicies = new CpuScalingPolicies( + new SparseArray<>() {{ + put(0, new int[]{0}); + put(1, new int[]{1}); + }}, + new SparseArray<>() {{ + put(0, new int[]{1, 12, 123}); + put(1, new int[]{12, 1234}); + }}); + mBatteryStatsImpl.setCpuScalingPolicies(scalingPolicies); // Derived from freqs above, 2 clusters with {3, 2} freqs in each of them. final int[] clusterFreqs = {3, 2}; - when(mPowerProfile.getNumCpuClusters()).thenReturn(clusterFreqs.length); - for (int i = 0; i < clusterFreqs.length; ++i) { - when(mPowerProfile.getNumSpeedStepsInCpuCluster(i)) - .thenReturn(clusterFreqs[i]); - } + final long[][] uidTimesMs = { {4, 10, 5, 9, 4}, {5, 1, 12, 2, 10}, @@ -736,14 +745,21 @@ public class BatteryStatsCpuTimesTest { }; final ArrayList<BatteryStatsImpl.StopwatchTimer> partialTimers = getPartialTimers(partialTimerUids); - final long[] freqs = {1, 12, 123, 12, 1234}; + + CpuScalingPolicies scalingPolicies = new CpuScalingPolicies( + new SparseArray<>() {{ + put(0, new int[]{0}); + put(1, new int[]{1}); + }}, + new SparseArray<>() {{ + put(0, new int[]{1, 12, 123}); + put(1, new int[]{12, 1234}); + }}); + mBatteryStatsImpl.setCpuScalingPolicies(scalingPolicies); + // Derived from freqs above, 2 clusters with {3, 2} freqs in each of them. final int[] clusterFreqs = {3, 2}; - when(mPowerProfile.getNumCpuClusters()).thenReturn(clusterFreqs.length); - for (int i = 0; i < clusterFreqs.length; ++i) { - when(mPowerProfile.getNumSpeedStepsInCpuCluster(i)) - .thenReturn(clusterFreqs[i]); - } + final long[][] uidTimesMs = { {4, 10, 5, 9, 4}, {5, 1, 12, 2, 10}, @@ -764,7 +780,7 @@ public class BatteryStatsCpuTimesTest { mBatteryStatsImpl.readKernelUidCpuFreqTimesLocked(partialTimers, true, false, null); // VERIFY - final long[][] expectedWakeLockUidTimesUs = new long[clusterFreqs.length][]; + final long[][] expectedWakeLockUidTimesUs = new long[2][]; for (int cluster = 0; cluster < clusterFreqs.length; ++cluster) { expectedWakeLockUidTimesUs[cluster] = new long[clusterFreqs[cluster]]; } @@ -1357,11 +1373,7 @@ public class BatteryStatsCpuTimesTest { private void updateTimeBasesLocked(boolean unplugged, int screenState, long upTime, long realTime) { - // Set PowerProfile=null before calling updateTimeBasesLocked to avoid execution of - // BatteryStatsImpl.updateCpuTimeLocked - mBatteryStatsImpl.setPowerProfile(null); mBatteryStatsImpl.updateTimeBasesLocked(unplugged, screenState, upTime, realTime); - mBatteryStatsImpl.setPowerProfile(mPowerProfile); } private void initKernelCpuSpeedReaders(int count) { diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java index b2dad7348525..f20f061230e2 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java @@ -50,6 +50,7 @@ import android.view.Display; import androidx.test.filters.LargeTest; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader; import com.android.internal.os.KernelSingleUidTimeReader; import com.android.internal.os.LongArrayMultiStateCounter; @@ -70,9 +71,6 @@ import java.util.List; @RunWith(AndroidJUnit4.class) @SuppressWarnings("GuardedBy") public class BatteryStatsImplTest { - private static final long[] CPU_FREQS = {1, 2, 3, 4, 5}; - private static final int NUM_CPU_FREQS = CPU_FREQS.length; - @Mock private KernelCpuUidFreqTimeReader mKernelUidCpuFreqTimeReader; @Mock @@ -83,6 +81,16 @@ public class BatteryStatsImplTest { private KernelWakelockReader mKernelWakelockReader; private KernelWakelockStats mKernelWakelockStats = new KernelWakelockStats(); + private static final int NUM_CPU_FREQS = 5; + + private final CpuScalingPolicies mCpuScalingPolicies = new CpuScalingPolicies( + new SparseArray<>() {{ + put(0, new int[1]); + }}, + new SparseArray<>() {{ + put(0, new int[NUM_CPU_FREQS]); + }}); + private final MockClock mMockClock = new MockClock(); private MockBatteryStatsImpl mBatteryStatsImpl; @@ -91,13 +99,13 @@ public class BatteryStatsImplTest { MockitoAnnotations.initMocks(this); when(mKernelUidCpuFreqTimeReader.isFastCpuTimesReader()).thenReturn(true); - when(mKernelUidCpuFreqTimeReader.readFreqs(any())).thenReturn(CPU_FREQS); when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true); when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true); when(mKernelWakelockReader.readKernelWakelockStats( any(KernelWakelockStats.class))).thenReturn(mKernelWakelockStats); mBatteryStatsImpl = new MockBatteryStatsImpl(mMockClock) .setPowerProfile(mPowerProfile) + .setCpuScalingPolicies(mCpuScalingPolicies) .setKernelCpuUidFreqTimeReader(mKernelUidCpuFreqTimeReader) .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader) .setKernelWakelockReader(mKernelWakelockReader); diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java index e6454e4ce040..090c8c8c2c3c 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java @@ -1457,6 +1457,148 @@ public class BatteryStatsNoteTest extends TestCase { } @SmallTest + public void testGetPerStateActiveRadioDurationMs_initialModemActivity() { + final MockClock clock = new MockClock(); // holds realtime and uptime in ms + final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clock); + bi.setPowerProfile(mock(PowerProfile.class)); + + final int ratCount = RADIO_ACCESS_TECHNOLOGY_COUNT; + final int frequencyCount = ServiceState.FREQUENCY_RANGE_MMWAVE + 1; + final int txLevelCount = CellSignalStrength.getNumSignalStrengthLevels(); + + List<ActivityStatsTechSpecificInfo> specificInfoList = new ArrayList(); + + final long[][][] expectedDurationsMs = new long[ratCount][frequencyCount][txLevelCount]; + final long[][] expectedRxDurationsMs = new long[ratCount][frequencyCount]; + final long[][][] expectedTxDurationsMs = new long[ratCount][frequencyCount][txLevelCount]; + for (int rat = 0; rat < ratCount; rat++) { + for (int freq = 0; freq < frequencyCount; freq++) { + if (rat == RADIO_ACCESS_TECHNOLOGY_NR + || freq == ServiceState.FREQUENCY_RANGE_UNKNOWN) { + // Only the NR RAT should have per frequency data. + expectedRxDurationsMs[rat][freq] = 0; + } else { + expectedRxDurationsMs[rat][freq] = POWER_DATA_UNAVAILABLE; + } + for (int txLvl = 0; txLvl < txLevelCount; txLvl++) { + if (rat == RADIO_ACCESS_TECHNOLOGY_NR + || freq == ServiceState.FREQUENCY_RANGE_UNKNOWN) { + // Only the NR RAT should have per frequency data. + expectedTxDurationsMs[rat][freq][txLvl] = 0; + } else { + expectedTxDurationsMs[rat][freq][txLvl] = POWER_DATA_UNAVAILABLE; + } + } + } + } + + // The first modem activity pulled from modem with activity stats for each RATs. + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.UNKNOWN, + ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 101)); + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.GERAN, + ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 202)); + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.UTRAN, + ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 303)); + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.EUTRAN, + ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[]{3, 9, 133, 48, 218}, 404)); + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.CDMA2000, + ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 505)); + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.IWLAN, + ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 606)); + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.NGRAN, + ServiceState.FREQUENCY_RANGE_UNKNOWN, new int[txLevelCount], 707)); + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.NGRAN, + ServiceState.FREQUENCY_RANGE_LOW, new int[txLevelCount], 808)); + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.NGRAN, + ServiceState.FREQUENCY_RANGE_MID, new int[txLevelCount], 909)); + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.NGRAN, + ServiceState.FREQUENCY_RANGE_HIGH, new int[txLevelCount], 1010)); + specificInfoList.add(new ActivityStatsTechSpecificInfo( + AccessNetworkConstants.AccessNetworkType.NGRAN, + ServiceState.FREQUENCY_RANGE_MMWAVE, new int[txLevelCount], 1111)); + + + final ActivityStatsTechSpecificInfo[] specificInfos = specificInfoList.toArray( + new ActivityStatsTechSpecificInfo[specificInfoList.size()]); + final ModemActivityInfo mai = new ModemActivityInfo(0L, 2002L, 3003L, specificInfos); + final ModemAndBatteryState state = new ModemAndBatteryState(bi, mai, specificInfos); + + + IntConsumer incrementTime = inc -> { + state.currentTimeMs += inc; + clock.realtime = clock.uptime = state.currentTimeMs; + + final int currRat = state.currentRat; + final int currRant = state.currentRadioAccessNetworkType; + final int currFreqRange = + currRat == RADIO_ACCESS_TECHNOLOGY_NR ? state.currentFrequencyRange : 0; + int currSignalStrength = state.currentSignalStrengths.get(currRat); + + if (state.modemActive) { + // Don't count the duration if the modem is not active + expectedDurationsMs[currRat][currFreqRange][currSignalStrength] += inc; + } + + // Evaluate the HAL provided time in states. + final ActivityStatsTechSpecificInfo info = state.getSpecificInfo(currRant, + currFreqRange); + switch (state.modemState) { + case SLEEP: + long sleepMs = state.modemActivityInfo.getSleepTimeMillis(); + state.modemActivityInfo.setSleepTimeMillis(sleepMs + inc); + break; + case IDLE: + long idleMs = state.modemActivityInfo.getIdleTimeMillis(); + state.modemActivityInfo.setIdleTimeMillis(idleMs + inc); + break; + case RECEIVING: + long rxMs = info.getReceiveTimeMillis(); + info.setReceiveTimeMillis(rxMs + inc); + expectedRxDurationsMs[currRat][currFreqRange] += inc; + break; + case TRANSMITTING: + int[] txMs = info.getTransmitTimeMillis().clone(); + txMs[currSignalStrength] += inc; + info.setTransmitTimeMillis(txMs); + expectedTxDurationsMs[currRat][currFreqRange][currSignalStrength] += inc; + break; + } + }; + + // On battery, but the modem is not active + bi.updateTimeBasesLocked(true, Display.STATE_OFF, state.currentTimeMs * 1000, + state.currentTimeMs * 1000); + bi.setOnBatteryInternal(true); + state.noteModemControllerActivity(); + // Ensure the first modem activity should not be counted up. + checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs, + expectedTxDurationsMs, bi, state.currentTimeMs); + // Start counting. + state.setRatType(TelephonyManager.NETWORK_TYPE_NR, BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR, + AccessNetworkConstants.AccessNetworkType.NGRAN); + // Frequency changed to low. + state.setFrequencyRange(ServiceState.FREQUENCY_RANGE_LOW); + incrementTime.accept(300); + state.setModemState(ModemState.RECEIVING); + incrementTime.accept(500); + state.setModemState(ModemState.TRANSMITTING); + incrementTime.accept(600); + state.noteModemControllerActivity(); + checkPerStateActiveRadioDurations(expectedDurationsMs, expectedRxDurationsMs, + expectedTxDurationsMs, bi, state.currentTimeMs); + } + + @SmallTest public void testGetPerStateActiveRadioDurationMs_withModemActivity() { final MockClock clock = new MockClock(); // holds realtime and uptime in ms final MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clock); diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java index 3135215d65f7..534aa89e1699 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java @@ -34,6 +34,7 @@ import android.util.SparseArray; import androidx.test.InstrumentationRegistry; +import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.PowerProfile; import com.android.internal.power.EnergyConsumerStats; @@ -59,6 +60,9 @@ public class BatteryUsageStatsRule implements TestRule { private BatteryUsageStats mBatteryUsageStats; private boolean mScreenOn; + private boolean mDefaultCpuScalingPolicy = true; + private SparseArray<int[]> mCpusByPolicy = new SparseArray<>(); + private SparseArray<int[]> mFreqsByPolicy = new SparseArray<>(); public BatteryUsageStatsRule() { this(0, null); @@ -74,6 +78,13 @@ public class BatteryUsageStatsRule implements TestRule { mMockClock.currentTime = currentTime; mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir); mBatteryStats.setPowerProfile(mPowerProfile); + + mCpusByPolicy.put(0, new int[]{0, 1, 2, 3}); + mCpusByPolicy.put(4, new int[]{4, 5, 6, 7}); + mFreqsByPolicy.put(0, new int[]{300000, 1000000, 2000000}); + mFreqsByPolicy.put(4, new int[]{300000, 1000000, 2500000, 3000000}); + mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy)); + mBatteryStats.onSystemReady(); } @@ -82,6 +93,19 @@ public class BatteryUsageStatsRule implements TestRule { return this; } + public BatteryUsageStatsRule setCpuScalingPolicy(int policy, int[] relatedCpus, + int[] frequencies) { + if (mDefaultCpuScalingPolicy) { + mCpusByPolicy.clear(); + mFreqsByPolicy.clear(); + mDefaultCpuScalingPolicy = false; + } + mCpusByPolicy.put(policy, relatedCpus); + mFreqsByPolicy.put(policy, frequencies); + mBatteryStats.setCpuScalingPolicies(new CpuScalingPolicies(mCpusByPolicy, mFreqsByPolicy)); + return this; + } + public BatteryUsageStatsRule setAveragePower(String key, double value) { when(mPowerProfile.getAveragePower(key)).thenReturn(value); when(mPowerProfile.getAveragePowerOrDefault(eq(key), anyDouble())).thenReturn(value); @@ -103,23 +127,14 @@ public class BatteryUsageStatsRule implements TestRule { return this; } - public BatteryUsageStatsRule setNumCpuClusters(int number) { - when(mPowerProfile.getNumCpuClusters()).thenReturn(number); - return this; - } - - public BatteryUsageStatsRule setNumSpeedStepsInCpuCluster(int cluster, int speeds) { - when(mPowerProfile.getNumSpeedStepsInCpuCluster(cluster)).thenReturn(speeds); + public BatteryUsageStatsRule setAveragePowerForCpuScalingPolicy(int policy, double value) { + when(mPowerProfile.getAveragePowerForCpuScalingPolicy(policy)).thenReturn(value); return this; } - public BatteryUsageStatsRule setAveragePowerForCpuCluster(int cluster, double value) { - when(mPowerProfile.getAveragePowerForCpuCluster(cluster)).thenReturn(value); - return this; - } - - public BatteryUsageStatsRule setAveragePowerForCpuCore(int cluster, int step, double value) { - when(mPowerProfile.getAveragePowerForCpuCore(cluster, step)).thenReturn(value); + public BatteryUsageStatsRule setAveragePowerForCpuScalingStep(int policy, int step, + double value) { + when(mPowerProfile.getAveragePowerForCpuScalingStep(policy, step)).thenReturn(value); return this; } @@ -193,6 +208,12 @@ public class BatteryUsageStatsRule implements TestRule { return mPowerProfile; } + public CpuScalingPolicies getCpuScalingPolicies() { + synchronized (mBatteryStats) { + return mBatteryStats.getCpuScalingPolicies(); + } + } + public MockBatteryStatsImpl getBatteryStats() { return mBatteryStats; } diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java index ced996f35bdf..888bc623f669 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java @@ -65,15 +65,14 @@ public class CpuPowerCalculatorTest { @Rule public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720) - .setNumCpuClusters(2) - .setNumSpeedStepsInCpuCluster(0, 2) - .setNumSpeedStepsInCpuCluster(1, 2) - .setAveragePowerForCpuCluster(0, 360) - .setAveragePowerForCpuCluster(1, 480) - .setAveragePowerForCpuCore(0, 0, 300) - .setAveragePowerForCpuCore(0, 1, 400) - .setAveragePowerForCpuCore(1, 0, 500) - .setAveragePowerForCpuCore(1, 1, 600); + .setCpuScalingPolicy(0, new int[]{0, 1}, new int[]{100, 200}) + .setCpuScalingPolicy(2, new int[]{2, 3}, new int[]{300, 400}) + .setAveragePowerForCpuScalingPolicy(0, 360) + .setAveragePowerForCpuScalingPolicy(2, 480) + .setAveragePowerForCpuScalingStep(0, 0, 300) + .setAveragePowerForCpuScalingStep(0, 1, 400) + .setAveragePowerForCpuScalingStep(2, 0, 500) + .setAveragePowerForCpuScalingStep(2, 1, 600); private final KernelCpuSpeedReader[] mMockKernelCpuSpeedReaders = new KernelCpuSpeedReader[]{ mock(KernelCpuSpeedReader.class), @@ -179,7 +178,8 @@ public class CpuPowerCalculatorTest { mStatsRule.getUidStats(APP_UID1).getProcessStatsLocked("bar").addCpuTimeLocked(5432, 2345); CpuPowerCalculator calculator = - new CpuPowerCalculator(mStatsRule.getPowerProfile()); + new CpuPowerCalculator(mStatsRule.getCpuScalingPolicies(), + mStatsRule.getPowerProfile()); mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator); @@ -246,8 +246,8 @@ public class CpuPowerCalculatorTest { mStatsRule.getUidStats(APP_UID1).getProcessStatsLocked("foo").addCpuTimeLocked(4321, 1234); mStatsRule.getUidStats(APP_UID1).getProcessStatsLocked("bar").addCpuTimeLocked(5432, 2345); - CpuPowerCalculator calculator = - new CpuPowerCalculator(mStatsRule.getPowerProfile()); + CpuPowerCalculator calculator = new CpuPowerCalculator(mStatsRule.getCpuScalingPolicies(), + mStatsRule.getPowerProfile()); mStatsRule.apply(calculator); @@ -287,7 +287,6 @@ public class CpuPowerCalculatorTest { when(mMockUserInfoProvider.exists(anyInt())).thenReturn(true); when(mMockCpuUidFreqTimeReader.allUidTimesAvailable()).thenReturn(true); - when(mMockCpuUidFreqTimeReader.readFreqs(any())).thenReturn(new long[]{100, 200, 300, 400}); when(mMockKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true); @@ -353,8 +352,8 @@ public class CpuPowerCalculatorTest { mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, null); mStatsRule.getBatteryStats().updateCpuTimesForAllUids(); - CpuPowerCalculator calculator = - new CpuPowerCalculator(mStatsRule.getPowerProfile()); + CpuPowerCalculator calculator = new CpuPowerCalculator(mStatsRule.getCpuScalingPolicies(), + mStatsRule.getPowerProfile()); mStatsRule.apply(new BatteryUsageStatsQuery.Builder() .powerProfileModeledOnly() @@ -461,8 +460,8 @@ public class CpuPowerCalculatorTest { clusterChargesUC[1] += 20000000; mStatsRule.getBatteryStats().updateCpuTimeLocked(true, true, clusterChargesUC); - CpuPowerCalculator calculator = - new CpuPowerCalculator(mStatsRule.getPowerProfile()); + CpuPowerCalculator calculator = new CpuPowerCalculator(mStatsRule.getCpuScalingPolicies(), + mStatsRule.getPowerProfile()); mStatsRule.apply(new BatteryUsageStatsQuery.Builder() .includePowerModels() diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java index 62e56f9eeed1..d3ec0d7e3f6e 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java @@ -76,6 +76,9 @@ public class MobileRadioPowerCalculatorTest { .initMeasuredEnergyStatsLocked(); BatteryStatsImpl stats = mStatsRule.getBatteryStats(); + // The first ModemActivityInfo doesn't count up. + setInitialEmptyModemActivityInfo(stats); + // Scan for a cell stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE, TelephonyManager.SIM_STATE_READY, @@ -193,6 +196,9 @@ public class MobileRadioPowerCalculatorTest { .initMeasuredEnergyStatsLocked(); BatteryStatsImpl stats = mStatsRule.getBatteryStats(); + // The first ModemActivityInfo doesn't count up. + setInitialEmptyModemActivityInfo(stats); + // Scan for a cell stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE, TelephonyManager.SIM_STATE_READY, @@ -347,6 +353,9 @@ public class MobileRadioPowerCalculatorTest { .initMeasuredEnergyStatsLocked(); BatteryStatsImpl stats = mStatsRule.getBatteryStats(); + // The first ModemActivityInfo doesn't count up. + setInitialEmptyModemActivityInfo(stats); + // Scan for a cell stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE, TelephonyManager.SIM_STATE_READY, @@ -583,6 +592,9 @@ public class MobileRadioPowerCalculatorTest { .initMeasuredEnergyStatsLocked(); BatteryStatsImpl stats = mStatsRule.getBatteryStats(); + // The first ModemActivityInfo doesn't count up. + setInitialEmptyModemActivityInfo(stats); + // Scan for a cell stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE, TelephonyManager.SIM_STATE_READY, @@ -660,6 +672,9 @@ public class MobileRadioPowerCalculatorTest { .initMeasuredEnergyStatsLocked(); BatteryStatsImpl stats = mStatsRule.getBatteryStats(); + // The first ModemActivityInfo doesn't count up. + setInitialEmptyModemActivityInfo(stats); + stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, 0, -1, 0, 0); @@ -841,6 +856,9 @@ public class MobileRadioPowerCalculatorTest { .initMeasuredEnergyStatsLocked(); BatteryStatsImpl stats = mStatsRule.getBatteryStats(); + // The first ModemActivityInfo doesn't count up. + setInitialEmptyModemActivityInfo(stats); + stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH, 0, -1, 0, 0); @@ -1023,4 +1041,10 @@ public class MobileRadioPowerCalculatorTest { assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(2.08130); assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0); } + + public void setInitialEmptyModemActivityInfo(BatteryStatsImpl stats) { + // Initial empty ModemActivityInfo. + final ModemActivityInfo emptyMai = new ModemActivityInfo(0L, 0L, 0L, new int[5], 0L); + stats.noteModemControllerActivity(emptyMai, 0, 0, 0, mNetworkStatsManager); + } } diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java index b032cbe67485..6d3f1f27b572 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java @@ -16,18 +16,18 @@ package com.android.server.power.stats; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.app.usage.NetworkStatsManager; import android.net.NetworkStats; import android.os.Handler; import android.os.Looper; +import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.os.Clock; +import com.android.internal.os.CpuScalingPolicies; import com.android.internal.os.KernelCpuSpeedReader; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader; import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader; @@ -73,7 +73,6 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { }; mCpuUidFreqTimeReader = mock(KernelCpuUidFreqTimeReader.class); - when(mCpuUidFreqTimeReader.readFreqs(any())).thenReturn(new long[]{100, 200}); mKernelWakelockReader = null; } @@ -136,8 +135,25 @@ public class MockBatteryStatsImpl extends BatteryStatsImpl { @NonNull NetworkStatsManager networkStatsManager) { return mNetworkStats; } + public MockBatteryStatsImpl setPowerProfile(PowerProfile powerProfile) { mPowerProfile = powerProfile; + setTestCpuScalingPolicies(); + return this; + } + + public MockBatteryStatsImpl setTestCpuScalingPolicies() { + SparseArray<int[]> cpusByPolicy = new SparseArray<>(); + cpusByPolicy.put(0, new int[]{0}); + SparseArray<int[]> freqsByPolicy = new SparseArray<>(); + freqsByPolicy.put(0, new int[]{100, 200, 300}); + + setCpuScalingPolicies(new CpuScalingPolicies(freqsByPolicy, freqsByPolicy)); + return this; + } + + public MockBatteryStatsImpl setCpuScalingPolicies(CpuScalingPolicies cpuScalingPolicies) { + mCpuScalingPolicies = cpuScalingPolicies; return this; } diff --git a/services/tests/servicestests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java index 3c5c0a39745b..4dae2d548057 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java @@ -62,15 +62,14 @@ public class SystemServicePowerCalculatorTest { @Rule public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule() .setAveragePower(PowerProfile.POWER_CPU_ACTIVE, 720) - .setNumCpuClusters(2) - .setNumSpeedStepsInCpuCluster(0, 2) - .setNumSpeedStepsInCpuCluster(1, 2) - .setAveragePowerForCpuCluster(0, 360) - .setAveragePowerForCpuCluster(1, 480) - .setAveragePowerForCpuCore(0, 0, 300) - .setAveragePowerForCpuCore(0, 1, 400) - .setAveragePowerForCpuCore(1, 0, 500) - .setAveragePowerForCpuCore(1, 1, 600); + .setCpuScalingPolicy(0, new int[]{0, 1}, new int[]{100, 200}) + .setCpuScalingPolicy(2, new int[]{2, 3}, new int[]{300, 400}) + .setAveragePowerForCpuScalingPolicy(0, 360) + .setAveragePowerForCpuScalingPolicy(2, 480) + .setAveragePowerForCpuScalingStep(0, 0, 300) + .setAveragePowerForCpuScalingStep(0, 1, 400) + .setAveragePowerForCpuScalingStep(2, 0, 500) + .setAveragePowerForCpuScalingStep(2, 1, 600); @Mock private BatteryStatsImpl.UserInfoProvider mMockUserInfoProvider; @@ -113,9 +112,10 @@ public class SystemServicePowerCalculatorTest { prepareBatteryStats(null); SystemServicePowerCalculator calculator = new SystemServicePowerCalculator( - mStatsRule.getPowerProfile()); + mStatsRule.getCpuScalingPolicies(), mStatsRule.getPowerProfile()); - mStatsRule.apply(new CpuPowerCalculator(mStatsRule.getPowerProfile()), calculator); + mStatsRule.apply(new CpuPowerCalculator(mStatsRule.getCpuScalingPolicies(), + mStatsRule.getPowerProfile()), calculator); assertThat(mStatsRule.getUidBatteryConsumer(APP_UID1) .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES)) @@ -145,9 +145,10 @@ public class SystemServicePowerCalculatorTest { prepareBatteryStats(new long[]{50000000, 100000000}); SystemServicePowerCalculator calculator = new SystemServicePowerCalculator( - mStatsRule.getPowerProfile()); + mStatsRule.getCpuScalingPolicies(), mStatsRule.getPowerProfile()); - mStatsRule.apply(new CpuPowerCalculator(mStatsRule.getPowerProfile()), calculator); + mStatsRule.apply(new CpuPowerCalculator(mStatsRule.getCpuScalingPolicies(), + mStatsRule.getPowerProfile()), calculator); assertThat(mStatsRule.getUidBatteryConsumer(APP_UID1) .getConsumedPower(BatteryConsumer.POWER_COMPONENT_SYSTEM_SERVICES)) diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java index d1814199a0b1..b81b776019f9 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java @@ -106,7 +106,7 @@ public class CpuWakeupStatsTest { for (int i = 0; i < 100; i++) { final long now = mRandom.nextLong(retention + 1, 100 * retention); obj.noteWakeupTimeAndReason(now, i, KERNEL_REASON_SOUND_TRIGGER_IRQ); - assertThat(obj.mWakeupEvents.closestIndexOnOrBefore(now - retention)).isLessThan(0); + assertThat(obj.mWakeupEvents.lastIndexOnOrBefore(now - retention)).isLessThan(0); } } diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java index b02618e97b11..99bc25abc4a1 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java @@ -18,8 +18,8 @@ package com.android.server.power.stats.wakeups; import static com.google.common.truth.Truth.assertThat; +import android.util.LongSparseArray; import android.util.SparseIntArray; -import android.util.TimeSparseArray; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -73,7 +73,7 @@ public class WakingActivityHistoryTest { assertThat(history.mWakingActivity.contains(subsystem + 1)).isFalse(); assertThat(history.mWakingActivity.contains(subsystem - 1)).isFalse(); - final TimeSparseArray<SparseIntArray> recordedHistory = history.mWakingActivity.get( + final LongSparseArray<SparseIntArray> recordedHistory = history.mWakingActivity.get( subsystem); assertThat(recordedHistory.size()).isEqualTo(1); @@ -119,7 +119,7 @@ public class WakingActivityHistoryTest { assertThat(history.mWakingActivity.contains(subsystem + 1)).isFalse(); assertThat(history.mWakingActivity.contains(subsystem - 1)).isFalse(); - final TimeSparseArray<SparseIntArray> recordedHistory = history.mWakingActivity.get( + final LongSparseArray<SparseIntArray> recordedHistory = history.mWakingActivity.get( subsystem); assertThat(recordedHistory.size()).isEqualTo(1); @@ -266,7 +266,7 @@ public class WakingActivityHistoryTest { final long time = random.nextLong(firstTime + mTestRetention + 100, 456 * mTestRetention); history.recordActivity(subsystem, time, new SparseIntArray()); - assertThat(history.mWakingActivity.get(subsystem).closestIndexOnOrBefore( + assertThat(history.mWakingActivity.get(subsystem).lastIndexOnOrBefore( time - mTestRetention)).isLessThan(0); } } diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java index 3adee0f06761..83fa29a5dd66 100644 --- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java +++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java @@ -31,7 +31,7 @@ import android.content.Context; import android.content.res.Configuration; import android.test.suitebuilder.annotation.SmallTest; import android.util.AtomicFile; -import android.util.TimeSparseArray; +import android.util.LongSparseArray; import androidx.test.InstrumentationRegistry; import androidx.test.runner.AndroidJUnit4; @@ -568,7 +568,7 @@ public class UsageStatsDatabaseTest { mUsageStatsDatabase.forceIndexFiles(); final int len = mUsageStatsDatabase.mSortedStatFiles.length; for (int i = 0; i < len; i++) { - final TimeSparseArray<AtomicFile> files = mUsageStatsDatabase.mSortedStatFiles[i]; + final LongSparseArray<AtomicFile> files = mUsageStatsDatabase.mSortedStatFiles[i]; // The stats file for each interval type equals to max allowed. assertEquals(UsageStatsDatabase.MAX_FILES_PER_INTERVAL_TYPE[i], files.size()); 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 eaf483869be4..ba0743980077 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -4150,6 +4150,30 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testSetListenerAccessForUser_grantWithNameTooLong_throws() { + UserHandle user = UserHandle.of(mContext.getUserId() + 10); + ComponentName c = new ComponentName("com.example.package", + com.google.common.base.Strings.repeat("Blah", 150)); + + assertThrows(IllegalArgumentException.class, + () -> mBinderService.setNotificationListenerAccessGrantedForUser( + c, user.getIdentifier(), /* enabled= */ true, true)); + } + + @Test + public void testSetListenerAccessForUser_revokeWithNameTooLong_okay() throws Exception { + UserHandle user = UserHandle.of(mContext.getUserId() + 10); + ComponentName c = new ComponentName("com.example.package", + com.google.common.base.Strings.repeat("Blah", 150)); + + mBinderService.setNotificationListenerAccessGrantedForUser( + c, user.getIdentifier(), /* enabled= */ false, true); + + verify(mListeners).setPackageOrComponentEnabled( + c.flattenToString(), user.getIdentifier(), true, /* enabled= */ false, true); + } + + @Test public void testSetAssistantAccessForUser() throws Exception { UserInfo ui = new UserInfo(); ui.id = mContext.getUserId() + 10; diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java index fc4e6e0a5d66..efe1af336ecd 100644 --- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java +++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLoggingLatencyTest.java @@ -38,6 +38,7 @@ import android.os.BatteryStatsInternal; import android.os.Process; import android.os.RemoteException; +import androidx.test.filters.FlakyTest; import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.util.FakeLatencyTracker; @@ -47,6 +48,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.Timeout; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.ArgumentCaptor; @@ -54,10 +56,13 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @RunWith(JUnit4.class) +@FlakyTest(bugId = 275746222) public class SoundTriggerMiddlewareLoggingLatencyTest { @Rule public TestableDeviceConfigRule mDeviceConfigRule = new TestableDeviceConfigRule(); + @Rule + public Timeout mGlobalTimeout = Timeout.seconds(30); private FakeLatencyTracker mLatencyTracker; @Mock 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 5369b93c9534..bdd178b0b317 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -2046,6 +2046,7 @@ public class DisplayContentTests extends WindowTestsBase { // Once transition starts, rotation is applied and transition shows DC rotating. testPlayer.startTransition(); + waitUntilHandlersIdle(); assertNotEquals(origRot, dc.getConfiguration().windowConfiguration.getRotation()); assertNotNull(testPlayer.mLastReady); assertTrue(testPlayer.mController.isPlaying()); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java index 2a8f0ffc4d49..42422d91d598 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java @@ -26,6 +26,7 @@ import static android.view.DisplayCutout.NO_CUTOUT; import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DEFAULT; import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_DISABLED; import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_ENABLED; +import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean; @@ -1128,6 +1129,22 @@ public class DisplayRotationTests { SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0)); } + @Test + public void testReturnsUserRotation_FixedToUserRotationIfNoAutoRotation_AutoRotationNotSupport() + throws Exception { + mBuilder.setSupportAutoRotation(false).build(); + configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false); + mTarget.setFixedToUserRotation(FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION); + + freezeRotation(Surface.ROTATION_180); + + assertEquals(WindowManagerPolicy.USER_ROTATION_LOCKED, mTarget.getUserRotationMode()); + assertEquals(Surface.ROTATION_180, mTarget.getUserRotation()); + + assertEquals(Surface.ROTATION_180, mTarget.rotationForOrientation( + SCREEN_ORIENTATION_UNSPECIFIED, Surface.ROTATION_0)); + } + // ======================== // Non-rotation API Tests // ======================== @@ -1148,6 +1165,15 @@ public class DisplayRotationTests { + " fixed to user rotation.", mTarget.isFixedToUserRotation()); } + @Test + public void testIsFixedToUserRotation_FixedToUserRotationIfNoAutoRotation() throws Exception { + mBuilder.build(); + mTarget.setFixedToUserRotation(FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION); + + assertFalse("Display rotation should respect app requested orientation if" + + " fixed to user rotation if no auto rotation.", mTarget.isFixedToUserRotation()); + } + private void moveTimeForward(long timeMillis) { sCurrentUptimeMillis += timeMillis; sClock.fastForward(timeMillis); diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index 204cbf79dba9..994dcf1e2ea5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -31,17 +31,12 @@ import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL; -import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.clearInvocations; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.verify; import android.app.StatusBarManager; @@ -268,8 +263,6 @@ public class InsetsPolicyTest extends WindowTestsBase { navBar.setHasSurface(true); navBarProvider.setServerVisible(true); final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); - spyOn(policy); - doNothing().when(policy).startAnimation(anyBoolean(), any()); // Make both system bars invisible. mAppWindow.setRequestedVisibleTypes( @@ -305,8 +298,6 @@ public class InsetsPolicyTest extends WindowTestsBase { addNavigationBar().getControllableInsetProvider().setServerVisible(true); final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); - spyOn(policy); - doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(mAppWindow); policy.showTransient(navigationBars() | statusBars(), true /* isGestureOnSystemBar */); @@ -341,8 +332,6 @@ public class InsetsPolicyTest extends WindowTestsBase { mAppWindow.mAboveInsetsState.addSource(navBarSource); mAppWindow.mAboveInsetsState.addSource(statusBarSource); final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); - spyOn(policy); - doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(mAppWindow); policy.showTransient(navigationBars() | statusBars(), true /* isGestureOnSystemBar */); @@ -390,8 +379,6 @@ public class InsetsPolicyTest extends WindowTestsBase { final WindowState app2 = addWindow(TYPE_APPLICATION, "app"); final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); - spyOn(policy); - doNothing().when(policy).startAnimation(anyBoolean(), any()); policy.updateBarControlTarget(app); policy.showTransient(navigationBars() | statusBars(), true /* isGestureOnSystemBar */); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 28241d30e168..f332b6988da0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -563,6 +563,36 @@ public class TaskTests extends WindowTestsBase { assertEquals(freeformBounds, task.getBounds()); } + @Test + public void testTopActivityEligibleForUserAspectRatioButton() { + DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay(); + final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN).setDisplay(display).build(); + final Task task = rootTask.getBottomMostTask(); + final ActivityRecord root = task.getTopNonFinishingActivity(); + spyOn(mWm.mLetterboxConfiguration); + + // When device config flag is disabled the button is not enabled + doReturn(false).when(mWm.mLetterboxConfiguration) + .isUserAppAspectRatioSettingsEnabled(); + doReturn(false).when(mWm.mLetterboxConfiguration) + .isTranslucentLetterboxingEnabled(); + assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton); + + // The flag is enabled + doReturn(true).when(mWm.mLetterboxConfiguration) + .isUserAppAspectRatioSettingsEnabled(); + spyOn(root); + doReturn(task).when(root).getOrganizedTask(); + // When the flag is enabled and the top activity is not in size compat mode. + doReturn(false).when(root).inSizeCompatMode(); + assertTrue(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton); + + // When in size compat mode the button is not enabled + doReturn(true).when(root).inSizeCompatMode(); + assertFalse(task.getTaskInfo().topActivityEligibleForUserAspectRatioButton); + } + /** * Tests that a task with forced orientation has orientation-consistent bounds within the * parent. diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java index 4639ee0ae33d..45ecc3f762ec 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java @@ -77,6 +77,10 @@ class TestDisplayContent extends DisplayContent { spyOn(inputMonitor); doNothing().when(inputMonitor).resumeDispatchingLw(any()); + final InsetsPolicy insetsPolicy = getInsetsPolicy(); + WindowTestsBase.suppressInsetsAnimation(insetsPolicy.getPermanentControlTarget()); + WindowTestsBase.suppressInsetsAnimation(insetsPolicy.getTransientControlTarget()); + // For devices that set the sysprop ro.bootanim.set_orientation_<display_id> // See DisplayRotation#readDefaultDisplayRotation for context. // Without that, meaning of height and width in context of the tests can be swapped if diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index adf3f3976f38..bd111ada8550 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -233,7 +233,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override - public void onKeyguardOccludedChangedLw(boolean occluded, boolean waitAppTransition) { + public void onKeyguardOccludedChangedLw(boolean occluded) { } public void setSafeMode(boolean safeMode) { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 5b7b1b297a1d..5c1b262a9dc8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -82,6 +82,7 @@ import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.util.DisplayMetrics; import android.util.MergedConfiguration; +import android.view.ContentRecordingSession; import android.view.IWindow; import android.view.IWindowSessionCallback; import android.view.InputChannel; @@ -102,6 +103,7 @@ import androidx.test.platform.app.InstrumentationRegistry; import com.android.compatibility.common.util.AdoptShellPermissionsRule; import com.android.internal.os.IResultReceiver; +import com.android.server.LocalServices; import org.junit.Rule; import org.junit.Test; @@ -769,6 +771,63 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test + public void setContentRecordingSession_sessionNull_returnsTrue() { + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + + boolean result = wmInternal.setContentRecordingSession(/* incomingSession= */ null); + + assertThat(result).isTrue(); + } + + @Test + public void setContentRecordingSession_sessionContentDisplay_returnsTrue() { + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + ContentRecordingSession session = ContentRecordingSession.createDisplaySession( + DEFAULT_DISPLAY); + + boolean result = wmInternal.setContentRecordingSession(session); + + assertThat(result).isTrue(); + } + + @Test + public void setContentRecordingSession_sessionContentTask_noMatchingTask_returnsFalse() { + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + IBinder launchCookie = new Binder(); + ContentRecordingSession session = ContentRecordingSession.createTaskSession(launchCookie); + + boolean result = wmInternal.setContentRecordingSession(session); + + assertThat(result).isFalse(); + } + + @Test + public void setContentRecordingSession_sessionContentTask_matchingTask_returnsTrue() { + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + ActivityRecord activityRecord = createActivityRecord(createTask(mDefaultDisplay)); + ContentRecordingSession session = ContentRecordingSession.createTaskSession( + activityRecord.mLaunchCookie); + + boolean result = wmInternal.setContentRecordingSession(session); + + assertThat(result).isTrue(); + } + + @Test + public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerToken() { + WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class); + Task task = createTask(mDefaultDisplay); + ActivityRecord activityRecord = createActivityRecord(task); + ContentRecordingSession session = ContentRecordingSession.createTaskSession( + activityRecord.mLaunchCookie); + + wmInternal.setContentRecordingSession(session); + + assertThat(session.getTokenToRecord()).isEqualTo( + task.mRemoteToken.toWindowContainerToken().asBinder()); + } + + @Test public void testisLetterboxBackgroundMultiColored() { assertThat(setupLetterboxConfigurationWithBackgroundType( LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING)).isTrue(); 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 be8ee7832a5d..d5547ec69247 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -222,6 +222,10 @@ class WindowTestsBase extends SystemServiceTestsBase { displayPolicy.finishWindowsDrawn(); displayPolicy.finishScreenTurningOn(); + final InsetsPolicy insetsPolicy = mDefaultDisplay.getInsetsPolicy(); + suppressInsetsAnimation(insetsPolicy.getTransientControlTarget()); + suppressInsetsAnimation(insetsPolicy.getPermanentControlTarget()); + mTransaction = mSystemServicesTestRule.mTransaction; mMockSession = mock(Session.class); @@ -278,6 +282,15 @@ class WindowTestsBase extends SystemServiceTestsBase { checkDeviceSpecificOverridesNotApplied(); } + /** + * The test doesn't create real SurfaceControls, but mocked ones. This prevents the target from + * controlling them, or it will cause {@link NullPointerException}. + */ + static void suppressInsetsAnimation(InsetsControlTarget target) { + spyOn(target); + Mockito.doNothing().when(target).notifyInsetsControlChanged(); + } + @After public void tearDown() throws Exception { if (mUseFakeSettingsProvider) { diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java index a8e374f0edc2..f1c5865fff73 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java +++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java @@ -26,9 +26,9 @@ import android.os.SystemProperties; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.LongSparseArray; import android.util.Slog; import android.util.SparseArray; -import android.util.TimeSparseArray; import android.util.TimeUtils; import com.android.internal.annotations.VisibleForTesting; @@ -117,8 +117,8 @@ public class UsageStatsDatabase { private final Object mLock = new Object(); private final File[] mIntervalDirs; - @VisibleForTesting - final TimeSparseArray<AtomicFile>[] mSortedStatFiles; + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + final LongSparseArray<AtomicFile>[] mSortedStatFiles; private final UnixCalendar mCal; private final File mVersionFile; private final File mBackupsDir; @@ -155,7 +155,7 @@ public class UsageStatsDatabase { mVersionFile = new File(dir, "version"); mBackupsDir = new File(dir, "backups"); mUpdateBreadcrumb = new File(dir, "breadcrumb"); - mSortedStatFiles = new TimeSparseArray[mIntervalDirs.length]; + mSortedStatFiles = new LongSparseArray[mIntervalDirs.length]; mPackageMappingsFile = new File(dir, "mappings"); mCal = new UnixCalendar(0); } @@ -186,8 +186,8 @@ public class UsageStatsDatabase { } // Delete files that are in the future. - for (TimeSparseArray<AtomicFile> files : mSortedStatFiles) { - final int startIndex = files.closestIndexOnOrAfter(currentTimeMillis); + for (LongSparseArray<AtomicFile> files : mSortedStatFiles) { + final int startIndex = files.firstIndexOnOrAfter(currentTimeMillis); if (startIndex < 0) { continue; } @@ -221,7 +221,7 @@ public class UsageStatsDatabase { */ public boolean checkinDailyFiles(CheckinAction checkinAction) { synchronized (mLock) { - final TimeSparseArray<AtomicFile> files = + final LongSparseArray<AtomicFile> files = mSortedStatFiles[UsageStatsManager.INTERVAL_DAILY]; final int fileCount = files.size(); @@ -292,7 +292,7 @@ public class UsageStatsDatabase { // Index the available usage stat files on disk. for (int i = 0; i < mSortedStatFiles.length; i++) { if (mSortedStatFiles[i] == null) { - mSortedStatFiles[i] = new TimeSparseArray<>(); + mSortedStatFiles[i] = new LongSparseArray<>(); } else { mSortedStatFiles[i].clear(); } @@ -700,7 +700,7 @@ public class UsageStatsDatabase { int filesDeleted = 0; int filesMoved = 0; - for (TimeSparseArray<AtomicFile> files : mSortedStatFiles) { + for (LongSparseArray<AtomicFile> files : mSortedStatFiles) { final int fileCount = files.size(); for (int i = 0; i < fileCount; i++) { final AtomicFile file = files.valueAt(i); @@ -834,9 +834,9 @@ public class UsageStatsDatabase { } synchronized (mLock) { - final TimeSparseArray<AtomicFile> intervalStats = mSortedStatFiles[intervalType]; + final LongSparseArray<AtomicFile> intervalStats = mSortedStatFiles[intervalType]; - int endIndex = intervalStats.closestIndexOnOrBefore(endTime); + int endIndex = intervalStats.lastIndexOnOrBefore(endTime); if (endIndex < 0) { // All the stats start after this range ends, so nothing matches. if (DEBUG) { @@ -857,7 +857,7 @@ public class UsageStatsDatabase { } } - int startIndex = intervalStats.closestIndexOnOrBefore(beginTime); + int startIndex = intervalStats.lastIndexOnOrBefore(beginTime); if (startIndex < 0) { // All the stats available have timestamps after beginTime, which means they all // match. @@ -899,7 +899,7 @@ public class UsageStatsDatabase { int bestBucket = -1; long smallestDiff = Long.MAX_VALUE; for (int i = mSortedStatFiles.length - 1; i >= 0; i--) { - final int index = mSortedStatFiles[i].closestIndexOnOrBefore(beginTimeStamp); + final int index = mSortedStatFiles[i].lastIndexOnOrBefore(beginTimeStamp); int size = mSortedStatFiles[i].size(); if (index >= 0 && index < size) { // We have some results here, check if they are better than our current match. @@ -1523,7 +1523,7 @@ public class UsageStatsDatabase { pw.println("Database Summary:"); pw.increaseIndent(); for (int i = 0; i < mSortedStatFiles.length; i++) { - final TimeSparseArray<AtomicFile> files = mSortedStatFiles[i]; + final LongSparseArray<AtomicFile> files = mSortedStatFiles[i]; final int size = files.size(); pw.print(UserUsageStatsService.intervalToString(i)); pw.print(" stats files: "); diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java index 92032086dc24..fd56b6ed1074 100644 --- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java @@ -46,10 +46,10 @@ import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.AtomicFile; +import android.util.LongSparseArray; import android.util.Slog; import android.util.SparseArrayMap; import android.util.SparseIntArray; -import android.util.TimeSparseArray; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; @@ -1024,7 +1024,7 @@ class UserUsageStatsService { } private void dumpFileDetailsForInterval(IndentingPrintWriter ipw, int interval) { - final TimeSparseArray<AtomicFile> files = mDatabase.mSortedStatFiles[interval]; + final LongSparseArray<AtomicFile> files = mDatabase.mSortedStatFiles[interval]; final int numFiles = files.size(); for (int i = 0; i < numFiles; i++) { final long filename = files.keyAt(i); diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index 13945a119e6f..997015ff1c08 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -407,7 +407,9 @@ public class SoundTriggerService extends SystemService { var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE, "SoundTriggerSessionLogs for package: " + Objects.requireNonNull(originatorIdentity.packageName) - + "#" + sessionId); + + "#" + sessionId + + " - " + originatorIdentity.uid + + "|" + originatorIdentity.pid); return new SoundTriggerSessionStub(client, newSoundTriggerHelper(moduleProperties, eventLogger), eventLogger); } @@ -428,7 +430,9 @@ public class SoundTriggerService extends SystemService { var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE, "SoundTriggerSessionLogs for package: " + Objects.requireNonNull(originatorIdentity.packageName) + "#" - + sessionId); + + sessionId + + " - " + originatorIdentity.uid + + "|" + originatorIdentity.pid); return new SoundTriggerSessionStub(client, newSoundTriggerHelper(moduleProperties, eventLogger), eventLogger); } @@ -1801,7 +1805,9 @@ public class SoundTriggerService extends SystemService { ServiceEvent.Type.ATTACH, identity.packageName + "#" + sessionId)); var eventLogger = new EventLogger(SESSION_MAX_EVENT_SIZE, "LocalSoundTriggerEventLogger for package: " + - identity.packageName + "#" + sessionId); + identity.packageName + "#" + sessionId + + " - " + identity.uid + + "|" + identity.pid); return new SessionImpl(newSoundTriggerHelper(underlyingModule, eventLogger, isTrusted), client, eventLogger, identity); diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java index 2f2cb594ff3a..55cbf29553f6 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/DefaultHalFactory.java @@ -22,7 +22,7 @@ import android.os.HwBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; -import android.util.Log; +import android.util.Slog; import java.util.ArrayList; import java.util.Arrays; @@ -62,7 +62,7 @@ class DefaultHalFactory implements HalFactory { android.hardware.soundtrigger3.ISoundTriggerHw.class.getCanonicalName() + "/default"; if (ServiceManager.isDeclared(aidlServiceName)) { - Log.i(TAG, "Connecting to default soundtrigger3.ISoundTriggerHw"); + Slog.i(TAG, "Connecting to default soundtrigger3.ISoundTriggerHw"); return new SoundTriggerHw3Compat(ServiceManager.waitForService(aidlServiceName), () -> { // This property needs to be defined in an init.rc script and @@ -72,7 +72,7 @@ class DefaultHalFactory implements HalFactory { } // Fallback to soundtrigger-V2.x (HIDL). - Log.i(TAG, "Connecting to default soundtrigger-V2.x.ISoundTriggerHw"); + Slog.i(TAG, "Connecting to default soundtrigger-V2.x.ISoundTriggerHw"); ISoundTriggerHw driver = ISoundTriggerHw.getService(true); return SoundTriggerHw2Compat.create(driver, () -> { // This property needs to be defined in an init.rc script and @@ -81,7 +81,7 @@ class DefaultHalFactory implements HalFactory { }, mCaptureStateNotifier); } else if (mockHal == USE_MOCK_HAL_V2) { // Use V2 mock. - Log.i(TAG, "Connecting to mock soundtrigger-V2.x.ISoundTriggerHw"); + Slog.i(TAG, "Connecting to mock soundtrigger-V2.x.ISoundTriggerHw"); HwBinder.setTrebleTestingOverride(true); try { ISoundTriggerHw driver = ISoundTriggerHw.getService("mock", true); @@ -89,7 +89,7 @@ class DefaultHalFactory implements HalFactory { try { driver.debug(null, new ArrayList<>(Arrays.asList("reboot"))); } catch (Exception e) { - Log.e(TAG, "Failed to reboot mock HAL", e); + Slog.e(TAG, "Failed to reboot mock HAL", e); } }, mCaptureStateNotifier); } finally { @@ -100,14 +100,14 @@ class DefaultHalFactory implements HalFactory { final String aidlServiceName = android.hardware.soundtrigger3.ISoundTriggerHw.class.getCanonicalName() + "/mock"; - Log.i(TAG, "Connecting to mock soundtrigger3.ISoundTriggerHw"); + Slog.i(TAG, "Connecting to mock soundtrigger3.ISoundTriggerHw"); return new SoundTriggerHw3Compat(ServiceManager.waitForService(aidlServiceName), () -> { try { ServiceManager.waitForService(aidlServiceName).shellCommand(null, null, null, new String[]{"reboot"}, null, null); } catch (Exception e) { - Log.e(TAG, "Failed to reboot mock HAL", e); + Slog.e(TAG, "Failed to reboot mock HAL", e); } }); } else { diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java index d195fbedcf2f..e3d64d4bf9db 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java @@ -17,7 +17,7 @@ package com.android.server.soundtrigger_middleware; import android.annotation.NonNull; -import android.util.Log; +import android.util.Slog; import java.util.LinkedList; import java.util.List; @@ -94,7 +94,7 @@ class ExternalCaptureStateTracker implements ICaptureStateNotifier { } } } catch (Exception e) { - Log.e(TAG, "Exception caught while setting capture state", e); + Slog.e(TAG, "Exception caught while setting capture state", e); } } @@ -102,7 +102,7 @@ class ExternalCaptureStateTracker implements ICaptureStateNotifier { * Called by native code when the remote service died. */ private void binderDied() { - Log.w(TAG, "Audio policy service died"); + Slog.w(TAG, "Audio policy service died"); mNeedToConnect.release(); } } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java index c3e0a3cd0292..0f63347ccef8 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalEnforcer.java @@ -27,7 +27,7 @@ import android.media.soundtrigger_middleware.PhraseRecognitionEventSys; import android.media.soundtrigger_middleware.RecognitionEventSys; import android.os.DeadObjectException; import android.os.IBinder; -import android.util.Log; +import android.util.Slog; import java.util.HashMap; import java.util.Map; @@ -227,10 +227,10 @@ public class SoundTriggerHalEnforcer implements ISoundTriggerHal { } if (e.getCause() instanceof DeadObjectException) { // Server is dead, no need to reboot. - Log.e(TAG, "HAL died"); + Slog.e(TAG, "HAL died"); throw new RecoverableException(Status.DEAD_OBJECT); } - Log.e(TAG, "Exception caught from HAL, rebooting HAL"); + Slog.e(TAG, "Exception caught from HAL, rebooting HAL"); reboot(); throw e; } @@ -257,14 +257,14 @@ public class SoundTriggerHalEnforcer implements ISoundTriggerHal { synchronized (mModelStates) { ModelState state = mModelStates.get(model); if (state == null) { - Log.wtfStack(TAG, "Unexpected recognition event for model: " + model); + Slog.wtfStack(TAG, "Unexpected recognition event for model: " + model); reboot(); return; } if (event.recognitionEvent.recognitionStillActive && event.recognitionEvent.status != RecognitionStatus.SUCCESS && event.recognitionEvent.status != RecognitionStatus.FORCED) { - Log.wtfStack(TAG, + Slog.wtfStack(TAG, "recognitionStillActive is only allowed when the recognition status " + "is SUCCESS"); reboot(); @@ -283,14 +283,14 @@ public class SoundTriggerHalEnforcer implements ISoundTriggerHal { synchronized (mModelStates) { ModelState state = mModelStates.get(model); if (state == null) { - Log.wtfStack(TAG, "Unexpected recognition event for model: " + model); + Slog.wtfStack(TAG, "Unexpected recognition event for model: " + model); reboot(); return; } if (event.phraseRecognitionEvent.common.recognitionStillActive && event.phraseRecognitionEvent.common.status != RecognitionStatus.SUCCESS && event.phraseRecognitionEvent.common.status != RecognitionStatus.FORCED) { - Log.wtfStack(TAG, + Slog.wtfStack(TAG, "recognitionStillActive is only allowed when the recognition status " + "is SUCCESS"); reboot(); @@ -309,13 +309,13 @@ public class SoundTriggerHalEnforcer implements ISoundTriggerHal { synchronized (mModelStates) { ModelState state = mModelStates.get(modelHandle); if (state == null) { - Log.wtfStack(TAG, "Unexpected unload event for model: " + modelHandle); + Slog.wtfStack(TAG, "Unexpected unload event for model: " + modelHandle); reboot(); return; } if (state == ModelState.ACTIVE) { - Log.wtfStack(TAG, "Trying to unload an active model: " + modelHandle); + Slog.wtfStack(TAG, "Trying to unload an active model: " + modelHandle); reboot(); return; } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java index 0390f034ab23..5e525e0d194e 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHalWatchdog.java @@ -23,7 +23,7 @@ import android.media.soundtrigger.Properties; import android.media.soundtrigger.RecognitionConfig; import android.media.soundtrigger.SoundModel; import android.os.IBinder; -import android.util.Log; +import android.util.Slog; import java.util.Objects; @@ -172,7 +172,7 @@ public class SoundTriggerHalWatchdog implements ISoundTriggerHal { Watchdog() { mTask = mTimer.createTask(() -> { - Log.e(TAG, "HAL deadline expired. Rebooting.", mException); + Slog.e(TAG, "HAL deadline expired. Rebooting.", mException); reboot(); }, TIMEOUT_MS); } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java index df2e9b41662b..730e92cb2aee 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java @@ -32,7 +32,7 @@ import android.os.IHwBinder; import android.os.RemoteException; import android.os.SystemClock; import android.system.OsConstants; -import android.util.Log; +import android.util.Slog; import java.io.IOException; import java.util.HashMap; @@ -240,7 +240,7 @@ final class SoundTriggerHw2Compat implements ISoundTriggerHal { try { hidlModel.data.close(); } catch (IOException e) { - Log.e(TAG, "Failed to close file", e); + Slog.e(TAG, "Failed to close file", e); } } } @@ -276,7 +276,7 @@ final class SoundTriggerHw2Compat implements ISoundTriggerHal { try { hidlModel.common.data.close(); } catch (IOException e) { - Log.e(TAG, "Failed to close file", e); + Slog.e(TAG, "Failed to close file", e); } } } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java index 3b800de2f30b..5a064da314c6 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import android.media.soundtrigger_middleware.ISoundTriggerCallback; import android.media.soundtrigger_middleware.ISoundTriggerModule; import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; -import android.util.Log; +import android.util.Slog; import java.util.ArrayList; import java.util.List; @@ -85,7 +85,7 @@ public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareIntern try { modules.add(new SoundTriggerModule(halFactory, audioSessionProvider)); } catch (Exception e) { - Log.e(TAG, "Failed to add a SoundTriggerModule instance", e); + Slog.e(TAG, "Failed to add a SoundTriggerModule instance", e); } } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java index 7ec2d9fd7b23..0b9ed8c20e8e 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java @@ -36,7 +36,7 @@ import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceSpecificException; -import android.util.Log; +import android.util.Slog; import android.util.SparseArray; import com.android.internal.util.Preconditions; @@ -150,7 +150,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware e.getMessage()); } - Log.wtf(TAG, "Unexpected exception", e); + Slog.wtf(TAG, "Unexpected exception", e); throw new ServiceSpecificException(Status.INTERNAL_ERROR, e.getMessage()); } @@ -701,7 +701,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware try { mCallback.onRecognition(modelHandle, event, captureSession); } catch (Exception e) { - Log.w(TAG, "Client callback exception.", e); + Slog.w(TAG, "Client callback exception.", e); } } @@ -719,7 +719,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware try { mCallback.onPhraseRecognition(modelHandle, event, captureSession); } catch (Exception e) { - Log.w(TAG, "Client callback exception.", e); + Slog.w(TAG, "Client callback exception.", e); } } @@ -734,7 +734,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware try { mCallback.onModelUnloaded(modelHandle); } catch (Exception e) { - Log.w(TAG, "Client callback exception.", e); + Slog.w(TAG, "Client callback exception.", e); } } @@ -746,7 +746,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware } catch (RemoteException e) { // Dead client will be handled by binderDied() - no need to handle here. // In any case, client callbacks are considered best effort. - Log.e(TAG, "Client callback exception.", e); + Slog.e(TAG, "Client callback exception.", e); } } @@ -761,7 +761,7 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware } catch (RemoteException e) { // Dead client will be handled by binderDied() - no need to handle here. // In any case, client callbacks are considered best effort. - Log.e(TAG, "Client callback exception.", e); + Slog.e(TAG, "Client callback exception.", e); } } @@ -795,11 +795,11 @@ public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddleware // Check if state updated unexpectedly to log race conditions. for (Map.Entry<Integer, ModelState> entry : mLoadedModels.entrySet()) { if (cachedMap.get(entry.getKey()) != entry.getValue().activityState) { - Log.e(TAG, "Unexpected state update in binderDied. Race occurred!"); + Slog.e(TAG, "Unexpected state update in binderDied. Race occurred!"); } } if (mLoadedModels.size() != cachedMap.size()) { - Log.e(TAG, "Unexpected state update in binderDied. Race occurred!"); + Slog.e(TAG, "Unexpected state update in binderDied. Race occurred!"); } try { // Detach diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java index e793f317d41f..45a7fafa90a7 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java @@ -31,7 +31,7 @@ import android.media.soundtrigger_middleware.RecognitionEventSys; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; -import android.util.Log; +import android.util.Slog; import java.util.ArrayList; import java.util.HashMap; @@ -136,7 +136,7 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo @Override public void binderDied() { - Log.w(TAG, "Underlying HAL driver died."); + Slog.w(TAG, "Underlying HAL driver died."); List<ISoundTriggerCallback> callbacks; synchronized (this) { callbacks = new ArrayList<>(mActiveSessions.size()); @@ -270,7 +270,7 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo try { mAudioSessionProvider.releaseSession(audioSession.mSessionHandle); } catch (Exception ee) { - Log.e(TAG, "Failed to release session.", ee); + Slog.e(TAG, "Failed to release session.", ee); } throw e; } @@ -286,7 +286,7 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo checkValid(); Model loadedModel = new Model(); int result = loadedModel.load(model, audioSession); - Log.d(TAG, String.format("loadPhraseModel()->%d", result)); + Slog.d(TAG, String.format("loadPhraseModel()->%d", result)); return result; } catch (Exception e) { // We must do this outside the lock, to avoid possible deadlocks with the remote @@ -294,7 +294,7 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo try { mAudioSessionProvider.releaseSession(audioSession.mSessionHandle); } catch (Exception ee) { - Log.e(TAG, "Failed to release session.", ee); + Slog.e(TAG, "Failed to release session.", ee); } throw e; } diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java index b5c1d7d9e5f4..c7f0c5f753db 100644 --- a/telephony/java/android/telephony/data/DataCallResponse.java +++ b/telephony/java/android/telephony/data/DataCallResponse.java @@ -36,8 +36,10 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Description of the response of a setup data call connection request. @@ -172,63 +174,57 @@ public final class DataCallResponse implements Parcelable { @Nullable List<InetAddress> dnsAddresses, @Nullable List<InetAddress> gatewayAddresses, @Nullable List<InetAddress> pcscfAddresses, int mtu) { - mCause = cause; - mSuggestedRetryTime = suggestedRetryTime; - mId = id; - mLinkStatus = linkStatus; - mProtocolType = protocolType; - mInterfaceName = (interfaceName == null) ? "" : interfaceName; - mAddresses = (addresses == null) - ? new ArrayList<>() : new ArrayList<>(addresses); - mDnsAddresses = (dnsAddresses == null) - ? new ArrayList<>() : new ArrayList<>(dnsAddresses); - mGatewayAddresses = (gatewayAddresses == null) - ? new ArrayList<>() : new ArrayList<>(gatewayAddresses); - mPcscfAddresses = (pcscfAddresses == null) - ? new ArrayList<>() : new ArrayList<>(pcscfAddresses); - mMtu = mMtuV4 = mMtuV6 = mtu; - mHandoverFailureMode = HANDOVER_FAILURE_MODE_LEGACY; - mPduSessionId = PDU_SESSION_ID_NOT_SET; - mDefaultQos = null; - mQosBearerSessions = new ArrayList<>(); - mSliceInfo = null; - mTrafficDescriptors = new ArrayList<>(); + this(cause, suggestedRetryTime, id, + linkStatus, protocolType, + interfaceName == null ? "" : interfaceName, + addresses == null ? Collections.emptyList() : addresses, + dnsAddresses == null ? Collections.emptyList() : dnsAddresses, + gatewayAddresses == null ? Collections.emptyList() : gatewayAddresses, + pcscfAddresses == null ? Collections.emptyList() : pcscfAddresses, + mtu, mtu /* mtuV4 */, mtu /* mtuV6 */, + HANDOVER_FAILURE_MODE_LEGACY, PDU_SESSION_ID_NOT_SET, + null /* defaultQos */, Collections.emptyList() /* qosBearerSessions */, + null /* sliceInfo */, + Collections.emptyList() /* trafficDescriptors */); } private DataCallResponse(@DataFailureCause int cause, long suggestedRetryTime, int id, @LinkStatus int linkStatus, @ProtocolType int protocolType, - @Nullable String interfaceName, @Nullable List<LinkAddress> addresses, - @Nullable List<InetAddress> dnsAddresses, @Nullable List<InetAddress> gatewayAddresses, - @Nullable List<InetAddress> pcscfAddresses, int mtu, int mtuV4, int mtuV6, + @NonNull String interfaceName, @NonNull List<LinkAddress> addresses, + @NonNull List<InetAddress> dnsAddresses, @NonNull List<InetAddress> gatewayAddresses, + @NonNull List<InetAddress> pcscfAddresses, int mtu, int mtuV4, int mtuV6, @HandoverFailureMode int handoverFailureMode, int pduSessionId, - @Nullable Qos defaultQos, @Nullable List<QosBearerSession> qosBearerSessions, + @Nullable Qos defaultQos, @NonNull List<QosBearerSession> qosBearerSessions, @Nullable NetworkSliceInfo sliceInfo, - @Nullable List<TrafficDescriptor> trafficDescriptors) { + @NonNull List<TrafficDescriptor> trafficDescriptors) { mCause = cause; mSuggestedRetryTime = suggestedRetryTime; mId = id; mLinkStatus = linkStatus; mProtocolType = protocolType; - mInterfaceName = (interfaceName == null) ? "" : interfaceName; - mAddresses = (addresses == null) - ? new ArrayList<>() : new ArrayList<>(addresses); - mDnsAddresses = (dnsAddresses == null) - ? new ArrayList<>() : new ArrayList<>(dnsAddresses); - mGatewayAddresses = (gatewayAddresses == null) - ? new ArrayList<>() : new ArrayList<>(gatewayAddresses); - mPcscfAddresses = (pcscfAddresses == null) - ? new ArrayList<>() : new ArrayList<>(pcscfAddresses); + mInterfaceName = interfaceName; + mAddresses = new ArrayList<>(addresses); + mDnsAddresses = new ArrayList<>(dnsAddresses); + mGatewayAddresses = new ArrayList<>(gatewayAddresses); + mPcscfAddresses = new ArrayList<>(pcscfAddresses); mMtu = mtu; mMtuV4 = mtuV4; mMtuV6 = mtuV6; mHandoverFailureMode = handoverFailureMode; mPduSessionId = pduSessionId; mDefaultQos = defaultQos; - mQosBearerSessions = (qosBearerSessions == null) - ? new ArrayList<>() : new ArrayList<>(qosBearerSessions); + mQosBearerSessions = new ArrayList<>(qosBearerSessions); mSliceInfo = sliceInfo; - mTrafficDescriptors = (trafficDescriptors == null) - ? new ArrayList<>() : new ArrayList<>(trafficDescriptors); + mTrafficDescriptors = new ArrayList<>(trafficDescriptors); + + if (mLinkStatus == LINK_STATUS_ACTIVE + || mLinkStatus == LINK_STATUS_DORMANT) { + Objects.requireNonNull( + mInterfaceName, "Active data calls must be on a valid interface!"); + if (mCause != DataFailCause.NONE) { + throw new IllegalStateException("Active data call must not have a failure!"); + } + } } /** @hide */ @@ -241,24 +237,39 @@ public final class DataCallResponse implements Parcelable { mProtocolType = source.readInt(); mInterfaceName = source.readString(); mAddresses = new ArrayList<>(); - source.readList(mAddresses, LinkAddress.class.getClassLoader(), android.net.LinkAddress.class); + source.readList(mAddresses, + LinkAddress.class.getClassLoader(), + android.net.LinkAddress.class); mDnsAddresses = new ArrayList<>(); - source.readList(mDnsAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class); + source.readList(mDnsAddresses, + InetAddress.class.getClassLoader(), + java.net.InetAddress.class); mGatewayAddresses = new ArrayList<>(); - source.readList(mGatewayAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class); + source.readList(mGatewayAddresses, + InetAddress.class.getClassLoader(), + java.net.InetAddress.class); mPcscfAddresses = new ArrayList<>(); - source.readList(mPcscfAddresses, InetAddress.class.getClassLoader(), java.net.InetAddress.class); + source.readList(mPcscfAddresses, + InetAddress.class.getClassLoader(), + java.net.InetAddress.class); mMtu = source.readInt(); mMtuV4 = source.readInt(); mMtuV6 = source.readInt(); mHandoverFailureMode = source.readInt(); mPduSessionId = source.readInt(); - mDefaultQos = source.readParcelable(Qos.class.getClassLoader(), android.telephony.data.Qos.class); + mDefaultQos = source.readParcelable(Qos.class.getClassLoader(), + android.telephony.data.Qos.class); mQosBearerSessions = new ArrayList<>(); - source.readList(mQosBearerSessions, QosBearerSession.class.getClassLoader(), android.telephony.data.QosBearerSession.class); - mSliceInfo = source.readParcelable(NetworkSliceInfo.class.getClassLoader(), android.telephony.data.NetworkSliceInfo.class); + source.readList(mQosBearerSessions, + QosBearerSession.class.getClassLoader(), + android.telephony.data.QosBearerSession.class); + mSliceInfo = source.readParcelable( + NetworkSliceInfo.class.getClassLoader(), + android.telephony.data.NetworkSliceInfo.class); mTrafficDescriptors = new ArrayList<>(); - source.readList(mTrafficDescriptors, TrafficDescriptor.class.getClassLoader(), android.telephony.data.TrafficDescriptor.class); + source.readList(mTrafficDescriptors, + TrafficDescriptor.class.getClassLoader(), + android.telephony.data.TrafficDescriptor.class); } /** @@ -322,28 +333,36 @@ public final class DataCallResponse implements Parcelable { * @return A list of addresses of this data connection. */ @NonNull - public List<LinkAddress> getAddresses() { return mAddresses; } + public List<LinkAddress> getAddresses() { + return Collections.unmodifiableList(mAddresses); + } /** * @return A list of DNS server addresses, e.g., "192.0.1.3" or * "192.0.1.11 2001:db8::1". Empty list if no dns server addresses returned. */ @NonNull - public List<InetAddress> getDnsAddresses() { return mDnsAddresses; } + public List<InetAddress> getDnsAddresses() { + return Collections.unmodifiableList(mDnsAddresses); + } /** * @return A list of default gateway addresses, e.g., "192.0.1.3" or * "192.0.1.11 2001:db8::1". Empty list if the addresses represent point to point connections. */ @NonNull - public List<InetAddress> getGatewayAddresses() { return mGatewayAddresses; } + public List<InetAddress> getGatewayAddresses() { + return Collections.unmodifiableList(mGatewayAddresses); + } /** * @return A list of Proxy Call State Control Function address via PCO (Protocol Configuration * Option) for IMS client. */ @NonNull - public List<InetAddress> getPcscfAddresses() { return mPcscfAddresses; } + public List<InetAddress> getPcscfAddresses() { + return Collections.unmodifiableList(mPcscfAddresses); + } /** * @return MTU (maximum transmission unit) in bytes received from network. Zero or negative @@ -404,7 +423,7 @@ public final class DataCallResponse implements Parcelable { */ @NonNull public List<QosBearerSession> getQosBearerSessions() { - return mQosBearerSessions; + return Collections.unmodifiableList(mQosBearerSessions); } /** @@ -420,7 +439,7 @@ public final class DataCallResponse implements Parcelable { */ @NonNull public List<TrafficDescriptor> getTrafficDescriptors() { - return mTrafficDescriptors; + return Collections.unmodifiableList(mTrafficDescriptors); } @NonNull @@ -461,18 +480,6 @@ public final class DataCallResponse implements Parcelable { DataCallResponse other = (DataCallResponse) o; - final boolean isQosBearerSessionsSame = - (mQosBearerSessions == null || other.mQosBearerSessions == null) - ? mQosBearerSessions == other.mQosBearerSessions - : (mQosBearerSessions.size() == other.mQosBearerSessions.size() - && mQosBearerSessions.containsAll(other.mQosBearerSessions)); - - final boolean isTrafficDescriptorsSame = - (mTrafficDescriptors == null || other.mTrafficDescriptors == null) - ? mTrafficDescriptors == other.mTrafficDescriptors - : (mTrafficDescriptors.size() == other.mTrafficDescriptors.size() - && mTrafficDescriptors.containsAll(other.mTrafficDescriptors)); - return mCause == other.mCause && mSuggestedRetryTime == other.mSuggestedRetryTime && mId == other.mId @@ -493,42 +500,20 @@ public final class DataCallResponse implements Parcelable { && mHandoverFailureMode == other.mHandoverFailureMode && mPduSessionId == other.mPduSessionId && Objects.equals(mDefaultQos, other.mDefaultQos) - && isQosBearerSessionsSame + && mQosBearerSessions.size() == other.mQosBearerSessions.size() // non-null + && mQosBearerSessions.containsAll(other.mQosBearerSessions) // non-null && Objects.equals(mSliceInfo, other.mSliceInfo) - && isTrafficDescriptorsSame; + && mTrafficDescriptors.size() == other.mTrafficDescriptors.size() // non-null + && mTrafficDescriptors.containsAll(other.mTrafficDescriptors); // non-null } @Override public int hashCode() { - // Generate order-independent hashes for lists - int addressesHash = mAddresses.stream() - .map(LinkAddress::hashCode) - .mapToInt(Integer::intValue) - .sum(); - int dnsAddressesHash = mDnsAddresses.stream() - .map(InetAddress::hashCode) - .mapToInt(Integer::intValue) - .sum(); - int gatewayAddressesHash = mGatewayAddresses.stream() - .map(InetAddress::hashCode) - .mapToInt(Integer::intValue) - .sum(); - int pcscfAddressesHash = mPcscfAddresses.stream() - .map(InetAddress::hashCode) - .mapToInt(Integer::intValue) - .sum(); - int qosBearerSessionsHash = mQosBearerSessions.stream() - .map(QosBearerSession::hashCode) - .mapToInt(Integer::intValue) - .sum(); - int trafficDescriptorsHash = mTrafficDescriptors.stream() - .map(TrafficDescriptor::hashCode) - .mapToInt(Integer::intValue) - .sum(); return Objects.hash(mCause, mSuggestedRetryTime, mId, mLinkStatus, mProtocolType, - mInterfaceName, addressesHash, dnsAddressesHash, gatewayAddressesHash, - pcscfAddressesHash, mMtu, mMtuV4, mMtuV6, mHandoverFailureMode, mPduSessionId, - mDefaultQos, qosBearerSessionsHash, mSliceInfo, trafficDescriptorsHash); + mInterfaceName, Set.copyOf(mAddresses), Set.copyOf(mDnsAddresses), + Set.copyOf(mGatewayAddresses), Set.copyOf(mPcscfAddresses), mMtu, mMtuV4, mMtuV6, + mHandoverFailureMode, mPduSessionId, mDefaultQos, Set.copyOf(mQosBearerSessions), + mSliceInfo, Set.copyOf(mTrafficDescriptors)); } @Override @@ -616,15 +601,15 @@ public final class DataCallResponse implements Parcelable { private @ProtocolType int mProtocolType; - private String mInterfaceName; + private String mInterfaceName = ""; - private List<LinkAddress> mAddresses; + private List<LinkAddress> mAddresses = Collections.emptyList(); - private List<InetAddress> mDnsAddresses; + private List<InetAddress> mDnsAddresses = Collections.emptyList(); - private List<InetAddress> mGatewayAddresses; + private List<InetAddress> mGatewayAddresses = Collections.emptyList(); - private List<InetAddress> mPcscfAddresses; + private List<InetAddress> mPcscfAddresses = Collections.emptyList(); private int mMtu; @@ -636,11 +621,11 @@ public final class DataCallResponse implements Parcelable { private int mPduSessionId = PDU_SESSION_ID_NOT_SET; - private Qos mDefaultQos; + private @Nullable Qos mDefaultQos; private List<QosBearerSession> mQosBearerSessions = new ArrayList<>(); - private NetworkSliceInfo mSliceInfo; + private @Nullable NetworkSliceInfo mSliceInfo; private List<TrafficDescriptor> mTrafficDescriptors = new ArrayList<>(); @@ -653,7 +638,9 @@ public final class DataCallResponse implements Parcelable { /** * Set data call fail cause. * - * @param cause Data call fail cause. {@link DataFailCause#NONE} indicates no error. + * @param cause Data call fail cause. {@link DataFailCause#NONE} indicates no error, which + * is the only valid value for data calls that are {@link LINK_STATUS_ACTIVE} or + * {@link LINK_STATUS_DORMANT}. * @return The same instance of the builder. */ public @NonNull Builder setCause(@DataFailureCause int cause) { @@ -722,10 +709,13 @@ public final class DataCallResponse implements Parcelable { /** * Set the network interface name. * - * @param interfaceName The network interface name (e.g. "rmnet_data1"). + * @param interfaceName The network interface name (e.g. "rmnet_data1"). This value may not + * be null for valid data calls (those that are {@link LINK_STATUS_ACTIVE} or + * {@link LINK_STATUS_DORMANT}). * @return The same instance of the builder. */ - public @NonNull Builder setInterfaceName(@NonNull String interfaceName) { + public @NonNull Builder setInterfaceName(@Nullable String interfaceName) { + if (interfaceName == null) interfaceName = ""; mInterfaceName = interfaceName; return this; } @@ -737,6 +727,7 @@ public final class DataCallResponse implements Parcelable { * @return The same instance of the builder. */ public @NonNull Builder setAddresses(@NonNull List<LinkAddress> addresses) { + Objects.requireNonNull(addresses); mAddresses = addresses; return this; } @@ -748,6 +739,7 @@ public final class DataCallResponse implements Parcelable { * @return The same instance of the builder. */ public @NonNull Builder setDnsAddresses(@NonNull List<InetAddress> dnsAddresses) { + Objects.requireNonNull(dnsAddresses); mDnsAddresses = dnsAddresses; return this; } @@ -759,6 +751,7 @@ public final class DataCallResponse implements Parcelable { * @return The same instance of the builder. */ public @NonNull Builder setGatewayAddresses(@NonNull List<InetAddress> gatewayAddresses) { + Objects.requireNonNull(gatewayAddresses); mGatewayAddresses = gatewayAddresses; return this; } @@ -771,6 +764,7 @@ public final class DataCallResponse implements Parcelable { * @return The same instance of the builder. */ public @NonNull Builder setPcscfAddresses(@NonNull List<InetAddress> pcscfAddresses) { + Objects.requireNonNull(pcscfAddresses); mPcscfAddresses = pcscfAddresses; return this; } diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java index db369756c50e..346622f0f467 100644 --- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java +++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java @@ -61,8 +61,11 @@ public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test { options.setTestMethodName("testCollectAllApexInfo"); // Collect APEX package names from /apex, then pass them as expectation to be verified. + // The package names are collected from the find name with deduplication (NB: we used to + // deduplicate by dropping directory names with '@', but there's a DCLA case where it only + // has one directory with '@'. So we have to keep it and deduplicate the current way). CommandResult result = getDevice().executeShellV2Command( - "ls -d /apex/*/ |grep -v @ |grep -v /apex/sharedlibs |cut -d/ -f3"); + "ls -d /apex/*/ |grep -v /apex/sharedlibs |cut -d/ -f3 |cut -d@ -f1 |sort |uniq"); assertTrue(result.getStatus() == CommandStatus.SUCCESS); String[] packageNames = result.getStdout().split("\n"); for (var i = 0; i < packageNames.length; i++) { diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp index 8faedebb4601..feae3b79520f 100644 --- a/tests/FlickerTests/Android.bp +++ b/tests/FlickerTests/Android.bp @@ -108,6 +108,7 @@ android_test { ":FlickerTestsAppLaunch-src", ":FlickerTestsQuickswitch-src", ":FlickerTestsRotation-src", + ":FlickerTestsNotification-src", ], } diff --git a/tests/FlickerTests/AndroidTestTemplate.xml b/tests/FlickerTests/AndroidTestTemplate.xml index 1ede943a9fa2..ed63ec0a0e83 100644 --- a/tests/FlickerTests/AndroidTestTemplate.xml +++ b/tests/FlickerTests/AndroidTestTemplate.xml @@ -85,6 +85,8 @@ value="/data/user/0/com.android.server.wm.flicker.quickswitch/files"/> <option name="directory-keys" value="/data/user/0/com.android.server.wm.flicker.rotation/files"/> + <option name="directory-keys" + value="/data/user/0/com.android.server.wm.flicker.notification/files"/> <option name="collect-on-run-ended-only" value="true"/> <option name="clean-up" value="true"/> </metrics_collector> diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt new file mode 100644 index 000000000000..43f4ce9b4f6b --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenThirdActivityOverSplitTest.kt @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.activityembedding + +import android.platform.test.annotations.Presubmit +import android.tools.common.datatypes.Rect +import android.tools.common.traces.component.ComponentNameMatcher +import android.tools.device.flicker.junit.FlickerParametersRunnerFactory +import android.tools.device.flicker.legacy.FlickerBuilder +import android.tools.device.flicker.legacy.LegacyFlickerTest +import android.tools.device.flicker.legacy.LegacyFlickerTestFactory +import androidx.test.filters.RequiresDevice +import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test launching a secondary activity over an existing split. By default the new secondary + * activity will stack over the previous secondary activity. + * + * Setup: From Activity A launch a split A|B. + * + * Transitions: Let B start C, expect C to cover B and end up in split A|C. + * + * To run this test: `atest FlickerTests:OpenThirdActivityOverSplitTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class OpenThirdActivityOverSplitTest (flicker: LegacyFlickerTest) : + ActivityEmbeddingTestBase(flicker) { + /** {@inheritDoc} */ + override val transition: FlickerBuilder.() -> Unit = { + setup { + tapl.setExpectedRotationCheckEnabled(false) + // Launch a split. + testApp.launchViaIntent(wmHelper) + testApp.launchSecondaryActivity(wmHelper) + + startDisplayBounds = + wmHelper.currentState.layerState.physicalDisplayBounds + ?: error("Can't get display bounds") + } + transitions { + testApp.launchThirdActivity(wmHelper) + } + teardown { + tapl.goHome() + testApp.exit(wmHelper) + } + } + + /** Main activity remains visible throughout the transition. */ + @Presubmit + @Test + fun mainActivityWindowAlwaysVisible() { + flicker.assertWm { isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) } + } + + /** Main activity remains visible throughout the transition and takes up half of the screen. */ + @Presubmit + @Test + fun mainActivityLayersAlwaysVisible() { + flicker.assertLayers { + isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + } + + flicker.assertLayersStart { + val display = this.entry.displays.firstOrNull { it.isOn && !it.isVirtual } + ?: error("No non-virtual and on display found") + val mainActivityRegion = + this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + val secondaryActivityRegion = + this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + .region + mainActivityRegion + .plus(secondaryActivityRegion) + .coversExactly(display.layerStackSpace) + } + + flicker.assertLayersEnd { + val display = this.entry.displays.firstOrNull { it.isOn && !it.isVirtual } + ?: error("No non-virtual and on display found") + val mainActivityRegion = + this.visibleRegion(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT) + val secondaryActivityRegion = + this.visibleRegion(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + secondaryActivityRegion.isEmpty() + val thirdActivityRegion = + this.visibleRegion(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + mainActivityRegion + .plus(thirdActivityRegion.region) + .coversExactly(display.layerStackSpace) + } + } + + /** Third activity launches during the transition and covers up secondary activity. */ + @Presubmit + @Test + fun thirdActivityWindowLaunchesIntoSplit() { + flicker.assertWm { + isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + .isAppWindowInvisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + .then() + .isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + .isAppWindowVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + .then() + .isAppWindowVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + .isAppWindowInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) // expectation + } + } + + /** Third activity launches during the transition and covers up secondary activity. */ + @Presubmit + @Test + fun thirdActivityLayerLaunchesIntoSplit() { + flicker.assertLayers { + isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + .isInvisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + .then() + .isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + .isVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + .then() + .isVisible(ActivityEmbeddingAppHelper.THIRD_ACTIVITY_COMPONENT) + .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT) + } + } + + /** Assert the background animation layer is never visible during transition. */ + @Presubmit + @Test + fun backgroundLayerNeverVisible() { + val backgroundColorLayer = ComponentNameMatcher("", "Animation Background") + flicker.assertLayers { + isInvisible(backgroundColorLayer) + } + } + + companion object { + /** {@inheritDoc} */ + private var startDisplayBounds = Rect.EMPTY + /** + * Creates the test configurations. + * + * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and + * navigation modes. + */ + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams() = LegacyFlickerTestFactory.nonRotationTests() + } +}
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt index 865d5b4b771d..dbbc771809de 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt @@ -17,7 +17,7 @@ package com.android.server.wm.flicker.close import android.platform.test.annotations.FlakyTest -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt index c1086332a656..566f393efaea 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTestCfArm.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.close -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt index ea9710c633b1..ed930fc8c236 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt @@ -17,7 +17,7 @@ package com.android.server.wm.flicker.close import android.platform.test.annotations.FlakyTest -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt index d65555a97d78..49ed183c2ccf 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTestCfArm.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.close -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt index 351eb1e5d2c3..06beec19cbf0 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt @@ -59,6 +59,23 @@ constructor( .waitForAndVerify() } + /** Clicks the button to launch a third activity over a secondary activity. */ + fun launchThirdActivity(wmHelper: WindowManagerStateHelper) { + val launchButton = + uiDevice.wait( + Until.findObject(By.res(getPackage(), "launch_third_activity_button")), + FIND_TIMEOUT + ) + require(launchButton != null) { "Can't find launch third activity button on screen." } + launchButton.click() + wmHelper + .StateSyncBuilder() + .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED) + .withActivityState(SECONDARY_ACTIVITY_COMPONENT, PlatformConsts.STATE_STOPPED) + .withActivityState(THIRD_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED) + .waitForAndVerify() + } + /** * Clicks the button to finishes the secondary activity launched through * [launchSecondaryActivity], waits for the main activity to resume. @@ -166,6 +183,9 @@ constructor( val SECONDARY_ACTIVITY_COMPONENT = ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT.toFlickerComponent() + val THIRD_ACTIVITY_COMPONENT = + ActivityOptions.ActivityEmbedding.ThirdActivity.COMPONENT.toFlickerComponent() + val ALWAYS_EXPAND_ACTIVITY_COMPONENT = ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT.toFlickerComponent() diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt index 2563bfb03f61..4fd4a61e4adc 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTestCfArm.kt @@ -17,7 +17,7 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt index 33302face72a..e39a578fd321 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTest.kt @@ -18,7 +18,7 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.Postsubmit import android.platform.test.annotations.Presubmit -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt index 45fb453f5df8..6d0b6f48d7c6 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentColdTestCfArm.kt @@ -17,7 +17,7 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt index 12ee7d0bff78..d2c38076e72d 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt @@ -18,7 +18,7 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt index 1371fd7502d6..3e0958a27aaf 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTestCfArm.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.launch -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt index 46eb2571165a..2a2a3db12513 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt @@ -21,7 +21,7 @@ import android.platform.test.annotations.Presubmit import android.tools.common.NavBar import android.tools.common.Rotation import android.tools.common.traces.component.ComponentNameMatcher -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt index 1497e50641eb..eec6bfde8b9f 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt @@ -19,7 +19,7 @@ package com.android.server.wm.flicker.launch import android.platform.test.annotations.FlakyTest import android.platform.test.annotations.Presubmit import android.tools.common.Rotation -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.FlickerBuilder import android.tools.device.flicker.legacy.LegacyFlickerTest diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt index 9b6c136f54b3..ab6a1ea36222 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTestCfArm.kt @@ -16,7 +16,7 @@ package com.android.server.wm.flicker.launch -import android.tools.device.flicker.annotation.FlickerServiceCompatible +import android.tools.common.flicker.annotation.FlickerServiceCompatible import android.tools.device.flicker.junit.FlickerParametersRunnerFactory import android.tools.device.flicker.legacy.LegacyFlickerTest import android.tools.device.flicker.legacy.LegacyFlickerTestFactory diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt index 4a1bd7e165ae..1bdb6e717b12 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenCameraFromHomeOnDoubleClickPowerButtonTest.kt @@ -18,6 +18,8 @@ package com.android.server.wm.flicker.launch import android.os.SystemClock import android.platform.test.annotations.Postsubmit +import android.tools.common.flicker.subject.layers.LayersTraceSubject +import android.tools.common.traces.component.ComponentNameMatcher import android.tools.device.apphelpers.CameraAppHelper import android.tools.device.apphelpers.StandardAppHelper import android.tools.device.flicker.junit.FlickerParametersRunnerFactory @@ -137,8 +139,14 @@ class OpenCameraFromHomeOnDoubleClickPowerButtonTest(flicker: LegacyFlickerTest) @Postsubmit @Test - override fun visibleLayersShownMoreThanOneConsecutiveEntry() = - super.visibleLayersShownMoreThanOneConsecutiveEntry() + override fun visibleLayersShownMoreThanOneConsecutiveEntry() { + flicker.assertLayers { + this.visibleLayersShownMoreThanOneConsecutiveEntry( + LayersTraceSubject.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS + + listOf(CAMERA_BACKGROUND) + ) + } + } @Postsubmit @Test @@ -161,5 +169,12 @@ class OpenCameraFromHomeOnDoubleClickPowerButtonTest(flicker: LegacyFlickerTest) @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams() = LegacyFlickerTestFactory.nonRotationTests() + + private val CAMERA_BACKGROUND = + ComponentNameMatcher( + "Background for SurfaceView" + + "[com.google.android.GoogleCamera/" + + "com.google.android.apps.camera.legacy.app.activity.main.CameraActivity]" + ) } } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt index 39c8ca089dbd..4164c0d440c0 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt @@ -121,7 +121,7 @@ class TaskTransitionTest(flicker: LegacyFlickerTest) : BaseTest(flicker) { @FlakyTest(bugId = 265007895) @Test fun transitionHasColorBackground() { - val backgroundColorLayer = ComponentNameMatcher("", "Animation Background") + val backgroundColorLayer = ComponentNameMatcher("", "animation-background") val displayBounds = WindowUtils.getDisplayBounds(flicker.scenario.startRotation) flicker.assertLayers { this.invoke("LAUNCH_NEW_TASK_ACTIVITY coversExactly displayBounds") { diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 64302831202e..7a2e74bdb8e7 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -200,6 +200,13 @@ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" android:exported="false"/> <activity + android:name=".ActivityEmbeddingThirdActivity" + android:label="ActivityEmbedding Third" + android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding" + android:theme="@style/CutoutShortEdges" + android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" + android:exported="false"/> + <activity android:name=".ActivityEmbeddingAlwaysExpandActivity" android:label="ActivityEmbedding AlwaysExpand" android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding" diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml index 239aba59f4a7..67314463161d 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_secondary_activity_layout.xml @@ -27,4 +27,12 @@ android:layout_height="48dp" android:text="Finish" /> + <Button + android:id="@+id/launch_third_activity_button" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:layout_centerHorizontal="true" + android:onClick="launchThirdActivity" + android:text="Launch a third activity" /> + </LinearLayout>
\ No newline at end of file diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java index 6e78750cdeee..dc21027bc99c 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java @@ -17,6 +17,7 @@ package com.android.server.wm.flicker.testapp; import android.app.Activity; +import android.content.Intent; import android.graphics.Color; import android.os.Bundle; import android.view.View; @@ -40,4 +41,9 @@ public class ActivityEmbeddingSecondaryActivity extends Activity { } }); } + + public void launchThirdActivity(View view) { + startActivity(new Intent().setComponent( + ActivityOptions.ActivityEmbedding.ThirdActivity.COMPONENT)); + } } diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java new file mode 100644 index 000000000000..3bd72818d894 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingThirdActivity.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm.flicker.testapp; + +import android.app.Activity; +import android.graphics.Color; +import android.os.Bundle; + +/** + * Activity to be used also as a secondary activity to split with + * {@link ActivityEmbeddingMainActivity}. + */ +public class ActivityEmbeddingThirdActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_embedding_base_layout); + findViewById(R.id.root_activity_layout).setBackgroundColor(Color.RED); + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index 52106189840d..d84ac427f027 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -99,6 +99,12 @@ public class ActivityOptions { FLICKER_APP_PACKAGE + ".ActivityEmbeddingSecondaryActivity"); } + public static class ThirdActivity { + public static final String LABEL = "ActivityEmbeddingThirdActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".ActivityEmbeddingThirdActivity"); + } + public static class AlwaysExpandActivity { public static final String LABEL = "ActivityEmbeddingAlwaysExpandActivity"; public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java index f4a04a163ebb..b6b99242c414 100644 --- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/NotificationTest.java @@ -97,6 +97,8 @@ public final class NotificationTest { assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)); // Do not run on TV. Direct Reply isn't supported on TV. assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)); + // Do not run on Wear. Direct Reply isn't supported on Wear. + assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_WATCH)); } @After |