diff options
422 files changed, 14492 insertions, 2610 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/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 74be14fa9c34..c2bc5a2426af 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -17442,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 { @@ -17533,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); } @@ -17618,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 { @@ -48402,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); } @@ -54931,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; 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 7a9be35c0e46..7c3d8fb856a4 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1061,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(); @@ -3335,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); 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/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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index f01dee5c758a..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 */ 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/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/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/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/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/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/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index e6bdfe1b95c4..7664bada2c28 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -112,9 +112,17 @@ 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"; @@ -195,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) { @@ -424,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 @@ -442,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 @@ -465,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 @@ -473,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; @@ -483,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; } } @@ -558,7 +568,13 @@ public class GraphicsEnvironment { 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; } @@ -627,10 +643,10 @@ 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, false, packageName, features); + nativeSetAngleInfo(paths, false, packageName, features); return true; } @@ -652,10 +668,10 @@ public class GraphicsEnvironment { return false; } - // 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, system ANGLE will be used. Call nativeSetAngleInfo() with + // the application package name and ANGLE features to use. final String[] features = getAngleEglFeatures(context, bundle); - setAngleInfo("", true, packageName, features); + nativeSetAngleInfo("system", false, packageName, features); return true; } @@ -936,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, boolean useSystemAngle, String packageName, - 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/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/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/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index cc3a662870bb..f621b9340aca 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -432,8 +432,7 @@ public abstract class WallpaperService extends Service { @Override public void resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration mergedConfiguration, InsetsState insetsState, - boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, - int syncSeqId, boolean dragResizing) { + boolean forceLayout, int displayId, int syncSeqId, boolean dragResizing) { Message msg = mCaller.obtainMessageIO(MSG_WINDOW_RESIZED, reportDraw ? 1 : 0, mergedConfiguration); @@ -1287,9 +1286,9 @@ public abstract class WallpaperService extends Service { visibleFrame.intersect(mInsetsState.getDisplayFrame()); WindowInsets windowInsets = mInsetsState.calculateInsets(visibleFrame, null /* ignoringVisibilityState */, config.isScreenRound(), - false /* alwaysConsumeSystemBars */, mLayout.softInputMode, - mLayout.flags, SYSTEM_UI_FLAG_VISIBLE, mLayout.type, - config.windowConfiguration.getWindowingMode(), null /* idSideMap */); + mLayout.softInputMode, mLayout.flags, SYSTEM_UI_FLAG_VISIBLE, + mLayout.type, config.windowConfiguration.getWindowingMode(), + null /* idSideMap */); if (!fixedSize) { final Rect padding = mIWallpaperEngine.mDisplayPadding; 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/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index d554514349c3..33edc27118f2 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -56,8 +56,7 @@ oneway interface IWindow { void resized(in ClientWindowFrames frames, boolean reportDraw, in MergedConfiguration newMergedConfiguration, in InsetsState insetsState, - boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, - int syncSeqId, boolean dragResizing); + boolean forceLayout, int displayId, int syncSeqId, boolean dragResizing); /** * Called when this window retrieved control over a specified set of insets sources. diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 072a7f5ea304..e4b6e02caad1 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -754,10 +754,8 @@ interface IWindowManager /** * Called to get the expected window insets. - * - * @return {@code true} if system bars are always consumed. */ - boolean getWindowInsets(int displayId, in IBinder token, out InsetsState outInsetsState); + void getWindowInsets(int displayId, in IBinder token, out InsetsState outInsetsState); /** * Returns a list of {@link android.view.DisplayInfo} for the logical display. This is not diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 6c5f195ba2a0..4c50858b13b2 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -40,6 +40,7 @@ import static android.view.InsetsState.ISIDE_LEFT; import static android.view.InsetsState.ISIDE_RIGHT; import static android.view.InsetsState.ISIDE_TOP; import static android.view.WindowInsets.Type.ime; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY; import static android.view.inputmethod.ImeTracker.TOKEN_NONE; @@ -63,7 +64,6 @@ import android.view.InsetsState.InternalInsetsSide; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation.Bounds; -import android.view.WindowManager.LayoutParams; import android.view.animation.Interpolator; import android.view.inputmethod.ImeTracker; @@ -401,8 +401,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro private Insets getInsetsFromState(InsetsState state, Rect frame, @Nullable @InternalInsetsSide SparseIntArray idSideMap) { return state.calculateInsets(frame, null /* ignoringVisibilityState */, - false /* isScreenRound */, false /* alwaysConsumeSystemBars */, - LayoutParams.SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode*/, + false /* isScreenRound */, SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode */, 0 /* legacyWindowFlags */, 0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, idSideMap).getInsets(mTypes); } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 5019b85ca503..e5d031b7a51c 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -797,9 +797,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } WindowInsets insets = state.calculateInsets(mFrame, mState /* ignoringVisibilityState*/, - mLastInsets.isRound(), mLastInsets.shouldAlwaysConsumeSystemBars(), - mLastLegacySoftInputMode, mLastLegacyWindowFlags, mLastLegacySystemUiFlags, - mWindowType, mLastWindowingMode, null /* idSideMap */); + mLastInsets.isRound(), mLastLegacySoftInputMode, mLastLegacyWindowFlags, + mLastLegacySystemUiFlags, mWindowType, mLastWindowingMode, + null /* idSideMap */); mHost.dispatchWindowInsetsAnimationProgress(insets, Collections.unmodifiableList(runningAnimations)); if (DEBUG) { @@ -955,21 +955,20 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } /** - * @see InsetsState#calculateInsets(Rect, InsetsState, boolean, boolean, int, int, int, int, - * int, android.util.SparseIntArray) + * @see InsetsState#calculateInsets(Rect, InsetsState, boolean, int, int, int, int, int, + * android.util.SparseIntArray) */ @VisibleForTesting - public WindowInsets calculateInsets(boolean isScreenRound, boolean alwaysConsumeSystemBars, - int windowType, int windowingMode, int legacySoftInputMode, int legacyWindowFlags, - int legacySystemUiFlags) { + public WindowInsets calculateInsets(boolean isScreenRound, int windowType, int windowingMode, + int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags) { mWindowType = windowType; mLastWindowingMode = windowingMode; mLastLegacySoftInputMode = legacySoftInputMode; mLastLegacyWindowFlags = legacyWindowFlags; mLastLegacySystemUiFlags = legacySystemUiFlags; mLastInsets = mState.calculateInsets(mFrame, null /* ignoringVisibilityState*/, - isScreenRound, alwaysConsumeSystemBars, legacySoftInputMode, legacyWindowFlags, - legacySystemUiFlags, windowType, windowingMode, null /* idSideMap */); + isScreenRound, legacySoftInputMode, legacyWindowFlags, legacySystemUiFlags, + windowType, windowingMode, null /* idSideMap */); return mLastInsets; } 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..828938b99e8f 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; @@ -39,6 +40,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.WindowConfiguration; +import android.app.WindowConfiguration.WindowingMode; import android.graphics.Insets; import android.graphics.Rect; import android.os.Parcel; @@ -135,21 +137,26 @@ public class InsetsState implements Parcelable { * @return The calculated insets. */ public WindowInsets calculateInsets(Rect frame, @Nullable InsetsState ignoringVisibilityState, - boolean isScreenRound, boolean alwaysConsumeSystemBars, - int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags, - int windowType, @WindowConfiguration.WindowingMode int windowingMode, + boolean isScreenRound, int legacySoftInputMode, int legacyWindowFlags, + int legacySystemUiFlags, int windowType, @WindowingMode int windowingMode, @Nullable @InternalInsetsSide SparseIntArray idSideMap) { Insets[] typeInsetsMap = new Insets[Type.SIZE]; Insets[] typeMaxInsetsMap = new Insets[Type.SIZE]; 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/View.java b/core/java/android/view/View.java index 1ecfd74b28a9..acbccf754aa7 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> @@ -30780,13 +30841,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final Rect mCaptionInsets = new Rect(); /** - * In multi-window we force show the system bars. Because we don't want that the surface - * size changes in this mode, we instead have a flag whether the system bars sizes should - * always be consumed, so the app is treated like there are no virtual system bars at all. - */ - boolean mAlwaysConsumeSystemBars; - - /** * The internal insets given by this window. This value is * supplied by the client (through * {@link ViewTreeObserver.OnComputeInternalInsetsListener}) and will diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index fe25fd20a5b9..122d37cd739f 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -82,7 +82,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS; import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW; -import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS; import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER; @@ -740,7 +739,6 @@ public final class ViewRootImpl implements ViewParent, final Rect mPendingBackDropFrame = new Rect(); - boolean mPendingAlwaysConsumeSystemBars; private int mRelayoutSeq; private final Rect mWinFrameInScreen = new Rect(); private final InsetsState mTempInsets = new InsetsState(); @@ -1365,9 +1363,6 @@ public final class ViewRootImpl implements ViewParent, } } - mAttachInfo.mAlwaysConsumeSystemBars = - (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS) != 0; - mPendingAlwaysConsumeSystemBars = mAttachInfo.mAlwaysConsumeSystemBars; mInsetsController.onStateChanged(mTempInsets); mInsetsController.onControlsChanged(mTempControls.get()); final InsetsState state = mInsetsController.getState(); @@ -1881,8 +1876,8 @@ public final class ViewRootImpl implements ViewParent, final MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg2; CompatibilityInfo.applyOverrideScaleIfNeeded(mergedConfiguration); final boolean forceNextWindowRelayout = args.argi1 != 0; - final int displayId = args.argi3; - final boolean dragResizing = args.argi5 != 0; + final int displayId = args.argi2; + final boolean dragResizing = args.argi4 != 0; final Rect frame = frames.frame; final Rect displayFrame = frames.displayFrame; @@ -1934,8 +1929,7 @@ public final class ViewRootImpl implements ViewParent, } mForceNextWindowRelayout |= forceNextWindowRelayout; - mPendingAlwaysConsumeSystemBars = args.argi2 != 0; - mSyncSeqId = args.argi4 > mSyncSeqId ? args.argi4 : mSyncSeqId; + mSyncSeqId = args.argi3 > mSyncSeqId ? args.argi3 : mSyncSeqId; if (msg == MSG_RESIZED_REPORT) { reportNextDraw("resized"); @@ -2820,8 +2814,8 @@ public final class ViewRootImpl implements ViewParent, if (mLastWindowInsets == null || forceConstruct) { final Configuration config = getConfiguration(); mLastWindowInsets = mInsetsController.calculateInsets( - config.isScreenRound(), mAttachInfo.mAlwaysConsumeSystemBars, - mWindowAttributes.type, config.windowConfiguration.getWindowingMode(), + config.isScreenRound(), mWindowAttributes.type, + config.windowConfiguration.getWindowingMode(), mWindowAttributes.softInputMode, mWindowAttributes.flags, (mWindowAttributes.systemUiVisibility | mWindowAttributes.subtreeSystemUiVisibility)); @@ -3283,8 +3277,6 @@ public final class ViewRootImpl implements ViewParent, surfaceSizeChanged = true; mLastSurfaceSize.set(mSurfaceSize.x, mSurfaceSize.y); } - final boolean alwaysConsumeSystemBarsChanged = - mPendingAlwaysConsumeSystemBars != mAttachInfo.mAlwaysConsumeSystemBars; updateColorModeIfNeeded(lp.getColorMode()); surfaceCreated = !hadSurface && mSurface.isValid(); surfaceDestroyed = hadSurface && !mSurface.isValid(); @@ -3298,10 +3290,6 @@ public final class ViewRootImpl implements ViewParent, if (surfaceReplaced) { mSurfaceSequenceId++; } - if (alwaysConsumeSystemBarsChanged) { - mAttachInfo.mAlwaysConsumeSystemBars = mPendingAlwaysConsumeSystemBars; - dispatchApplyInsets = true; - } if (updateCaptionInsets()) { dispatchApplyInsets = true; } @@ -3859,7 +3847,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()); @@ -8299,9 +8295,6 @@ public final class ViewRootImpl implements ViewParent, CompatibilityInfo.applyOverrideScaleIfNeeded(mPendingMergedConfiguration); mInsetsController.onStateChanged(mTempInsets); mInsetsController.onControlsChanged(mTempControls.get()); - - mPendingAlwaysConsumeSystemBars = - (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0; } final int transformHint = SurfaceControl.rotationToBufferTransform( @@ -8924,7 +8917,7 @@ public final class ViewRootImpl implements ViewParent, @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private void dispatchResized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout, - boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) { + int displayId, int syncSeqId, boolean dragResizing) { Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED); SomeArgs args = SomeArgs.obtain(); final boolean sameProcessCall = (Binder.getCallingPid() == android.os.Process.myPid()); @@ -8943,10 +8936,9 @@ public final class ViewRootImpl implements ViewParent, ? new MergedConfiguration(mergedConfiguration) : mergedConfiguration; args.arg3 = insetsState; args.argi1 = forceLayout ? 1 : 0; - args.argi2 = alwaysConsumeSystemBars ? 1 : 0; - args.argi3 = displayId; - args.argi4 = syncSeqId; - args.argi5 = dragResizing ? 1 : 0; + args.argi2 = displayId; + args.argi3 = syncSeqId; + args.argi4 = dragResizing ? 1 : 0; msg.obj = args; mHandler.sendMessage(msg); @@ -10354,12 +10346,11 @@ public final class ViewRootImpl implements ViewParent, @Override public void resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration mergedConfiguration, InsetsState insetsState, - boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, - boolean dragResizing) { + boolean forceLayout, int displayId, int syncSeqId, boolean dragResizing) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState, - forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing); + forceLayout, displayId, syncSeqId, dragResizing); } } diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 4acaea849586..7757c256164e 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; @@ -1432,8 +1429,8 @@ public final class WindowInsets { /** @hide */ @NonNull - public Builder setAlwaysConsumeSystemBars(boolean alwaysConsumeSystemBars) { - mAlwaysConsumeSystemBars = alwaysConsumeSystemBars; + public Builder setForceConsumingTypes(@InsetsType int forceConsumingTypes) { + mForceConsumingTypes = forceConsumingTypes; return this; } @@ -1453,7 +1450,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/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 99a4f6b41ef3..c9368f16f0ed 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -79,16 +79,9 @@ public final class WindowManagerGlobal { public static final int RELAYOUT_RES_SURFACE_RESIZED = 1 << 2; /** - * In multi-window we force show the system bars. Because we don't want that the surface size - * changes in this mode, we instead have a flag whether the system bar sizes should always be - * consumed, so the app is treated like there is no virtual system bars at all. - */ - public static final int RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS = 1 << 3; - - /** * The window manager has told the window it cannot draw this frame and should retry again. */ - public static final int RELAYOUT_RES_CANCEL_AND_REDRAW = 1 << 4; + public static final int RELAYOUT_RES_CANCEL_AND_REDRAW = 1 << 3; /** * Flag for relayout: the client will be later giving @@ -99,13 +92,7 @@ public final class WindowManagerGlobal { public static final int ADD_FLAG_IN_TOUCH_MODE = 0x1; public static final int ADD_FLAG_APP_VISIBLE = 0x2; - public static final int ADD_FLAG_USE_BLAST = 0x8; - - /** - * Like {@link #RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS}, but as a "hint" when adding the - * window. - */ - public static final int ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS = 0x4; + public static final int ADD_FLAG_USE_BLAST = 0x4; public static final int ADD_OKAY = 0; public static final int ADD_BAD_APP_TOKEN = -1; diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 7d3d283a45f2..1efac4d2927f 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -631,8 +631,8 @@ public class WindowlessWindowManager implements IWindowSession { mTmpFrames.displayFrame.set(mTmpFrames.frame); mTmpConfig.setConfiguration(mConfiguration, mConfiguration); s.mClient.resized(mTmpFrames, false /* reportDraw */, mTmpConfig, state, - false /* forceLayout */, false /* alwaysConsumeSystemBars */, s.mDisplayId, - Integer.MAX_VALUE, false /* dragResizing */); + false /* forceLayout */, s.mDisplayId, Integer.MAX_VALUE, + false /* dragResizing */); } catch (RemoteException e) { // Too bad } 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/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/window/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java index 954f68633e57..d08d5cfaa2b5 100644 --- a/core/java/android/window/WindowMetricsController.java +++ b/core/java/android/window/WindowMetricsController.java @@ -112,15 +112,14 @@ public final class WindowMetricsController { Rect bounds, boolean isScreenRound, int windowingMode) { try { final InsetsState insetsState = new InsetsState(); - final boolean alwaysConsumeSystemBars = WindowManagerGlobal.getWindowManagerService() - .getWindowInsets(displayId, token, insetsState); + WindowManagerGlobal.getWindowManagerService().getWindowInsets( + displayId, token, insetsState); final float overrideInvScale = CompatibilityInfo.getOverrideInvertedScale(); if (overrideInvScale != 1f) { insetsState.scale(overrideInvScale); } return insetsState.calculateInsets(bounds, null /* ignoringVisibilityState */, - isScreenRound, alwaysConsumeSystemBars, SOFT_INPUT_ADJUST_NOTHING, - 0 /* flags */, SYSTEM_UI_FLAG_VISIBLE, + isScreenRound, SOFT_INPUT_ADJUST_NOTHING, 0 /* flags */, SYSTEM_UI_FLAG_VISIBLE, WindowManager.LayoutParams.INVALID_WINDOW_TYPE, windowingMode, null /* idSideMap */); } catch (RemoteException e) { @@ -145,13 +144,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/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/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/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java index 600058e88e4b..0ac7765d4550 100644 --- a/core/java/com/android/internal/view/BaseIWindow.java +++ b/core/java/com/android/internal/view/BaseIWindow.java @@ -53,7 +53,7 @@ public class BaseIWindow extends IWindow.Stub { @Override public void resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout, - boolean alwaysConsumeSystemBars, int displayId, int seqId, boolean dragResizing) { + int displayId, int seqId, boolean dragResizing) { if (reportDraw) { try { mSession.finishDrawing(this, null /* postDrawTransaction */, seqId); 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_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp index afc3cbd15f88..8fc30d1c248d 100644 --- a/core/jni/android_os_GraphicsEnvironment.cpp +++ b/core/jni/android_os_GraphicsEnvironment.cpp @@ -50,7 +50,7 @@ void setGpuStats_native(JNIEnv* env, jobject clazz, jstring driverPackageName, appPackageNameChars.c_str(), vulkanVersion); } -void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useSystemAngle, +void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useNativeDriver, jstring packageName, jobjectArray featuresObj) { ScopedUtfChars pathChars(env, path); ScopedUtfChars packageNameChars(env, packageName); @@ -73,7 +73,7 @@ void setAngleInfo_native(JNIEnv* env, jobject clazz, jstring path, jboolean useS } } - android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useSystemAngle, + android::GraphicsEnv::getInstance().setAngleInfo(pathChars.c_str(), useNativeDriver, packageNameChars.c_str(), features); } @@ -118,7 +118,7 @@ const JNINativeMethod g_methods[] = { reinterpret_cast<void*>(setGpuStats_native)}, {"setInjectLayersPrSetDumpable", "()Z", reinterpret_cast<void*>(setInjectLayersPrSetDumpable_native)}, - {"setAngleInfo", "(Ljava/lang/String;ZLjava/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/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/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/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java index f45db23ace76..a9c0e41b23bd 100644 --- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java @@ -81,7 +81,6 @@ public class ImeInsetsSourceConsumerTest { Insets.of(10, 10, 10, 10), rect, rect, rect, rect)); mController.calculateInsets( false, - false, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, SOFT_INPUT_ADJUST_RESIZE, 0, 0); mImeConsumer = mController.getImeSourceConsumer(); diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index 06920524acfc..76e4a6bfe2c7 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -171,7 +171,6 @@ public class InsetsControllerTest { mController.onStateChanged(state); mController.calculateInsets( false, - false, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, SOFT_INPUT_ADJUST_RESIZE, 0, 0); mController.onFrameChanged(new Rect(0, 0, 100, 100)); diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index fde1a6d7b04c..6b32350c42ed 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -100,7 +100,7 @@ public class InsetsStateTest { .setVisible(true); SparseIntArray typeSideMap = new SparseIntArray(); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, + SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, typeSideMap); assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets()); assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all())); @@ -119,8 +119,7 @@ public class InsetsStateTest { .setFrame(new Rect(0, 100, 100, 300)) .setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, - null); + SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(100, insets.getStableInsetBottom()); assertEquals(Insets.of(0, 0, 0, 100), insets.getInsetsIgnoringVisibility(systemBars())); assertEquals(Insets.of(0, 0, 0, 200), insets.getSystemWindowInsets()); @@ -138,7 +137,7 @@ public class InsetsStateTest { .setFrame(new Rect(80, 0, 100, 300)) .setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); + 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets()); assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars())); assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(navigationBars())); @@ -153,7 +152,7 @@ public class InsetsStateTest { .setFrame(new Rect(80, 0, 100, 300)) .setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); + 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets()); assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.statusBars())); assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.navigationBars())); @@ -168,8 +167,7 @@ public class InsetsStateTest { .setFrame(new Rect(0, 200, 100, 300)) .setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, - null); + SOFT_INPUT_ADJUST_NOTHING, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(0, insets.getSystemWindowInsetBottom()); assertEquals(100, insets.getInsets(ime()).bottom); assertTrue(insets.isVisible(ime())); @@ -184,10 +182,10 @@ public class InsetsStateTest { .setFrame(new Rect(0, 200, 100, 300)) .setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, SOFT_INPUT_ADJUST_NOTHING, 0, SYSTEM_UI_FLAG_LAYOUT_STABLE, TYPE_APPLICATION, + SOFT_INPUT_ADJUST_NOTHING, 0, SYSTEM_UI_FLAG_LAYOUT_STABLE, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(100, insets.getSystemWindowInsetTop()); - insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false, + insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, SOFT_INPUT_ADJUST_NOTHING, 0, 0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(0, insets.getSystemWindowInsetTop()); @@ -199,10 +197,10 @@ public class InsetsStateTest { .setFrame(new Rect(0, 0, 100, 100)) .setVisible(false); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, SOFT_INPUT_ADJUST_NOTHING, FLAG_FULLSCREEN, SYSTEM_UI_FLAG_LAYOUT_STABLE, + SOFT_INPUT_ADJUST_NOTHING, FLAG_FULLSCREEN, SYSTEM_UI_FLAG_LAYOUT_STABLE, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(0, insets.getSystemWindowInsetTop()); - insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false, + insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, SOFT_INPUT_ADJUST_NOTHING, 0, 0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(0, insets.getSystemWindowInsetTop()); @@ -214,19 +212,19 @@ public class InsetsStateTest { .setFrame(new Rect(0, 0, 100, 100)) .setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS, + SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS, 0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(0, insets.getSystemWindowInsetTop()); insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS, + SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS, 0 /* legacySystemUiFlags */, TYPE_SYSTEM_ERROR, WINDOWING_MODE_UNDEFINED, null); assertEquals(100, insets.getSystemWindowInsetTop()); insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS, + SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS, 0 /* legacySystemUiFlags */, TYPE_WALLPAPER, WINDOWING_MODE_UNDEFINED, null); assertEquals(100, insets.getSystemWindowInsetTop()); insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS, + SOFT_INPUT_ADJUST_NOTHING, FLAG_LAYOUT_NO_LIMITS, 0 /* legacySystemUiFlags */, TYPE_APPLICATION, WINDOWING_MODE_FREEFORM, null); assertEquals(100, insets.getSystemWindowInsetTop()); } @@ -268,7 +266,7 @@ public class InsetsStateTest { .setFrame(new Rect(80, 0, 100, 300)) .setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); + 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets()); assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars())); assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(navigationBars())); @@ -283,7 +281,7 @@ public class InsetsStateTest { .setFrame(new Rect(80, 0, 100, 300)) .setVisible(true); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, - false, 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); + 0, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets()); assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(statusBars())); assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(navigationBars())); @@ -298,7 +296,7 @@ public class InsetsStateTest { .setFrame(new Rect(0, 200, 100, 300)) .setVisible(true); mState.removeSource(ID_IME); - WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, false, + WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), null, false, SOFT_INPUT_ADJUST_RESIZE, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, null); assertEquals(0, insets.getSystemWindowInsetBottom()); } @@ -530,7 +528,7 @@ public class InsetsStateTest { new Rect(0, 0, 1, 2), new Rect(197, 296, 200, 300), new Rect(197, 296, 200, 300))); - DisplayCutout cutout = mState.calculateInsets(new Rect(1, 1, 199, 300), null, false, false, + DisplayCutout cutout = mState.calculateInsets(new Rect(1, 1, 199, 300), null, false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, new SparseIntArray()).getDisplayCutout(); assertEquals(0, cutout.getSafeInsetLeft()); @@ -556,7 +554,7 @@ public class InsetsStateTest { new RoundedCorner(POSITION_BOTTOM_RIGHT, 20, 180, 380), new RoundedCorner(POSITION_BOTTOM_LEFT, 20, 20, 380))); WindowInsets windowInsets = mState.calculateInsets(new Rect(1, 2, 197, 396), null, false, - false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION, + SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, new SparseIntArray()); assertEquals(new RoundedCorner(POSITION_TOP_LEFT, 10, 9, 8), windowInsets.getRoundedCorner(POSITION_TOP_LEFT)); @@ -573,7 +571,7 @@ public class InsetsStateTest { mState.setDisplayFrame(new Rect(0, 0, 200, 400)); mState.setDisplayShape(DisplayShape.createDefaultDisplayShape(200, 400, false)); WindowInsets windowInsets = mState.calculateInsets(new Rect(10, 20, 200, 400), null, false, - false, SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION, + SOFT_INPUT_ADJUST_UNSPECIFIED, 0, 0, TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED, new SparseIntArray()); final DisplayShape expect = 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/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/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/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/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java index 5e42782431fd..586a794d5020 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java @@ -347,8 +347,7 @@ public class SystemWindows { @Override public void resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration newMergedConfiguration, InsetsState insetsState, - boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, - boolean dragResizing) {} + boolean forceLayout, int displayId, int syncSeqId, boolean dragResizing) {} @Override public void insetsControlChanged(InsetsState insetsState, 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/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..1d879a130848 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); @@ -206,8 +214,7 @@ public class TaskSnapshotWindow { @Override public void resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration mergedConfiguration, InsetsState insetsState, - boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int seqId, - boolean dragResizing) { + boolean forceLayout, int displayId, int seqId, boolean dragResizing) { final TaskSnapshotWindow snapshot = mOuter.get(); if (snapshot == null) { return; 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/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/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/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt index f4828f14d5ed..863cae3f83e7 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/splitscreen/SplitScreenUtils.kt @@ -114,13 +114,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) } 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..4197550e4c95 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 @@ -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/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/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/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/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 34362d9ced91..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; @@ -175,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. @@ -475,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(); } @@ -579,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: @@ -589,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: @@ -613,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 diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java index fc77cd6ae434..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,6 +41,9 @@ 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 @@ -180,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/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/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/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index d2084047583b..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.core.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 12f7452fe913..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,10 +23,11 @@ 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.LogMessageImpl 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 @@ -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/LogBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt index 885873824d1c..e0051f59469d 100644 --- a/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt +++ b/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt @@ -21,6 +21,7 @@ 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 @@ -77,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>? = @@ -178,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 @@ -199,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 } 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/layout/udfps_keyguard_view.xml index 6d908be48ff0..360ef2672e75 100644 --- a/core/res/res/values-watch/colors.xml +++ b/packages/SystemUI/res-keyguard/layout/udfps_keyguard_view.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2021 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,9 +14,12 @@ ~ 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"> -<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 + <!-- Add fingerprint views here. See udfps_keyguard_view_internal.xml. --> + +</com.android.systemui.biometrics.UdfpsKeyguardView> 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/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/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/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 91937af6f540..4e0e8d0dbcd7 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -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/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 4aec90858515..21350727908c 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -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 72d43d48863a..45744dfcab55 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -303,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; @@ -3742,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 */ @@ -3761,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) { @@ -3778,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/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/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/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/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/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/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 df83aafecb4b..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) { 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/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/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/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/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/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/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/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/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/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/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/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 44436b912724..d97db3b27c87 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -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); @@ -1625,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(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index fe1b3656bb4f..025c461110ef 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -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/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt index 4f81cd6f0758..2c560c952732 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt @@ -197,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/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/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/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/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/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/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/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/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 39b13d95345f..0d3dfaeb85b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -2177,9 +2177,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { */ @Override public void setLockscreenUser(int newUserId) { - if (mLockscreenWallpaper != null && !mWallpaperManager.isLockscreenLiveWallpaperEnabled()) { - mLockscreenWallpaper.setCurrentUser(newUserId); - } if (mWallpaperSupported) { mWallpaperChangedReceiver.onReceive(mContext, null); } 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/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index 77381dd3311b..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 @@ -27,11 +27,9 @@ 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.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; @@ -43,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; @@ -64,11 +60,6 @@ public abstract class StatusBarViewModule { public static final String STATUS_BAR_FRAGMENT = "status_bar_fragment"; - @Binds - @IntoSet - abstract StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener( - SystemBarAttributesListener systemBarAttributesListener); - /** * Creates a new {@link CollapsedStatusBarFragment}. * 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/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/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/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/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/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/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 4263091c5ee8..5abab6239b1e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -3002,6 +3002,16 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { } @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( 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/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 e042564646e7..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, 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 ee5c1cc31b0b..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 @@ -89,7 +89,11 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { faceAuthRepository = FakeDeviceEntryFaceAuthRepository() keyguardTransitionRepository = FakeKeyguardTransitionRepository() keyguardTransitionInteractor = - KeyguardTransitionInteractor(keyguardTransitionRepository, testScope.backgroundScope) + KeyguardTransitionInteractorFactory.create( + scope = TestScope().backgroundScope, + repository = keyguardTransitionRepository, + ) + .keyguardTransitionInteractor underTest = SystemUIKeyguardFaceAuthInteractor( 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 6836733f1580..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,7 +2,7 @@ package com.android.systemui.log import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.log.core.LogLevel +import com.android.systemui.log.core.Logger import com.google.common.truth.Truth.assertThat import java.io.PrintWriter import java.io.StringWriter @@ -33,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() @@ -42,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() @@ -54,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() @@ -73,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() @@ -94,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/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/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/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/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt index 45247977283a..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( 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/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/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/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/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/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/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/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/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/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/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/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 2249bd47de9d..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); 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/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/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..b793c162e38b 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); @@ -2975,6 +2982,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 +3049,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 +4043,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 +5130,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 +6108,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 +6212,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/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 3e810dc7cd45..32104f68c091 100644 --- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java +++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java @@ -12159,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; @@ -14614,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); } } 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 17b460b51105..472994f2e2ff 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -2038,7 +2038,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperData data = null; synchronized (mLock) { if (mIsLockscreenLiveWallpaperEnabled) { - clearWallpaperLocked(callingPackage, false, which, userId); + clearWallpaperLocked(callingPackage, false, which, userId, null); } else { clearWallpaperLocked(false, which, userId, null); } @@ -2059,7 +2059,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } private void clearWallpaperLocked(String callingPackage, boolean defaultFailed, - int which, int userId) { + int which, int userId, IRemoteCallback reply) { // Might need to bring it in the first time to establish our rewrite if (!mWallpaperMap.contains(userId)) { @@ -2113,9 +2113,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub withCleanCallingIdentity(() -> clearWallpaperComponentLocked(wallpaper)); } - // TODO(b/266818039) remove this version of the method private void clearWallpaperLocked(boolean defaultFailed, int which, int userId, IRemoteCallback reply) { + + if (mIsLockscreenLiveWallpaperEnabled) { + String callingPackage = mPackageManagerInternal.getNameForUid(getCallingUid()); + clearWallpaperLocked(callingPackage, defaultFailed, which, userId, reply); + return; + } + if (which != FLAG_SYSTEM && which != FLAG_LOCK) { throw new IllegalArgumentException("Must specify exactly one kind of wallpaper to clear"); } @@ -3158,7 +3164,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 +3195,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 +3228,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; } } @@ -3279,15 +3291,21 @@ public class WallpaperManagerService extends IWallpaperManager.Stub boolean setWallpaperComponent(ComponentName name, String callingPackage, @SetWallpaperFlags int which, int userId) { if (mIsLockscreenLiveWallpaperEnabled) { - return setWallpaperComponentInternal(name, callingPackage, which, userId); + return setWallpaperComponentInternal(name, callingPackage, which, userId, null); } else { setWallpaperComponentInternalLegacy(name, callingPackage, which, userId); return true; } } + private boolean setWallpaperComponent(ComponentName name, @SetWallpaperFlags int which, + int userId) { + String callingPackage = mPackageManagerInternal.getNameForUid(getCallingUid()); + return setWallpaperComponentInternal(name, callingPackage, which, userId, null); + } + private boolean setWallpaperComponentInternal(ComponentName name, String callingPackage, - @SetWallpaperFlags int which, int userIdIn) { + @SetWallpaperFlags int which, int userIdIn, IRemoteCallback reply) { if (DEBUG) { Slog.v(TAG, "Setting new live wallpaper: which=" + which + ", component: " + name); } @@ -3336,6 +3354,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub Slog.d(TAG, "publish system wallpaper changed!"); } liveSync.complete(); + if (reply != null) reply.sendResult(null); } }; @@ -3433,7 +3452,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; + } } } 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/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 e8f331de11b3..aeaf78327d57 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1467,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; @@ -1501,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; @@ -1509,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; } } @@ -5926,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; 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..18c2f4e7c215 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 @@ -1287,20 +1285,6 @@ public class DisplayPolicy { } /** - * @return true if the system bars are forced to be consumed - */ - public boolean areSystemBarsForcedConsumedLw() { - return mForceConsumeSystemBars; - } - - /** - * @return true if the system bars are forced to stay visible - */ - public boolean areSystemBarsForcedShownLw() { - return mForceShowSystemBars; - } - - /** * Computes the frames of display (its logical size, rotation and cutout should already be set) * used to layout window. This method only changes the given display frames, insets state and * some temporal states, but doesn't change the window frames used to show on screen. @@ -1694,7 +1678,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 +2356,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/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 798dc85ec11b..4620266c981e 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -64,6 +64,8 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.DisplayThread; import com.android.server.statusbar.StatusBarManagerInternal; +import java.io.PrintWriter; + /** * Policy that implements who gets control over the windows generating insets. */ @@ -114,6 +116,7 @@ class InsetsPolicy { 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 @InsetsType int mForcedShowingTypes; private boolean mAnimatingShown; private final boolean mHideNavBarForKeyboard; @@ -127,7 +130,6 @@ class InsetsPolicy { mHideNavBarForKeyboard = r.getBoolean(R.bool.config_hideNavBarForKeyboard); } - /** Updates the target which can control system bars. */ void updateBarControlTarget(@Nullable WindowState focusedWin) { if (mFocusedWin != focusedWin) { @@ -514,7 +516,7 @@ 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; @@ -567,13 +569,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,7 +576,7 @@ 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; @@ -603,7 +598,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; } @@ -696,6 +716,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; 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 20936dcf5b42..d03388a8455e 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -419,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, @@ -435,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(); } } @@ -486,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/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/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 79a54c3cfb32..5b3bbd50cb2d 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 @@ -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..cba77ead0030 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<>(); @@ -1789,9 +1789,6 @@ public class WindowManagerService extends IWindowManager.Stub winAnimator.mEnterAnimationPending = true; winAnimator.mEnteringAnimation = true; - if (displayPolicy.areSystemBarsForcedConsumedLw()) { - res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS; - } if (displayContent.isInTouchMode()) { res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE; } @@ -2494,9 +2491,6 @@ public class WindowManagerService extends IWindowManager.Stub if (win.mActivityRecord != null) { win.mActivityRecord.updateReportedVisibilityLocked(); } - if (displayPolicy.areSystemBarsForcedConsumedLw()) { - result |= WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS; - } if (!win.isGoneForLayout()) { win.mResizedWhileGone = false; } @@ -5359,8 +5353,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 +5474,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 +5488,7 @@ public class WindowManagerService extends IWindowManager.Stub callback = mWaitingForDrawnCallbacks.remove(container); } if (callback != null) { - callback.run(); + callback.sendToTarget(); } break; } @@ -5520,17 +5512,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 +6059,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 +6086,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 +7800,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 +7824,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(); } } @@ -9057,7 +9040,7 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public boolean getWindowInsets(int displayId, IBinder token, InsetsState outInsetsState) { + public void getWindowInsets(int displayId, IBinder token, InsetsState outInsetsState) { final long origId = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { @@ -9068,7 +9051,6 @@ public class WindowManagerService extends IWindowManager.Stub } final WindowToken winToken = dc.getWindowToken(token); dc.getInsetsPolicy().getInsetsForWindowMetrics(winToken, outInsetsState); - return dc.getDisplayPolicy().areSystemBarsForcedConsumedLw(); } } finally { Binder.restoreCallingIdentity(origId); 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..ef44702041a5 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3732,8 +3732,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final boolean isDragResizeChanged = isDragResizeChanged(); final boolean forceRelayout = syncWithBuffers || isDragResizeChanged; final DisplayContent displayContent = getDisplayContent(); - final boolean alwaysConsumeSystemBars = - displayContent.getDisplayPolicy().areSystemBarsForcedConsumedLw(); final int displayId = displayContent.getDisplayId(); if (isDragResizeChanged) { @@ -3745,7 +3743,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP try { mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration, - getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId, + getCompatInsetsState(), forceRelayout, displayId, syncWithBuffers ? mSyncSeqId : -1, isDragResizing); if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration .getMergedConfiguration().windowConfiguration.getRotation()) { @@ -5665,7 +5663,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/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 775a65acb04f..2f1d67d34c43 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -16226,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(); } @@ -19502,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; } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index ec3e73406004..2f2e1cbaef5e 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1559,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); 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/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/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 e820a9e368c2..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); @@ -1450,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() { @@ -1503,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/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..be918ec2637e 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -70,6 +70,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(); 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/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/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/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/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/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 3db53eb08ea1..2e055e8a665e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -3354,8 +3354,7 @@ public class ActivityRecordTests extends WindowTestsBase { // to client if the app didn't request IME visible. assertFalse(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput); verify(app2.mClient, atLeastOnce()).resized(any(), anyBoolean(), any(), - insetsStateCaptor.capture(), anyBoolean(), anyBoolean(), anyInt(), anyInt(), - anyBoolean()); + insetsStateCaptor.capture(), anyBoolean(), anyInt(), anyInt(), anyBoolean()); assertFalse(app2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); } 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/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java index 3f8acc651110..40f86ddb611c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java @@ -46,8 +46,7 @@ public class TestIWindow extends IWindow.Stub { @Override public void resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration mergedConfig, InsetsState insetsState, boolean forceLayout, - boolean alwaysConsumeSystemBars, int displayId, int seqId, boolean dragResizing) - throws RemoteException { + int displayId, int seqId, boolean dragResizing) throws RemoteException { } @Override 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/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 0ddd3135506e..c13c2c3e35d0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -778,8 +778,8 @@ public class WindowStateTests extends WindowTestsBase { doThrow(new RemoteException("test")).when(win.mClient).resized(any() /* frames */, anyBoolean() /* reportDraw */, any() /* mergedConfig */, any() /* insetsState */, anyBoolean() /* forceLayout */, - anyBoolean() /* alwaysConsumeSystemBars */, anyInt() /* displayId */, - anyInt() /* seqId */, anyBoolean() /* dragResizing */); + anyInt() /* displayId */, anyInt() /* seqId */, + anyBoolean() /* dragResizing */); } catch (RemoteException ignored) { } win.reportResized(); 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/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/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, |