From d1bcb69fce9b465f637fddfd01879602d22ac1f2 Mon Sep 17 00:00:00 2001 From: Minkyoung Kim Date: Thu, 4 Jul 2024 11:03:47 +0900 Subject: [PATCH 001/388] fix(high contrast text) : Make simplified paint have SRC_OVER blend mode. Change-Id: Ib65709dfffaa7c8b181be54a32ae73c1559bb8bb --- libs/hwui/hwui/DrawTextFunctor.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h index 1fcb6920db14..0a801992affe 100644 --- a/libs/hwui/hwui/DrawTextFunctor.h +++ b/libs/hwui/hwui/DrawTextFunctor.h @@ -51,6 +51,7 @@ static void simplifyPaint(int color, Paint* paint) { paint->setStrokeWidth(kHighContrastTextBorderWidth + 0.04 * paint->getSkFont().getSize()); paint->setStrokeJoin(SkPaint::kRound_Join); paint->setLooper(nullptr); + paint->setBlendMode(SkBlendMode::kSrcOver); } class DrawTextFunctor { -- GitLab From ba9d00af95b1fb9a0d33c18f99d36fbb9e59cf74 Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Thu, 8 Aug 2024 15:34:35 +0800 Subject: [PATCH 002/388] Apply new scheduling group for multi-window mode apps Currently it only applies for freeform window mode. For example, if there are 4 visible freeform apps: The top 2 will be SCHED_GROUP_TOP_APP. And the bottom 2 will be SCHED_GROUP_FOREGROUND_MW. That makes the non-top visible apps have higher priority than SCHED_GROUP_DEFAULT, so they may be more responsive. Bug: 200769420 Test: MockingOomAdjusterTests#testUpdateOomAdj_DoOne_VisibleActivities Flag: com.android.window.flags.process_priority_policy_for_multi_window_mode Change-Id: I2a78e3e4c7f67d2a1361810413486421e22f74b5 --- .../core/java/com/android/server/am/OomAdjuster.java | 10 ++++++++++ .../core/java/com/android/server/am/ProcessList.java | 3 +++ .../com/android/server/wm/WindowProcessController.java | 9 +++++++-- .../com/android/server/am/MockingOomAdjusterTests.java | 9 +++++++++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index ab63e247b556..144c2f33a6aa 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -73,6 +73,7 @@ import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE; import static android.media.audio.Flags.roForegroundAudioControl; import static android.os.Process.THREAD_GROUP_BACKGROUND; import static android.os.Process.THREAD_GROUP_DEFAULT; +import static android.os.Process.THREAD_GROUP_FOREGROUND_WINDOW; import static android.os.Process.THREAD_GROUP_RESTRICTED; import static android.os.Process.THREAD_GROUP_TOP_APP; import static android.os.Process.THREAD_PRIORITY_DISPLAY; @@ -117,6 +118,7 @@ import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ; import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ; import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND; import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT; +import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW; import static com.android.server.am.ProcessList.SCHED_GROUP_RESTRICTED; import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP; import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP_BOUND; @@ -1713,6 +1715,11 @@ public class OomAdjuster { // The recently used non-top visible freeform app. schedGroup = SCHED_GROUP_TOP_APP; mAdjType = "perceptible-freeform-activity"; + } else if ((flags + & WindowProcessController.ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE) != 0) { + // Currently the only case is from freeform apps which are not close to top. + schedGroup = SCHED_GROUP_FOREGROUND_WINDOW; + mAdjType = "vis-multi-window-activity"; } foregroundActivities = true; mHasVisibleActivities = true; @@ -3442,6 +3449,9 @@ public class OomAdjuster { case SCHED_GROUP_RESTRICTED: processGroup = THREAD_GROUP_RESTRICTED; break; + case SCHED_GROUP_FOREGROUND_WINDOW: + processGroup = THREAD_GROUP_FOREGROUND_WINDOW; + break; default: processGroup = THREAD_GROUP_DEFAULT; break; diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index bb0c24b4f8c6..7fa5b89ad863 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -303,6 +303,9 @@ public final class ProcessList { // Activity manager's version of Process.THREAD_GROUP_TOP_APP // Disambiguate between actual top app and processes bound to the top app static final int SCHED_GROUP_TOP_APP_BOUND = 4; + // Activity manager's version of Process.THREAD_GROUP_FOREGROUND_WINDOW + // The priority is like between default and top-app. + static final int SCHED_GROUP_FOREGROUND_WINDOW = 5; // The minimum number of cached apps we want to be able to keep around, // without empty apps being able to push them out of memory. diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 2bae0a826417..fb058227e5e1 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -328,6 +328,7 @@ public class WindowProcessController extends ConfigurationContainer 1 && minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) { - stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM; + && minTaskLayer > 1) { + if (minTaskLayer <= 1 + MAX_NUM_PERCEPTIBLE_FREEFORM) { + stateFlags |= ACTIVITY_STATE_FLAG_PERCEPTIBLE_FREEFORM; + } else { + stateFlags |= ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE; + } } stateFlags |= minTaskLayer & ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER; if (visible) { diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 8656b991b5fc..774f533ad289 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -60,6 +60,7 @@ import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ; import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ; import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND; import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT; +import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW; import static com.android.server.am.ProcessList.SCHED_GROUP_RESTRICTED; import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP; import static com.android.server.am.ProcessList.SCHED_GROUP_TOP_APP_BOUND; @@ -507,6 +508,14 @@ public class MockingOomAdjusterTests { updateOomAdj(app); assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_TOP_APP); assertEquals("perceptible-freeform-activity", app.mState.getAdjType()); + + doReturn(WindowProcessController.ACTIVITY_STATE_FLAG_IS_VISIBLE + | WindowProcessController.ACTIVITY_STATE_FLAG_VISIBLE_MULTI_WINDOW_MODE) + .when(wpc).getActivityStateFlags(); + updateOomAdj(app); + assertProcStates(app, PROCESS_STATE_TOP, VISIBLE_APP_ADJ, + SCHED_GROUP_FOREGROUND_WINDOW); + assertEquals("vis-multi-window-activity", app.mState.getAdjType()); } @SuppressWarnings("GuardedBy") -- GitLab From 9f9e91c29f4eb6c2c8eb8befd7f419bb1f62054b Mon Sep 17 00:00:00 2001 From: Felix Stern Date: Thu, 15 Aug 2024 14:25:32 +0000 Subject: [PATCH 003/388] Fix showing the IME from EmbeddedWindow - With the refactor in [1], the IME does not show, when requested from EmbeddedWindow. - This CL introduces a new parent interface (InsetsTarget) for InsetsControlTarget and InputType. However, an EmbeddedWindow may not be a controlTarget, as it should not be able to control other insets types, except of the IME. - Previously the initiallyVisible of an IME control was always false [2]. This will now take into account, whether there was an ongoing transition (e.g., in case of app -> Launcher, in this case it is not initiallyVisible), or otherwise whether it is is client- and serverVisible (otherwise it cannot be currently be shown). - The leash was hidden in InsetsSourceProvider.ControlAdapter#startAnimation, which is due to this change also not needed anymore (otherwise the leash could have a different visibility than given in initiallyVisible). - DisplayImeController was not using initiallyVisible, but uses it now to update its own imeVisible state and start an animation, if necessary. [1]: I8e3a74ee579f085cb582040fdba725e7a63d6b85 [2]: I459debedb243b4345b9981b88adf6e4a0ea9b44a Test: atest CtsSurfaceControlTests:android.view.surfacecontrol.cts.SurfaceControlViewHostTests#testImeVisible Flag: android.view.inputmethod.refactor_insets_controller Fix: 355047142 Change-Id: I5aeb3ccfbfe7d4cd0b7b5f0e0400769e65bb70a4 --- .../android/view/WindowlessWindowManager.java | 9 +++- .../wm/shell/common/DisplayImeController.java | 24 ++++++---- .../com/android/server/wm/DisplayContent.java | 21 +++++--- .../server/wm/EmbeddedWindowController.java | 31 ++++++++++++ .../server/wm/ImeInsetsSourceProvider.java | 37 +++++++------- .../com/android/server/wm/InputTarget.java | 6 +-- .../server/wm/InsetsControlTarget.java | 16 ++++--- .../com/android/server/wm/InsetsPolicy.java | 4 +- .../server/wm/InsetsSourceProvider.java | 36 ++++++++++---- .../server/wm/InsetsStateController.java | 4 +- .../com/android/server/wm/InsetsTarget.java | 48 +++++++++++++++++++ .../java/com/android/server/wm/Session.java | 21 +++++++- .../server/wm/DisplayContentTests.java | 37 +++++++++----- .../android/server/wm/TaskFragmentTest.java | 2 +- 14 files changed, 218 insertions(+), 78 deletions(-) create mode 100644 services/core/java/com/android/server/wm/InsetsTarget.java diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index d2747e465071..5129461095a3 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -587,7 +587,14 @@ public class WindowlessWindowManager implements IWindowSession { @Override public void updateRequestedVisibleTypes(IWindow window, - @InsetsType int requestedVisibleTypes, @Nullable ImeTracker.Token imeStatsToken) { + @InsetsType int requestedVisibleTypes, @Nullable ImeTracker.Token imeStatsToken) + throws RemoteException { + if (android.view.inputmethod.Flags.refactorInsetsController()) { + // Embedded windows do not control insets (except for IME). The host window is + // responsible for controlling the insets. + mRealWm.updateRequestedVisibleTypes(window, + requestedVisibleTypes & WindowInsets.Type.ime(), imeStatsToken); + } } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java index 5b01a0d87b5e..ddb5494a52b0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java @@ -309,21 +309,29 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged lastSurfacePosition); } else { if (!haveSameLeash(mImeSourceControl, imeSourceControl)) { - applyVisibilityToLeash(imeSourceControl); - if (android.view.inputmethod.Flags.refactorInsetsController()) { pendingImeStartAnimation = true; + // The starting point for the IME should be it's previous state + // (whether it is initiallyVisible or not) + updateImeVisibility(imeSourceControl.isInitiallyVisible()); } + applyVisibilityToLeash(imeSourceControl); } if (!mImeShowing) { removeImeSurface(mDisplayId); } } - } else if (!android.view.inputmethod.Flags.refactorInsetsController() - && mAnimation != null) { - // we don"t want to cancel the hide animation, when the control is lost, but - // continue the bar to slide to the end (even without visible IME) - mAnimation.cancel(); + } else { + if (!android.view.inputmethod.Flags.refactorInsetsController() + && mAnimation != null) { + // we don't want to cancel the hide animation, when the control is lost, but + // continue the bar to slide to the end (even without visible IME) + mAnimation.cancel(); + } else if (android.view.inputmethod.Flags.refactorInsetsController() && mImeShowing + && mAnimation == null) { + // There is no leash, so the IME cannot be in a showing state + updateImeVisibility(false); + } } if (positionChanged) { if (android.view.inputmethod.Flags.refactorInsetsController()) { @@ -345,7 +353,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged if (android.view.inputmethod.Flags.refactorInsetsController()) { if (pendingImeStartAnimation) { - startAnimation(true, true /* forceRestart */); + startAnimation(mImeRequestedVisible, true /* forceRestart */); } } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 0597ed7a1c41..81e5cd13a4f5 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -710,6 +710,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp @Retention(RetentionPolicy.SOURCE) @interface InputMethodTarget {} + /** The surface parent window of the IME container. */ + private WindowContainer mInputMethodSurfaceParentWindow; /** The surface parent of the IME container. */ @VisibleForTesting SurfaceControl mInputMethodSurfaceParent; @@ -1535,6 +1537,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mDisplayRotation.getLastOrientation(); } + WindowContainer getImeParentWindow() { + return mInputMethodSurfaceParentWindow; + } + void registerRemoteAnimations(RemoteAnimationDefinition definition) { mAppTransitionController.registerRemoteAnimations(definition); } @@ -4739,13 +4745,17 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp Slog.i(TAG_WM, "ImeContainer is organized. Skip updateImeParent."); } // Leave the ImeContainer where the DisplayAreaPolicy placed it. - // FEATURE_IME is organized by vendor so they are responible for placing the surface. + // FEATURE_IME is organized by vendor so they are responsible for placing the surface. + mInputMethodSurfaceParentWindow = null; mInputMethodSurfaceParent = null; return; } - final SurfaceControl newParent = computeImeParent(); + final var newParentWindow = computeImeParent(); + final SurfaceControl newParent = + newParentWindow != null ? newParentWindow.getSurfaceControl() : null; if (newParent != null && newParent != mInputMethodSurfaceParent) { + mInputMethodSurfaceParentWindow = newParentWindow; mInputMethodSurfaceParent = newParent; getSyncTransaction().reparent(mImeWindowsContainer.mSurfaceControl, newParent); if (DEBUG_IME_VISIBILITY) { @@ -4806,7 +4816,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * Computes the window the IME should be attached to. */ @VisibleForTesting - SurfaceControl computeImeParent() { + WindowContainer computeImeParent() { if (!ImeTargetVisibilityPolicy.canComputeImeParent(mImeLayeringTarget, mImeInputTarget)) { return null; } @@ -4814,11 +4824,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // screen. If it's not covering the entire screen the IME might extend beyond the apps // bounds. if (shouldImeAttachedToApp()) { - return mImeLayeringTarget.mActivityRecord.getSurfaceControl(); + return mImeLayeringTarget.mActivityRecord; } // Otherwise, we just attach it to where the display area policy put it. - return mImeWindowsContainer.getParent() != null - ? mImeWindowsContainer.getParent().getSurfaceControl() : null; + return mImeWindowsContainer.getParent(); } void setLayoutNeeded() { diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java index 169a76fe3afd..5514294ed477 100644 --- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java +++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java @@ -33,6 +33,7 @@ import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.InputApplicationHandle; import android.view.InputChannel; +import android.view.WindowInsets; import android.window.InputTransferToken; import com.android.internal.protolog.ProtoLog; @@ -222,6 +223,10 @@ class EmbeddedWindowController { private boolean mIsFocusable; + // The EmbeddedWindow can only request the IME. All other insets types are requested by + // the host window. + private @WindowInsets.Type.InsetsType int mRequestedVisibleTypes = 0; + /** * @param session calling session to check ownership of the window * @param clientToken client token used to clean up the map if the embedding process dies @@ -310,6 +315,27 @@ class EmbeddedWindowController { return mClient; } + @Override + public boolean isRequestedVisible(@WindowInsets.Type.InsetsType int types) { + return (mRequestedVisibleTypes & types) != 0; + } + + @Override + public @WindowInsets.Type.InsetsType int getRequestedVisibleTypes() { + return mRequestedVisibleTypes; + } + + /** + * Only the IME can be requested from the EmbeddedWindow. + * @param requestedVisibleTypes other types than {@link WindowInsets.Type.IME} are + * not sent to system server via WindowlessWindowManager. + */ + void setRequestedVisibleTypes(@WindowInsets.Type.InsetsType int requestedVisibleTypes) { + if (mRequestedVisibleTypes != requestedVisibleTypes) { + mRequestedVisibleTypes = requestedVisibleTypes; + } + } + @Override public int getPid() { return mOwnerPid; @@ -375,6 +401,11 @@ class EmbeddedWindowController { @Override public boolean shouldControlIme() { + if (android.view.inputmethod.Flags.refactorInsetsController()) { + // EmbeddedWindow should never be able to control the IME directly, but only the + // RemoteInsetsControlTarget. + return false; + } return mHostWindowState != null; } diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 6b916ef04e38..5cbcd7327fcb 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -268,7 +268,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { // TODO(b/353463205) change statsToken to be NonNull, after the flag is permanently enabled @Override - protected boolean updateClientVisibility(InsetsControlTarget caller, + protected boolean updateClientVisibility(InsetsTarget caller, @Nullable ImeTracker.Token statsToken) { InsetsControlTarget controlTarget = getControlTarget(); if (caller != controlTarget) { @@ -283,12 +283,13 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); controlTarget.setImeInputTargetRequestedVisibility(imeVisible); - } else { + } else if (caller instanceof InsetsControlTarget) { // In case of a virtual display that cannot show the IME, the // controlTarget will be null here, as no controlTarget was set yet. In // that case, proceed similar to the multi window mode (fallback = // RemoteInsetsControlTarget of the default display) - controlTarget = mDisplayContent.getImeHostOrFallback(caller.getWindow()); + controlTarget = mDisplayContent.getImeHostOrFallback( + ((InsetsControlTarget) caller).getWindow()); if (controlTarget != caller) { ImeTracker.forLogging().onProgress(statsToken, @@ -300,8 +301,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { } } - WindowState windowState = caller.getWindow(); - invokeOnImeRequestedChangedListener(windowState, statsToken); + invokeOnImeRequestedChangedListener(caller, statsToken); } else { // TODO(b/353463205) add ImeTracker? } @@ -309,20 +309,16 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { return false; } boolean changed = super.updateClientVisibility(caller, statsToken); - if (!Flags.refactorInsetsController()) { + if (!Flags.refactorInsetsController() && caller instanceof InsetsControlTarget) { if (changed && caller.isRequestedVisible(mSource.getType())) { - reportImeDrawnForOrganizerIfNeeded(caller); + reportImeDrawnForOrganizerIfNeeded((InsetsControlTarget) caller); } } changed |= mDisplayContent.onImeInsetsClientVisibilityUpdate(); if (Flags.refactorInsetsController()) { if (changed) { - // RemoteInsetsControlTarget does not have a window. In this case, we use the - // windowState from the imeInputTarget - WindowState windowState = caller.getWindow() != null ? caller.getWindow() - : ((mDisplayContent.getImeInputTarget() != null) - ? mDisplayContent.getImeInputTarget().getWindowState() : null); - invokeOnImeRequestedChangedListener(windowState, statsToken); + invokeOnImeRequestedChangedListener(mDisplayContent.getImeInputTarget(), + statsToken); } else { // TODO(b/329229469) change phase and check cancelled / failed ImeTracker.forLogging().onCancelled(statsToken, @@ -334,32 +330,31 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { void onInputTargetChanged(InputTarget target) { if (Flags.refactorInsetsController() && target != null) { - WindowState targetWin = target.getWindowState(); InsetsControlTarget imeControlTarget = getControlTarget(); - if (target != imeControlTarget && targetWin != null) { + if (target != imeControlTarget) { // If the targetWin is not the imeControlTarget (=RemoteInsetsControlTarget) let it // know about the new requestedVisibleTypes for the IME. if (imeControlTarget != null) { imeControlTarget.setImeInputTargetRequestedVisibility( - (targetWin.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0); + (target.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0); } } } } // TODO(b/353463205) check callers to see if we can make statsToken @NonNull - private void invokeOnImeRequestedChangedListener(WindowState windowState, + private void invokeOnImeRequestedChangedListener(InsetsTarget insetsTarget, @Nullable ImeTracker.Token statsToken) { final var imeListener = mDisplayContent.mWmService.mOnImeRequestedChangedListener; if (imeListener != null) { - if (windowState != null) { + if (insetsTarget != null) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_POSTING_CHANGED_IME_VISIBILITY); mDisplayContent.mWmService.mH.post(() -> { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_WM_INVOKING_IME_REQUESTED_LISTENER); - imeListener.onImeRequestedChanged(windowState.mClient.asBinder(), - windowState.isRequestedVisible(WindowInsets.Type.ime()), statsToken); + imeListener.onImeRequestedChanged(insetsTarget.getWindowToken(), + insetsTarget.isRequestedVisible(WindowInsets.Type.ime()), statsToken); }); } else { ImeTracker.forLogging().onFailed(statsToken, @@ -676,7 +671,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { return target == mDisplayContent.getImeFallback(); } - private boolean isImeInputTarget(@NonNull InsetsControlTarget target) { + private boolean isImeInputTarget(@NonNull InsetsTarget target) { return target == mDisplayContent.getImeInputTarget(); } diff --git a/services/core/java/com/android/server/wm/InputTarget.java b/services/core/java/com/android/server/wm/InputTarget.java index 0c0b794182e7..40ce9db8a608 100644 --- a/services/core/java/com/android/server/wm/InputTarget.java +++ b/services/core/java/com/android/server/wm/InputTarget.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import android.os.IBinder; import android.util.proto.ProtoOutputStream; /** @@ -25,16 +24,13 @@ import android.util.proto.ProtoOutputStream; * Both WindowState and EmbeddedWindows can receive input. This consolidates some common properties * of both targets. */ -interface InputTarget { +interface InputTarget extends InsetsTarget { /* Get the WindowState associated with the target. */ WindowState getWindowState(); /* Display id of the target. */ int getDisplayId(); - /* Client IWindow for the target. */ - IBinder getWindowToken(); - /* Owning pid of the target. */ int getPid(); int getUid(); diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java index 07e249a2004f..7043aacfc44d 100644 --- a/services/core/java/com/android/server/wm/InsetsControlTarget.java +++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java @@ -18,6 +18,7 @@ package com.android.server.wm; import android.annotation.Nullable; import android.inputmethodservice.InputMethodService; +import android.os.IBinder; import android.view.WindowInsets; import android.view.WindowInsets.Type.InsetsType; import android.view.inputmethod.ImeTracker; @@ -25,7 +26,7 @@ import android.view.inputmethod.ImeTracker; /** * Generalization of an object that can control insets state. */ -interface InsetsControlTarget { +interface InsetsControlTarget extends InsetsTarget { /** * Notifies the control target that the insets control has changed. @@ -42,16 +43,17 @@ interface InsetsControlTarget { return null; } - /** - * @return {@code true} if any of the {@link InsetsType} is requested visible by this target. - */ + @Override + default IBinder getWindowToken() { + return null; + } + + @Override default boolean isRequestedVisible(@InsetsType int types) { return (WindowInsets.Type.defaultVisible() & types) != 0; } - /** - * @return {@link InsetsType}s which are requested visible by this target. - */ + @Override default @InsetsType int getRequestedVisibleTypes() { return WindowInsets.Type.defaultVisible(); } diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 129078b0a235..b414a862f874 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -434,7 +434,7 @@ class InsetsPolicy { return originalState; } - void onRequestedVisibleTypesChanged(InsetsControlTarget caller, + void onRequestedVisibleTypesChanged(InsetsTarget caller, @Nullable ImeTracker.Token statsToken) { mStateController.onRequestedVisibleTypesChanged(caller, statsToken); checkAbortTransient(caller); @@ -449,7 +449,7 @@ class InsetsPolicy { * * @param caller who changed the insets state. */ - private void checkAbortTransient(InsetsControlTarget caller) { + private void checkAbortTransient(InsetsTarget caller) { if (mShowingTransientTypes == 0) { return; } diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index b66b8bc2115b..71778563d6c1 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -529,13 +529,27 @@ class InsetsSourceProvider { setClientVisible((WindowInsets.Type.defaultVisible() & mSource.getType()) != 0); return; } + boolean initiallyVisible = mClientVisible; final Point surfacePosition = getWindowFrameSurfacePosition(); mAdapter = new ControlAdapter(surfacePosition); if (mSource.getType() == WindowInsets.Type.ime()) { + if (android.view.inputmethod.Flags.refactorInsetsController()) { + if (mClientVisible && mServerVisible) { + WindowContainer imeParentWindow = mDisplayContent.getImeParentWindow(); + // If the IME is attached to an app window, only consider it initially visible + // if the parent is visible and wasn't part of a transition. + initiallyVisible = + imeParentWindow != null && !imeParentWindow.inTransitionSelfOrParent() + && imeParentWindow.isVisible() + && imeParentWindow.isVisibleRequested(); + } else { + initiallyVisible = false; + } + } setClientVisible(target.isRequestedVisible(WindowInsets.Type.ime())); } final Transaction t = mWindowContainer.getSyncTransaction(); - mWindowContainer.startAnimation(t, mAdapter, !mClientVisible /* hidden */, + mWindowContainer.startAnimation(t, mAdapter, !initiallyVisible /* hidden */, ANIMATION_TYPE_INSETS_CONTROL); // The leash was just created. We cannot dispatch it until its surface transaction is @@ -545,14 +559,16 @@ class InsetsSourceProvider { final SurfaceControl leash = mAdapter.mCapturedLeash; mControlTarget = target; updateVisibility(); - boolean initiallyVisible = mClientVisible; if (mSource.getType() == WindowInsets.Type.ime()) { - // The IME cannot be initially visible, see ControlAdapter#startAnimation below. - // Also, the ImeInsetsSourceConsumer clears the client visibility upon losing control, - // but this won't have reached here yet by the time the new control is created. - // Note: The DisplayImeController needs the correct previous client's visibility, so we - // only override the initiallyVisible here. - initiallyVisible = false; + if (!android.view.inputmethod.Flags.refactorInsetsController()) { + // The IME cannot be initially visible, see ControlAdapter#startAnimation below. + // Also, the ImeInsetsSourceConsumer clears the client visibility upon losing + // control, but this won't have reached here yet by the time the new control is + // created. + // Note: The DisplayImeController needs the correct previous client's visibility, + // so we only override the initiallyVisible here. + initiallyVisible = false; + } } mControl = new InsetsSourceControl(mSource.getId(), mSource.getType(), leash, initiallyVisible, surfacePosition, getInsetsHint()); @@ -572,7 +588,7 @@ class InsetsSourceProvider { mSeamlessRotating = false; } - boolean updateClientVisibility(InsetsControlTarget caller, + boolean updateClientVisibility(InsetsTarget caller, @Nullable ImeTracker.Token statsToken) { final boolean requestedVisible = caller.isRequestedVisible(mSource.getType()); if (caller != mControlTarget || requestedVisible == mClientVisible) { @@ -776,7 +792,7 @@ class InsetsSourceProvider { @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) { // TODO(b/166736352): Check if we still need to control the IME visibility here. if (mSource.getType() == WindowInsets.Type.ime()) { - if (!android.view.inputmethod.Flags.refactorInsetsController() || !mClientVisible) { + if (!android.view.inputmethod.Flags.refactorInsetsController()) { // TODO: use 0 alpha and remove t.hide() once b/138459974 is fixed. t.setAlpha(animationLeash, 1 /* alpha */); t.hide(animationLeash); diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 098a691e0490..0727885c6a61 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -217,7 +217,7 @@ class InsetsStateController { } } - void onRequestedVisibleTypesChanged(InsetsControlTarget caller, + void onRequestedVisibleTypesChanged(InsetsTarget caller, @Nullable ImeTracker.Token statsToken) { boolean changed = false; for (int i = mProviders.size() - 1; i >= 0; i--) { @@ -236,7 +236,7 @@ class InsetsStateController { } } - @InsetsType int getFakeControllingTypes(InsetsControlTarget target) { + @InsetsType int getFakeControllingTypes(InsetsTarget target) { @InsetsType int types = 0; for (int i = mProviders.size() - 1; i >= 0; i--) { final InsetsSourceProvider provider = mProviders.valueAt(i); diff --git a/services/core/java/com/android/server/wm/InsetsTarget.java b/services/core/java/com/android/server/wm/InsetsTarget.java new file mode 100644 index 000000000000..b918ca3dbff6 --- /dev/null +++ b/services/core/java/com/android/server/wm/InsetsTarget.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.annotation.Nullable; +import android.os.IBinder; +import android.view.WindowInsets; + +/** + * A common parent for {@link InputTarget} and {@link InsetsControlTarget}: Some types (like the + * {@link EmbeddedWindowController.EmbeddedWindow}) should not be a control target for insets in + * general, but should be able to request the IME. To archive this, the InsetsTarget contains the + * minimal information that those interfaces share (and what is needed to show the IME. + */ +public interface InsetsTarget { + + /** + * @return Client IWindow token for the target. + */ + @Nullable + IBinder getWindowToken(); + + /** + * @param types The {@link WindowInsets.Type}s which requestedVisibility status is returned. + * @return {@code true} if any of the {@link WindowInsets.Type.InsetsType} is requested + * visible by this target. + */ + boolean isRequestedVisible(@WindowInsets.Type.InsetsType int types); + + /** + * @return {@link WindowInsets.Type.InsetsType}s which are requested visible by this target. + */ + @WindowInsets.Type.InsetsType int getRequestedVisibleTypes(); +} diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 7c875c1f3322..db479f7f073b 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -708,8 +708,25 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { win.getDisplayContent().getInsetsPolicy().onRequestedVisibleTypesChanged(win, imeStatsToken); } else { - ImeTracker.forLogging().onFailed(imeStatsToken, - ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES); + EmbeddedWindowController.EmbeddedWindow embeddedWindow = null; + if (android.view.inputmethod.Flags.refactorInsetsController()) { + embeddedWindow = mService.mEmbeddedWindowController.getByWindowToken( + window.asBinder()); + } + if (embeddedWindow != null) { + // If there is no WindowState for the IWindow, it could be still an + // EmbeddedWindow. Therefore, check the EmbeddedWindowController as well + // TODO(b/329229469) Use different phase here + ImeTracker.forLogging().onProgress(imeStatsToken, + ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES); + embeddedWindow.setRequestedVisibleTypes( + requestedVisibleTypes & WindowInsets.Type.ime()); + embeddedWindow.getDisplayContent().getInsetsPolicy() + .onRequestedVisibleTypesChanged(embeddedWindow, imeStatsToken); + } else { + ImeTracker.forLogging().onFailed(imeStatsToken, + ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES); + } } } } 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 f2ea1c972b90..63d57621d45c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -369,8 +369,10 @@ public class DisplayContentTests extends WindowTestsBase { "startingWin"); startingWin.setHasSurface(true); assertTrue(startingWin.canBeImeTarget()); + final WindowContainer imeSurfaceParentWindow = mock(WindowContainer.class); final SurfaceControl imeSurfaceParent = mock(SurfaceControl.class); - doReturn(imeSurfaceParent).when(mDisplayContent).computeImeParent(); + doReturn(imeSurfaceParent).when(imeSurfaceParentWindow).getSurfaceControl(); + doReturn(imeSurfaceParentWindow).when(mDisplayContent).computeImeParent(); spyOn(imeContainer); mDisplayContent.setImeInputTarget(startingWin); @@ -406,8 +408,11 @@ public class DisplayContentTests extends WindowTestsBase { "startingWin"); startingWin.setHasSurface(true); assertTrue(startingWin.canBeImeTarget()); + final WindowContainer imeSurfaceParentWindow = mock(WindowContainer.class); final SurfaceControl imeSurfaceParent = mock(SurfaceControl.class); - doReturn(imeSurfaceParent).when(mDisplayContent).computeImeParent(); + doReturn(imeSurfaceParent).when(imeSurfaceParentWindow).getSurfaceControl(); + doReturn(imeSurfaceParentWindow).when(mDisplayContent).computeImeParent(); + // Main precondition for this test: organize the ImeContainer. final IDisplayAreaOrganizer mockImeOrganizer = mock(IDisplayAreaOrganizer.class); @@ -639,10 +644,11 @@ public class DisplayContentTests extends WindowTestsBase { ws.matchesDisplayAreaBounds()); assertTrue("IME shouldn't be attached to app", - dc.computeImeParent() != dc.getImeTarget(IME_TARGET_LAYERING).getWindow() - .mActivityRecord.getSurfaceControl()); + dc.computeImeParent().getSurfaceControl() != dc.getImeTarget( + IME_TARGET_LAYERING).getWindow().mActivityRecord.getSurfaceControl()); assertEquals("IME should be attached to display", - dc.getImeContainer().getParent().getSurfaceControl(), dc.computeImeParent()); + dc.getImeContainer().getParent().getSurfaceControl(), + dc.computeImeParent().getSurfaceControl()); } private WindowState[] createNotDrawnWindowsOn(DisplayContent displayContent, int... types) { @@ -1190,8 +1196,9 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayContent dc = createNewDisplay(); dc.setImeLayeringTarget(createWindow(null, TYPE_BASE_APPLICATION, "app")); dc.setImeInputTarget(dc.getImeTarget(IME_TARGET_LAYERING).getWindow()); - assertEquals(dc.getImeTarget(IME_TARGET_LAYERING).getWindow() - .mActivityRecord.getSurfaceControl(), dc.computeImeParent()); + assertEquals(dc.getImeTarget( + IME_TARGET_LAYERING).getWindow().mActivityRecord.getSurfaceControl(), + dc.computeImeParent().getSurfaceControl()); } @Test @@ -1201,7 +1208,8 @@ public class DisplayContentTests extends WindowTestsBase { dc.getImeTarget(IME_TARGET_LAYERING).getWindow().setWindowingMode( WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW); dc.setImeInputTarget(dc.getImeTarget(IME_TARGET_LAYERING).getWindow()); - assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent()); + assertEquals(dc.getImeContainer().getParentSurfaceControl(), + dc.computeImeParent().getSurfaceControl()); } @SetupWindows(addWindows = W_ACTIVITY) @@ -1212,7 +1220,7 @@ public class DisplayContentTests extends WindowTestsBase { mDisplayContent.setImeLayeringTarget(mAppWindow); // The surface parent of IME should be the display instead of app window. assertEquals(mDisplayContent.getImeContainer().getParentSurfaceControl(), - mDisplayContent.computeImeParent()); + mDisplayContent.computeImeParent().getSurfaceControl()); } @Test @@ -1220,7 +1228,8 @@ public class DisplayContentTests extends WindowTestsBase { final DisplayContent dc = createNewDisplay(); dc.setImeLayeringTarget(createWindow(null, TYPE_STATUS_BAR, "statusBar")); dc.setImeInputTarget(dc.getImeTarget(IME_TARGET_LAYERING).getWindow()); - assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent()); + assertEquals(dc.getImeContainer().getParentSurfaceControl(), + dc.computeImeParent().getSurfaceControl()); } @SetupWindows(addWindows = W_ACTIVITY) @@ -1231,7 +1240,8 @@ public class DisplayContentTests extends WindowTestsBase { doReturn(true).when(mDisplayContent).shouldImeAttachedToApp(); mDisplayContent.setImeLayeringTarget(app1); mDisplayContent.setImeInputTarget(app1); - assertEquals(app1.mActivityRecord.getSurfaceControl(), mDisplayContent.computeImeParent()); + assertEquals(app1.mActivityRecord.getSurfaceControl(), + mDisplayContent.computeImeParent().getSurfaceControl()); mDisplayContent.setImeLayeringTarget(app2); // Expect null means no change IME parent when the IME layering target not yet // request IME to be the input target. @@ -1249,7 +1259,7 @@ public class DisplayContentTests extends WindowTestsBase { mDisplayContent.setImeInputTarget(app); assertFalse(mDisplayContent.shouldImeAttachedToApp()); assertEquals(mDisplayContent.getImeContainer().getParentSurfaceControl(), - mDisplayContent.computeImeParent()); + mDisplayContent.computeImeParent().getSurfaceControl()); } @Test @@ -1274,7 +1284,8 @@ public class DisplayContentTests extends WindowTestsBase { assertEquals(dc.getImeTarget(IME_TARGET_LAYERING), dc.getImeInputTarget()); // The ImeParent should be the display. - assertEquals(dc.getImeContainer().getParent().getSurfaceControl(), dc.computeImeParent()); + assertEquals(dc.getImeContainer().getParent().getSurfaceControl(), + dc.computeImeParent().getSurfaceControl()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 6be1af2c143f..c57c2f27b222 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -887,7 +887,7 @@ public class TaskFragmentTest extends WindowTestsBase { // The ImeParent should be the display. assertEquals(mDisplayContent.getImeContainer().getParent().getSurfaceControl(), - mDisplayContent.computeImeParent()); + mDisplayContent.computeImeParent().getSurfaceControl()); } @Test -- GitLab From 3455a2182c68b40a3e18f74f94711f81e4bb34e9 Mon Sep 17 00:00:00 2001 From: Tyler Freeman Date: Tue, 3 Sep 2024 18:01:08 +0000 Subject: [PATCH 004/388] fix(force invert): hide Container type treatment behind feature flag Bug: 364172907 Change-Id: If92a59ae84e395f64267980baeba5d7ceaef7e39 Test: nope Flag: EXEMPT bugfix --- libs/hwui/RenderNode.cpp | 12 +++++++++--- libs/hwui/RenderNode.h | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp index 589abb4d87f4..2c23864317a4 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -404,13 +404,19 @@ void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) { } } +inline bool RenderNode::isForceInvertDark(TreeInfo& info) { + return CC_UNLIKELY( + info.forceDarkType == android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK); +} + inline bool RenderNode::shouldEnableForceDark(TreeInfo* info) { return CC_UNLIKELY( info && - (!info->disableForceDark || - info->forceDarkType == android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK)); + (!info->disableForceDark || isForceInvertDark(*info))); } + + void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) { if (!shouldEnableForceDark(info)) { return; @@ -421,7 +427,7 @@ void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) { children.push_back(node); }); if (mDisplayList.hasText()) { - if (mDisplayList.hasFill()) { + if (isForceInvertDark(*info) && mDisplayList.hasFill()) { // Handle a special case for custom views that draw both text and background in the // same RenderNode, which would otherwise be altered to white-on-white text. usage = UsageHint::Container; diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h index c9045427bd42..afbbce7e27ee 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -234,6 +234,7 @@ private: void syncDisplayList(TreeObserver& observer, TreeInfo* info); void handleForceDark(TreeInfo* info); bool shouldEnableForceDark(TreeInfo* info); + bool isForceInvertDark(TreeInfo& info); void prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer); void pushStagingPropertiesChanges(TreeInfo& info); -- GitLab From 68a3199ab0e85a8d1f377f943f1470bcd301c41d Mon Sep 17 00:00:00 2001 From: Garvita Jain Date: Tue, 3 Sep 2024 08:14:20 +0000 Subject: [PATCH 005/388] Add new mime type for MHT files. Mime map does not have a mime type map for MHT files. Adding multipart/related as the mime type for MHT extension files. BUG: 183687627 Test: atest MimeMapTest Change-Id: Ic1f7aea571c6f086022ea3d7b09acf46337e8959 Flag: EXEMPT bugfix --- mime/java-res/android.mime.types | 1 + 1 file changed, 1 insertion(+) diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types index 5cf807d68d77..17cdee48a082 100644 --- a/mime/java-res/android.mime.types +++ b/mime/java-res/android.mime.types @@ -132,6 +132,7 @@ # Optional additions that should not override any previous mapping. ?application/x-wifi-config ?xml +?multipart/related mht # Special cases where Android has a strong opinion about mappings, so we # define them very last and make them override in both directions (no "?"). -- GitLab From 7f9b9ca43f6c6a3317418e57f2fbec4d2b153813 Mon Sep 17 00:00:00 2001 From: Liefu Liu Date: Thu, 29 Aug 2024 21:33:19 -0700 Subject: [PATCH 006/388] Defined the config_rawContactsEligibleDefaultAccountTypes in the framework's config. Account types in this config_rawContactsEligibleDefaultAccountTypes array can be set as the default account. OEM can customize their own default account types. Bug: b/364731608 Test: unit test Flag:com.android.providers.contacts.flags.enable_new_default_account_rule_flag modified: core/res/res/values/config.xml modified: core/res/res/values/symbols.xml Change-Id: I6b4813bb27dd8a1c7f8b91509628f462a230224e --- core/res/res/values/config.xml | 7 +++++++ core/res/res/values/symbols.xml | 1 + 2 files changed, 8 insertions(+) diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index b6468ee7a12c..9812290baf82 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5831,6 +5831,13 @@ should also be non-empty.--> + + + + + + false diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index bbe661e78b1d..5bcd6bc7ac32 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4501,6 +4501,7 @@ + -- GitLab From 77d047831d4be697b66f5e5d56d824cae6621c35 Mon Sep 17 00:00:00 2001 From: "an.xi" Date: Thu, 5 Sep 2024 16:12:09 +0800 Subject: [PATCH 007/388] HDMI-CEC: Restore full volume device condition to send cec volume keys [1/1] Volume adjustment is done on AVR while a headphone is connected. The current output audio device is already updated from hdmi_arc to headphone. This patch adds full volume check for a tv device, such that it only send cec volume keyevents when current device is a full volume device. Test: verified by vendor Bug: b/359078216 Flag: EXEMPT bugfix Change-Id: Iac4f6f4bf53733ee54ceb8f2da2885b21272dcfc Signed-off-by: an.xi --- services/core/java/com/android/server/audio/AudioService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 4e24cf38fe73..b43a0fd16e4a 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -285,7 +285,6 @@ import java.util.Objects; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CancellationException; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -4004,6 +4003,7 @@ public class AudioService extends IAudioService.Stub && isFullVolumeDevice(device); boolean tvConditions = mHdmiTvClient != null && mHdmiSystemAudioSupported + && isFullVolumeDevice(device) && !isAbsoluteVolumeDevice(device) && !isA2dpAbsoluteVolumeDevice(device); -- GitLab From e27e42255231ecb6ce6a6187034d249f1c2501b2 Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Wed, 4 Sep 2024 17:56:02 -0700 Subject: [PATCH 008/388] Improved `dumpsys device_config` so it calls the mainline implementation. That will allow it to include more information, like the property change listeners. Example: $ adb shell dumpsys device_config | grep listeners | head -3 245 listeners for 185 namespaces: accessibility: 2 listeners activity_manager: 5 listeners Bug: 364399200 Test: see above Flag: android.provider.flags.dump_improvements Change-Id: Ifab219bccededb07de558dced9d6203efe568042 --- packages/SettingsProvider/Android.bp | 1 + .../settings/DeviceConfigService.java | 22 ++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp index 3e62b7b2cf6e..6306fe9d45aa 100644 --- a/packages/SettingsProvider/Android.bp +++ b/packages/SettingsProvider/Android.bp @@ -36,6 +36,7 @@ android_library { "aconfig_new_storage_flags_lib", "aconfigd_java_utils", "aconfig_demo_flags_java_lib", + "configinfra_framework_flags_java_lib", "device_config_service_flags_java", "libaconfig_java_proto_lite", "SettingsLibDeviceStateRotationLock", diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java index bfbf41dc87ce..fbce6ca07b3e 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java @@ -99,13 +99,23 @@ public final class DeviceConfigService extends Binder { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.print("SyncDisabledForTests: "); - MyShellCommand.getSyncDisabledForTests(pw, pw); - - pw.print("Is mainline: "); - pw.println(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()); + if (android.provider.flags.Flags.dumpImprovements()) { + pw.print("SyncDisabledForTests: "); + MyShellCommand.getSyncDisabledForTests(pw, pw); + + pw.print("UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService(): "); + pw.println(UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()); + + pw.println("DeviceConfig provider: "); + try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(fd)) { + DeviceConfig.dump(pfd, pw, /* prefix= */ " ", args); + } catch (IOException e) { + pw.print("IOException creating ParcelFileDescriptor: "); + pw.println(e); + } + } - final IContentProvider iprovider = mProvider.getIContentProvider(); + IContentProvider iprovider = mProvider.getIContentProvider(); pw.println("DeviceConfig flags:"); for (String line : MyShellCommand.listAll(iprovider)) { pw.println(line); -- GitLab From 14d11fb699ec762472aeb903f73d9e135e7ec49a Mon Sep 17 00:00:00 2001 From: wenyu zhang Date: Mon, 9 Sep 2024 13:35:43 +0000 Subject: [PATCH 009/388] Update doc to reuse getDevicesForAttributes for get input devices Change-Id: I119d9a33b0a84a67bcf774e78d5e6d41ab2d8a89 Bug: b/364923030, b/357122624 Flag: com.android.media.audioserver.enable_audio_input_device_routing --- media/java/android/media/AudioManager.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index ca468fc1ff44..41a061aa85c2 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -6317,7 +6317,14 @@ public class AudioManager { /** * @hide * Get the audio devices that would be used for the routing of the given audio attributes. - * @param attributes the {@link AudioAttributes} for which the routing is being queried + * @param attributes the {@link AudioAttributes} for which the routing is being queried. + * For queries about output devices (playback use cases), a valid usage must be specified in + * the audio attributes via AudioAttributes.Builder.setUsage(). The capture preset MUST NOT + * be changed from default. + * For queries about input devices (capture use case), a valid capture preset MUST be + * specified in the audio attributes via AudioAttributes.Builder.setCapturePreset(). If a + * capture preset is present, then this has precedence over any usage or content type also + * present in the audio attrirutes. * @return an empty list if there was an issue with the request, a list of audio devices * otherwise (typically one device, except for duplicated paths). */ -- GitLab From dc81885dc6251c7d94a1ec98ed78597d6084b4bb Mon Sep 17 00:00:00 2001 From: Willie Koomson Date: Tue, 11 Jun 2024 22:43:33 +0000 Subject: [PATCH 010/388] Write RemoteViews actions to proto (2/3) This change adds support for writing the following RemoteViews actions to proto: ResourceReflectionAction SetCompoundButtonCheckedAction SetDrawableTintAction SetEmptyViewAction SetIntTagAction SetRadioGroupCheckedAction SetRemoteCollectionItemListAdapterAction SetRippleDrawableColorAction Test: RemoteViewsProto test Bug: 308041327 Flag: android.appwidget.flags.remote_views_proto Change-Id: I90240baa9f9b2ce87dee6d09471445c5bcf9640c --- core/java/android/widget/RemoteViews.java | 480 +++++++++++++++++++- core/proto/android/widget/remoteviews.proto | 54 +++ 2 files changed, 533 insertions(+), 1 deletion(-) diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 89ea85200cd6..60b85837e8e0 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1002,6 +1002,55 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_EMPTY_VIEW_ACTION_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION); + out.write(RemoteViewsProto.SetEmptyViewAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID, + appResources.getResourceName(mViewId)); + out.end(token); + } + + public static PendingResources createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetEmptyViewAction.VIEW_ID: + values.put(RemoteViewsProto.SetEmptyViewAction.VIEW_ID, + in.readString(RemoteViewsProto.SetEmptyViewAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID: + values.put(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID, + in.readString(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, new long[]{RemoteViewsProto.SetEmptyViewAction.VIEW_ID, + RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetEmptyViewAction.VIEW_ID); + int emptyViewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID); + return new SetEmptyView(viewId, emptyViewId); + }; + } } private static class SetPendingIntentTemplate extends Action { @@ -1242,6 +1291,68 @@ public class RemoteViews implements Parcelable, Filter { mItems.visitUris(visitor); } + + @Override + public boolean canWriteToProto() { + // Skip actions that do not contain items (intent only actions) + return mItems != null; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + if (mItems == null) return; + final long token = out.start( + RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION); + out.write(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID, + appResources.getResourceName(mViewId)); + final long itemsToken = out.start( + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS); + mItems.writeToProto(context, out, /* attached= */ true); + out.end(itemsToken); + out.end(token); + } + } + + private PendingResources createSetRemoteCollectionItemListAdapterActionFromProto( + ProtoInputStream in) throws Exception { + final LongSparseArray values = new LongSparseArray<>(); + + final long token = in.start( + RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID: + values.put(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID, + in.readString( + RemoteViewsProto + .SetRemoteCollectionItemListAdapterAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS: + final long itemsToken = in.start( + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS); + values.put(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS, + RemoteCollectionItems.createFromProto(in)); + in.end(itemsToken); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, + new long[]{RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID, + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID); + return new SetRemoteCollectionItemListAdapterAction(viewId, + ((PendingResources) values.get( + RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS)) + .create(context, resources, rootData, depth)); + }; } /** @@ -2035,6 +2146,68 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_DRAWABLE_TINT_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION); + out.write(RemoteViewsProto.SetDrawableTintAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER, mColorFilter); + out.write(RemoteViewsProto.SetDrawableTintAction.FILTER_MODE, + PorterDuff.modeToInt(mFilterMode)); + out.write(RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND, mTargetBackground); + out.end(token); + } + + public static PendingResources createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetDrawableTintAction.VIEW_ID: + values.put(RemoteViewsProto.SetDrawableTintAction.VIEW_ID, + in.readString(RemoteViewsProto.SetDrawableTintAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND: + values.put(RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND, + in.readBoolean( + RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND)); + break; + case (int) RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER: + values.put(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER, + in.readInt(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER)); + break; + case (int) RemoteViewsProto.SetDrawableTintAction.FILTER_MODE: + values.put(RemoteViewsProto.SetDrawableTintAction.FILTER_MODE, + PorterDuff.intToMode(in.readInt( + RemoteViewsProto.SetDrawableTintAction.FILTER_MODE))); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, new long[]{RemoteViewsProto.SetDrawableTintAction.VIEW_ID}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetDrawableTintAction.VIEW_ID); + return new SetDrawableTint(viewId, (boolean) values.get( + RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND, false), + (int) values.get(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER, 0), + (PorterDuff.Mode) values.get( + RemoteViewsProto.SetDrawableTintAction.FILTER_MODE)); + }; + } } /** @@ -2046,7 +2219,7 @@ public class RemoteViews implements Parcelable, Filter { * target {@link View#getBackground()}. *

*/ - private class SetRippleDrawableColor extends Action { + private static class SetRippleDrawableColor extends Action { ColorStateList mColorStateList; SetRippleDrawableColor(@IdRes int id, ColorStateList colorStateList) { @@ -2081,6 +2254,58 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_RIPPLE_DRAWABLE_COLOR_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION); + out.write(RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID, + appResources.getResourceName(mViewId)); + writeColorStateListToProto(out, mColorStateList, + RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST); + out.end(token); + } + + public static PendingResources createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID: + values.put(RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID, + in.readString( + RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST: + values.put(RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST, + createColorStateListFromProto(in, + RemoteViewsProto + .SetRippleDrawableColorAction.COLOR_STATE_LIST)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, + new long[]{RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID, + RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID); + return new SetRippleDrawableColor(viewId, (ColorStateList) values.get( + RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST)); + }; + } } /** @@ -2986,6 +3211,82 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return RESOURCE_REFLECTION_ACTION_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION); + out.write(RemoteViewsProto.ResourceReflectionAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME, mMethodName); + out.write(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE, mType); + out.write(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE, mResourceType); + if (mResId != 0) { + out.write(RemoteViewsProto.ResourceReflectionAction.RES_ID, + appResources.getResourceName(mResId)); + } + out.end(token); + } + + public static PendingResources createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.ResourceReflectionAction.VIEW_ID: + values.put(RemoteViewsProto.ResourceReflectionAction.VIEW_ID, + in.readString(RemoteViewsProto.ResourceReflectionAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.ResourceReflectionAction.METHOD_NAME: + values.put(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME, + in.readString( + RemoteViewsProto.ResourceReflectionAction.METHOD_NAME)); + break; + case (int) RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE: + values.put(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE, + in.readInt( + RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE)); + break; + case (int) RemoteViewsProto.ResourceReflectionAction.RES_ID: + values.put(RemoteViewsProto.ResourceReflectionAction.RES_ID, + in.readString(RemoteViewsProto.ResourceReflectionAction.RES_ID)); + break; + case (int) RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE: + values.put(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE, + in.readInt( + RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, new long[]{RemoteViewsProto.ResourceReflectionAction.VIEW_ID, + RemoteViewsProto.ResourceReflectionAction.METHOD_NAME, + RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.ResourceReflectionAction.VIEW_ID); + + int resId = (values.indexOfKey(RemoteViewsProto.ResourceReflectionAction.RES_ID) + >= 0) ? getAsIdentifier(resources, values, + RemoteViewsProto.ResourceReflectionAction.RES_ID) : 0; + return new ResourceReflectionAction(viewId, + (String) values.get(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME), + (int) values.get(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE), + (int) values.get(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE, + 0), resId); + }; + } } private static final class AttributeReflectionAction extends BaseReflectionAction { @@ -4592,6 +4893,61 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_INT_TAG_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_INT_TAG_ACTION); + out.write(RemoteViewsProto.SetIntTagAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.SetIntTagAction.KEY, + appResources.getResourceName(mKey)); // rebase + out.write(RemoteViewsProto.SetIntTagAction.TAG, mTag); + out.end(token); + } + + public static PendingResources createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_INT_TAG_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetIntTagAction.VIEW_ID: + values.put(RemoteViewsProto.SetIntTagAction.VIEW_ID, + in.readString(RemoteViewsProto.SetIntTagAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetIntTagAction.KEY: + values.put(RemoteViewsProto.SetIntTagAction.KEY, + in.readString(RemoteViewsProto.SetIntTagAction.KEY)); + break; + case (int) RemoteViewsProto.SetIntTagAction.TAG: + values.put(RemoteViewsProto.SetIntTagAction.TAG, + in.readInt(RemoteViewsProto.SetIntTagAction.TAG)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, new long[]{RemoteViewsProto.SetIntTagAction.VIEW_ID, + RemoteViewsProto.SetIntTagAction.KEY}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetIntTagAction.VIEW_ID); + int keyId = getAsIdentifier(resources, values, + RemoteViewsProto.SetIntTagAction.KEY); + return new SetIntTagAction(viewId, keyId, + (int) values.get(RemoteViewsProto.SetIntTagAction.TAG, 0)); + }; + } } private static class SetCompoundButtonCheckedAction extends Action { @@ -4642,6 +4998,56 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_COMPOUND_BUTTON_CHECKED_TAG; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start( + RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION); + out.write(RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID, + appResources.getResourceName(mViewId)); + out.write(RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED, mChecked); + out.end(token); + } + + public static PendingResources createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID: + values.put(RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID, + in.readString( + RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED: + values.put(RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED, + in.readBoolean( + RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, + new long[]{RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID); + return new SetCompoundButtonCheckedAction(viewId, (boolean) values.get( + RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED, false)); + }; + } } private static class SetRadioGroupCheckedAction extends Action { @@ -4706,6 +5112,61 @@ public class RemoteViews implements Parcelable, Filter { public int getActionTag() { return SET_RADIO_GROUP_CHECKED; } + + @Override + public boolean canWriteToProto() { + return true; + } + + @Override + public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { + final long token = out.start(RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION); + out.write(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID, + appResources.getResourceName(mViewId)); + if (mCheckedId != -1) { + out.write(RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID, + appResources.getResourceName(mCheckedId)); + } + out.end(token); + } + + public static PendingResources createFromProto(ProtoInputStream in) + throws Exception { + final LongSparseArray values = new LongSparseArray<>(); + + final long token = in.start(RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION); + while (in.nextField() != NO_MORE_FIELDS) { + switch (in.getFieldNumber()) { + case (int) RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID: + values.put(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID, + in.readString(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID)); + break; + case (int) RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID: + values.put(RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID, + in.readString( + RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID)); + break; + default: + Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" + + ProtoUtils.currentFieldToString(in)); + } + } + in.end(token); + + checkContainsKeys(values, + new long[]{RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID}); + + return (context, resources, rootData, depth) -> { + int viewId = getAsIdentifier(resources, values, + RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID); + + int checkedId = (values.indexOfKey( + RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID) >= 0) + ? getAsIdentifier(resources, values, + RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID) : -1; + return new SetRadioGroupCheckedAction(viewId, checkedId); + }; + } } private static class SetViewOutlinePreferredRadiusAction extends Action { @@ -8417,6 +8878,7 @@ public class RemoteViews implements Parcelable, Filter { public static PendingResources createFromProto(ProtoInputStream in) throws Exception { final LongSparseArray values = new LongSparseArray<>(); + values.put(RemoteViewsProto.RemoteCollectionItems.IDS, new ArrayList()); values.put(RemoteViewsProto.RemoteCollectionItems.VIEWS, new ArrayList>()); @@ -9174,6 +9636,22 @@ public class RemoteViews implements Parcelable, Filter { return ReflectionAction.createFromProto(in); case (int) RemoteViewsProto.Action.REMOVE_FROM_PARENT_ACTION: return RemoveFromParentAction.createFromProto(in); + case (int) RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION: + return ResourceReflectionAction.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION: + return SetCompoundButtonCheckedAction.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION: + return SetDrawableTint.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION: + return SetEmptyView.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_INT_TAG_ACTION: + return SetIntTagAction.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION: + return SetRadioGroupCheckedAction.createFromProto(in); + case (int) RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION: + return rv.createSetRemoteCollectionItemListAdapterActionFromProto(in); + case (int) RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION: + return SetRippleDrawableColor.createFromProto(in); default: throw new RuntimeException("Unhandled field while reading Action proto!\n" + ProtoUtils.currentFieldToString(in)); diff --git a/core/proto/android/widget/remoteviews.proto b/core/proto/android/widget/remoteviews.proto index 47c97b08666b..f477d32cd915 100644 --- a/core/proto/android/widget/remoteviews.proto +++ b/core/proto/android/widget/remoteviews.proto @@ -299,6 +299,14 @@ message RemoteViewsProto { NightModeReflectionAction night_mode_reflection_action = 5; ReflectionAction reflection_action = 6; RemoveFromParentAction remove_from_parent_action = 7; + ResourceReflectionAction resource_reflection_action = 8; + SetCompoundButtonCheckedAction set_compound_button_checked_action = 9; + SetDrawableTintAction set_drawable_tint_action = 10; + SetEmptyViewAction set_empty_view_action = 11; + SetIntTagAction set_int_tag_action = 12; + SetRadioGroupCheckedAction set_radio_group_checked_action = 13; + SetRemoteCollectionItemListAdapterAction set_remote_collection_item_list_adapter_action = 14; + SetRippleDrawableColorAction set_ripple_drawable_color_action = 15; } } @@ -374,6 +382,52 @@ message RemoteViewsProto { message RemoveFromParentAction { optional string view_id = 1; } + + message ResourceReflectionAction { + optional string view_id = 1; + optional string method_name = 2; + optional int32 resource_type = 3; + optional string res_id = 4; + optional int32 parameter_type = 5; + } + + message SetCompoundButtonCheckedAction { + optional string view_id = 1; + optional bool checked = 2; + } + + message SetDrawableTintAction { + optional string view_id = 1; + optional bool target_background = 2; + optional int32 color_filter = 3; + optional int32 filter_mode = 4; + } + + message SetEmptyViewAction { + optional string view_id = 1; + optional string empty_view_id = 2; + } + + message SetIntTagAction { + optional string view_id = 1; + optional string key = 2; + optional int32 tag = 3; + } + + message SetRadioGroupCheckedAction { + optional string view_id = 1; + optional string checked_id = 2; + } + + message SetRemoteCollectionItemListAdapterAction { + optional string view_id = 1; + optional RemoteCollectionItems items = 2; + } + + message SetRippleDrawableColorAction { + optional string view_id = 1; + optional android.content.res.ColorStateListProto color_state_list = 2; + } } -- GitLab From 5f68c3bb0acda26d9c0456269a6d239120a6e7a1 Mon Sep 17 00:00:00 2001 From: Dipankar Bhardwaj Date: Wed, 21 Aug 2024 14:26:50 +0000 Subject: [PATCH 011/388] DO NOT MERGE Restrict access to directories Restricted access to Android/data, Android/obb and Android/sandbox directories and its sub-directories. Replacing path's pattern match check with file equality check. Test: atest DocumentsClientTest Bug: 341680936 Flag: EXEMPT bug fix Change-Id: I8879900e57e1702d11797b81e86d0cc3f55bac22 Merged-In: I8879900e57e1702d11797b81e86d0cc3f55bac22 --- .../ExternalStorageProvider.java | 79 ++++++++++++++++--- 1 file changed, 68 insertions(+), 11 deletions(-) diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 992de22f4265..fdeccfa7b9d0 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -16,8 +16,6 @@ package com.android.externalstorage; -import static java.util.regex.Pattern.CASE_INSENSITIVE; - import android.annotation.NonNull; import android.annotation.Nullable; import android.app.usage.StorageStatsManager; @@ -61,12 +59,15 @@ import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.UUID; -import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * Presents content of the shared (a.k.a. "external") storage. @@ -89,12 +90,9 @@ public class ExternalStorageProvider extends FileSystemProvider { private static final Uri BASE_URI = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build(); - /** - * Regex for detecting {@code /Android/data/}, {@code /Android/obb/} and - * {@code /Android/sandbox/} along with all their subdirectories and content. - */ - private static final Pattern PATTERN_RESTRICTED_ANDROID_SUBTREES = - Pattern.compile("^Android/(?:data|obb|sandbox)(?:/.+)?", CASE_INSENSITIVE); + private static final String PRIMARY_EMULATED_STORAGE_PATH = "/storage/emulated/"; + + private static final String STORAGE_PATH = "/storage/"; private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, @@ -308,10 +306,69 @@ public class ExternalStorageProvider extends FileSystemProvider { return false; } - final String path = getPathFromDocId(documentId); - return PATTERN_RESTRICTED_ANDROID_SUBTREES.matcher(path).matches(); + try { + final RootInfo root = getRootFromDocId(documentId); + final String canonicalPath = getPathFromDocId(documentId); + return isRestrictedPath(root.rootId, canonicalPath); + } catch (Exception e) { + return true; + } } + /** + * Based on the given root id and path, we restrict path access if file is Android/data or + * Android/obb or Android/sandbox or one of their subdirectories. + * + * @param canonicalPath of the file + * @return true if path is restricted + */ + private boolean isRestrictedPath(String rootId, String canonicalPath) { + if (rootId == null || canonicalPath == null) { + return true; + } + + final String rootPath; + if (rootId.equalsIgnoreCase(ROOT_ID_PRIMARY_EMULATED)) { + // Creates "/storage/emulated/" + rootPath = PRIMARY_EMULATED_STORAGE_PATH + UserHandle.myUserId(); + } else { + // Creates "/storage/" + rootPath = STORAGE_PATH + rootId; + } + List restrictedPathList = Arrays.asList( + Paths.get(rootPath, "Android", "data"), + Paths.get(rootPath, "Android", "obb"), + Paths.get(rootPath, "Android", "sandbox")); + // We need to identify restricted parent paths which actually exist on the device + List validRestrictedPathsToCheck = restrictedPathList.stream().filter( + Files::exists).collect(Collectors.toList()); + + boolean isRestricted = false; + java.nio.file.Path filePathToCheck = Paths.get(rootPath, canonicalPath); + try { + while (filePathToCheck != null) { + for (java.nio.file.Path restrictedPath : validRestrictedPathsToCheck) { + if (Files.isSameFile(restrictedPath, filePathToCheck)) { + isRestricted = true; + Log.v(TAG, "Restricting access for path: " + filePathToCheck); + break; + } + } + if (isRestricted) { + break; + } + + filePathToCheck = filePathToCheck.getParent(); + } + } catch (Exception e) { + Log.w(TAG, "Error in checking file equality check.", e); + isRestricted = true; + } + + return isRestricted; + } + + /** * Check that the directory is the root of storage or blocked file from tree. *

-- GitLab From 01006f7f97083ae49a546f9e0a94db7bdfd2a152 Mon Sep 17 00:00:00 2001 From: Dipankar Bhardwaj Date: Wed, 21 Aug 2024 14:26:50 +0000 Subject: [PATCH 012/388] DO NOT MERGE Restrict access to directories Restricted access to Android/data, Android/obb and Android/sandbox directories and its sub-directories. Replacing path's pattern match check with file equality check. Test: atest DocumentsClientTest Bug: 341680936 Flag: EXEMPT bug fix Change-Id: I8879900e57e1702d11797b81e86d0cc3f55bac22 Merged-In: I8879900e57e1702d11797b81e86d0cc3f55bac22 --- .../ExternalStorageProvider.java | 79 ++++++++++++++++--- 1 file changed, 68 insertions(+), 11 deletions(-) diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 992de22f4265..fdeccfa7b9d0 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -16,8 +16,6 @@ package com.android.externalstorage; -import static java.util.regex.Pattern.CASE_INSENSITIVE; - import android.annotation.NonNull; import android.annotation.Nullable; import android.app.usage.StorageStatsManager; @@ -61,12 +59,15 @@ import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.UUID; -import java.util.regex.Pattern; +import java.util.stream.Collectors; /** * Presents content of the shared (a.k.a. "external") storage. @@ -89,12 +90,9 @@ public class ExternalStorageProvider extends FileSystemProvider { private static final Uri BASE_URI = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build(); - /** - * Regex for detecting {@code /Android/data/}, {@code /Android/obb/} and - * {@code /Android/sandbox/} along with all their subdirectories and content. - */ - private static final Pattern PATTERN_RESTRICTED_ANDROID_SUBTREES = - Pattern.compile("^Android/(?:data|obb|sandbox)(?:/.+)?", CASE_INSENSITIVE); + private static final String PRIMARY_EMULATED_STORAGE_PATH = "/storage/emulated/"; + + private static final String STORAGE_PATH = "/storage/"; private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, @@ -308,10 +306,69 @@ public class ExternalStorageProvider extends FileSystemProvider { return false; } - final String path = getPathFromDocId(documentId); - return PATTERN_RESTRICTED_ANDROID_SUBTREES.matcher(path).matches(); + try { + final RootInfo root = getRootFromDocId(documentId); + final String canonicalPath = getPathFromDocId(documentId); + return isRestrictedPath(root.rootId, canonicalPath); + } catch (Exception e) { + return true; + } } + /** + * Based on the given root id and path, we restrict path access if file is Android/data or + * Android/obb or Android/sandbox or one of their subdirectories. + * + * @param canonicalPath of the file + * @return true if path is restricted + */ + private boolean isRestrictedPath(String rootId, String canonicalPath) { + if (rootId == null || canonicalPath == null) { + return true; + } + + final String rootPath; + if (rootId.equalsIgnoreCase(ROOT_ID_PRIMARY_EMULATED)) { + // Creates "/storage/emulated/" + rootPath = PRIMARY_EMULATED_STORAGE_PATH + UserHandle.myUserId(); + } else { + // Creates "/storage/" + rootPath = STORAGE_PATH + rootId; + } + List restrictedPathList = Arrays.asList( + Paths.get(rootPath, "Android", "data"), + Paths.get(rootPath, "Android", "obb"), + Paths.get(rootPath, "Android", "sandbox")); + // We need to identify restricted parent paths which actually exist on the device + List validRestrictedPathsToCheck = restrictedPathList.stream().filter( + Files::exists).collect(Collectors.toList()); + + boolean isRestricted = false; + java.nio.file.Path filePathToCheck = Paths.get(rootPath, canonicalPath); + try { + while (filePathToCheck != null) { + for (java.nio.file.Path restrictedPath : validRestrictedPathsToCheck) { + if (Files.isSameFile(restrictedPath, filePathToCheck)) { + isRestricted = true; + Log.v(TAG, "Restricting access for path: " + filePathToCheck); + break; + } + } + if (isRestricted) { + break; + } + + filePathToCheck = filePathToCheck.getParent(); + } + } catch (Exception e) { + Log.w(TAG, "Error in checking file equality check.", e); + isRestricted = true; + } + + return isRestricted; + } + + /** * Check that the directory is the root of storage or blocked file from tree. *

-- GitLab From 820ec78730411ae55734c1a8298bbb1dec3cda94 Mon Sep 17 00:00:00 2001 From: Juan Sebastian Martinez Date: Tue, 10 Sep 2024 10:42:10 -0700 Subject: [PATCH 013/388] Cleanup of the haptic volume slider flag. This feature has been released. Test: atest VolumeDialogImplTest Test: presubmit Flag: NONE This is a flag cleanup CL Bug: 316953430 Change-Id: Icde13d325cee067e5eeb1f4bddc5eb3f6acb7f9c --- packages/SystemUI/aconfig/systemui.aconfig | 9 +------- .../systemui/volume/VolumeDialogImpl.java | 7 ++----- .../systemui/volume/VolumeDialogImplTest.java | 21 ++----------------- 3 files changed, 5 insertions(+), 32 deletions(-) diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index ad14035d9d0a..3e2c99ddb72c 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -544,13 +544,6 @@ flag { } } -flag { - name: "haptic_volume_slider" - namespace: "systemui" - description: "Adds haptic feedback to the volume slider." - bug: "316953430" -} - flag { name: "new_volume_panel" namespace: "systemui" @@ -1401,4 +1394,4 @@ flag { namespace: "systemui" description: "Allow non-touchscreen devices to bypass falsing" bug: "319809270" -} \ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 7786453814e0..bd93aee46ba0 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -35,7 +35,6 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.internal.jank.InteractionJankMonitor.CUJ_VOLUME_CONTROL; import static com.android.internal.jank.InteractionJankMonitor.Configuration.Builder; import static com.android.settingslib.flags.Flags.volumeDialogAudioSharingFix; -import static com.android.systemui.Flags.hapticVolumeSlider; import static com.android.systemui.volume.Events.DISMISS_REASON_POSTURE_CHANGED; import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED; @@ -928,10 +927,8 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } private void addSliderHapticsToRow(VolumeRow row) { - if (hapticVolumeSlider()) { - row.createPlugin(mVibratorHelper, mSystemClock); - HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin); - } + row.createPlugin(mVibratorHelper, mSystemClock); + HapticSliderViewBinder.bind(row.slider, row.mHapticPlugin); } @VisibleForTesting void addSliderHapticsToRows() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 1e2648b228f3..ecc7909a857a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -20,7 +20,6 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL; import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; -import static com.android.systemui.Flags.FLAG_HAPTIC_VOLUME_SLIDER; import static com.android.systemui.volume.Events.DISMISS_REASON_UNKNOWN; import static com.android.systemui.volume.Events.SHOW_REASON_UNKNOWN; import static com.android.systemui.volume.VolumeDialogControllerImpl.DYNAMIC_STREAM_BROADCAST; @@ -51,7 +50,6 @@ import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.media.AudioSystem; import android.os.SystemClock; -import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.provider.Settings; import android.testing.TestableLooper; @@ -285,23 +283,8 @@ public class VolumeDialogImplTest extends SysuiTestCase { } @Test - @DisableFlags(FLAG_HAPTIC_VOLUME_SLIDER) - public void addSliderHaptics_withHapticsDisabled_doesNotDeliverOnProgressChangedHaptics() { - // GIVEN that the slider haptics flag is disabled and we try to add haptics to volume rows - mDialog.addSliderHapticsToRows(); - - // WHEN haptics try to be delivered to a volume stream - boolean canDeliverHaptics = - mDialog.canDeliverProgressHapticsToStream(AudioSystem.STREAM_MUSIC, true, 50); - - // THEN the result is that haptics are not successfully delivered - assertFalse(canDeliverHaptics); - } - - @Test - @EnableFlags(FLAG_HAPTIC_VOLUME_SLIDER) - public void addSliderHaptics_withHapticsEnabled_canDeliverOnProgressChangedHaptics() { - // GIVEN that the slider haptics flag is enabled and we try to add haptics to volume rows + public void addSliderHaptics_canDeliverOnProgressChangedHaptics() { + // GIVEN that the slider haptics are added to rows mDialog.addSliderHapticsToRows(); // WHEN haptics try to be delivered to a volume stream -- GitLab From d25450377c2d5af19a7073d6b0e08c4d04672a55 Mon Sep 17 00:00:00 2001 From: wangdongdong6 Date: Wed, 11 Sep 2024 16:02:28 +0800 Subject: [PATCH 014/388] [Bugfix]Modify SurfaceControl setColor callName error. Modify SurfaceControl checkCallStackDebugging callName to 'setColor'. Bug:365909423 Change-Id: I0269cc2b26b141601327e7ef728dfc5f2ed9d82e --- core/java/android/view/SurfaceControl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index 0bdb4ad645f3..505a82a1518f 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -3491,7 +3491,7 @@ public final class SurfaceControl implements Parcelable { checkPreconditions(sc); if (SurfaceControlRegistry.sCallStackDebuggingEnabled) { SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging( - "reparent", this, sc, + "setColor", this, sc, "r=" + color[0] + " g=" + color[1] + " b=" + color[2]); } nativeSetColor(mNativeObject, sc.mNativeObject, color); -- GitLab From 86f8f8bc2e880738bc736b06bf3b996167ec154f Mon Sep 17 00:00:00 2001 From: Harshit Mahajan Date: Wed, 11 Sep 2024 02:03:52 +0000 Subject: [PATCH 015/388] Fix Environment#getDataSystemDeDirectory API We don't need to restrict the API to be ModuleLibrary. Bug: 354693320 Test: TH Flag: android.crashrecovery.flags.enable_crashrecovery Change-Id: I30b63954a7630dde65277733f72fd345e7f31307 --- core/api/module-lib-current.txt | 4 ---- core/api/system-current.txt | 1 + core/java/android/os/Environment.java | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index df707d18a827..1ed8ccd9711d 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -384,10 +384,6 @@ package android.os { field public static final int DEVICE_INITIAL_SDK_INT; } - public class Environment { - method @FlaggedApi("android.crashrecovery.flags.enable_crashrecovery") @NonNull public static java.io.File getDataSystemDeDirectory(); - } - public class IpcDataCache { ctor public IpcDataCache(int, @NonNull String, @NonNull String, @NonNull String, @NonNull android.os.IpcDataCache.QueryHandler); method public void disableForCurrentProcess(); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 60dc52b655d6..fa08504149f1 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -10772,6 +10772,7 @@ package android.os { public class Environment { method @NonNull public static java.io.File getDataCePackageDirectoryForUser(@NonNull java.util.UUID, @NonNull android.os.UserHandle, @NonNull String); method @NonNull public static java.io.File getDataDePackageDirectoryForUser(@NonNull java.util.UUID, @NonNull android.os.UserHandle, @NonNull String); + method @FlaggedApi("android.crashrecovery.flags.enable_crashrecovery") @NonNull public static java.io.File getDataSystemDeDirectory(); method @NonNull public static java.util.Collection getInternalMediaDirectories(); method @NonNull public static java.io.File getOdmDirectory(); method @NonNull public static java.io.File getOemDirectory(); diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 2c7b9c02330e..89a5e5d6637d 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -415,7 +415,7 @@ public class Environment { * Returns the base directory for per-user system directory, device encrypted. * {@hide} */ - @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @SystemApi @FlaggedApi(android.crashrecovery.flags.Flags.FLAG_ENABLE_CRASHRECOVERY) public static @NonNull File getDataSystemDeDirectory() { return buildPath(getDataDirectory(), "system_de"); -- GitLab From 4c6e54230eb535f730fbe20b02357e2e5d780b2e Mon Sep 17 00:00:00 2001 From: Anton Potapov Date: Wed, 11 Sep 2024 15:04:24 +0100 Subject: [PATCH 016/388] Use platform WindowMetrics to get window bounds Getting current insets is a long operation. This is why platform WindowMetricsController provides WindowMetrics with insetsProvider instead of resolving those inplace. AndroidX WindowMetricsCalculator resolves those insets when translates platform WindowMetrics to their androidX counterpart, which causes a framedrop. This change swaps androidX implementation with native one because we only need screen bounds to determine its size. Flag: EXEMPT bugfix Fixes: 363545395 Test: atest VolumePanelScreenshotTest Test: manual on foldable Change-Id: Ie1bd7a666097be6714b3a8a0134302e4d59ba58c --- .../android/compose/windowsizeclass/WindowSizeClass.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt b/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt index 4674d6e5f25a..c01396a96b6e 100644 --- a/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt +++ b/packages/SystemUI/compose/core/src/com/android/compose/windowsizeclass/WindowSizeClass.kt @@ -16,15 +16,16 @@ package com.android.compose.windowsizeclass +import android.view.WindowManager import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi import androidx.compose.material3.windowsizeclass.WindowSizeClass import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.graphics.toComposeRect import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity -import androidx.window.layout.WindowMetricsCalculator val LocalWindowSizeClass = staticCompositionLocalOf { @@ -41,7 +42,10 @@ fun calculateWindowSizeClass(): WindowSizeClass { LocalConfiguration.current val density = LocalDensity.current val context = LocalContext.current - val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context) + val metrics = + remember(context) { + context.getSystemService(WindowManager::class.java)!!.currentWindowMetrics + } val size = with(density) { metrics.bounds.toComposeRect().size.toDpSize() } return WindowSizeClass.calculateFromSize(size) } -- GitLab From 338668402a8a23dcc042676feb9bf6f013806595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20Kozynski?= Date: Thu, 5 Sep 2024 15:23:44 -0400 Subject: [PATCH 017/388] Add accessibility for new tiles. Accessibility is as follows: * For small tiles, we use the content description and the state description to indicate the current name and state. If the tile is a toggle (as determined by its accessibility class name) we add ToggleableState (and the corresponding role). * For large tiles, we use the text in the label, as well as the state description. If the secondary label content is contained in the state description, we ignore it for a11y as it will be read (stateDescription has precedence because it will be read on state changes). * For single target large tiles, their role and ToggleableState is the same as for small tiles. * For dual target large tiles, they should always be a Button, with the dual target being a Toggle. The content description and state description is also applied to the toggle button. * Long press actions have the correct description label. Test: atest PlatformScenarioTests Test: atest TileUiStateTest Bug: 359523013 Flag: com.android.systemui.qs_ui_refactor_compose_fragment Change-Id: I178873fb813e4986e88c71f19be319d7fbb7edd8 --- .../qs/panels/ui/viewmodel/TileUiStateTest.kt | 270 ++++++++++++++++++ .../qs/composefragment/QSFragmentCompose.kt | 25 +- .../panels/ui/compose/QuickQuickSettings.kt | 20 +- .../systemui/qs/panels/ui/compose/Tile.kt | 158 +++++++--- .../qs/panels/ui/viewmodel/TileUiState.kt | 84 +++++- .../systemui/qs/tiles/BluetoothTile.java | 10 +- .../systemui/qs/tiles/FontScalingTile.kt | 9 +- 7 files changed, 495 insertions(+), 81 deletions(-) create mode 100644 packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt new file mode 100644 index 000000000000..b144f0678471 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiStateTest.kt @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.qs.panels.ui.viewmodel + +import android.content.res.Resources +import android.content.res.mainResources +import android.service.quicksettings.Tile +import android.widget.Button +import android.widget.Switch +import androidx.compose.ui.semantics.Role +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.res.R +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class TileUiStateTest : SysuiTestCase() { + + private val kosmos = testKosmos() + private val resources: Resources + get() = kosmos.mainResources + + @Test + fun stateUnavailable_secondaryLabelNotmodified() { + val testString = "TEST STRING" + val state = + QSTile.State().apply { + state = Tile.STATE_UNAVAILABLE + secondaryLabel = testString + } + + val uiState = state.toUiState() + + assertThat(uiState.state).isEqualTo(Tile.STATE_UNAVAILABLE) + } + + @Test + fun accessibilityRole_switch() { + val stateSwitch = + QSTile.State().apply { expandedAccessibilityClassName = Switch::class.java.name } + val uiState = stateSwitch.toUiState() + assertThat(uiState.accessibilityRole).isEqualTo(Role.Switch) + } + + @Test + fun accessibilityRole_button() { + val stateButton = + QSTile.State().apply { expandedAccessibilityClassName = Button::class.java.name } + val uiState = stateButton.toUiState() + assertThat(uiState.accessibilityRole).isEqualTo(Role.Button) + } + + @Test + fun accessibilityRole_switchWithSecondaryClick() { + val stateSwitchWithSecondaryClick = + QSTile.State().apply { + expandedAccessibilityClassName = Switch::class.java.name + handlesSecondaryClick = true + } + val uiState = stateSwitchWithSecondaryClick.toUiState() + assertThat(uiState.accessibilityRole).isEqualTo(Role.Button) + } + + @Test + fun switchInactive_secondaryLabelNotModified() { + val testString = "TEST STRING" + val state = + QSTile.State().apply { + expandedAccessibilityClassName = Switch::class.java.name + state = Tile.STATE_INACTIVE + secondaryLabel = testString + } + + val uiState = state.toUiState() + + assertThat(uiState.secondaryLabel).isEqualTo(testString) + } + + @Test + fun switchActive_secondaryLabelNotModified() { + val testString = "TEST STRING" + val state = + QSTile.State().apply { + expandedAccessibilityClassName = Switch::class.java.name + state = Tile.STATE_ACTIVE + secondaryLabel = testString + } + + val uiState = state.toUiState() + + assertThat(uiState.secondaryLabel).isEqualTo(testString) + } + + @Test + fun buttonInactive_secondaryLabelNotModifiedWhenEmpty() { + val state = + QSTile.State().apply { + expandedAccessibilityClassName = Button::class.java.name + state = Tile.STATE_INACTIVE + secondaryLabel = "" + } + + val uiState = state.toUiState() + + assertThat(uiState.secondaryLabel).isEmpty() + } + + @Test + fun buttonActive_secondaryLabelNotModifiedWhenEmpty() { + val state = + QSTile.State().apply { + expandedAccessibilityClassName = Button::class.java.name + state = Tile.STATE_ACTIVE + secondaryLabel = "" + } + + val uiState = state.toUiState() + + assertThat(uiState.secondaryLabel).isEmpty() + } + + @Test + fun buttonUnavailable_emptySecondaryLabel_default() { + val state = + QSTile.State().apply { + expandedAccessibilityClassName = Button::class.java.name + state = Tile.STATE_UNAVAILABLE + secondaryLabel = "" + } + + val uiState = state.toUiState() + + assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.tile_unavailable)) + } + + @Test + fun switchUnavailable_emptySecondaryLabel_defaultUnavailable() { + val state = + QSTile.State().apply { + expandedAccessibilityClassName = Switch::class.java.name + state = Tile.STATE_UNAVAILABLE + secondaryLabel = "" + } + + val uiState = state.toUiState() + + assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.tile_unavailable)) + } + + @Test + fun switchInactive_emptySecondaryLabel_defaultOff() { + val state = + QSTile.State().apply { + expandedAccessibilityClassName = Switch::class.java.name + state = Tile.STATE_INACTIVE + secondaryLabel = "" + } + + val uiState = state.toUiState() + + assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.switch_bar_off)) + } + + @Test + fun switchActive_emptySecondaryLabel_defaultOn() { + val state = + QSTile.State().apply { + expandedAccessibilityClassName = Switch::class.java.name + state = Tile.STATE_ACTIVE + secondaryLabel = "" + } + + val uiState = state.toUiState() + + assertThat(uiState.secondaryLabel).isEqualTo(resources.getString(R.string.switch_bar_on)) + } + + @Test + fun disabledByPolicy_inactive_appearsAsUnavailable() { + val stateDisabledByPolicy = + QSTile.State().apply { + state = Tile.STATE_INACTIVE + disabledByPolicy = true + } + + val uiState = stateDisabledByPolicy.toUiState() + + assertThat(uiState.state).isEqualTo(Tile.STATE_UNAVAILABLE) + } + + @Test + fun disabledByPolicy_active_appearsAsUnavailable() { + val stateDisabledByPolicy = + QSTile.State().apply { + state = Tile.STATE_ACTIVE + disabledByPolicy = true + } + + val uiState = stateDisabledByPolicy.toUiState() + + assertThat(uiState.state).isEqualTo(Tile.STATE_UNAVAILABLE) + } + + @Test + fun disabledByPolicy_clickLabel() { + val stateDisabledByPolicy = + QSTile.State().apply { + state = Tile.STATE_INACTIVE + disabledByPolicy = true + } + + val uiState = stateDisabledByPolicy.toUiState() + assertThat(uiState.accessibilityUiState.clickLabel) + .isEqualTo( + resources.getString( + R.string.accessibility_tile_disabled_by_policy_action_description + ) + ) + } + + @Test + fun notDisabledByPolicy_clickLabel_null() { + val stateDisabledByPolicy = + QSTile.State().apply { + state = Tile.STATE_INACTIVE + disabledByPolicy = false + } + + val uiState = stateDisabledByPolicy.toUiState() + assertThat(uiState.accessibilityUiState.clickLabel).isNull() + } + + @Test + fun disabledByPolicy_unavailableInStateDescription() { + val state = + QSTile.State().apply { + disabledByPolicy = true + state = Tile.STATE_INACTIVE + } + + val uiState = state.toUiState() + assertThat(uiState.accessibilityUiState.stateDescription) + .contains(resources.getString(R.string.tile_unavailable)) + } + + private fun QSTile.State.toUiState() = toUiState(resources) +} + +private val TileUiState.accessibilityRole: Role + get() = accessibilityUiState.accessibilityRole diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt index c2f1c3dcd426..af167d4f6918 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -158,7 +158,7 @@ constructor( override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? + savedInstanceState: Bundle?, ): View { val context = inflater.context return ComposeView(context).apply { @@ -181,7 +181,7 @@ constructor( notificationScrimClippingParams.bottom, notificationScrimClippingParams.radius, ) - } + }, ) { AnimatedContent(targetState = qsState) { when (it) { @@ -272,7 +272,7 @@ constructor( qsExpansionFraction: Float, panelExpansionFraction: Float, headerTranslation: Float, - squishinessFraction: Float + squishinessFraction: Float, ) { viewModel.qsExpansionValue = qsExpansionFraction viewModel.panelExpansionFractionValue = panelExpansionFraction @@ -318,12 +318,12 @@ constructor( override fun setTransitionToFullShadeProgress( isTransitioningToFullShade: Boolean, qsTransitionFraction: Float, - qsSquishinessFraction: Float + qsSquishinessFraction: Float, ) { super.setTransitionToFullShadeProgress( isTransitioningToFullShade, qsTransitionFraction, - qsSquishinessFraction + qsSquishinessFraction, ) } @@ -334,7 +334,7 @@ constructor( bottom: Int, cornerRadius: Int, visible: Boolean, - fullWidth: Boolean + fullWidth: Boolean, ) { notificationScrimClippingParams.isEnabled = visible notificationScrimClippingParams.top = top @@ -402,7 +402,7 @@ constructor( launch { setListenerJob( heightListener, - viewModel.containerViewModel.editModeViewModel.isEditing + viewModel.containerViewModel.editModeViewModel.isEditing, ) { onQsHeightChanged() } @@ -410,7 +410,7 @@ constructor( launch { setListenerJob( qsContainerController, - viewModel.containerViewModel.editModeViewModel.isEditing + viewModel.containerViewModel.editModeViewModel.isEditing, ) { setCustomizerShowing(it) } @@ -422,6 +422,7 @@ constructor( @Composable private fun QuickQuickSettingsElement() { val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle() + val bottomPadding = dimensionResource(id = R.dimen.qqs_layout_padding_bottom) DisposableEffect(Unit) { qqsVisible.value = true @@ -441,7 +442,7 @@ constructor( ) } .onSizeChanged { size -> qqsHeight.value = size.height } - .padding(top = { qqsPadding }) + .padding(top = { qqsPadding }, bottom = { bottomPadding.roundToPx() }) ) { val qsEnabled by viewModel.qsEnabled.collectAsStateWithLifecycle() if (qsEnabled) { @@ -450,7 +451,7 @@ constructor( modifier = Modifier.collapseExpandSemanticAction( stringResource(id = R.string.accessibility_quick_settings_expand) - ) + ), ) } } @@ -482,7 +483,7 @@ constructor( FooterActions( viewModel = viewModel.footerActionsViewModel, qsVisibilityLifecycleOwner = this@QSFragmentCompose, - modifier = Modifier.sysuiResTag("qs_footer_actions") + modifier = Modifier.sysuiResTag("qs_footer_actions"), ) } } @@ -562,7 +563,7 @@ private fun View.setBackPressedDispatcher() { private suspend inline fun setListenerJob( listenerFlow: MutableStateFlow, dataFlow: Flow, - crossinline onCollect: suspend Listener.(Data) -> Unit + crossinline onCollect: suspend Listener.(Data) -> Unit, ) { coroutineScope { try { diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt index eeb55ca19bc3..fde40da2e5bc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt @@ -22,18 +22,16 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.util.fastMap import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel @Composable -fun QuickQuickSettings( - viewModel: QuickQuickSettingsViewModel, - modifier: Modifier = Modifier, -) { +fun QuickQuickSettings(viewModel: QuickQuickSettingsViewModel, modifier: Modifier = Modifier) { val sizedTiles by viewModel.tileViewModels.collectAsStateWithLifecycle(initialValue = emptyList()) - val tiles = sizedTiles.map { it.tile } + val tiles = sizedTiles.fastMap { it.tile } DisposableEffect(tiles) { val token = Any() @@ -44,14 +42,18 @@ fun QuickQuickSettings( TileLazyGrid( modifier = modifier.sysuiResTag("qqs_tile_layout"), - columns = GridCells.Fixed(columns) + columns = GridCells.Fixed(columns), ) { items( - tiles.size, + sizedTiles.size, key = { index -> sizedTiles[index].tile.spec.spec }, - span = { index -> GridItemSpan(sizedTiles[index].width) } + span = { index -> GridItemSpan(sizedTiles[index].width) }, ) { index -> - Tile(tile = tiles[index], iconOnly = sizedTiles[index].isIcon, modifier = Modifier) + Tile( + tile = sizedTiles[index].tile, + iconOnly = sizedTiles[index].isIcon, + modifier = Modifier, + ) } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt index 93037d1d2572..afd47a7f7758 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt @@ -18,6 +18,7 @@ package com.android.systemui.qs.panels.ui.compose +import android.content.res.Resources import android.graphics.drawable.Animatable import android.service.quicksettings.Tile.STATE_ACTIVE import android.service.quicksettings.Tile.STATE_INACTIVE @@ -71,6 +72,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -85,13 +87,19 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.Shape import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.positionInRoot +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.clearAndSetSemantics +import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.onClick +import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription +import androidx.compose.ui.semantics.toggleableState import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp @@ -106,12 +114,15 @@ import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.ui.compose.Icon import com.android.systemui.common.ui.compose.load +import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.plugins.qs.QSTile import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.shared.model.SizedTileImpl +import com.android.systemui.qs.panels.ui.compose.TileDefaults.longPressLabel import com.android.systemui.qs.panels.ui.model.GridCell import com.android.systemui.qs.panels.ui.model.SpacerGridCell import com.android.systemui.qs.panels.ui.model.TileGridCell +import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.TileUiState import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel @@ -126,15 +137,15 @@ import kotlinx.coroutines.delay object TileType +private const val TEST_TAG_SMALL = "qs_tile_small" +private const val TEST_TAG_LARGE = "qs_tile_large" +private const val TEST_TAG_TOGGLE = "qs_tile_toggle_target" + @Composable -fun Tile( - tile: TileViewModel, - iconOnly: Boolean, - showLabels: Boolean = false, - modifier: Modifier, -) { +fun Tile(tile: TileViewModel, iconOnly: Boolean, showLabels: Boolean = false, modifier: Modifier) { val state by tile.state.collectAsStateWithLifecycle(tile.currentState) - val uiState = remember(state) { state.toUiState() } + val resources = resources() + val uiState = remember(state, resources) { state.toUiState(resources) } val colors = TileDefaults.getColorForState(uiState) // TODO(b/361789146): Draw the shapes instead of clipping @@ -150,6 +161,7 @@ fun Tile( onClick = tile::onClick, onLongClick = tile::onLongClick, modifier = modifier.height(tileHeight()), + uiState = uiState, ) { val icon = getTileIcon(icon = uiState.icon) if (iconOnly) { @@ -169,6 +181,7 @@ fun Tile( } }, onLongClick = { tile.onLongClick(it) }, + accessibilityUiState = uiState.accessibilityUiState, ) } } @@ -185,6 +198,7 @@ private fun TileContainer( onClick: (Expandable) -> Unit = {}, onLongClick: (Expandable) -> Unit = {}, modifier: Modifier = Modifier, + uiState: TileUiState? = null, content: @Composable BoxScope.(Expandable) -> Unit, ) { Column( @@ -194,7 +208,7 @@ private fun TileContainer( modifier = modifier, ) { val backgroundColor = - if (iconOnly) { + if (iconOnly || uiState?.handlesSecondaryClick != true) { colors.iconBackground } else { colors.background @@ -202,18 +216,43 @@ private fun TileContainer( Expandable( color = backgroundColor, shape = shape, - modifier = Modifier.height(tileHeight()).clip(shape) + modifier = Modifier.height(tileHeight()).clip(shape), ) { + val longPressLabel = longPressLabel() Box( modifier = Modifier.fillMaxSize() .thenIf(clickEnabled) { Modifier.combinedClickable( onClick = { onClick(it) }, - onLongClick = { onLongClick(it) } + onLongClick = { onLongClick(it) }, + onClickLabel = uiState?.accessibilityUiState?.clickLabel, + onLongClickLabel = longPressLabel, ) } - .tilePadding(), + .thenIf(uiState != null) { + uiState as TileUiState + Modifier.semantics { + role = uiState.accessibilityUiState.accessibilityRole + if ( + uiState.accessibilityUiState.accessibilityRole == + Role.Switch + ) { + uiState.accessibilityUiState.toggleableState?.let { + toggleableState = it + } + } + stateDescription = uiState.accessibilityUiState.stateDescription + } + .sysuiResTag(if (iconOnly) TEST_TAG_SMALL else TEST_TAG_LARGE) + .thenIf(iconOnly) { + Modifier.semantics { + contentDescription = + uiState.accessibilityUiState.contentDescription + } + } + } + .tilePadding() ) { content(it) } @@ -238,21 +277,39 @@ private fun LargeTileContent( icon: Icon, colors: TileColors, iconShape: Shape, + accessibilityUiState: AccessibilityUiState? = null, toggleClickSupported: Boolean = false, onClick: () -> Unit = {}, onLongClick: () -> Unit = {}, ) { Row( verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = tileHorizontalArrangement() + horizontalArrangement = tileHorizontalArrangement(), ) { // Icon + val longPressLabel = longPressLabel() Box( modifier = Modifier.size(TileDefaults.ToggleTargetSize).thenIf(toggleClickSupported) { Modifier.clip(iconShape) .background(colors.iconBackground, { 1f }) - .combinedClickable(onClick = onClick, onLongClick = onLongClick) + .combinedClickable( + onClick = onClick, + onLongClick = onLongClick, + onLongClickLabel = longPressLabel, + ) + .thenIf(accessibilityUiState != null) { + accessibilityUiState as AccessibilityUiState + Modifier.semantics { + contentDescription = accessibilityUiState.contentDescription + stateDescription = accessibilityUiState.stateDescription + accessibilityUiState.toggleableState?.let { + toggleableState = it + } + role = Role.Switch + } + .sysuiResTag(TEST_TAG_TOGGLE) + } } ) { TileIcon(icon = icon, color = colors.icon, modifier = Modifier.align(Alignment.Center)) @@ -260,16 +317,19 @@ private fun LargeTileContent( // Labels Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) { - Text( - label, - color = colors.label, - modifier = Modifier.tileMarquee(), - ) + Text(label, color = colors.label, modifier = Modifier.tileMarquee()) if (!TextUtils.isEmpty(secondaryLabel)) { Text( secondaryLabel ?: "", color = colors.secondaryLabel, - modifier = Modifier.tileMarquee(), + modifier = + Modifier.tileMarquee().thenIf( + accessibilityUiState + ?.stateDescription + ?.contains(secondaryLabel ?: "") == true + ) { + Modifier.clearAndSetSemantics {} + }, ) } } @@ -277,10 +337,7 @@ private fun LargeTileContent( } private fun Modifier.tileMarquee(): Modifier { - return basicMarquee( - iterations = 1, - initialDelayMillis = 200, - ) + return basicMarquee(iterations = 1, initialDelayMillis = 200) } @Composable @@ -320,11 +377,11 @@ fun DefaultEditTileGrid( Column( verticalArrangement = spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)), - modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState()) + modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState()), ) { AnimatedContent( targetState = currentListState.dragInProgress, - modifier = Modifier.wrapContentSize() + modifier = Modifier.wrapContentSize(), ) { dragIsInProgress -> EditGridHeader(Modifier.dragAndDropRemoveZone(currentListState, onRemoveTile)) { if (dragIsInProgress) { @@ -348,12 +405,12 @@ fun DefaultEditTileGrid( AnimatedVisibility( visible = !currentListState.dragInProgress, enter = fadeIn(), - exit = fadeOut() + exit = fadeOut(), ) { Column( verticalArrangement = spacedBy(dimensionResource(id = R.dimen.qs_label_container_margin)), - modifier = modifier.fillMaxSize() + modifier = modifier.fillMaxSize(), ) { EditGridHeader { Text(text = "Hold and drag to add tiles.") } @@ -381,14 +438,14 @@ fun DefaultEditTileGrid( @Composable private fun EditGridHeader( modifier: Modifier = Modifier, - content: @Composable BoxScope.() -> Unit + content: @Composable BoxScope.() -> Unit, ) { CompositionLocalProvider( LocalContentColor provides MaterialTheme.colorScheme.onBackground.copy(alpha = .5f) ) { Box( contentAlignment = Alignment.Center, - modifier = modifier.fillMaxWidth().height(EditModeTileDefaults.EditGridHeaderHeight) + modifier = modifier.fillMaxWidth().height(EditModeTileDefaults.EditGridHeaderHeight), ) { content() } @@ -403,7 +460,7 @@ private fun RemoveTileTarget() { modifier = Modifier.fillMaxHeight() .border(1.dp, LocalContentColor.current, shape = CircleShape) - .padding(10.dp) + .padding(10.dp), ) { Icon(imageVector = Icons.Default.Clear, contentDescription = null) Text(text = "Remove") @@ -454,7 +511,7 @@ private fun CurrentTilesGrid( gridContentOffset = coordinates.positionInRoot() } .testTag(CURRENT_TILES_GRID_TEST_TAG), - columns = GridCells.Fixed(columns) + columns = GridCells.Fixed(columns), ) { editTiles( listState.tiles, @@ -488,7 +545,7 @@ private fun AvailableTileGrid( // Available tiles TileLazyGrid( modifier = Modifier.height(availableGridHeight).testTag(AVAILABLE_TILES_GRID_TEST_TAG), - columns = GridCells.Fixed(columns) + columns = GridCells.Fixed(columns), ) { groupedTiles.forEach { category, tiles -> stickyHeader { @@ -498,7 +555,7 @@ private fun AvailableTileGrid( color = labelColors.label, modifier = Modifier.background(Color.Black) - .padding(start = 16.dp, bottom = 8.dp, top = 8.dp) + .padding(start = 16.dp, bottom = 8.dp, top = 8.dp), ) } editTiles( @@ -542,7 +599,7 @@ fun LazyGridScope.editTiles( count = cells.size, key = { cells[it].key(it, dragAndDropState) }, span = { cells[it].span }, - contentType = { TileType } + contentType = { TileType }, ) { index -> when (val cell = cells[index]) { is TileGridCell -> @@ -552,7 +609,7 @@ fun LazyGridScope.editTiles( Modifier.background( color = MaterialTheme.colorScheme.secondary, alpha = { EditModeTileDefaults.PLACEHOLDER_ALPHA }, - shape = RoundedCornerShape(TileDefaults.InactiveCornerRadius) + shape = RoundedCornerShape(TileDefaults.InactiveCornerRadius), ) .animateItem() ) @@ -565,7 +622,7 @@ fun LazyGridScope.editTiles( onClick = onClick, onResize = onResize, showLabels = showLabels, - indicatePosition = indicatePosition + indicatePosition = indicatePosition, ) } is SpacerGridCell -> SpacerGridCell() @@ -604,7 +661,7 @@ private fun LazyGridItemScope.TileGridCell( modifier = Modifier.height(tileHeight) .animateItem() - .semantics { + .semantics(mergeDescendants = true) { onClick(onClickActionName) { false } this.stateDescription = stateDescription } @@ -613,7 +670,7 @@ private fun LazyGridItemScope.TileGridCell( onClick, onResize, dragAndDropState, - ) + ), ) } @@ -645,7 +702,7 @@ fun EditTile( TileIcon( icon = tileViewModel.icon, color = colors.icon, - modifier = Modifier.align(Alignment.Center) + modifier = Modifier.align(Alignment.Center), ) } else { LargeTileContent( @@ -694,11 +751,7 @@ private fun TileIcon( } } if (loadedDrawable !is Animatable) { - Icon( - icon = icon, - tint = color, - modifier = iconModifier, - ) + Icon(icon = icon, tint = color, modifier = iconModifier) } else if (icon is Icon.Resource) { val image = AnimatedImageVector.animatedVectorResource(id = icon.res) val painter = @@ -716,7 +769,7 @@ private fun TileIcon( painter = painter, contentDescription = icon.contentDescription?.load(), colorFilter = ColorFilter.tint(color = color), - modifier = iconModifier + modifier = iconModifier, ) } } @@ -765,6 +818,8 @@ private object TileDefaults { val TileHeight = 72.dp val IconTileWithLabelHeight = 140.dp + @Composable fun longPressLabel() = stringResource(id = R.string.accessibility_long_click_tile) + /** An active tile without dual target uses the active color as background */ @Composable fun activeTileColors(): TileColors = @@ -850,7 +905,7 @@ private object TileDefaults { } else { InactiveCornerRadius }, - label = label + label = label, ) return RoundedCornerShape(animatedCornerRadius) } @@ -858,3 +913,14 @@ private object TileDefaults { private const val CURRENT_TILES_GRID_TEST_TAG = "CurrentTilesGrid" private const val AVAILABLE_TILES_GRID_TEST_TAG = "AvailableTilesGrid" + +/** + * A composable function that returns the [Resources]. It will be recomposed when [Configuration] + * gets updated. + */ +@Composable +@ReadOnlyComposable +private fun resources(): Resources { + LocalConfiguration.current + return LocalContext.current.resources +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt index 45051fea76b6..aa420800be7b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt @@ -16,8 +16,16 @@ package com.android.systemui.qs.panels.ui.viewmodel +import android.content.res.Resources +import android.service.quicksettings.Tile +import android.text.TextUtils +import android.widget.Switch import androidx.compose.runtime.Immutable +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.state.ToggleableState import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.tileimpl.SubtitleArrayMapping +import com.android.systemui.res.R import java.util.function.Supplier @Immutable @@ -27,14 +35,78 @@ data class TileUiState( val state: Int, val handlesSecondaryClick: Boolean, val icon: Supplier, + val accessibilityUiState: AccessibilityUiState, ) -fun QSTile.State.toUiState(): TileUiState { +data class AccessibilityUiState( + val contentDescription: String, + val stateDescription: String, + val accessibilityRole: Role, + val toggleableState: ToggleableState? = null, + val clickLabel: String? = null, +) + +fun QSTile.State.toUiState(resources: Resources): TileUiState { + val accessibilityRole = + if (expandedAccessibilityClassName == Switch::class.java.name && !handlesSecondaryClick) { + Role.Switch + } else { + Role.Button + } + // State handling and description + val stateDescription = StringBuilder() + val stateText = + if (accessibilityRole == Role.Switch || state == Tile.STATE_UNAVAILABLE) { + getStateText(resources) + } else { + "" + } + val secondaryLabel = getSecondaryLabel(stateText) + if (!TextUtils.isEmpty(stateText)) { + stateDescription.append(stateText) + } + if (disabledByPolicy && state != Tile.STATE_UNAVAILABLE) { + stateDescription.append(", ") + stateDescription.append(getUnavailableText(spec, resources)) + } + if ( + !TextUtils.isEmpty(this.stateDescription) && + !stateDescription.contains(this.stateDescription!!) + ) { + stateDescription.append(", ") + stateDescription.append(this.stateDescription) + } + val toggleableState = + if (accessibilityRole == Role.Switch || handlesSecondaryClick) { + ToggleableState(state == Tile.STATE_ACTIVE) + } else { + null + } return TileUiState( - label?.toString() ?: "", - secondaryLabel?.toString() ?: "", - state, - handlesSecondaryClick, - icon?.let { Supplier { icon } } ?: iconSupplier ?: Supplier { null }, + label = label?.toString() ?: "", + secondaryLabel = secondaryLabel?.toString() ?: "", + state = if (disabledByPolicy) Tile.STATE_UNAVAILABLE else state, + handlesSecondaryClick = handlesSecondaryClick, + icon = icon?.let { Supplier { icon } } ?: iconSupplier ?: Supplier { null }, + AccessibilityUiState( + contentDescription?.toString() ?: "", + stateDescription.toString(), + accessibilityRole, + toggleableState, + resources + .getString(R.string.accessibility_tile_disabled_by_policy_action_description) + .takeIf { disabledByPolicy }, + ), ) } + +private fun QSTile.State.getStateText(resources: Resources): CharSequence { + val arrayResId = SubtitleArrayMapping.getSubtitleId(spec) + val array = resources.getStringArray(arrayResId) + return array[state] +} + +private fun getUnavailableText(spec: String?, resources: Resources): String { + val arrayResId = SubtitleArrayMapping.getSubtitleId(spec) + return resources.getStringArray(arrayResId)[Tile.STATE_UNAVAILABLE] +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 7ceb78638f6c..7bff827dee03 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -32,7 +32,7 @@ import android.provider.Settings; import android.service.quicksettings.Tile; import android.text.TextUtils; import android.util.Log; -import android.widget.Switch; +import android.widget.Button; import androidx.annotation.VisibleForTesting; @@ -59,13 +59,13 @@ import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.res.R; import com.android.systemui.statusbar.policy.BluetoothController; -import kotlinx.coroutines.Job; - import java.util.List; import java.util.concurrent.Executor; import javax.inject.Inject; +import kotlinx.coroutines.Job; + /** Quick settings tile: Bluetooth **/ public class BluetoothTile extends QSTileImpl { @@ -147,6 +147,8 @@ public class BluetoothTile extends QSTileImpl { } } + + @Override public Intent getLongClickIntent() { return new Intent(Settings.ACTION_BLUETOOTH_SETTINGS); @@ -221,7 +223,7 @@ public class BluetoothTile extends QSTileImpl { state.state = Tile.STATE_INACTIVE; } - state.expandedAccessibilityClassName = Switch.class.getName(); + state.expandedAccessibilityClassName = Button.class.getName(); state.forceExpandIcon = mFeatureFlags.isEnabled(Flags.BLUETOOTH_QS_TILE_DIALOG); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt index 078698c2872d..7606293454f8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt @@ -55,7 +55,7 @@ constructor( qsLogger: QSLogger, private val keyguardStateController: KeyguardStateController, private val dialogTransitionAnimator: DialogTransitionAnimator, - private val fontScalingDialogDelegateProvider: Provider + private val fontScalingDialogDelegateProvider: Provider, ) : QSTileImpl( host, @@ -66,7 +66,7 @@ constructor( metricsLogger, statusBarStateController, activityStarter, - qsLogger + qsLogger, ) { private val icon = ResourceIcon.get(R.drawable.ic_qs_font_scaling) @@ -86,7 +86,7 @@ constructor( expandable?.dialogTransitionController( DialogCuj( InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, - INTERACTION_JANK_TAG + INTERACTION_JANK_TAG, ) ) controller?.let { dialogTransitionAnimator.show(dialog, controller) } @@ -102,7 +102,7 @@ constructor( /* cancelAction= */ null, /* dismissShade= */ true, /* afterKeyguardGone= */ true, - /* deferred= */ false + /* deferred= */ false, ) } } @@ -110,6 +110,7 @@ constructor( override fun handleUpdateState(state: QSTile.State?, arg: Any?) { state?.label = mContext.getString(R.string.quick_settings_font_scaling_label) state?.icon = icon + state?.contentDescription = state?.label } override fun getLongClickIntent(): Intent? { -- GitLab From 7730064a8f8dbe2fe6d7b7ab83759858357ed6b1 Mon Sep 17 00:00:00 2001 From: Toshiki Kikuchi Date: Thu, 5 Sep 2024 17:36:08 +0900 Subject: [PATCH 018/388] Add enter_desktop_by_default_on_freeform_displays flag Flag: EXEMPT adding flag Bug: 361419732 Test: m Change-Id: Id48c23aa8570449839fcfa6fbc758b4e5b5c76cc --- .../android/window/flags/lse_desktop_experience.aconfig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index 8e81951ba4ca..c62b7c471def 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -258,3 +258,10 @@ flag { description: "Creates a shell transition when display focus switches." bug: "356109871" } + +flag { + name: "enter_desktop_by_default_on_freeform_displays" + namespace: "lse_desktop_experience" + description: "Allow entering desktop mode by default on freeform displays" + bug: "361419732" +} -- GitLab From e5632bcbbaaa37be76f831e96cafdfe8c7f49484 Mon Sep 17 00:00:00 2001 From: "heqing.liu" Date: Wed, 11 Sep 2024 15:30:41 +0800 Subject: [PATCH 019/388] SystemUIBottomSheetDialog call dismiss() when hide it SystemUIBottomSheetDialog is missing a call to dismiss(), that cause OOM bug: 365902595 Change-Id: Id0c9019d30017357e3c9146bb83e9726b7c18f17 --- .../ui/viewmodel/ConnectingDisplayViewModel.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt index 81ea2e74d799..62720a5b1377 100644 --- a/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/display/ui/viewmodel/ConnectingDisplayViewModel.kt @@ -79,7 +79,7 @@ constructor( .combine(concurrentDisplaysInProgessFlow) { pendingDisplay, concurrentDisplaysInProgress -> if (pendingDisplay == null) { - hideDialog() + dismissDialog() } else { showDialog(pendingDisplay, concurrentDisplaysInProgress) } @@ -88,17 +88,17 @@ constructor( } private fun showDialog(pendingDisplay: PendingDisplay, concurrentDisplaysInProgess: Boolean) { - hideDialog() + dismissDialog() dialog = bottomSheetFactory .createDialog( onStartMirroringClickListener = { scope.launch(bgDispatcher) { pendingDisplay.enable() } - hideDialog() + dismissDialog() }, onCancelMirroring = { scope.launch(bgDispatcher) { pendingDisplay.ignore() } - hideDialog() + dismissDialog() }, navbarBottomInsetsProvider = { Utils.getNavbarInsets(context).bottom }, showConcurrentDisplayInfo = concurrentDisplaysInProgess @@ -106,8 +106,8 @@ constructor( .apply { show() } } - private fun hideDialog() { - dialog?.hide() + private fun dismissDialog() { + dialog?.dismiss() dialog = null } -- GitLab From c1ac706069866d38ced126947719deb62ce45698 Mon Sep 17 00:00:00 2001 From: Jernej Virag Date: Thu, 12 Sep 2024 14:46:32 +0200 Subject: [PATCH 020/388] Avoid bouncing user unlocked broadcast on main thread We can directly call onReceive in bg thread without going through main thread first. Bug: 364610391 Flag: EXEMPT trivial bugfix Test: atest MediaResumeListenerTest Change-Id: Ia40600e710dd0b22320b8b31ada8c563b9db383f --- .../domain/resume/MediaResumeListener.kt | 41 ++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt index 9ee59d1875a6..ad84a5eb74ab 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt @@ -16,6 +16,7 @@ package com.android.systemui.media.controls.domain.resume +import android.annotation.WorkerThread import android.content.BroadcastReceiver import android.content.ComponentName import android.content.Context @@ -41,6 +42,7 @@ import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.settings.UserTracker import com.android.systemui.tuner.TunerService import com.android.systemui.util.Utils +import com.android.systemui.util.kotlin.logD import com.android.systemui.util.time.SystemClock import java.io.PrintWriter import java.util.concurrent.ConcurrentLinkedQueue @@ -86,11 +88,12 @@ constructor( @VisibleForTesting val userUnlockReceiver = object : BroadcastReceiver() { + @WorkerThread override fun onReceive(context: Context, intent: Intent) { if (Intent.ACTION_USER_UNLOCKED == intent.action) { val userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1) if (userId == currentUserId) { - backgroundExecutor.execute { loadMediaResumptionControls() } + loadMediaResumptionControls() } } } @@ -109,7 +112,7 @@ constructor( override fun addTrack( desc: MediaDescription, component: ComponentName, - browser: ResumeMediaBrowser + browser: ResumeMediaBrowser, ) { val token = browser.token val appIntent = browser.appIntent @@ -123,7 +126,7 @@ constructor( Log.e(TAG, "Error getting package information", e) } - Log.d(TAG, "Adding resume controls for ${browser.userId}: $desc") + logD(TAG) { "Adding resume controls for ${browser.userId}: $desc" } mediaDataManager.addResumptionControls( browser.userId, desc, @@ -131,7 +134,7 @@ constructor( token, appName.toString(), appIntent, - component.packageName + component.packageName, ) } } @@ -144,8 +147,8 @@ constructor( broadcastDispatcher.registerReceiver( userUnlockReceiver, unlockFilter, - null, - UserHandle.ALL + backgroundExecutor, + UserHandle.ALL, ) userTracker.addCallback(userTrackerCallback, mainExecutor) loadSavedComponents() @@ -163,7 +166,7 @@ constructor( mediaDataManager.setMediaResumptionEnabled(useMediaResumption) } }, - Settings.Secure.MEDIA_CONTROLS_RESUME + Settings.Secure.MEDIA_CONTROLS_RESUME, ) } @@ -197,11 +200,11 @@ constructor( } resumeComponents.add(component to lastPlayed) } - Log.d( - TAG, + + logD(TAG) { "loaded resume components for $currentUserId: " + - "${resumeComponents.toArray().contentToString()}" - ) + resumeComponents.toArray().contentToString() + } if (needsUpdate) { // Save any missing times that we had to fill in @@ -228,7 +231,7 @@ constructor( mediaBrowserFactory.create(mediaBrowserCallback, it.first, currentUserId) browser.findRecentMedia() } else { - Log.d(TAG, "User $currentUserId does not have component ${it.first}") + logD(TAG) { "User $currentUserId does not have component ${it.first}" } } } } @@ -240,7 +243,7 @@ constructor( data: MediaData, immediately: Boolean, receivedSmartspaceCardLatency: Int, - isSsReactivated: Boolean + isSsReactivated: Boolean, ) { if (useMediaResumption) { // If this had been started from a resume state, disconnect now that it's live @@ -281,7 +284,7 @@ constructor( mediaBrowserFactory.create( object : ResumeMediaBrowser.Callback() { override fun onConnected() { - Log.d(TAG, "Connected to $componentName") + logD(TAG) { "Connected to $componentName" } } override fun onError() { @@ -292,20 +295,20 @@ constructor( override fun addTrack( desc: MediaDescription, component: ComponentName, - browser: ResumeMediaBrowser + browser: ResumeMediaBrowser, ) { // Since this is a test, just save the component for later - Log.d( - TAG, + logD(TAG) { "Can get resumable media for ${browser.userId} from $componentName" - ) + } + mediaDataManager.setResumeAction(key, getResumeAction(componentName)) updateResumptionList(componentName) mediaBrowser = null } }, componentName, - currentUserId + currentUserId, ) mediaBrowser?.testConnection() } -- GitLab From 9ebcfd453e8e8bcd4328737253449721145b6fbc Mon Sep 17 00:00:00 2001 From: Liefu Liu Date: Thu, 12 Sep 2024 16:46:49 +0000 Subject: [PATCH 021/388] Remove DEFAULT_ACCOUNT_STATE_INVALID from ContactsContract.java. This state is not used anywhere and it is not possible to set it. Bug: 359957527,366015874,366205609 Fix: 366205609,366015874,366205478 Flag:android.provider.new_default_account_api_enabled Change-Id: Ic064cd3a603be96f1eef487e1d87fb3a74e4eeb7 --- core/api/current.txt | 1 - .../android/provider/ContactsContract.java | 19 +++++++++---------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index dada20eb14dc..e7ccc10cfb4c 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -36938,7 +36938,6 @@ package android.provider { method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccountAndState ofLocal(); method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccountAndState ofNotSet(); field public static final int DEFAULT_ACCOUNT_STATE_CLOUD = 3; // 0x3 - field public static final int DEFAULT_ACCOUNT_STATE_INVALID = 0; // 0x0 field public static final int DEFAULT_ACCOUNT_STATE_LOCAL = 2; // 0x2 field public static final int DEFAULT_ACCOUNT_STATE_NOT_SET = 1; // 0x1 } diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index f6eb4b52984d..01c230fe8f63 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -3027,8 +3027,6 @@ public final class ContactsContract { * specified {@link Account} will be saved in the default account. * The default account can have one of the following four states: *

    - *
  • {@link #DEFAULT_ACCOUNT_STATE_INVALID}: An invalid state that should not - * occur on the device.
  • *
  • {@link #DEFAULT_ACCOUNT_STATE_NOT_SET}: The default account has not * been set by the user.
  • *
  • {@link #DEFAULT_ACCOUNT_STATE_LOCAL}: The default account is set to @@ -3037,15 +3035,11 @@ public final class ContactsContract { * {@link Account} will be saved in a null or custom local account.
  • *
  • {@link #DEFAULT_ACCOUNT_STATE_CLOUD}: The default account is set to a * cloud-synced account. New raw contacts requested for insertion without a specified - * {@link Account} will be saved in {@link mCloudAccount}.
  • + * {@link Account} will be saved in the default cloud account. *
*/ @FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED) public static final class DefaultAccountAndState { - // The state of the default account. - /** A state that is invalid. */ - public static final int DEFAULT_ACCOUNT_STATE_INVALID = 0; - /** A state indicating that default account is not set. */ public static final int DEFAULT_ACCOUNT_STATE_NOT_SET = 1; @@ -3086,7 +3080,7 @@ public final class ContactsContract { */ public DefaultAccountAndState(@DefaultAccountState int state, @Nullable Account cloudAccount) { - if (state == DEFAULT_ACCOUNT_STATE_INVALID) { + if (!isValidDefaultAccountState(state)) { throw new IllegalArgumentException("Invalid default account state."); } if ((state == DEFAULT_ACCOUNT_STATE_CLOUD) != (cloudAccount != null)) { @@ -3170,6 +3164,12 @@ public final class ContactsContract { that.mCloudAccount); } + private static boolean isValidDefaultAccountState(int state) { + return state == DEFAULT_ACCOUNT_STATE_NOT_SET + || state == DEFAULT_ACCOUNT_STATE_LOCAL + || state == DEFAULT_ACCOUNT_STATE_CLOUD; + } + /** * Annotation for all default account states. * @@ -3178,8 +3178,7 @@ public final class ContactsContract { @Retention(RetentionPolicy.SOURCE) @IntDef( prefix = {"DEFAULT_ACCOUNT_STATE_"}, - value = {DEFAULT_ACCOUNT_STATE_INVALID, - DEFAULT_ACCOUNT_STATE_NOT_SET, + value = {DEFAULT_ACCOUNT_STATE_NOT_SET, DEFAULT_ACCOUNT_STATE_LOCAL, DEFAULT_ACCOUNT_STATE_CLOUD}) public @interface DefaultAccountState { } -- GitLab From f151e30a8b2796e9024cc62687e3c044defa8d82 Mon Sep 17 00:00:00 2001 From: Guojing Yuan Date: Thu, 12 Sep 2024 17:56:41 +0000 Subject: [PATCH 022/388] Mark IOnAssociationsChangedListener as oneway System process shouldn't be blocked by client execution. Fix: 363168094 Flag: EXEMPT bugfix Test: m build Change-Id: I653fb72eb4a2dd3e763ec29aa10426d43d420b8d --- .../IOnAssociationsChangedListener.aidl | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/core/java/android/companion/IOnAssociationsChangedListener.aidl b/core/java/android/companion/IOnAssociationsChangedListener.aidl index d3694564ab7b..eba3804cf5b3 100644 --- a/core/java/android/companion/IOnAssociationsChangedListener.aidl +++ b/core/java/android/companion/IOnAssociationsChangedListener.aidl @@ -19,23 +19,6 @@ package android.companion; import android.companion.AssociationInfo; /** @hide */ -interface IOnAssociationsChangedListener { - - /* - * IMPORTANT: This method is intentionally NOT "oneway". - * - * The method is intentionally "blocking" to make sure that the clients of the - * addOnAssociationsChangedListener() API (@SystemAPI guarded by a "signature" permission) are - * able to prevent race conditions that may arise if their own clients (applications) - * effectively get notified about the changes before system services do. - * - * This is safe for 2 reasons: - * 1. The addOnAssociationsChangedListener() is only available to the system components - * (guarded by a "signature" permission). - * See android.permission.MANAGE_COMPANION_DEVICES. - * 2. On the Java side addOnAssociationsChangedListener() in CDM takes an Executor, and the - * proxy implementation of onAssociationsChanged() simply "post" a Runnable to it. - * See CompanionDeviceManager.OnAssociationsChangedListenerProxy class. - */ +oneway interface IOnAssociationsChangedListener { void onAssociationsChanged(in List associations); } \ No newline at end of file -- GitLab From d7e90a617da28769ba42214737cc99d2fa5f983e Mon Sep 17 00:00:00 2001 From: Alec Mouri Date: Thu, 29 Aug 2024 19:21:35 +0000 Subject: [PATCH 023/388] Add support for ISO gainmap metadata Bug: 349357636 Flag: com.android.graphics.hwui.flags.iso_gainmap_apis Test: builds Change-Id: Id13791404168ac5b880c51f07f56c1c4287d5d31 --- core/api/current.txt | 6 ++ graphics/java/android/graphics/Gainmap.java | 93 +++++++++++++++++++-- libs/hwui/aconfig/hwui_flags.aconfig | 8 ++ libs/hwui/jni/Gainmap.cpp | 60 +++++++++++++ 4 files changed, 161 insertions(+), 6 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index baf142a0640c..e7089078a62f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -16128,24 +16128,30 @@ package android.graphics { ctor public Gainmap(@NonNull android.graphics.Bitmap); ctor @FlaggedApi("com.android.graphics.hwui.flags.gainmap_constructor_with_metadata") public Gainmap(@NonNull android.graphics.Gainmap, @NonNull android.graphics.Bitmap); method public int describeContents(); + method @FlaggedApi("com.android.graphics.hwui.flags.iso_gainmap_apis") @Nullable public android.graphics.ColorSpace getAlternativeImagePrimaries(); method @NonNull public float getDisplayRatioForFullHdr(); method @NonNull public float[] getEpsilonHdr(); method @NonNull public float[] getEpsilonSdr(); method @NonNull public android.graphics.Bitmap getGainmapContents(); + method @FlaggedApi("com.android.graphics.hwui.flags.iso_gainmap_apis") public int getGainmapDirection(); method @NonNull public float[] getGamma(); method @NonNull public float getMinDisplayRatioForHdrTransition(); method @NonNull public float[] getRatioMax(); method @NonNull public float[] getRatioMin(); + method @FlaggedApi("com.android.graphics.hwui.flags.iso_gainmap_apis") public void setAlternativeImagePrimaries(@Nullable android.graphics.ColorSpace); method public void setDisplayRatioForFullHdr(@FloatRange(from=1.0f) float); method public void setEpsilonHdr(float, float, float); method public void setEpsilonSdr(float, float, float); method public void setGainmapContents(@NonNull android.graphics.Bitmap); + method @FlaggedApi("com.android.graphics.hwui.flags.iso_gainmap_apis") public void setGainmapDirection(int); method public void setGamma(float, float, float); method public void setMinDisplayRatioForHdrTransition(@FloatRange(from=1.0f) float); method public void setRatioMax(float, float, float); method public void setRatioMin(float, float, float); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field @FlaggedApi("com.android.graphics.hwui.flags.iso_gainmap_apis") public static final int GAINMAP_DIRECTION_HDR_TO_SDR = 1; // 0x1 + field @FlaggedApi("com.android.graphics.hwui.flags.iso_gainmap_apis") public static final int GAINMAP_DIRECTION_SDR_TO_HDR = 0; // 0x0 } public class HardwareBufferRenderer implements java.lang.AutoCloseable { diff --git a/graphics/java/android/graphics/Gainmap.java b/graphics/java/android/graphics/Gainmap.java index 0a6fb8424094..63ca3b8313ce 100644 --- a/graphics/java/android/graphics/Gainmap.java +++ b/graphics/java/android/graphics/Gainmap.java @@ -18,7 +18,9 @@ package android.graphics; import android.annotation.FlaggedApi; import android.annotation.FloatRange; +import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -26,6 +28,9 @@ import com.android.graphics.hwui.flags.Flags; import libcore.util.NativeAllocationRegistry; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Gainmap represents a mechanism for augmenting an SDR image to produce an HDR one with variable * display adjustment capability. It is a combination of a set of metadata describing how to apply @@ -83,6 +88,27 @@ import libcore.util.NativeAllocationRegistry; */ public final class Gainmap implements Parcelable { + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"GAINMAP_DIRECTION_"}, + value = {GAINMAP_DIRECTION_SDR_TO_HDR, + GAINMAP_DIRECTION_HDR_TO_SDR}) + public @interface GainmapDirection {} + + /** + * The gainmap will be applied as if the base image were SDR, and fully applying the gainmap + * results in an HDR image. + */ + @FlaggedApi(Flags.FLAG_ISO_GAINMAP_APIS) + public static final int GAINMAP_DIRECTION_SDR_TO_HDR = 0; + + /** + * The gainmap will be applied as if the base image were HDR, and fully applying the gainmap + * results in an SDR image. + */ + @FlaggedApi(Flags.FLAG_ISO_GAINMAP_APIS) + public static final int GAINMAP_DIRECTION_HDR_TO_SDR = 1; + // Use a Holder to allow static initialization of Gainmap in the boot image. private static class NoImagePreloadHolder { public static final NativeAllocationRegistry sRegistry = @@ -252,8 +278,9 @@ public final class Gainmap implements Parcelable { } /** - * Sets the hdr/sdr ratio at which point the gainmap is fully applied. - * @param max The hdr/sdr ratio at which the gainmap is fully applied. Must be >= 1.0f + * Sets the hdr/sdr ratio at which point applying the gainmap results in an HDR rendition. + * @param max The hdr/sdr ratio at which point applying the gainmap results in an HDR rendition. + * Must be >= 1.0f */ public void setDisplayRatioForFullHdr(@FloatRange(from = 1.0f) float max) { if (!Float.isFinite(max) || max < 1f) { @@ -264,7 +291,7 @@ public final class Gainmap implements Parcelable { } /** - * Gets the hdr/sdr ratio at which point the gainmap is fully applied. + * Gets the hdr/sdr ratio at which point applying the gainmap results in an HDR rendition */ @NonNull public float getDisplayRatioForFullHdr() { @@ -272,8 +299,9 @@ public final class Gainmap implements Parcelable { } /** - * Sets the hdr/sdr ratio below which only the SDR image is displayed. - * @param min The minimum hdr/sdr ratio at which to begin applying the gainmap. Must be >= 1.0f + * Sets the hdr/sdr ratio below which applying the gainmap results in an SDR rendition. + * @param min The minimum hdr/sdr ratio at which point applying the gainmap results in an SDR + * rendition. Must be >= 1.0f */ public void setMinDisplayRatioForHdrTransition(@FloatRange(from = 1.0f) float min) { if (!Float.isFinite(min) || min < 1f) { @@ -284,13 +312,62 @@ public final class Gainmap implements Parcelable { } /** - * Gets the hdr/sdr ratio below which only the SDR image is displayed. + * Gets the hdr/sdr ratio below which applying the gainmap results in an SDR rendition. */ @NonNull public float getMinDisplayRatioForHdrTransition() { return nGetDisplayRatioSdr(mNativePtr); } + /** + * Sets the colorspace that the gainmap math should be applied in. + * Only the primaries are what is relevant for applying the gainmap. The transfer and range + * characteritics are ignored. + * + * If the supplied ColorSpace is null, then applying the gainmap will be done using the color + * gamut of the base image. + */ + @FlaggedApi(Flags.FLAG_ISO_GAINMAP_APIS) + public void setAlternativeImagePrimaries(@Nullable ColorSpace colorSpace) { + long colorSpaceInstance = colorSpace == null ? 0 : colorSpace.getNativeInstance(); + nSetAlternativeColorSpace(mNativePtr, colorSpaceInstance); + } + + /** + * Gets the colorspace that the gainmap math should be applied in. + * Only the primaries are what is relevant for applying the gainmap. The transfer and range + * characteritics are ignored. + * + * If the returned ColorSpace is null, then applying the gainmap will be done using the color + * gamut of the base image. + */ + @FlaggedApi(Flags.FLAG_ISO_GAINMAP_APIS) + @Nullable + public ColorSpace getAlternativeImagePrimaries() { + return nGetAlternativeColorSpace(mNativePtr); + } + + /** + * Sets the direction that the gainmap math should be applied in. + */ + @FlaggedApi(Flags.FLAG_ISO_GAINMAP_APIS) + public void setGainmapDirection(@GainmapDirection int direction) { + if (direction != GAINMAP_DIRECTION_SDR_TO_HDR + && direction != GAINMAP_DIRECTION_HDR_TO_SDR) { + throw new IllegalArgumentException("Invalid gainmap direction: " + direction); + } + nSetDirection(mNativePtr, direction); + } + + /** + * Gets the direction that the gainmap math should be applied in. + */ + @FlaggedApi(Flags.FLAG_ISO_GAINMAP_APIS) + public @GainmapDirection int getGainmapDirection() { + return nGetDirection(mNativePtr); + } + + /** * No special parcel contents. */ @@ -361,6 +438,10 @@ public final class Gainmap implements Parcelable { private static native void nSetDisplayRatioSdr(long ptr, float min); private static native float nGetDisplayRatioSdr(long ptr); + private static native void nSetAlternativeColorSpace(long ptr, long colorSpacePtr); + private static native ColorSpace nGetAlternativeColorSpace(long ptr); + private static native void nSetDirection(long ptr, int direction); + private static native int nGetDirection(long ptr); private static native void nWriteGainmapToParcel(long ptr, Parcel dest); private static native void nReadGainmapFromParcel(long ptr, Parcel src); } diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig index faea6d42b156..ab052b902e02 100644 --- a/libs/hwui/aconfig/hwui_flags.aconfig +++ b/libs/hwui/aconfig/hwui_flags.aconfig @@ -121,3 +121,11 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "iso_gainmap_apis" + is_exported: true + namespace: "core_graphics" + description: "APIs that expose gainmap metadata corresponding to those defined in ISO 21496-1" + bug: "349357636" +} diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp index 0fffee744be0..71972d01b94f 100644 --- a/libs/hwui/jni/Gainmap.cpp +++ b/libs/hwui/jni/Gainmap.cpp @@ -16,6 +16,9 @@ #include +#include "SkColorType.h" +#include "SkGainmapInfo.h" + #ifdef __ANDROID__ #include #endif @@ -36,6 +39,28 @@ static Gainmap* fromJava(jlong gainmap) { return reinterpret_cast(gainmap); } +static SkGainmapInfo::BaseImageType baseImageTypeFromJava(jint direction) { + switch (direction) { + case 0: + return SkGainmapInfo::BaseImageType::kSDR; + case 1: + return SkGainmapInfo::BaseImageType::kHDR; + default: + LOG_ALWAYS_FATAL("Unrecognized Gainmap direction: %d", direction); + } +} + +static jint baseImageTypeToJava(SkGainmapInfo::BaseImageType type) { + switch (type) { + case SkGainmapInfo::BaseImageType::kSDR: + return 0; + case SkGainmapInfo::BaseImageType::kHDR: + return 1; + default: + LOG_ALWAYS_FATAL("Unrecognized base image: %d", type); + } +} + static int getCreateFlags(const sk_sp& bitmap) { int flags = 0; if (bitmap->info().alphaType() == kPremul_SkAlphaType) { @@ -169,6 +194,36 @@ static jfloat Gainmap_getDisplayRatioSdr(JNIEnv*, jobject, jlong gainmapPtr) { return fromJava(gainmapPtr)->info.fDisplayRatioSdr; } +static void Gainmap_setAlternativeColorSpace(JNIEnv*, jobject, jlong gainmapPtr, + jlong colorSpacePtr) { + auto colorSpace = GraphicsJNI::getNativeColorSpace(colorSpacePtr); + fromJava(gainmapPtr)->info.fGainmapMathColorSpace = colorSpace; +} + +static jobject Gainmap_getAlternativeColorSpace(JNIEnv* env, jobject, jlong gainmapPtr) { + const auto javaGainmap = fromJava(gainmapPtr); + auto colorSpace = javaGainmap->info.fGainmapMathColorSpace.get(); + if (colorSpace == nullptr) { + return nullptr; + } + + auto colorType = javaGainmap->bitmap->colorType(); + // A8 bitmaps don't support colorspaces, but an alternative colorspace is + // still valid for configuring the gainmap math, so use RGBA8888 instead. + if (colorType == kAlpha_8_SkColorType) { + colorType = kRGBA_8888_SkColorType; + } + return GraphicsJNI::getColorSpace(env, colorSpace, colorType); +} + +static void Gainmap_setDirection(JNIEnv*, jobject, jlong gainmapPtr, jint direction) { + fromJava(gainmapPtr)->info.fBaseImageType = baseImageTypeFromJava(direction); +} + +static jint Gainmap_getDirection(JNIEnv* env, jobject, jlong gainmapPtr) { + return baseImageTypeToJava(fromJava(gainmapPtr)->info.fBaseImageType); +} + // ---------------------------------------------------------------------------- // Serialization // ---------------------------------------------------------------------------- @@ -260,6 +315,11 @@ static const JNINativeMethod gGainmapMethods[] = { {"nGetDisplayRatioHdr", "(J)F", (void*)Gainmap_getDisplayRatioHdr}, {"nSetDisplayRatioSdr", "(JF)V", (void*)Gainmap_setDisplayRatioSdr}, {"nGetDisplayRatioSdr", "(J)F", (void*)Gainmap_getDisplayRatioSdr}, + {"nSetAlternativeColorSpace", "(JJ)V", (void*)Gainmap_setAlternativeColorSpace}, + {"nGetAlternativeColorSpace", "(J)Landroid/graphics/ColorSpace;", + (void*)Gainmap_getAlternativeColorSpace}, + {"nSetDirection", "(JI)V", (void*)Gainmap_setDirection}, + {"nGetDirection", "(J)I", (void*)Gainmap_getDirection}, {"nWriteGainmapToParcel", "(JLandroid/os/Parcel;)V", (void*)Gainmap_writeToParcel}, {"nReadGainmapFromParcel", "(JLandroid/os/Parcel;)V", (void*)Gainmap_readFromParcel}, }; -- GitLab From b27f9146272d3b491fea7c15174d05044fd5bcfa Mon Sep 17 00:00:00 2001 From: Eric Miao Date: Fri, 10 May 2024 09:19:57 -0700 Subject: [PATCH 024/388] Track Bitmap native allocations Bug: 331243037 Flag: com.android.libcore.native_metrics This CL will make use of the new API to create NativeAllocationRegistry that associates with Bitmap.class if libcore.native_metrics is enabled (otherwise falls back to the legacy API). This allows tracking of native allocations that are Bitmap specific. Change-Id: I2d2875eeaf2b1ab6d1a0419977390f31206ed560 --- graphics/java/android/graphics/Bitmap.java | 29 ++++++++++++++-------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 6d31578ac020..e07471cd64bc 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -127,6 +127,22 @@ public final class Bitmap implements Parcelable { */ private static final WeakHashMap sAllBitmaps = new WeakHashMap<>(); + /** + * @hide + */ + private static NativeAllocationRegistry getRegistry(boolean malloc, long size) { + final long free = nativeGetNativeFinalizer(); + if (com.android.libcore.Flags.nativeMetrics()) { + Class cls = Bitmap.class; + return malloc ? NativeAllocationRegistry.createMalloced(cls, free, size) + : NativeAllocationRegistry.createNonmalloced(cls, free, size); + } else { + ClassLoader loader = Bitmap.class.getClassLoader(); + return malloc ? NativeAllocationRegistry.createMalloced(loader, free, size) + : NativeAllocationRegistry.createNonmalloced(loader, free, size); + } + } + /** * Private constructor that must receive an already allocated native bitmap * int (pointer). @@ -151,7 +167,6 @@ public final class Bitmap implements Parcelable { mWidth = width; mHeight = height; mRequestPremultiplied = requestPremultiplied; - mNinePatchChunk = ninePatchChunk; mNinePatchInsets = ninePatchInsets; if (density >= 0) { @@ -159,17 +174,9 @@ public final class Bitmap implements Parcelable { } mNativePtr = nativeBitmap; - final int allocationByteCount = getAllocationByteCount(); - NativeAllocationRegistry registry; - if (fromMalloc) { - registry = NativeAllocationRegistry.createMalloced( - Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount); - } else { - registry = NativeAllocationRegistry.createNonmalloced( - Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount); - } - registry.registerNativeAllocation(this, nativeBitmap); + getRegistry(fromMalloc, allocationByteCount).registerNativeAllocation(this, mNativePtr); + synchronized (Bitmap.class) { sAllBitmaps.put(this, null); } -- GitLab From 13c4422e972e2d267540f3b4dbd046ea9d1778ac Mon Sep 17 00:00:00 2001 From: Eric Miao Date: Fri, 30 Aug 2024 02:39:21 +0000 Subject: [PATCH 025/388] Track HardwareBuffer native allocations Bug: b/363028387 Flag: com.android.libcore.native_metrics This CL will make use of the new API to create NativeAllocationRegistry that associates with HardwareBuffer.class if libcore.native_metrics is enabled (otherwise falls back to the legacy API). This allows tracking of native allocations that are HardwareBuffer specific. Change-Id: I848cf66dacf1cf82baf1ff8d2012866ee548973f --- core/java/android/hardware/HardwareBuffer.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java index ce0f9f598897..8c87ad3353b6 100644 --- a/core/java/android/hardware/HardwareBuffer.java +++ b/core/java/android/hardware/HardwareBuffer.java @@ -277,6 +277,17 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { return new HardwareBuffer(nativeObject); } + /** + * @hide + */ + private static NativeAllocationRegistry getRegistry(long size) { + final long func = nGetNativeFinalizer(); + final Class cls = HardwareBuffer.class; + return com.android.libcore.Flags.nativeMetrics() + ? NativeAllocationRegistry.createNonmalloced(cls, func, size) + : NativeAllocationRegistry.createNonmalloced(cls.getClassLoader(), func, size); + } + /** * Private use only. See {@link #create(int, int, int, int, long)}. May also be * called from JNI using an already allocated native HardwareBuffer. @@ -285,10 +296,7 @@ public final class HardwareBuffer implements Parcelable, AutoCloseable { private HardwareBuffer(long nativeObject) { mNativeObject = nativeObject; long bufferSize = nEstimateSize(nativeObject); - ClassLoader loader = HardwareBuffer.class.getClassLoader(); - NativeAllocationRegistry registry = new NativeAllocationRegistry( - loader, nGetNativeFinalizer(), bufferSize); - mCleaner = registry.registerNativeAllocation(this, mNativeObject); + mCleaner = getRegistry(bufferSize).registerNativeAllocation(this, mNativeObject); mCloseGuard.open("HardwareBuffer.close"); } -- GitLab From cff4ac9610132c285f0a38ebdba64e77ff85b470 Mon Sep 17 00:00:00 2001 From: Makoto Onuki Date: Thu, 12 Sep 2024 16:47:10 -0700 Subject: [PATCH 026/388] Move SystemUiRavenTests to postsubmit Flag: EXEMPT host test change only Bug: 292141694 Test: repo upload hook Test: Tree hugger Change-Id: I8c31c489bc70f936088484b51ab7779be87d0592 --- ravenwood/TEST_MAPPING | 6 ++++-- ravenwood/scripts/update-test-mapping.sh | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/ravenwood/TEST_MAPPING b/ravenwood/TEST_MAPPING index 7f9d9c29484c..74580c84aca5 100644 --- a/ravenwood/TEST_MAPPING +++ b/ravenwood/TEST_MAPPING @@ -164,11 +164,13 @@ { "name": "RavenwoodServicesTest", "host": true - }, + } + // AUTO-GENERATED-END + ], + "ravenwood-postsubmit": [ { "name": "SystemUiRavenTests", "host": true } - // AUTO-GENERATED-END ] } diff --git a/ravenwood/scripts/update-test-mapping.sh b/ravenwood/scripts/update-test-mapping.sh index b6cf5b857682..e478b50cc2b9 100755 --- a/ravenwood/scripts/update-test-mapping.sh +++ b/ravenwood/scripts/update-test-mapping.sh @@ -20,6 +20,9 @@ set -e +# Tests that shouldn't be in presubmit. +EXEMPT='^(SystemUiRavenTests)$' + main() { local script_name="${0##*/}" local script_dir="${0%/*}" @@ -30,7 +33,7 @@ main() { local footer="$(sed -ne '/AUTO-GENERATED-END/,$p' "$test_mapping")" echo "Getting all tests" - local tests=( $("$script_dir/list-ravenwood-tests.sh") ) + local tests=( $("$script_dir/list-ravenwood-tests.sh" | grep -vP "$EXEMPT") ) local num_tests="${#tests[@]}" -- GitLab From 7aca35e22dd73d7277821f46dd1869777cafe507 Mon Sep 17 00:00:00 2001 From: Makoto Onuki Date: Thu, 12 Sep 2024 17:10:02 -0700 Subject: [PATCH 027/388] Allow to add a bug number to ravenwood annotations Flag: EXEMPT host test change only Bug: 292141694 Test: treehugger Change-Id: I50389646482067671f1df6f159561e9058eca4e3 --- .../ravenwood/annotation/RavenwoodRemove.java | 14 ++++++++++++++ .../ravenwood/annotation/RavenwoodReplace.java | 5 +++++ .../ravenwood/annotation/RavenwoodThrow.java | 5 +++++ .../test/annotations/DisabledOnRavenwood.java | 5 +++++ .../test/annotations/IgnoreUnderRavenwood.java | 5 +++++ 5 files changed, 34 insertions(+) diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRemove.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRemove.java index 6727327c99be..b69c63748d81 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRemove.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodRemove.java @@ -35,4 +35,18 @@ import java.lang.annotation.Target; @Target({TYPE, FIELD, METHOD, CONSTRUCTOR}) @Retention(RetentionPolicy.CLASS) public @interface RavenwoodRemove { + /** + * One or more classes that aren't yet supported by Ravenwood, which is why this method throws. + */ + Class[] blockedBy() default {}; + + /** + * General free-form description of why this method throws. + */ + String reason() default ""; + + /** + * Tracking bug number, if any. + */ + long bug() default 0; } diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java index 83a7b6e54389..57cdfd2240d0 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodReplace.java @@ -42,4 +42,9 @@ public @interface RavenwoodReplace { * General free-form description of why this method is being replaced. */ String reason() default ""; + + /** + * Tracking bug number, if any. + */ + long bug() default 0; } diff --git a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java index 0bb1f39cd453..19e6af1c478d 100644 --- a/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java +++ b/ravenwood/annotations-src/android/ravenwood/annotation/RavenwoodThrow.java @@ -43,4 +43,9 @@ public @interface RavenwoodThrow { * General free-form description of why this method throws. */ String reason() default ""; + + /** + * Tracking bug number, if any. + */ + long bug() default 0; } diff --git a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnRavenwood.java index 1adb0f31da54..470165c6da43 100644 --- a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnRavenwood.java +++ b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnRavenwood.java @@ -51,4 +51,9 @@ public @interface DisabledOnRavenwood { * General free-form description of why this test is being ignored. */ String reason() default ""; + + /** + * Tracking bug number, if any. + */ + long bug() default 0; } diff --git a/ravenwood/junit-src/android/platform/test/annotations/IgnoreUnderRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/IgnoreUnderRavenwood.java index 7faa654b903c..1c06829dba06 100644 --- a/ravenwood/junit-src/android/platform/test/annotations/IgnoreUnderRavenwood.java +++ b/ravenwood/junit-src/android/platform/test/annotations/IgnoreUnderRavenwood.java @@ -51,4 +51,9 @@ public @interface IgnoreUnderRavenwood { * General free-form description of why this test is being ignored. */ String reason() default ""; + + /** + * Tracking bug number, if any. + */ + long bug() default 0; } -- GitLab From 3f016660a240d30796a8ccb95f2e27286de56d18 Mon Sep 17 00:00:00 2001 From: Hawkwood Glazier Date: Fri, 13 Sep 2024 02:55:07 +0000 Subject: [PATCH 028/388] Add Axis options to clock interface Test: NONE Bug: 364673977 Flag: NONE Api only change, callsites flagged Change-Id: I5ed57023db4a3abe814917db3b7fec247333f5ce --- .../shared/clocks/DefaultClockController.kt | 16 ++-- .../plugins/clocks/ClockProviderPlugin.kt | 76 +++++++++++++++---- 2 files changed, 70 insertions(+), 22 deletions(-) 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 502dbe3e423c..5ed11ad345ad 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 @@ -34,6 +34,7 @@ import com.android.systemui.plugins.clocks.ClockFaceConfig import com.android.systemui.plugins.clocks.ClockFaceController import com.android.systemui.plugins.clocks.ClockFaceEvents import com.android.systemui.plugins.clocks.ClockMessageBuffers +import com.android.systemui.plugins.clocks.ClockReactiveSetting import com.android.systemui.plugins.clocks.ClockSettings import com.android.systemui.plugins.clocks.DefaultClockFaceLayout import com.android.systemui.plugins.clocks.WeatherData @@ -73,7 +74,7 @@ class DefaultClockController( ClockConfig( DEFAULT_CLOCK_ID, resources.getString(R.string.clock_default_name), - resources.getString(R.string.clock_default_description) + resources.getString(R.string.clock_default_description), ) } @@ -84,14 +85,14 @@ class DefaultClockController( layoutInflater.inflate(R.layout.clock_default_small, parent, false) as AnimatableClockView, settings?.seedColor, - messageBuffers?.smallClockMessageBuffer + messageBuffers?.smallClockMessageBuffer, ) largeClock = LargeClockFaceController( layoutInflater.inflate(R.layout.clock_default_large, parent, false) as AnimatableClockView, settings?.seedColor, - messageBuffers?.largeClockMessageBuffer + messageBuffers?.largeClockMessageBuffer, ) clocks = listOf(smallClock.view, largeClock.view) @@ -272,8 +273,12 @@ class DefaultClockController( } override fun onWeatherDataChanged(data: WeatherData) {} + override fun onAlarmDataChanged(data: AlarmData) {} + override fun onZenDataChanged(data: ZenData) {} + + override fun onReactiveAxesChanged(axes: List) {} } open inner class DefaultClockAnimations( @@ -340,10 +345,9 @@ class DefaultClockController( } } - class AnimationState( - var fraction: Float, - ) { + class AnimationState(var fraction: Float) { var isActive: Boolean = fraction > 0.5f + fun update(newFraction: Float): Pair { if (newFraction == fraction) { return Pair(isActive, false) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt index 4812ff03ef36..8dc4815b6f57 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt @@ -69,11 +69,7 @@ interface ClockController { val events: ClockEvents /** Initializes various rendering parameters. If never called, provides reasonable defaults. */ - fun initialize( - resources: Resources, - dozeFraction: Float, - foldFraction: Float, - ) + fun initialize(resources: Resources, dozeFraction: Float, foldFraction: Float) /** Optional method for dumping debug information */ fun dump(pw: PrintWriter) @@ -109,11 +105,7 @@ data class ClockMessageBuffers( val largeClockMessageBuffer: MessageBuffer, ) -data class AodClockBurnInModel( - val scale: Float, - val translationX: Float, - val translationY: Float, -) +data class AodClockBurnInModel(val scale: Float, val translationX: Float, val translationY: Float) /** Specifies layout information for the */ interface ClockFaceLayout { @@ -180,8 +172,20 @@ interface ClockEvents { /** Call with zen/dnd information */ fun onZenDataChanged(data: ZenData) + + /** Update reactive axes for this clock */ + fun onReactiveAxesChanged(axes: List) } +/** Axis setting value for a clock */ +data class ClockReactiveSetting( + /** Axis key; matches ClockReactiveAxis.key */ + val key: String, + + /** Value to set this axis to */ + val value: Float, +) + /** Methods which trigger various clock animations */ interface ClockAnimations { /** Runs an enter animation (if any) */ @@ -264,9 +268,7 @@ enum class ClockTickRate(val value: Int) { } /** Some data about a clock design */ -data class ClockMetadata( - val clockId: ClockId, -) +data class ClockMetadata(val clockId: ClockId) data class ClockPickerConfig( val id: String, @@ -283,10 +285,46 @@ data class ClockPickerConfig( /** True if the clock will react to tone changes in the seed color */ val isReactiveToTone: Boolean = true, - /** True if the clock is capable of chagning style in reaction to touches */ + /** True if the clock is capable of changing style in reaction to touches */ val isReactiveToTouch: Boolean = false, + + /** Font axes that can be modified on this clock */ + val axes: List = listOf(), +) + +/** Represents an Axis that can be modified */ +data class ClockReactiveAxis( + /** Axis key, not user renderable */ + val key: String, + + /** Intended mode of user interaction */ + val type: AxisType, + + /** Maximum value the axis supports */ + val maxValue: Float, + + /** Minimum value the axis supports */ + val minValue: Float, + + /** Current value the axis is set to */ + val currentValue: Float, + + /** User-renderable name of the axis */ + val name: String, + + /** Description of the axis */ + val description: String, ) +/** Axis user interaction modes */ +enum class AxisType { + /** Boolean toggle. Swaps between minValue & maxValue */ + Toggle, + + /** Continuous slider between minValue & maxValue */ + Slider, +} + /** Render configuration for the full clock. Modifies the way systemUI behaves with this clock. */ data class ClockConfig( val id: String, @@ -300,7 +338,8 @@ data class ClockConfig( /** Transition to AOD should move smartspace like large clock instead of small clock */ val useAlternateSmartspaceAODTransition: Boolean = false, - @Deprecated("TODO(b/352049256): Remove") + /** Use ClockPickerConfig.isReactiveToTone instead */ + @Deprecated("TODO(b/352049256): Remove in favor of ClockPickerConfig.isReactiveToTone") val isReactiveToTone: Boolean = true, /** True if the clock is large frame clock, which will use weather in compose. */ @@ -331,6 +370,7 @@ data class ClockFaceConfig( data class ClockSettings( val clockId: ClockId? = null, val seedColor: Int? = null, + val axes: List? = null, ) { // Exclude metadata from equality checks var metadata: JSONObject = JSONObject() @@ -345,6 +385,8 @@ data class ClockSettings( return "" } + // TODO(b/364673977): Serialize axes + return JSONObject() .put(KEY_CLOCK_ID, setting.clockId) .put(KEY_SEED_COLOR, setting.seedColor) @@ -357,11 +399,13 @@ data class ClockSettings( return null } + // TODO(b/364673977): Deserialize axes + val json = JSONObject(jsonStr) val result = ClockSettings( if (!json.isNull(KEY_CLOCK_ID)) json.getString(KEY_CLOCK_ID) else null, - if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null + if (!json.isNull(KEY_SEED_COLOR)) json.getInt(KEY_SEED_COLOR) else null, ) if (!json.isNull(KEY_METADATA)) { result.metadata = json.getJSONObject(KEY_METADATA) -- GitLab From 04b85038184e7e93b6123434814230720128aea2 Mon Sep 17 00:00:00 2001 From: zhouwei Date: Thu, 12 Sep 2024 16:45:10 +0800 Subject: [PATCH 029/388] Fix notifyTaskDisplayChanged is called every time onDisplayChanged is triggered. The original intention was to send the callback when the task reparented to another display. So we need to add a check whether displayId has changed before calling notifyTaskDisplayChanged. Test: pressure test that notifyTaskDisplayChanged is triggered when the task moves from one display to another, and not triggered otherwise. Bug: b/365477008 Change-Id: I5848ead17a50ccd6ece0e3b0c10e59252a633f89 Signed-off-by: zhou.wei --- services/core/java/com/android/server/wm/Task.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 728f739f9f8d..eee282122792 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2790,11 +2790,15 @@ class Task extends TaskFragment { @Override void onDisplayChanged(DisplayContent dc) { + final int lastDisplayId = getDisplayId(); super.onDisplayChanged(dc); if (isLeafTask()) { final int displayId = (dc != null) ? dc.getDisplayId() : INVALID_DISPLAY; - mWmService.mAtmService.getTaskChangeNotificationController().notifyTaskDisplayChanged( - mTaskId, displayId); + //Send the callback when the task reparented to another display. + if (lastDisplayId != displayId) { + mWmService.mAtmService.getTaskChangeNotificationController() + .notifyTaskDisplayChanged(mTaskId, displayId); + } } if (isRootTask()) { updateSurfaceBounds(); -- GitLab From 9eac48c72fe80370a18683e70db7116caf274eb8 Mon Sep 17 00:00:00 2001 From: Hyosun Kim Date: Tue, 10 Sep 2024 11:04:38 +0000 Subject: [PATCH 030/388] Add onRegistrationFailure api Bug: 365634513 Test: atest SatelliteControllerTest Flag: com.android.internal.telephony.flags.carrier_roaming_nb_iot_ntn Change-Id: Iadf02c42747c14789d87c32e0d6fb563996336b2 --- .../satellite/ISatelliteModemStateCallback.aidl | 8 ++++++++ .../android/telephony/satellite/SatelliteManager.java | 8 ++++++++ .../telephony/satellite/SatelliteModemStateCallback.java | 9 +++++++++ 3 files changed, 25 insertions(+) diff --git a/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl index 66a20ae28f39..50e3a0e4a79d 100644 --- a/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl +++ b/telephony/java/android/telephony/satellite/ISatelliteModemStateCallback.aidl @@ -34,4 +34,12 @@ oneway interface ISatelliteModemStateCallback { * @param isEmergency True means satellite enabled for emergency mode, false otherwise. */ void onEmergencyModeChanged(in boolean isEmergency); + + /** + * Indicates that the satellite registration failed with following failure code + * + * @param causeCode the primary failure cause code of the procedure. + * For LTE (EMM), cause codes are TS 24.301 Sec 9.9.3.9 + */ + void onRegistrationFailure(in int causeCode); } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 284e2bd8aa6c..d5ee2c0809f5 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -19,6 +19,7 @@ package android.telephony.satellite; import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; +import android.annotation.Hide; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1573,6 +1574,13 @@ public final class SatelliteManager { executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onEmergencyModeChanged(isEmergency))); } + + @Hide + @Override + public void onRegistrationFailure(int causeCode) { + executor.execute(() -> Binder.withCleanCallingIdentity(() -> + callback.onRegistrationFailure(causeCode))); + } }; sSatelliteModemStateCallbackMap.put(callback, internalCallback); return telephony.registerForSatelliteModemStateChanged(internalCallback); diff --git a/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java index 423a7859dd6b..13af4694389b 100644 --- a/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteModemStateCallback.java @@ -45,4 +45,13 @@ public interface SatelliteModemStateCallback { */ @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN) default void onEmergencyModeChanged(boolean isEmergency) {}; + + /** + * Indicates that the satellite registration failed with following failure code + * + * @param causeCode the primary failure cause code of the procedure. + * For LTE (EMM), cause codes are TS 24.301 Sec 9.9.3.9 + * @hide + */ + default void onRegistrationFailure(int causeCode) {}; } -- GitLab From 4b4567997218521b45b85a8e9b7ebac80cd81683 Mon Sep 17 00:00:00 2001 From: Robin Lee Date: Thu, 12 Sep 2024 18:43:20 +0000 Subject: [PATCH 031/388] Continue to allow some orientation overrides Amend ignore_activity_orientation_request experiment to allow explicit OEM and User overrides, plus camera compat mode, to still override the UNSPECIFIED orientation of an app. This does nothing when the device_config flag below is left at the default value. Change-Id: If7e0c5f8fd5f665dc95a41b92ed512b4dc239122 Flag: EXEMPT deviceconfig - adb shell device_config put window_manager ignore_activity_orientation_request true Test: Manual - use a camera app on tangor Test: Manual - override calculator orientation in settings Bug: 357141415 --- .../java/com/android/server/wm/ActivityRecord.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index f52a74fcdf9f..ccc9b17ff840 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -8150,13 +8150,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * into account orientation per-app overrides applied by the device manufacturers. */ @Override + @ActivityInfo.ScreenOrientation protected int getOverrideOrientation() { - if (mWmService.mConstants.mIgnoreActivityOrientationRequest - && info.applicationInfo.category != ApplicationInfo.CATEGORY_GAME) { - return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + final int candidateOrientation; + if (!mWmService.mConstants.mIgnoreActivityOrientationRequest + || info.applicationInfo.category == ApplicationInfo.CATEGORY_GAME) { + candidateOrientation = super.getOverrideOrientation(); + } else { + candidateOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; } return mAppCompatController.getOrientationPolicy() - .overrideOrientationIfNeeded(super.getOverrideOrientation()); + .overrideOrientationIfNeeded(candidateOrientation); } /** -- GitLab From cdc00f517fd70176945d9e579cc05571b469f85f Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Fri, 13 Sep 2024 15:20:05 +0800 Subject: [PATCH 032/388] Extra common logic of default transition animation Make it more flexible to run other animation implementation. Bug: 326331384 Flag: EXEMPT simple refactor Test: Default activity/task switch Change-Id: I7aaebda531ed8a21138c3f42ddbbb7bb1c7d3765 --- .../transition/DefaultTransitionHandler.java | 125 +++++++++++++----- 1 file changed, 89 insertions(+), 36 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 4fc6c4489f2b..609ddbeb1818 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -828,24 +828,26 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { @NonNull Runnable finishCallback, @NonNull TransactionPool pool, @NonNull ShellExecutor mainExecutor, @Nullable Point position, float cornerRadius, @Nullable Rect clipRect, boolean isActivity) { + final DefaultAnimationAdapter adapter = new DefaultAnimationAdapter(anim, leash, + position, clipRect, cornerRadius, isActivity); + buildSurfaceAnimation(animations, anim, finishCallback, pool, mainExecutor, adapter); + } + + /** Builds an animator for the surface and adds it to the `animations` list. */ + static void buildSurfaceAnimation(@NonNull ArrayList animations, + @NonNull Animation anim, @NonNull Runnable finishCallback, + @NonNull TransactionPool pool, @NonNull ShellExecutor mainExecutor, + @NonNull AnimationAdapter updateListener) { final SurfaceControl.Transaction transaction = pool.acquire(); + updateListener.setTransaction(transaction); final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); - final Transformation transformation = new Transformation(); - final float[] matrix = new float[9]; // Animation length is already expected to be scaled. va.overrideDurationScale(1.0f); va.setDuration(anim.computeDurationHint()); - final ValueAnimator.AnimatorUpdateListener updateListener = animation -> { - final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime()); - - applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix, - position, cornerRadius, clipRect, isActivity); - }; va.addUpdateListener(updateListener); final Runnable finisher = () -> { - applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix, - position, cornerRadius, clipRect, isActivity); + updateListener.onAnimationUpdate(va); pool.release(transaction); mainExecutor.execute(() -> { @@ -1009,37 +1011,88 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { || animType == ANIM_FROM_STYLE; } - private static void applyTransformation(long time, SurfaceControl.Transaction t, - SurfaceControl leash, Animation anim, Transformation tmpTransformation, float[] matrix, - Point position, float cornerRadius, @Nullable Rect immutableClipRect, - boolean isActivity) { - tmpTransformation.clear(); - anim.getTransformation(time, tmpTransformation); - if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader() - && anim.getExtensionEdges() != 0x0 && isActivity) { - t.setEdgeExtensionEffect(leash, anim.getExtensionEdges()); + /** The animation adapter for buildSurfaceAnimation. */ + abstract static class AnimationAdapter implements ValueAnimator.AnimatorUpdateListener { + @NonNull final SurfaceControl mLeash; + @NonNull SurfaceControl.Transaction mTransaction; + private Choreographer mChoreographer; + + AnimationAdapter(@NonNull SurfaceControl leash) { + mLeash = leash; } - if (position != null) { - tmpTransformation.getMatrix().postTranslate(position.x, position.y); + + void setTransaction(@NonNull SurfaceControl.Transaction transaction) { + mTransaction = transaction; } - t.setMatrix(leash, tmpTransformation.getMatrix(), matrix); - t.setAlpha(leash, tmpTransformation.getAlpha()); - - final Rect clipRect = immutableClipRect == null ? null : new Rect(immutableClipRect); - Insets extensionInsets = Insets.min(tmpTransformation.getInsets(), Insets.NONE); - if (!extensionInsets.equals(Insets.NONE) && clipRect != null && !clipRect.isEmpty()) { - // Clip out any overflowing edge extension - clipRect.inset(extensionInsets); - t.setCrop(leash, clipRect); + + @Override + public void onAnimationUpdate(@NonNull ValueAnimator animator) { + applyTransformation(animator); + if (mChoreographer == null) { + mChoreographer = Choreographer.getInstance(); + } + mTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId()); + mTransaction.apply(); } - if (anim.hasRoundedCorners() && cornerRadius > 0 && clipRect != null) { - // We can only apply rounded corner if a crop is set - t.setCrop(leash, clipRect); - t.setCornerRadius(leash, cornerRadius); + abstract void applyTransformation(@NonNull ValueAnimator animator); + } + + private static class DefaultAnimationAdapter extends AnimationAdapter { + final Transformation mTransformation = new Transformation(); + final float[] mMatrix = new float[9]; + @NonNull final Animation mAnim; + @Nullable final Point mPosition; + @Nullable final Rect mClipRect; + final float mCornerRadius; + final boolean mIsActivity; + + DefaultAnimationAdapter(@NonNull Animation anim, @NonNull SurfaceControl leash, + @Nullable Point position, @Nullable Rect clipRect, float cornerRadius, + boolean isActivity) { + super(leash); + mAnim = anim; + mPosition = (position != null && (position.x != 0 || position.y != 0)) + ? position : null; + mClipRect = (clipRect != null && !clipRect.isEmpty()) ? clipRect : null; + mCornerRadius = cornerRadius; + mIsActivity = isActivity; } - t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); - t.apply(); + @Override + void applyTransformation(@NonNull ValueAnimator animator) { + final long currentPlayTime = Math.min(animator.getDuration(), + animator.getCurrentPlayTime()); + final Transformation transformation = mTransformation; + final SurfaceControl.Transaction t = mTransaction; + final SurfaceControl leash = mLeash; + transformation.clear(); + mAnim.getTransformation(currentPlayTime, transformation); + if (com.android.graphics.libgui.flags.Flags.edgeExtensionShader() + && mIsActivity && mAnim.getExtensionEdges() != 0) { + t.setEdgeExtensionEffect(leash, mAnim.getExtensionEdges()); + } + if (mPosition != null) { + transformation.getMatrix().postTranslate(mPosition.x, mPosition.y); + } + t.setMatrix(leash, transformation.getMatrix(), mMatrix); + t.setAlpha(leash, transformation.getAlpha()); + + if (mClipRect != null) { + Rect clipRect = mClipRect; + final Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE); + if (!extensionInsets.equals(Insets.NONE)) { + // Clip out any overflowing edge extension. + clipRect = new Rect(mClipRect); + clipRect.inset(extensionInsets); + t.setCrop(leash, clipRect); + } + if (mCornerRadius > 0 && mAnim.hasRoundedCorners()) { + // Rounded corner can only be applied if a crop is set. + t.setCrop(leash, clipRect); + t.setCornerRadius(leash, mCornerRadius); + } + } + } } } -- GitLab From 502d5d08314d4f0b22a62304411c809eebda2a2a Mon Sep 17 00:00:00 2001 From: Eghosa Ewansiha-Vlachavas Date: Tue, 10 Sep 2024 08:35:32 +0000 Subject: [PATCH 033/388] Add tests for the `FixedAspectRatioTaskPositionerDecorator` Flag: NONE(tests) Test: atest WMShellUnitTests:FixedAspectRatioDragPositioningDecoratorTests Fix: 365741940 Change-Id: I3e5bc9292319c6d3c7799632f41dfe5efdb9da2a --- ...FixedAspectRatioTaskPositionerDecorator.kt | 10 +- ...AspectRatioTaskPositionerDecoratorTests.kt | 636 ++++++++++++++++++ 2 files changed, 644 insertions(+), 2 deletions(-) create mode 100644 libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecoratorTests.kt diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecorator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecorator.kt index e8131a00ba40..3885761d0742 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecorator.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecorator.kt @@ -16,8 +16,10 @@ package com.android.wm.shell.windowdecor +import android.app.ActivityManager.RunningTaskInfo import android.graphics.PointF import android.graphics.Rect +import com.android.internal.annotations.VisibleForTesting import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT @@ -51,8 +53,7 @@ class FixedAspectRatioTaskPositionerDecorator ( return super.onDragPositioningStart(originalCtrlType, x, y) } - lastRepositionedBounds.set( - windowDecoration.mTaskInfo.configuration.windowConfiguration.bounds) + lastRepositionedBounds.set(getBounds(windowDecoration.mTaskInfo)) startingPoint.set(x, y) lastValidPoint.set(x, y) val startingBoundWidth = lastRepositionedBounds.width() @@ -255,4 +256,9 @@ class FixedAspectRatioTaskPositionerDecorator ( private fun requiresFixedAspectRatio(): Boolean { return originalCtrlType.isResizing() && !windowDecoration.mTaskInfo.isResizeable } + + @VisibleForTesting + fun getBounds(taskInfo: RunningTaskInfo): Rect { + return taskInfo.configuration.windowConfiguration.bounds + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecoratorTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecoratorTests.kt new file mode 100644 index 000000000000..ce17c1df50bc --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FixedAspectRatioTaskPositionerDecoratorTests.kt @@ -0,0 +1,636 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.windowdecor + +import android.app.ActivityManager +import android.graphics.PointF +import android.graphics.Rect +import android.util.MathUtils.abs +import android.util.MathUtils.max +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM +import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT +import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT +import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP +import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED +import com.android.wm.shell.windowdecor.DragPositioningCallback.CtrlType +import com.google.common.truth.Truth.assertThat +import com.google.testing.junit.testparameterinjector.TestParameter +import com.google.testing.junit.testparameterinjector.TestParameterInjector +import kotlin.math.min +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mockito.kotlin.any +import org.mockito.kotlin.argumentCaptor +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.never + +/** + * Tests for the [FixedAspectRatioTaskPositionerDecorator], written in parameterized form to check + * decorators behaviour for different variations of drag actions. + * + * Build/Install/Run: + * atest WMShellUnitTests:FixedAspectRatioTaskPositionerDecoratorTests + */ +@SmallTest +@RunWith(TestParameterInjector::class) +class FixedAspectRatioTaskPositionerDecoratorTests : ShellTestCase(){ + @Mock + private lateinit var mockDesktopWindowDecoration: DesktopModeWindowDecoration + @Mock + private lateinit var mockTaskPositioner: VeiledResizeTaskPositioner + + private lateinit var decoratedTaskPositioner: FixedAspectRatioTaskPositionerDecorator + + @Before + fun setUp() { + mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply { + isResizeable = false + configuration.windowConfiguration.setBounds(PORTRAIT_BOUNDS) + } + doReturn(PORTRAIT_BOUNDS).`when`(mockTaskPositioner).onDragPositioningStart( + any(), any(), any()) + doReturn(Rect()).`when`(mockTaskPositioner).onDragPositioningMove(any(), any()) + doReturn(Rect()).`when`(mockTaskPositioner).onDragPositioningEnd(any(), any()) + decoratedTaskPositioner = spy( + FixedAspectRatioTaskPositionerDecorator( + mockDesktopWindowDecoration, mockTaskPositioner) + ) + } + + @Test + fun testOnDragPositioningStart_noAdjustment( + @TestParameter testCase: ResizeableOrNotResizingTestCases + ) { + val originalX = 0f + val originalY = 0f + mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply { + isResizeable = testCase.isResizeable + } + + decoratedTaskPositioner.onDragPositioningStart(testCase.ctrlType, originalX, originalY) + + val capturedValues = getLatestOnStartArguments() + assertThat(capturedValues.ctrlType).isEqualTo(testCase.ctrlType) + assertThat(capturedValues.x).isEqualTo(originalX) + assertThat(capturedValues.y).isEqualTo(originalY) + } + + @Test + fun testOnDragPositioningStart_cornerResize_noAdjustment( + @TestParameter testCase: CornerResizeStartTestCases + ) { + val originalX = 0f + val originalY = 0f + + decoratedTaskPositioner.onDragPositioningStart(testCase.ctrlType, originalX, originalY) + + val capturedValues = getLatestOnStartArguments() + assertThat(capturedValues.ctrlType).isEqualTo(testCase.ctrlType) + assertThat(capturedValues.x).isEqualTo(originalX) + assertThat(capturedValues.y).isEqualTo(originalY) + } + + @Test + fun testOnDragPositioningStart_edgeResize_ctrlTypeAdjusted( + @TestParameter testCase: EdgeResizeStartTestCases, @TestParameter orientation: Orientation + ) { + val startingBounds = getAndMockBounds(orientation) + val startingPoint = getEdgeStartingPoint( + testCase.ctrlType, testCase.additionalEdgeCtrlType, startingBounds) + + decoratedTaskPositioner.onDragPositioningStart( + testCase.ctrlType, startingPoint.x, startingPoint.y) + + val adjustedCtrlType = testCase.ctrlType + testCase.additionalEdgeCtrlType + val capturedValues = getLatestOnStartArguments() + assertThat(capturedValues.ctrlType).isEqualTo(adjustedCtrlType) + assertThat(capturedValues.x).isEqualTo(startingPoint.x) + assertThat(capturedValues.y).isEqualTo(startingPoint.y) + } + + @Test + fun testOnDragPositioningMove_noAdjustment( + @TestParameter testCase: ResizeableOrNotResizingTestCases + ) { + val originalX = 0f + val originalY = 0f + decoratedTaskPositioner.onDragPositioningStart(testCase.ctrlType, originalX, originalX) + mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply { + isResizeable = testCase.isResizeable + } + + decoratedTaskPositioner.onDragPositioningMove( + originalX + SMALL_DELTA, originalY + SMALL_DELTA) + + val capturedValues = getLatestOnMoveArguments() + assertThat(capturedValues.x).isEqualTo(originalX + SMALL_DELTA) + assertThat(capturedValues.y).isEqualTo(originalY + SMALL_DELTA) + } + + @Test + fun testOnDragPositioningMove_cornerResize_invalidRegion_noResize( + @TestParameter testCase: InvalidCornerResizeTestCases, + @TestParameter orientation: Orientation + ) { + val startingBounds = getAndMockBounds(orientation) + val startingPoint = getCornerStartingPoint(testCase.ctrlType, startingBounds) + + decoratedTaskPositioner.onDragPositioningStart( + testCase.ctrlType, startingPoint.x, startingPoint.y) + + val updatedBounds = decoratedTaskPositioner.onDragPositioningMove( + startingPoint.x + testCase.dragDelta.x, + startingPoint.y + testCase.dragDelta.y) + + verify(mockTaskPositioner, never()).onDragPositioningMove(any(), any()) + assertThat(updatedBounds).isEqualTo(startingBounds) + } + + + @Test + fun testOnDragPositioningMove_cornerResize_validRegion_resizeToAdjustedCoordinates( + @TestParameter testCase: ValidCornerResizeTestCases, + @TestParameter orientation: Orientation + ) { + val startingBounds = getAndMockBounds(orientation) + val startingPoint = getCornerStartingPoint(testCase.ctrlType, startingBounds) + + decoratedTaskPositioner.onDragPositioningStart( + testCase.ctrlType, startingPoint.x, startingPoint.y) + + decoratedTaskPositioner.onDragPositioningMove( + startingPoint.x + testCase.dragDelta.x, startingPoint.y + testCase.dragDelta.y) + + val adjustedDragDelta = calculateAdjustedDelta( + testCase.ctrlType, testCase.dragDelta, orientation) + val capturedValues = getLatestOnMoveArguments() + val absChangeX = abs(capturedValues.x - startingPoint.x) + val absChangeY = abs(capturedValues.y - startingPoint.y) + val resultAspectRatio = max(absChangeX, absChangeY) / min(absChangeX, absChangeY) + assertThat(capturedValues.x).isEqualTo(startingPoint.x + adjustedDragDelta.x) + assertThat(capturedValues.y).isEqualTo(startingPoint.y + adjustedDragDelta.y) + assertThat(resultAspectRatio).isEqualTo(STARTING_ASPECT_RATIO) + } + + @Test + fun testOnDragPositioningMove_edgeResize_resizeToAdjustedCoordinates( + @TestParameter testCase: EdgeResizeTestCases, + @TestParameter orientation: Orientation + ) { + val startingBounds = getAndMockBounds(orientation) + val startingPoint = getEdgeStartingPoint( + testCase.ctrlType, testCase.additionalEdgeCtrlType, startingBounds) + + decoratedTaskPositioner.onDragPositioningStart( + testCase.ctrlType, startingPoint.x, startingPoint.y) + + decoratedTaskPositioner.onDragPositioningMove( + startingPoint.x + testCase.dragDelta.x, + startingPoint.y + testCase.dragDelta.y) + + val adjustedDragDelta = calculateAdjustedDelta( + testCase.ctrlType + testCase.additionalEdgeCtrlType, + testCase.dragDelta, + orientation) + val capturedValues = getLatestOnMoveArguments() + val absChangeX = abs(capturedValues.x - startingPoint.x) + val absChangeY = abs(capturedValues.y - startingPoint.y) + val resultAspectRatio = max(absChangeX, absChangeY) / min(absChangeX, absChangeY) + assertThat(capturedValues.x).isEqualTo(startingPoint.x + adjustedDragDelta.x) + assertThat(capturedValues.y).isEqualTo(startingPoint.y + adjustedDragDelta.y) + assertThat(resultAspectRatio).isEqualTo(STARTING_ASPECT_RATIO) + } + + @Test + fun testOnDragPositioningEnd_noAdjustment( + @TestParameter testCase: ResizeableOrNotResizingTestCases + ) { + val originalX = 0f + val originalY = 0f + decoratedTaskPositioner.onDragPositioningStart(testCase.ctrlType, originalX, originalX) + mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply { + isResizeable = testCase.isResizeable + } + + decoratedTaskPositioner.onDragPositioningEnd( + originalX + SMALL_DELTA, originalY + SMALL_DELTA) + + val capturedValues = getLatestOnEndArguments() + assertThat(capturedValues.x).isEqualTo(originalX + SMALL_DELTA) + assertThat(capturedValues.y).isEqualTo(originalY + SMALL_DELTA) + } + + @Test + fun testOnDragPositioningEnd_cornerResize_invalidRegion_endsAtPreviousValidPoint( + @TestParameter testCase: InvalidCornerResizeTestCases, + @TestParameter orientation: Orientation + ) { + val startingBounds = getAndMockBounds(orientation) + val startingPoint = getCornerStartingPoint(testCase.ctrlType, startingBounds) + + decoratedTaskPositioner.onDragPositioningStart( + testCase.ctrlType, startingPoint.x, startingPoint.y) + + decoratedTaskPositioner.onDragPositioningEnd( + startingPoint.x + testCase.dragDelta.x, + startingPoint.y + testCase.dragDelta.y) + + val capturedValues = getLatestOnEndArguments() + assertThat(capturedValues.x).isEqualTo(startingPoint.x) + assertThat(capturedValues.y).isEqualTo(startingPoint.y) + } + + @Test + fun testOnDragPositioningEnd_cornerResize_validRegion_endAtAdjustedCoordinates( + @TestParameter testCase: ValidCornerResizeTestCases, + @TestParameter orientation: Orientation + ) { + val startingBounds = getAndMockBounds(orientation) + val startingPoint = getCornerStartingPoint(testCase.ctrlType, startingBounds) + + decoratedTaskPositioner.onDragPositioningStart( + testCase.ctrlType, startingPoint.x, startingPoint.y) + + decoratedTaskPositioner.onDragPositioningEnd( + startingPoint.x + testCase.dragDelta.x, startingPoint.y + testCase.dragDelta.y) + + val adjustedDragDelta = calculateAdjustedDelta( + testCase.ctrlType, testCase.dragDelta, orientation) + val capturedValues = getLatestOnEndArguments() + val absChangeX = abs(capturedValues.x - startingPoint.x) + val absChangeY = abs(capturedValues.y - startingPoint.y) + val resultAspectRatio = max(absChangeX, absChangeY) / min(absChangeX, absChangeY) + assertThat(capturedValues.x).isEqualTo(startingPoint.x + adjustedDragDelta.x) + assertThat(capturedValues.y).isEqualTo(startingPoint.y + adjustedDragDelta.y) + assertThat(resultAspectRatio).isEqualTo(STARTING_ASPECT_RATIO) + } + + @Test + fun testOnDragPositioningEnd_edgeResize_endAtAdjustedCoordinates( + @TestParameter testCase: EdgeResizeTestCases, + @TestParameter orientation: Orientation + ) { + val startingBounds = getAndMockBounds(orientation) + val startingPoint = getEdgeStartingPoint( + testCase.ctrlType, testCase.additionalEdgeCtrlType, startingBounds) + + decoratedTaskPositioner.onDragPositioningStart( + testCase.ctrlType, startingPoint.x, startingPoint.y) + + decoratedTaskPositioner.onDragPositioningEnd( + startingPoint.x + testCase.dragDelta.x, + startingPoint.y + testCase.dragDelta.y) + + val adjustedDragDelta = calculateAdjustedDelta( + testCase.ctrlType + testCase.additionalEdgeCtrlType, + testCase.dragDelta, + orientation) + val capturedValues = getLatestOnEndArguments() + val absChangeX = abs(capturedValues.x - startingPoint.x) + val absChangeY = abs(capturedValues.y - startingPoint.y) + val resultAspectRatio = max(absChangeX, absChangeY) / min(absChangeX, absChangeY) + assertThat(capturedValues.x).isEqualTo(startingPoint.x + adjustedDragDelta.x) + assertThat(capturedValues.y).isEqualTo(startingPoint.y + adjustedDragDelta.y) + assertThat(resultAspectRatio).isEqualTo(STARTING_ASPECT_RATIO) + } + + /** + * Returns the most recent arguments passed to the `.onPositioningStart()` of the + * [mockTaskPositioner]. + */ + private fun getLatestOnStartArguments(): CtrlCoordinateCapture { + val captorCtrlType = argumentCaptor() + val captorCoordinates = argumentCaptor() + verify(mockTaskPositioner).onDragPositioningStart( + captorCtrlType.capture(), captorCoordinates.capture(), captorCoordinates.capture()) + + return CtrlCoordinateCapture(captorCtrlType.firstValue, captorCoordinates.firstValue, + captorCoordinates.secondValue) + } + + /** + * Returns the most recent arguments passed to the `.onPositioningMove()` of the + * [mockTaskPositioner]. + */ + private fun getLatestOnMoveArguments(): PointF { + val captorCoordinates = argumentCaptor() + verify(mockTaskPositioner).onDragPositioningMove( + captorCoordinates.capture(), captorCoordinates.capture()) + + return PointF(captorCoordinates.firstValue, captorCoordinates.secondValue) + } + + /** + * Returns the most recent arguments passed to the `.onPositioningEnd()` of the + * [mockTaskPositioner]. + */ + private fun getLatestOnEndArguments(): PointF { + val captorCoordinates = argumentCaptor() + verify(mockTaskPositioner).onDragPositioningEnd( + captorCoordinates.capture(), captorCoordinates.capture()) + + return PointF(captorCoordinates.firstValue, captorCoordinates.secondValue) + } + + /** + * Mocks the app bounds to correspond with a given orientation and returns the mocked bounds. + */ + private fun getAndMockBounds(orientation: Orientation): Rect { + val mockBounds = if (orientation.isPortrait) PORTRAIT_BOUNDS else LANDSCAPE_BOUNDS + doReturn(mockBounds).`when`(mockTaskPositioner).onDragPositioningStart( + any(), any(), any()) + doReturn(mockBounds).`when`(decoratedTaskPositioner).getBounds(any()) + return mockBounds + } + + /** + * Calculates the corner point a given drag action should start from, based on the [ctrlType], + * given the [startingBounds]. + */ + private fun getCornerStartingPoint(@CtrlType ctrlType: Int, startingBounds: Rect): PointF { + return when (ctrlType) { + CTRL_TYPE_BOTTOM + CTRL_TYPE_RIGHT -> + PointF(startingBounds.right.toFloat(), startingBounds.bottom.toFloat()) + + CTRL_TYPE_BOTTOM + CTRL_TYPE_LEFT -> + PointF(startingBounds.left.toFloat(), startingBounds.bottom.toFloat()) + + CTRL_TYPE_TOP + CTRL_TYPE_RIGHT -> + PointF(startingBounds.right.toFloat(), startingBounds.top.toFloat()) + // CTRL_TYPE_TOP + CTRL_TYPE_LEFT + else -> + PointF(startingBounds.left.toFloat(), startingBounds.top.toFloat()) + } + } + + /** + * Calculates the point along an edge the edge resize should start from, based on the starting + * edge ([edgeCtrlType]) and the additional edge we expect to resize ([additionalEdgeCtrlType]), + * given the [startingBounds]. + */ + private fun getEdgeStartingPoint( + @CtrlType edgeCtrlType: Int, @CtrlType additionalEdgeCtrlType: Int, startingBounds: Rect + ): PointF { + val simulatedCorner = getCornerStartingPoint( + edgeCtrlType + additionalEdgeCtrlType, startingBounds) + when (additionalEdgeCtrlType) { + CTRL_TYPE_TOP -> { + simulatedCorner.offset(0f, -SMALL_DELTA) + return simulatedCorner + } + CTRL_TYPE_BOTTOM -> { + simulatedCorner.offset(0f, SMALL_DELTA) + return simulatedCorner + } + CTRL_TYPE_LEFT -> { + simulatedCorner.offset(SMALL_DELTA, 0f) + return simulatedCorner + } + // CTRL_TYPE_RIGHT + else -> { + simulatedCorner.offset(-SMALL_DELTA, 0f) + return simulatedCorner + } + } + } + + /** + * Calculates the adjustments to the drag delta we expect for a given action and orientation. + */ + private fun calculateAdjustedDelta( + @CtrlType ctrlType: Int, delta: PointF, orientation: Orientation + ): PointF { + if ((abs(delta.x) < abs(delta.y) && delta.x != 0f) || delta.y == 0f) { + // Only respect x delta if it's less than y delta but non-zero (i.e there is a change + // in x to be applied), or if the y delta is zero (i.e there is no change in y to be + // applied). + val adjustedY = if (orientation.isPortrait) + delta.x * STARTING_ASPECT_RATIO else + delta.x / STARTING_ASPECT_RATIO + if (ctrlType.isBottomRightOrTopLeftCorner()) { + return PointF(delta.x, adjustedY) + } + return PointF(delta.x, -adjustedY) + } + // Respect y delta. + val adjustedX = if (orientation.isPortrait) + delta.y / STARTING_ASPECT_RATIO else + delta.y * STARTING_ASPECT_RATIO + if (ctrlType.isBottomRightOrTopLeftCorner()) { + return PointF(adjustedX, delta.y) + } + return PointF(-adjustedX, delta.y) + } + + private fun @receiver:CtrlType Int.isBottomRightOrTopLeftCorner(): Boolean { + return this == CTRL_TYPE_BOTTOM + CTRL_TYPE_RIGHT || this == CTRL_TYPE_TOP + CTRL_TYPE_LEFT + } + + private inner class CtrlCoordinateCapture(ctrl: Int, xValue: Float, yValue: Float) { + var ctrlType = ctrl + var x = xValue + var y = yValue + } + + companion object { + private val PORTRAIT_BOUNDS = Rect(100, 100, 200, 400) + private val LANDSCAPE_BOUNDS = Rect(100, 100, 400, 200) + private val STARTING_ASPECT_RATIO = PORTRAIT_BOUNDS.height() / PORTRAIT_BOUNDS.width() + private const val LARGE_DELTA = 50f + private const val SMALL_DELTA = 30f + + enum class Orientation( + val isPortrait: Boolean + ) { + PORTRAIT (true), + LANDSCAPE (false) + } + + enum class ResizeableOrNotResizingTestCases( + val ctrlType: Int, + val isResizeable: Boolean + ) { + NotResizing (CTRL_TYPE_UNDEFINED, false), + Resizeable (CTRL_TYPE_RIGHT, true) + } + + /** + * Tests cases for the start of a corner resize. + * @param ctrlType the control type of the corner the resize is initiated on. + */ + enum class CornerResizeStartTestCases( + val ctrlType: Int + ) { + BottomRightCorner (CTRL_TYPE_BOTTOM + CTRL_TYPE_RIGHT), + BottomLeftCorner (CTRL_TYPE_BOTTOM + CTRL_TYPE_LEFT), + TopRightCorner (CTRL_TYPE_TOP + CTRL_TYPE_RIGHT), + TopLeftCorner (CTRL_TYPE_TOP + CTRL_TYPE_LEFT) + } + + /** + * Tests cases for the moving and ending of a invalid corner resize. Where the compass point + * (e.g `SouthEast`) represents the direction of the drag. + * @param ctrlType the control type of the corner the resize is initiated on. + * @param dragDelta the delta of the attempted drag action, from the [ctrlType]'s + * corresponding corner point. Represented as a combination a different signed small and + * large deltas which correspond to the direction/angle of drag. + */ + enum class InvalidCornerResizeTestCases( + val ctrlType: Int, + val dragDelta: PointF + ) { + BottomRightCornerNorthEastDrag ( + CTRL_TYPE_BOTTOM + CTRL_TYPE_RIGHT, + PointF(LARGE_DELTA, -LARGE_DELTA)), + BottomRightCornerSouthWestDrag ( + CTRL_TYPE_BOTTOM + CTRL_TYPE_RIGHT, + PointF(-LARGE_DELTA, LARGE_DELTA)), + TopLeftCornerNorthEastDrag ( + CTRL_TYPE_TOP + CTRL_TYPE_LEFT, + PointF(LARGE_DELTA, -LARGE_DELTA)), + TopLeftCornerSouthWestDrag ( + CTRL_TYPE_TOP + CTRL_TYPE_LEFT, + PointF(-LARGE_DELTA, LARGE_DELTA)), + BottomLeftCornerSouthEastDrag ( + CTRL_TYPE_BOTTOM + CTRL_TYPE_LEFT, + PointF(LARGE_DELTA, LARGE_DELTA)), + BottomLeftCornerNorthWestDrag ( + CTRL_TYPE_BOTTOM + CTRL_TYPE_LEFT, + PointF(-LARGE_DELTA, -LARGE_DELTA)), + TopRightCornerSouthEastDrag ( + CTRL_TYPE_TOP + CTRL_TYPE_RIGHT, + PointF(LARGE_DELTA, LARGE_DELTA)), + TopRightCornerNorthWestDrag ( + CTRL_TYPE_TOP + CTRL_TYPE_RIGHT, + PointF(-LARGE_DELTA, -LARGE_DELTA)), + } + + /** + * Tests cases for the moving and ending of a valid corner resize. Where the compass point + * (e.g `SouthEast`) represents the direction of the drag, followed by the expected + * behaviour in that direction (i.e `RespectY` means the y delta will be respected whereas + * `RespectX` means the x delta will be respected). + * @param ctrlType the control type of the corner the resize is initiated on. + * @param dragDelta the delta of the attempted drag action, from the [ctrlType]'s + * corresponding corner point. Represented as a combination a different signed small and + * large deltas which correspond to the direction/angle of drag. + */ + enum class ValidCornerResizeTestCases( + val ctrlType: Int, + val dragDelta: PointF, + ) { + BottomRightCornerSouthEastDragRespectY ( + CTRL_TYPE_BOTTOM + CTRL_TYPE_RIGHT, + PointF(+LARGE_DELTA, SMALL_DELTA)), + BottomRightCornerSouthEastDragRespectX ( + CTRL_TYPE_BOTTOM + CTRL_TYPE_RIGHT, + PointF(SMALL_DELTA, LARGE_DELTA)), + BottomRightCornerNorthWestDragRespectY ( + CTRL_TYPE_BOTTOM + CTRL_TYPE_RIGHT, + PointF(-LARGE_DELTA, -SMALL_DELTA)), + BottomRightCornerNorthWestDragRespectX ( + CTRL_TYPE_BOTTOM + CTRL_TYPE_RIGHT, + PointF(-SMALL_DELTA, -LARGE_DELTA)), + TopLeftCornerSouthEastDragRespectY ( + CTRL_TYPE_TOP + CTRL_TYPE_LEFT, + PointF(LARGE_DELTA, SMALL_DELTA)), + TopLeftCornerSouthEastDragRespectX ( + CTRL_TYPE_TOP + CTRL_TYPE_LEFT, + PointF(SMALL_DELTA, LARGE_DELTA)), + TopLeftCornerNorthWestDragRespectY ( + CTRL_TYPE_TOP + CTRL_TYPE_LEFT, + PointF(-LARGE_DELTA, -SMALL_DELTA)), + TopLeftCornerNorthWestDragRespectX ( + CTRL_TYPE_TOP + CTRL_TYPE_LEFT, + PointF(-SMALL_DELTA, -LARGE_DELTA)), + BottomLeftCornerSouthWestDragRespectY ( + CTRL_TYPE_BOTTOM + CTRL_TYPE_LEFT, + PointF(-LARGE_DELTA, SMALL_DELTA)), + BottomLeftCornerSouthWestDragRespectX ( + CTRL_TYPE_BOTTOM + CTRL_TYPE_LEFT, + PointF(-SMALL_DELTA, LARGE_DELTA)), + BottomLeftCornerNorthEastDragRespectY ( + CTRL_TYPE_BOTTOM + CTRL_TYPE_LEFT, + PointF(LARGE_DELTA, -SMALL_DELTA)), + BottomLeftCornerNorthEastDragRespectX ( + CTRL_TYPE_BOTTOM + CTRL_TYPE_LEFT, + PointF(SMALL_DELTA, -LARGE_DELTA)), + TopRightCornerSouthWestDragRespectY ( + CTRL_TYPE_TOP + CTRL_TYPE_RIGHT, + PointF(-LARGE_DELTA, SMALL_DELTA)), + TopRightCornerSouthWestDragRespectX ( + CTRL_TYPE_TOP + CTRL_TYPE_RIGHT, + PointF(-SMALL_DELTA, LARGE_DELTA)), + TopRightCornerNorthEastDragRespectY ( + CTRL_TYPE_TOP + CTRL_TYPE_RIGHT, + PointF(LARGE_DELTA, -SMALL_DELTA)), + TopRightCornerNorthEastDragRespectX ( + CTRL_TYPE_TOP + CTRL_TYPE_RIGHT, + PointF(+SMALL_DELTA, -LARGE_DELTA)) + } + + /** + * Tests cases for the start of an edge resize. + * @param ctrlType the control type of the edge the resize is initiated on. + * @param additionalEdgeCtrlType the expected additional edge to be included in the ctrl + * type. + */ + enum class EdgeResizeStartTestCases( + val ctrlType: Int, + val additionalEdgeCtrlType: Int + ) { + BottomOfLeftEdgeResize (CTRL_TYPE_LEFT, CTRL_TYPE_BOTTOM), + TopOfLeftEdgeResize (CTRL_TYPE_LEFT, CTRL_TYPE_TOP), + BottomOfRightEdgeResize (CTRL_TYPE_RIGHT, CTRL_TYPE_BOTTOM), + TopOfRightEdgeResize (CTRL_TYPE_RIGHT, CTRL_TYPE_TOP), + RightOfTopEdgeResize (CTRL_TYPE_TOP, CTRL_TYPE_RIGHT), + LeftOfTopEdgeResize (CTRL_TYPE_TOP, CTRL_TYPE_LEFT), + RightOfBottomEdgeResize (CTRL_TYPE_BOTTOM, CTRL_TYPE_RIGHT), + LeftOfBottomEdgeResize (CTRL_TYPE_BOTTOM, CTRL_TYPE_LEFT) + } + + /** + * Tests cases for the moving and ending of an edge resize. + * @param ctrlType the control type of the edge the resize is initiated on. + * @param additionalEdgeCtrlType the expected additional edge to be included in the ctrl + * type. + * @param dragDelta the delta of the attempted drag action, from the [ctrlType]'s + * corresponding edge point. Represented as a combination a different signed small and + * large deltas which correspond to the direction/angle of drag. + */ + enum class EdgeResizeTestCases( + val ctrlType: Int, + val additionalEdgeCtrlType: Int, + val dragDelta: PointF + ) { + BottomOfLeftEdgeResize (CTRL_TYPE_LEFT, CTRL_TYPE_BOTTOM, PointF(-SMALL_DELTA, 0f)), + TopOfLeftEdgeResize (CTRL_TYPE_LEFT, CTRL_TYPE_TOP, PointF(-SMALL_DELTA, 0f)), + BottomOfRightEdgeResize (CTRL_TYPE_RIGHT, CTRL_TYPE_BOTTOM, PointF(SMALL_DELTA, 0f)), + TopOfRightEdgeResize (CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, PointF(SMALL_DELTA, 0f)), + RightOfTopEdgeResize (CTRL_TYPE_TOP, CTRL_TYPE_RIGHT, PointF(0f, -SMALL_DELTA)), + LeftOfTopEdgeResize (CTRL_TYPE_TOP, CTRL_TYPE_LEFT, PointF(0f, -SMALL_DELTA)), + RightOfBottomEdgeResize (CTRL_TYPE_BOTTOM, CTRL_TYPE_RIGHT, PointF(0f, SMALL_DELTA)), + LeftOfBottomEdgeResize (CTRL_TYPE_BOTTOM, CTRL_TYPE_LEFT, PointF(0f, SMALL_DELTA)) + } + } +} -- GitLab From 2082a209aabfa433044f4a2ebee76d2ea477cfcb Mon Sep 17 00:00:00 2001 From: Dennis Shen Date: Mon, 9 Sep 2024 19:25:22 +0000 Subject: [PATCH 034/388] Replace system_ext container with system Bug: b/365135457 Test: m Flag: EXEMPT refactor Change-Id: I9ed48711aa14ae6356ce949e6e53fbd22d6c4910 --- .../src/com/android/server/policy/feature/Android.bp | 2 +- .../android/server/policy/feature/device_state_flags.aconfig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp index 1db9e8d545e4..6393e11b7432 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp @@ -1,7 +1,7 @@ aconfig_declarations { name: "device_state_flags", package: "com.android.server.policy.feature.flags", - container: "system_ext", + container: "system", srcs: [ "device_state_flags.aconfig", ], diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig index f827b5508015..21e33dd1b99a 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig @@ -1,5 +1,5 @@ package: "com.android.server.policy.feature.flags" -container: "system_ext" +container: "system" flag { name: "enable_dual_display_blocking" -- GitLab From d720a4e372c11ec814a41007e9e9ef24f0814f06 Mon Sep 17 00:00:00 2001 From: Chris Poultney Date: Fri, 13 Sep 2024 09:14:56 -0400 Subject: [PATCH 035/388] Remove obsolete "next component" field from WallpaperData Justification: b/365991991#comment2 Fixes: 365991991 Flag: android.app.remove_next_wallpaper_component Test: See b/365991991#comment3 Change-Id: I488c75349269b61817f0ac624c6c97b21e15866d --- core/java/android/app/wallpaper.aconfig | 8 ++ .../server/wallpaper/WallpaperData.java | 3 + .../server/wallpaper/WallpaperDataParser.java | 38 +++++++--- .../wallpaper/WallpaperManagerService.java | 73 ++++++++++++------- 4 files changed, 86 insertions(+), 36 deletions(-) create mode 100644 core/java/android/app/wallpaper.aconfig diff --git a/core/java/android/app/wallpaper.aconfig b/core/java/android/app/wallpaper.aconfig new file mode 100644 index 000000000000..409162202b59 --- /dev/null +++ b/core/java/android/app/wallpaper.aconfig @@ -0,0 +1,8 @@ +package: "android.app" +container: "system" +flag { + name: "remove_next_wallpaper_component" + namespace: "systemui" + description: "Remove deprecated field WallpaperData#nextWallpaperComponent. Only effective after rebooting." + bug: "365991991" +} diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java index b792f7909fc8..a698429ff09e 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperData.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java @@ -80,8 +80,11 @@ class WallpaperData { */ ComponentName wallpaperComponent; + // TODO(b/347235611) Remove this field /** * The component name of the wallpaper that should be set next. + * + * @deprecated */ ComponentName nextWallpaperComponent; diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java index 4aefb54889aa..b15facb2945c 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java @@ -16,6 +16,7 @@ package com.android.server.wallpaper; +import static android.app.Flags.removeNextWallpaperComponent; import static android.app.WallpaperManager.FLAG_LOCK; import static android.app.WallpaperManager.FLAG_SYSTEM; import static android.app.WallpaperManager.ORIENTATION_UNKNOWN; @@ -187,13 +188,24 @@ public class WallpaperDataParser { } String comp = parser.getAttributeValue(null, "component"); - wallpaperToParse.nextWallpaperComponent = comp != null - ? ComponentName.unflattenFromString(comp) - : null; - if (wallpaperToParse.nextWallpaperComponent == null - || "android".equals(wallpaperToParse.nextWallpaperComponent - .getPackageName())) { - wallpaperToParse.nextWallpaperComponent = mImageWallpaper; + if (removeNextWallpaperComponent()) { + wallpaperToParse.wallpaperComponent = comp != null + ? ComponentName.unflattenFromString(comp) + : null; + if (wallpaperToParse.wallpaperComponent == null + || "android".equals(wallpaperToParse.wallpaperComponent + .getPackageName())) { + wallpaperToParse.wallpaperComponent = mImageWallpaper; + } + } else { + wallpaperToParse.nextWallpaperComponent = comp != null + ? ComponentName.unflattenFromString(comp) + : null; + if (wallpaperToParse.nextWallpaperComponent == null + || "android".equals(wallpaperToParse.nextWallpaperComponent + .getPackageName())) { + wallpaperToParse.nextWallpaperComponent = mImageWallpaper; + } } if (multiCrop()) { @@ -206,8 +218,12 @@ public class WallpaperDataParser { Slog.v(TAG, "cropRect:" + wallpaper.cropHint); Slog.v(TAG, "primaryColors:" + wallpaper.primaryColors); Slog.v(TAG, "mName:" + wallpaper.name); - Slog.v(TAG, "mNextWallpaperComponent:" - + wallpaper.nextWallpaperComponent); + if (removeNextWallpaperComponent()) { + Slog.v(TAG, "mWallpaperComponent:" + wallpaper.wallpaperComponent); + } else { + Slog.v(TAG, "mNextWallpaperComponent:" + + wallpaper.nextWallpaperComponent); + } } } } @@ -324,7 +340,9 @@ public class WallpaperDataParser { getAttributeInt(parser, "totalCropTop", 0), getAttributeInt(parser, "totalCropRight", 0), getAttributeInt(parser, "totalCropBottom", 0)); - if (multiCrop() && mImageWallpaper.equals(wallpaper.nextWallpaperComponent)) { + ComponentName componentName = removeNextWallpaperComponent() ? wallpaper.wallpaperComponent + : wallpaper.nextWallpaperComponent; + if (multiCrop() && mImageWallpaper.equals(componentName)) { wallpaper.mCropHints = new SparseArray<>(); for (Pair pair: screenDimensionPairs()) { Rect cropHint = new Rect( diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 4dcc6e112ecc..9a6b90b9e423 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -20,6 +20,7 @@ import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE; import static android.Manifest.permission.READ_WALLPAPER_INTERNAL; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; +import static android.app.Flags.removeNextWallpaperComponent; import static android.app.WallpaperManager.COMMAND_REAPPLY; import static android.app.WallpaperManager.FLAG_LOCK; import static android.app.WallpaperManager.FLAG_SYSTEM; @@ -1474,12 +1475,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } } - if (wallpaper.nextWallpaperComponent != null) { - int change = isPackageDisappearing(wallpaper.nextWallpaperComponent - .getPackageName()); - if (change == PACKAGE_PERMANENT_CHANGE - || change == PACKAGE_TEMPORARY_CHANGE) { - wallpaper.nextWallpaperComponent = null; + if (!removeNextWallpaperComponent()) { + if (wallpaper.nextWallpaperComponent != null) { + int change = isPackageDisappearing(wallpaper.nextWallpaperComponent + .getPackageName()); + if (change == PACKAGE_PERMANENT_CHANGE + || change == PACKAGE_TEMPORARY_CHANGE) { + wallpaper.nextWallpaperComponent = null; + } } } if (wallpaper.wallpaperComponent != null @@ -1494,14 +1497,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null); } } - if (wallpaper.nextWallpaperComponent != null - && isPackageModified(wallpaper.nextWallpaperComponent.getPackageName())) { - try { - mContext.getPackageManager().getServiceInfo(wallpaper.nextWallpaperComponent, - PackageManager.MATCH_DIRECT_BOOT_AWARE - | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); - } catch (NameNotFoundException e) { - wallpaper.nextWallpaperComponent = null; + if (!removeNextWallpaperComponent()) { + if (wallpaper.nextWallpaperComponent != null + && isPackageModified(wallpaper.nextWallpaperComponent.getPackageName())) { + try { + mContext.getPackageManager().getServiceInfo( + wallpaper.nextWallpaperComponent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); + } catch (NameNotFoundException e) { + wallpaper.nextWallpaperComponent = null; + } } } return changed; @@ -1628,7 +1634,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperData wallpaper = mWallpaperMap.get(UserHandle.USER_SYSTEM); // If we think we're going to be using the system image wallpaper imagery, make // sure we have something to render - if (mImageWallpaper.equals(wallpaper.nextWallpaperComponent)) { + boolean isImageComponent; + if (removeNextWallpaperComponent()) { + isImageComponent = wallpaper.wallpaperComponent == null + || mImageWallpaper.equals(wallpaper.wallpaperComponent); + } else { + isImageComponent = mImageWallpaper.equals(wallpaper.nextWallpaperComponent); + } + if (isImageComponent) { // No crop file? Make sure we've finished the processing sequence if necessary if (!wallpaper.cropExists()) { if (DEBUG) { @@ -1877,8 +1890,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if ((wallpaper.mWhich & FLAG_SYSTEM) != 0) mHomeWallpaperWaitingForUnlock = false; if ((wallpaper.mWhich & FLAG_LOCK) != 0) mLockWallpaperWaitingForUnlock = false; - final ComponentName cname = wallpaper.wallpaperComponent != null ? - wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent; + final ComponentName cname; + if (removeNextWallpaperComponent()) { + cname = wallpaper.wallpaperComponent; + } else { + cname = (wallpaper.wallpaperComponent != null) + ? wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent; + } if (!bindWallpaperComponentLocked(cname, true, false, wallpaper, reply)) { // We failed to bind the desired wallpaper, but that might // happen if the wallpaper isn't direct-boot aware @@ -1905,10 +1923,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } Slog.w(TAG, "Wallpaper isn't direct boot aware; using fallback until unlocked"); - // We might end up persisting the current wallpaper data - // while locked, so pretend like the component was actually - // bound into place - wallpaper.wallpaperComponent = wallpaper.nextWallpaperComponent; + if (!removeNextWallpaperComponent()) { + // We might end up persisting the current wallpaper data + // while locked, so pretend like the component was actually + // bound into place + wallpaper.wallpaperComponent = wallpaper.nextWallpaperComponent; + } final WallpaperData fallback = new WallpaperData(wallpaper.userId, wallpaper.mWhich); // files from the previous static wallpaper may still be stored in memory. @@ -3788,10 +3808,12 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper = mWallpaperMap.get(UserHandle.USER_SYSTEM); wallpaper.wallpaperId = makeWallpaperIdLocked(); // always bump id at restore wallpaper.allowBackup = true; // by definition if it was restored - if (wallpaper.nextWallpaperComponent != null - && !wallpaper.nextWallpaperComponent.equals(mImageWallpaper)) { + ComponentName componentName = + removeNextWallpaperComponent() ? wallpaper.wallpaperComponent + : wallpaper.nextWallpaperComponent; + if (componentName != null && !componentName.equals(mImageWallpaper)) { wallpaper.mBindSource = BindSource.RESTORE_SETTINGS_LIVE_SUCCESS; - if (!bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, false, false, + if (!bindWallpaperComponentLocked(componentName, false, false, wallpaper, null)) { // No such live wallpaper or other failure; fall back to the default // live wallpaper (since the profile being restored indicated that the @@ -3815,8 +3837,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (success) { mWallpaperCropper.generateCrop(wallpaper); // based on the new image + metadata wallpaper.mBindSource = BindSource.RESTORE_SETTINGS_STATIC; - bindWallpaperComponentLocked(wallpaper.nextWallpaperComponent, true, false, - wallpaper, null); + bindWallpaperComponentLocked(componentName, true, false, wallpaper, null); } } } -- GitLab From 21f0d639aa7d09031463c8e07e31fd267eec29ec Mon Sep 17 00:00:00 2001 From: Chris Poultney Date: Thu, 12 Sep 2024 09:31:46 -0400 Subject: [PATCH 036/388] Add accessor methods for current wallpaper component Bug: 347235611 Test: TBD Flag: EXEMPT refactor Change-Id: Iae9fd794f1e068ed742f95d19ff695c758f64a8b --- .../server/wallpaper/WallpaperData.java | 12 ++- .../server/wallpaper/WallpaperDataParser.java | 22 ++--- .../wallpaper/WallpaperManagerService.java | 93 +++++++++---------- .../WallpaperManagerServiceTests.java | 4 +- 4 files changed, 69 insertions(+), 62 deletions(-) diff --git a/services/core/java/com/android/server/wallpaper/WallpaperData.java b/services/core/java/com/android/server/wallpaper/WallpaperData.java index a698429ff09e..15f86e9c08ff 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperData.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperData.java @@ -78,7 +78,7 @@ class WallpaperData { /** * The component name of the currently set live wallpaper. */ - ComponentName wallpaperComponent; + private ComponentName mWallpaperComponent; // TODO(b/347235611) Remove this field /** @@ -195,7 +195,7 @@ class WallpaperData { */ WallpaperData(WallpaperData source) { this.userId = source.userId; - this.wallpaperComponent = source.wallpaperComponent; + this.mWallpaperComponent = source.mWallpaperComponent; this.mWhich = source.mWhich; this.wallpaperId = source.wallpaperId; this.cropHint.set(source.cropHint); @@ -230,6 +230,14 @@ class WallpaperData { return result; } + ComponentName getComponent() { + return mWallpaperComponent; + } + + void setComponent(ComponentName componentName) { + this.mWallpaperComponent = componentName; + } + @Override public String toString() { StringBuilder out = new StringBuilder(defaultString(this)); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java index b15facb2945c..e3e83b3e1fd7 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperDataParser.java @@ -189,13 +189,13 @@ public class WallpaperDataParser { String comp = parser.getAttributeValue(null, "component"); if (removeNextWallpaperComponent()) { - wallpaperToParse.wallpaperComponent = comp != null + wallpaperToParse.setComponent(comp != null ? ComponentName.unflattenFromString(comp) - : null; - if (wallpaperToParse.wallpaperComponent == null - || "android".equals(wallpaperToParse.wallpaperComponent + : null); + if (wallpaperToParse.getComponent() == null + || "android".equals(wallpaperToParse.getComponent() .getPackageName())) { - wallpaperToParse.wallpaperComponent = mImageWallpaper; + wallpaperToParse.setComponent(mImageWallpaper); } } else { wallpaperToParse.nextWallpaperComponent = comp != null @@ -219,7 +219,7 @@ public class WallpaperDataParser { Slog.v(TAG, "primaryColors:" + wallpaper.primaryColors); Slog.v(TAG, "mName:" + wallpaper.name); if (removeNextWallpaperComponent()) { - Slog.v(TAG, "mWallpaperComponent:" + wallpaper.wallpaperComponent); + Slog.v(TAG, "mWallpaperComponent:" + wallpaper.getComponent()); } else { Slog.v(TAG, "mNextWallpaperComponent:" + wallpaper.nextWallpaperComponent); @@ -340,7 +340,7 @@ public class WallpaperDataParser { getAttributeInt(parser, "totalCropTop", 0), getAttributeInt(parser, "totalCropRight", 0), getAttributeInt(parser, "totalCropBottom", 0)); - ComponentName componentName = removeNextWallpaperComponent() ? wallpaper.wallpaperComponent + ComponentName componentName = removeNextWallpaperComponent() ? wallpaper.getComponent() : wallpaper.nextWallpaperComponent; if (multiCrop() && mImageWallpaper.equals(componentName)) { wallpaper.mCropHints = new SparseArray<>(); @@ -480,7 +480,7 @@ public class WallpaperDataParser { out.startTag(null, tag); out.attributeInt(null, "id", wallpaper.wallpaperId); - if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) { + if (multiCrop() && mImageWallpaper.equals(wallpaper.getComponent())) { if (wallpaper.mCropHints == null) { Slog.e(TAG, "cropHints should not be null when saved"); wallpaper.mCropHints = new SparseArray<>(); @@ -580,10 +580,10 @@ public class WallpaperDataParser { } out.attribute(null, "name", wallpaper.name); - if (wallpaper.wallpaperComponent != null - && !wallpaper.wallpaperComponent.equals(mImageWallpaper)) { + if (wallpaper.getComponent() != null + && !wallpaper.getComponent().equals(mImageWallpaper)) { out.attribute(null, "component", - wallpaper.wallpaperComponent.flattenToShortString()); + wallpaper.getComponent().flattenToShortString()); } if (wallpaper.allowBackup) { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 9a6b90b9e423..6b78d06a9a8b 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -276,7 +276,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final boolean isMigration = moved && lockWallpaperChanged; final boolean isRestore = moved && !isMigration; final boolean isAppliedToLock = (wallpaper.mWhich & FLAG_LOCK) != 0; - final boolean needsUpdate = wallpaper.wallpaperComponent == null + final boolean needsUpdate = wallpaper.getComponent() == null || event != CLOSE_WRITE // includes the MOVED_TO case || wallpaper.imageWallpaperPending; @@ -527,7 +527,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub * @return true unless the wallpaper changed during the color computation */ private boolean extractColors(WallpaperData wallpaper) { - if (offloadColorExtraction()) return !mImageWallpaper.equals(wallpaper.wallpaperComponent); + if (offloadColorExtraction()) return !mImageWallpaper.equals(wallpaper.getComponent()); String cropFile = null; boolean defaultImageWallpaper = false; int wallpaperId; @@ -550,8 +550,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub synchronized (mLock) { // Not having a wallpaperComponent means it's a lock screen wallpaper. - final boolean imageWallpaper = mImageWallpaper.equals(wallpaper.wallpaperComponent) - || wallpaper.wallpaperComponent == null; + final boolean imageWallpaper = mImageWallpaper.equals(wallpaper.getComponent()) + || wallpaper.getComponent() == null; if (imageWallpaper && wallpaper.getCropFile().exists()) { cropFile = wallpaper.getCropFile().getAbsolutePath(); } else if (imageWallpaper && !wallpaper.cropExists() && !wallpaper.sourceExists()) { @@ -824,13 +824,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG); - t.traceBegin("WPMS.connectLocked-" + wallpaper.wallpaperComponent); + t.traceBegin("WPMS.connectLocked-" + wallpaper.getComponent()); if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken); mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId, null /* options */); mWindowManagerInternal.setWallpaperShowWhenLocked( mToken, (wallpaper.mWhich & FLAG_LOCK) != 0); - if (multiCrop() && mImageWallpaper.equals(wallpaper.wallpaperComponent)) { + if (multiCrop() && mImageWallpaper.equals(wallpaper.getComponent())) { mWindowManagerInternal.setWallpaperCropHints(mToken, mWallpaperCropper.getRelativeCropHints(wallpaper)); } else { @@ -906,7 +906,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId) { - Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.wallpaperComponent + Slog.w(TAG, "Wallpaper reconnect timed out for " + mWallpaper.getComponent() + ", reverting to built-in wallpaper!"); clearWallpaperLocked(mWallpaper.mWhich, mWallpaper.userId, false, null); } @@ -1035,9 +1035,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public void onServiceDisconnected(ComponentName name) { synchronized (mLock) { Slog.w(TAG, "Wallpaper service gone: " + name); - if (!Objects.equals(name, mWallpaper.wallpaperComponent)) { + if (!Objects.equals(name, mWallpaper.getComponent())) { Slog.e(TAG, "Does not match expected wallpaper component " - + mWallpaper.wallpaperComponent); + + mWallpaper.getComponent()); } mService = null; forEachDisplayConnector(connector -> connector.mEngine = null); @@ -1065,7 +1065,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub fgHandler.postDelayed(mResetRunnable, WALLPAPER_RECONNECT_TIMEOUT_MS); if (DEBUG_LIVE) { Slog.i(TAG, - "Started wallpaper reconnect timeout for " + mWallpaper.wallpaperComponent); + "Started wallpaper reconnect timeout for " + mWallpaper.getComponent()); } } @@ -1081,7 +1081,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } - final ComponentName wpService = mWallpaper.wallpaperComponent; + final ComponentName wpService = mWallpaper.getComponent(); // The broadcast of package update could be delayed after service disconnected. Try // to re-bind the service for 10 seconds. mWallpaper.mBindSource = BindSource.CONNECTION_TRY_TO_REBIND; @@ -1110,7 +1110,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // The wallpaper disappeared. If this isn't a system-default one, track // crashes and fall back to default if it continues to misbehave. if (this == mWallpaper.connection) { - final ComponentName wpService = mWallpaper.wallpaperComponent; + final ComponentName wpService = mWallpaper.getComponent(); if (!mWallpaper.wallpaperUpdating && mWallpaper.userId == mCurrentUserId && !Objects.equals(mDefaultWallpaperComponent, wpService) @@ -1188,7 +1188,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub synchronized (mLock) { // Do not broadcast changes on ImageWallpaper since it's handled // internally by this class. - boolean isImageWallpaper = mImageWallpaper.equals(mWallpaper.wallpaperComponent); + boolean isImageWallpaper = mImageWallpaper.equals(mWallpaper.getComponent()); if (isImageWallpaper && (!offloadColorExtraction() || primaryColors == null)) { return; } @@ -1303,7 +1303,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (mNewWallpaper.mWhich == FLAG_SYSTEM) { // New wp is system only, so old system+lock is now lock only final boolean originalIsStatic = mImageWallpaper.equals( - mOriginalSystem.wallpaperComponent); + mOriginalSystem.getComponent()); if (originalIsStatic) { // Static wp: image file rename has already been tried via // migrateStaticSystemToLockWallpaperLocked() and added to the lock wp map @@ -1314,8 +1314,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (DEBUG) { Slog.v(TAG, "static system+lock to system success"); } - lockWp.wallpaperComponent = - mOriginalSystem.wallpaperComponent; + lockWp.setComponent(mOriginalSystem.getComponent()); lockWp.connection = mOriginalSystem.connection; lockWp.connection.mWallpaper = lockWp; mOriginalSystem.mWhich = FLAG_LOCK; @@ -1376,7 +1375,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } for (WallpaperData wallpaper: getWallpapers()) { - final ComponentName wpService = wallpaper.wallpaperComponent; + final ComponentName wpService = wallpaper.getComponent(); if (wpService != null && wpService.getPackageName().equals(packageName)) { if (DEBUG_LIVE) { Slog.i(TAG, "Wallpaper " + wpService + " update has finished"); @@ -1402,8 +1401,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } for (WallpaperData wallpaper: getWallpapers()) { - if (wallpaper.wallpaperComponent != null - && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) { + if (wallpaper.getComponent() != null + && wallpaper.getComponent().getPackageName().equals(packageName)) { doPackagesChangedLocked(true, wallpaper); } } @@ -1417,10 +1416,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return; } for (WallpaperData wallpaper: getWallpapers()) { - if (wallpaper.wallpaperComponent != null - && wallpaper.wallpaperComponent.getPackageName().equals(packageName)) { + if (wallpaper.getComponent() != null + && wallpaper.getComponent().getPackageName().equals(packageName)) { if (DEBUG_LIVE) { - Slog.i(TAG, "Wallpaper service " + wallpaper.wallpaperComponent + Slog.i(TAG, "Wallpaper service " + wallpaper.getComponent() + " is updating"); } wallpaper.wallpaperUpdating = true; @@ -1462,15 +1461,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub boolean doPackagesChangedLocked(boolean doit, WallpaperData wallpaper) { boolean changed = false; - if (wallpaper.wallpaperComponent != null) { - int change = isPackageDisappearing(wallpaper.wallpaperComponent + if (wallpaper.getComponent() != null) { + int change = isPackageDisappearing(wallpaper.getComponent() .getPackageName()); if (change == PACKAGE_PERMANENT_CHANGE || change == PACKAGE_TEMPORARY_CHANGE) { changed = true; if (doit) { Slog.w(TAG, "Wallpaper uninstalled, removing: " - + wallpaper.wallpaperComponent); + + wallpaper.getComponent()); clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null); } } @@ -1485,15 +1484,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } } - if (wallpaper.wallpaperComponent != null - && isPackageModified(wallpaper.wallpaperComponent.getPackageName())) { + if (wallpaper.getComponent() != null + && isPackageModified(wallpaper.getComponent().getPackageName())) { try { - mContext.getPackageManager().getServiceInfo(wallpaper.wallpaperComponent, + mContext.getPackageManager().getServiceInfo(wallpaper.getComponent(), PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE); } catch (NameNotFoundException e) { Slog.w(TAG, "Wallpaper component gone, removing: " - + wallpaper.wallpaperComponent); + + wallpaper.getComponent()); clearWallpaperLocked(wallpaper.mWhich, wallpaper.userId, false, null); } } @@ -1636,8 +1635,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // sure we have something to render boolean isImageComponent; if (removeNextWallpaperComponent()) { - isImageComponent = wallpaper.wallpaperComponent == null - || mImageWallpaper.equals(wallpaper.wallpaperComponent); + isImageComponent = wallpaper.getComponent() == null + || mImageWallpaper.equals(wallpaper.getComponent()); } else { isImageComponent = mImageWallpaper.equals(wallpaper.nextWallpaperComponent); } @@ -1892,10 +1891,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final ComponentName cname; if (removeNextWallpaperComponent()) { - cname = wallpaper.wallpaperComponent; + cname = wallpaper.getComponent(); } else { - cname = (wallpaper.wallpaperComponent != null) - ? wallpaper.wallpaperComponent : wallpaper.nextWallpaperComponent; + cname = (wallpaper.getComponent() != null) + ? wallpaper.getComponent() : wallpaper.nextWallpaperComponent; } if (!bindWallpaperComponentLocked(cname, true, false, wallpaper, reply)) { // We failed to bind the desired wallpaper, but that might @@ -1927,7 +1926,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // We might end up persisting the current wallpaper data // while locked, so pretend like the component was actually // bound into place - wallpaper.wallpaperComponent = wallpaper.nextWallpaperComponent; + wallpaper.setComponent(wallpaper.nextWallpaperComponent); } final WallpaperData fallback = new WallpaperData(wallpaper.userId, wallpaper.mWhich); @@ -2004,7 +2003,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub // lock only case: set the system wallpaper component to both screens if (which == FLAG_LOCK) { - component = wallpaper.wallpaperComponent; + component = wallpaper.getComponent(); finalWhich = FLAG_LOCK | FLAG_SYSTEM; } else { component = null; @@ -2304,7 +2303,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub checkPermission(READ_WALLPAPER_INTERNAL); WallpaperData wallpaper = (which == FLAG_LOCK) ? mLockWallpaperMap.get(userId) : mWallpaperMap.get(userId); - if (wallpaper == null || !mImageWallpaper.equals(wallpaper.wallpaperComponent)) { + if (wallpaper == null || !mImageWallpaper.equals(wallpaper.getComponent())) { return null; } SparseArray relativeSuggestedCrops = @@ -2738,7 +2737,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub WallpaperData wallpaperData = (which == FLAG_LOCK ? mLockWallpaperMap : mWallpaperMap) .get(mCurrentUserId); if (wallpaperData == null) return false; - return mImageWallpaper.equals(wallpaperData.wallpaperComponent); + return mImageWallpaper.equals(wallpaperData.getComponent()); } } @@ -2974,7 +2973,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub final WallpaperData originalSystemWallpaper = mWallpaperMap.get(userId); final boolean systemIsStatic = originalSystemWallpaper != null && mImageWallpaper.equals( - originalSystemWallpaper.wallpaperComponent); + originalSystemWallpaper.getComponent()); final boolean systemIsBoth = mLockWallpaperMap.get(userId) == null; /* If we're setting system but not lock, and lock is currently sharing the system @@ -3168,7 +3167,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); } final boolean systemIsStatic = mImageWallpaper.equals( - originalSystemWallpaper.wallpaperComponent); + originalSystemWallpaper.getComponent()); final boolean systemIsBoth = mLockWallpaperMap.get(userId) == null; if (which == FLAG_SYSTEM && systemIsBoth && systemIsStatic) { @@ -3190,7 +3189,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub liveSync = new WallpaperDestinationChangeHandler( newWallpaper); boolean same = changingToSame(name, newWallpaper.connection, - newWallpaper.wallpaperComponent); + newWallpaper.getComponent()); /* * If we have a shared system+lock wallpaper, and we reapply the same wallpaper @@ -3221,7 +3220,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } boolean lockBitmapCleared = false; - if (!mImageWallpaper.equals(newWallpaper.wallpaperComponent)) { + if (!mImageWallpaper.equals(newWallpaper.getComponent())) { clearWallpaperBitmaps(newWallpaper); lockBitmapCleared = newWallpaper.mWhich == FLAG_LOCK; } @@ -3302,7 +3301,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } // Has the component changed? if (!force && changingToSame(componentName, wallpaper.connection, - wallpaper.wallpaperComponent)) { + wallpaper.getComponent())) { try { if (DEBUG_LIVE) { Slog.v(TAG, "Changing to the same component, ignoring"); @@ -3439,7 +3438,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub return false; } maybeDetachLastWallpapers(wallpaper); - wallpaper.wallpaperComponent = componentName; + wallpaper.setComponent(componentName); wallpaper.connection = newConn; newConn.mReply = reply; updateCurrentWallpapers(wallpaper); @@ -3564,7 +3563,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } private void clearWallpaperComponentLocked(WallpaperData wallpaper) { - wallpaper.wallpaperComponent = null; + wallpaper.setComponent(null); detachWallpaperLocked(wallpaper); } @@ -3809,7 +3808,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub wallpaper.wallpaperId = makeWallpaperIdLocked(); // always bump id at restore wallpaper.allowBackup = true; // by definition if it was restored ComponentName componentName = - removeNextWallpaperComponent() ? wallpaper.wallpaperComponent + removeNextWallpaperComponent() ? wallpaper.getComponent() : wallpaper.nextWallpaperComponent; if (componentName != null && !componentName.equals(mImageWallpaper)) { wallpaper.mBindSource = BindSource.RESTORE_SETTINGS_LIVE_SUCCESS; @@ -3885,7 +3884,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (multiCrop()) pw.print(" mCropHints="); pw.println(wallpaper.mCropHints); pw.print(" mName="); pw.println(wallpaper.name); pw.print(" mAllowBackup="); pw.println(wallpaper.allowBackup); - pw.print(" mWallpaperComponent="); pw.println(wallpaper.wallpaperComponent); + pw.print(" mWallpaperComponent="); pw.println(wallpaper.getComponent()); pw.print(" mWallpaperDimAmount="); pw.println(wallpaper.mWallpaperDimAmount); pw.print(" isColorExtracted="); pw.println(wallpaper.mIsColorExtractedFromDim); pw.println(" mUidToDimAmount:"); diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java index 15ae4634b573..0b762df86df9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java @@ -290,7 +290,7 @@ public class WallpaperManagerServiceTests { final WallpaperData fallbackData = mService.mFallbackWallpaper; assertEquals("Fallback wallpaper component should be ImageWallpaper.", - sImageWallpaperComponentName, fallbackData.wallpaperComponent); + sImageWallpaperComponentName, fallbackData.getComponent()); verifyLastWallpaperData(USER_SYSTEM, sDefaultWallpaperComponent); verifyDisplayData(); @@ -580,7 +580,7 @@ public class WallpaperManagerServiceTests { final WallpaperData lastData = mService.mLastWallpaper; assertNotNull("Last wallpaper must not be null", lastData); assertEquals("Last wallpaper component must be equals.", expectedComponent, - lastData.wallpaperComponent); + lastData.getComponent()); assertEquals("The user id in last wallpaper should be the last switched user", lastUserId, lastData.userId); assertNotNull("Must exist user data connection on last wallpaper data", -- GitLab From 584694d9df836bf2317208df4565b1815b085417 Mon Sep 17 00:00:00 2001 From: Chris Poultney Date: Fri, 13 Sep 2024 13:36:21 +0000 Subject: [PATCH 037/388] Add wallpaper team as OWNERS for wallpaper aconfig Bug: 365991991 Change-Id: If34ed61991278ab877952a758aae066c5b4df3e7 Test: builds with `m` --- core/java/android/app/OWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index adeb0451cd43..cd7e40cf174d 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -112,6 +112,7 @@ per-file *VoiceInteract* = file:/core/java/android/service/voice/OWNERS # Wallpaper per-file *Wallpaper* = file:/core/java/android/service/wallpaper/OWNERS +per-file wallpaper.aconfig = file:/core/java/android/service/wallpaper/OWNERS # WindowManager per-file *Activity* = file:/services/core/java/com/android/server/wm/OWNERS -- GitLab From cca7e057e364acdc881f041f53e251fb6793d07e Mon Sep 17 00:00:00 2001 From: Paul Duffin Date: Fri, 13 Sep 2024 15:05:21 +0100 Subject: [PATCH 038/388] Ensure consistent representation of field deprecated status Previously, fields that were inherited from a hidden class into a deprecated class were treated differently to its own fields. The former were not marked as `@Deprecated` but the latter were. That behavior has been corrected and this change updates the signature files accordingly. Bug: 366411703 Test: m checkapi Change-Id: I7d1975e73a9ede9198ba2c1a41f868c07dcf1d99 --- core/api/current.txt | 46 ++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index 245d12df8eca..a9ee34feaa2f 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -20396,23 +20396,23 @@ package android.hardware.fingerprint { method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.USE_BIOMETRIC, android.Manifest.permission.USE_FINGERPRINT}) public void authenticate(@Nullable android.hardware.fingerprint.FingerprintManager.CryptoObject, @Nullable android.os.CancellationSignal, int, @NonNull android.hardware.fingerprint.FingerprintManager.AuthenticationCallback, @Nullable android.os.Handler); method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean hasEnrolledFingerprints(); method @Deprecated @RequiresPermission(android.Manifest.permission.USE_FINGERPRINT) public boolean isHardwareDetected(); - field public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0 - field public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3 - field public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2 - field public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1 - field public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5 - field public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4 - field public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5 - field public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc - field public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1 - field public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7 - field public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9 - field public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb - field public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4 - field public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3 - field public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2 - field public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; // 0xa - field public static final int FINGERPRINT_ERROR_VENDOR = 8; // 0x8 + field @Deprecated public static final int FINGERPRINT_ACQUIRED_GOOD = 0; // 0x0 + field @Deprecated public static final int FINGERPRINT_ACQUIRED_IMAGER_DIRTY = 3; // 0x3 + field @Deprecated public static final int FINGERPRINT_ACQUIRED_INSUFFICIENT = 2; // 0x2 + field @Deprecated public static final int FINGERPRINT_ACQUIRED_PARTIAL = 1; // 0x1 + field @Deprecated public static final int FINGERPRINT_ACQUIRED_TOO_FAST = 5; // 0x5 + field @Deprecated public static final int FINGERPRINT_ACQUIRED_TOO_SLOW = 4; // 0x4 + field @Deprecated public static final int FINGERPRINT_ERROR_CANCELED = 5; // 0x5 + field @Deprecated public static final int FINGERPRINT_ERROR_HW_NOT_PRESENT = 12; // 0xc + field @Deprecated public static final int FINGERPRINT_ERROR_HW_UNAVAILABLE = 1; // 0x1 + field @Deprecated public static final int FINGERPRINT_ERROR_LOCKOUT = 7; // 0x7 + field @Deprecated public static final int FINGERPRINT_ERROR_LOCKOUT_PERMANENT = 9; // 0x9 + field @Deprecated public static final int FINGERPRINT_ERROR_NO_FINGERPRINTS = 11; // 0xb + field @Deprecated public static final int FINGERPRINT_ERROR_NO_SPACE = 4; // 0x4 + field @Deprecated public static final int FINGERPRINT_ERROR_TIMEOUT = 3; // 0x3 + field @Deprecated public static final int FINGERPRINT_ERROR_UNABLE_TO_PROCESS = 2; // 0x2 + field @Deprecated public static final int FINGERPRINT_ERROR_USER_CANCELED = 10; // 0xa + field @Deprecated public static final int FINGERPRINT_ERROR_VENDOR = 8; // 0x8 } @Deprecated public abstract static class FingerprintManager.AuthenticationCallback { @@ -36252,9 +36252,9 @@ package android.provider { method @Deprecated public static int getTypeLabelResource(int); field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/im"; field @Deprecated public static final String CUSTOM_PROTOCOL = "data6"; - field public static final String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX"; - field public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS"; - field public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES"; + field @Deprecated public static final String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX"; + field @Deprecated public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS"; + field @Deprecated public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES"; field @Deprecated public static final String PROTOCOL = "data5"; field @Deprecated public static final int PROTOCOL_AIM = 0; // 0x0 field @Deprecated public static final int PROTOCOL_CUSTOM = -1; // 0xffffffff @@ -36387,9 +36387,9 @@ package android.provider { method @Deprecated public static CharSequence getTypeLabel(android.content.res.Resources, int, @Nullable CharSequence); method @Deprecated public static int getTypeLabelResource(int); field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sip_address"; - field public static final String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX"; - field public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS"; - field public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES"; + field @Deprecated public static final String EXTRA_ADDRESS_BOOK_INDEX = "android.provider.extra.ADDRESS_BOOK_INDEX"; + field @Deprecated public static final String EXTRA_ADDRESS_BOOK_INDEX_COUNTS = "android.provider.extra.ADDRESS_BOOK_INDEX_COUNTS"; + field @Deprecated public static final String EXTRA_ADDRESS_BOOK_INDEX_TITLES = "android.provider.extra.ADDRESS_BOOK_INDEX_TITLES"; field @Deprecated public static final String SIP_ADDRESS = "data1"; field @Deprecated public static final int TYPE_HOME = 1; // 0x1 field @Deprecated public static final int TYPE_OTHER = 3; // 0x3 -- GitLab From 87a167d82f6998bd1ce9cda1d0d0ee0699beb89e Mon Sep 17 00:00:00 2001 From: Azhara Assanova Date: Fri, 13 Sep 2024 11:36:13 +0000 Subject: [PATCH 039/388] Migrate PermissionAnnotationDetector to global lint checks Cherry-picked from internal branch. Originally authored by tmagirescu@google.com. PermissionAnnotationDetector will no longer be a local lint check which could optionally be added to build targets, but instead a global lint check ran on every build. New AIDL Interfaces part of the system_server process will need to use @EnforcePermission annotations. The detector does not enforce old AIDL Interfaces to use the annotations. These are included in the exemptAidlInterfaces set generated by ExemptAidlInterfacesGenerator. The CL removes the reference to the PermissionAnnotationDetector from the Accessibility Service build targets. Instead the already-annotated AIDL Interfaces are removed from exemptAidlInterfaces, achieving the same effect. Bug: 363248121 Test: PermissionAnnotationDetectorTest Flag: EXEMPT lint check Merged-In: I2cad77cbc7883087dd95b9558d3543fcb321bbc8 Change-Id: I47873ec83d8f26a97f1f6ff70a056899dd9c5f45 --- services/accessibility/Android.bp | 5 - .../AccessibilityInputFilter.java | 1 - .../FingerprintGestureDispatcher.java | 1 - .../lint/aidl/EnforcePermissionUtils.kt | 23 ++ .../lint/AndroidFrameworkIssueRegistry.kt | 2 - .../lint/PermissionAnnotationDetectorTest.kt | 134 ---------- .../lint/AndroidGlobalIssueRegistry.kt | 2 + .../android/lint/aidl/ExemptAidlInterfaces.kt | 8 - .../aidl}/PermissionAnnotationDetector.kt | 9 +- .../aidl/PermissionAnnotationDetectorTest.kt | 230 ++++++++++++++++++ .../aidl/ExemptAidlInterfacesGenerator.kt | 15 +- 11 files changed, 263 insertions(+), 167 deletions(-) delete mode 100644 tools/lint/framework/checks/src/test/java/com/google/android/lint/PermissionAnnotationDetectorTest.kt rename tools/lint/{framework/checks/src/main/java/com/google/android/lint => global/checks/src/main/java/com/google/android/lint/aidl}/PermissionAnnotationDetector.kt (92%) create mode 100644 tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp index 7a99b605c4fb..8ea8c9a40a0c 100644 --- a/services/accessibility/Android.bp +++ b/services/accessibility/Android.bp @@ -19,11 +19,6 @@ java_library_static { defaults: [ "platform_service_defaults", ], - lint: { - error_checks: ["MissingPermissionAnnotation"], - baseline_filename: "lint-baseline.xml", - - }, srcs: [ ":services.accessibility-sources", "//frameworks/base/packages/SettingsLib/RestrictedLockUtils:SettingsLibRestrictedLockUtilsSrc", diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java index f9196f3e0e0e..754965533d0f 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java @@ -62,7 +62,6 @@ import java.util.StringJoiner; * * NOTE: This class has to be created and poked only from the main thread. */ -@SuppressWarnings("MissingPermissionAnnotation") class AccessibilityInputFilter extends InputFilter implements EventStreamTransformation { private static final String TAG = AccessibilityInputFilter.class.getSimpleName(); diff --git a/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java b/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java index e10e87c51d59..c9ec16edc54e 100644 --- a/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java +++ b/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java @@ -33,7 +33,6 @@ import java.util.List; /** * Encapsulate fingerprint gesture logic */ -@SuppressWarnings("MissingPermissionAnnotation") public class FingerprintGestureDispatcher extends IFingerprintClientActiveCallback.Stub implements Handler.Callback{ private static final int MSG_REGISTER = 1; diff --git a/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt b/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt index f5af99ec39ac..b79563f740ee 100644 --- a/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt +++ b/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt @@ -103,3 +103,26 @@ fun getHelperMethodFix( return fix.build() } + +/** + * PermissionAnnotationDetector uses this method to determine whether a specific file should be + * checked for unannotated methods. Only files located in directories whose paths begin with one + * of these prefixes will be considered. + */ +fun isSystemServicePath(context: JavaContext): Boolean { + val systemServicePathPrefixes = setOf( + "frameworks/base/services", + "frameworks/base/apex", + "frameworks/opt/wear", + "packages/modules" + ) + + val filePath = context.file.path + + // We perform `filePath.contains` instead of `filePath.startsWith` since getting the + // relative path of a source file is non-trivial. That is because `context.file.path` + // returns the path to where soong builds the file (i.e. /out/soong/...). Moreover, the + // logic to extract the relative path would need to consider several /out/soong/... + // locations patterns. + return systemServicePathPrefixes.any { filePath.contains(it) } +} diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt index 5c6469706e18..af753e5963a3 100644 --- a/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt +++ b/tools/lint/framework/checks/src/main/java/com/google/android/lint/AndroidFrameworkIssueRegistry.kt @@ -20,7 +20,6 @@ import com.android.tools.lint.client.api.IssueRegistry import com.android.tools.lint.client.api.Vendor import com.android.tools.lint.detector.api.CURRENT_API import com.google.android.lint.parcel.SaferParcelChecker -import com.google.android.lint.aidl.PermissionAnnotationDetector import com.google.auto.service.AutoService @AutoService(IssueRegistry::class) @@ -38,7 +37,6 @@ class AndroidFrameworkIssueRegistry : IssueRegistry() { SaferParcelChecker.ISSUE_UNSAFE_API_USAGE, // TODO: Currently crashes due to OOM issue // PackageVisibilityDetector.ISSUE_PACKAGE_NAME_NO_PACKAGE_VISIBILITY_FILTERS, - PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION, PermissionMethodDetector.ISSUE_PERMISSION_METHOD_USAGE, PermissionMethodDetector.ISSUE_CAN_BE_PERMISSION_METHOD, FeatureAutomotiveDetector.ISSUE, diff --git a/tools/lint/framework/checks/src/test/java/com/google/android/lint/PermissionAnnotationDetectorTest.kt b/tools/lint/framework/checks/src/test/java/com/google/android/lint/PermissionAnnotationDetectorTest.kt deleted file mode 100644 index bce848a2e3a7..000000000000 --- a/tools/lint/framework/checks/src/test/java/com/google/android/lint/PermissionAnnotationDetectorTest.kt +++ /dev/null @@ -1,134 +0,0 @@ -/* - * 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.google.android.lint.aidl - -import com.android.tools.lint.checks.infrastructure.LintDetectorTest -import com.android.tools.lint.checks.infrastructure.TestFile -import com.android.tools.lint.checks.infrastructure.TestLintTask -import com.android.tools.lint.detector.api.Detector -import com.android.tools.lint.detector.api.Issue - -@Suppress("UnstableApiUsage") -class PermissionAnnotationDetectorTest : LintDetectorTest() { - override fun getDetector(): Detector = PermissionAnnotationDetector() - - override fun getIssues(): List = listOf( - PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION, - ) - - override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) - - /** No issue scenario */ - - fun testDoesNotDetectIssuesInCorrectScenario() { - lint().files( - java( - """ - public class Foo extends IFoo.Stub { - @Override - @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") - public void testMethod() { } - } - """ - ).indented(), - *stubs - ) - .run() - .expectClean() - } - - fun testMissingAnnotation() { - lint().files( - java( - """ - public class Bar extends IBar.Stub { - public void testMethod() { } - } - """ - ).indented(), - *stubs - ) - .run() - .expect( - """ - src/Bar.java:2: Error: The method testMethod is not permission-annotated. [MissingPermissionAnnotation] - public void testMethod() { } - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 1 errors, 0 warnings - """ - ) - } - - fun testNoIssueWhenExtendingWithAnotherSubclass() { - lint().files( - java( - """ - public class Foo extends IFoo.Stub { - @Override - @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) - public void testMethod() { } - // not an AIDL method, just another method - public void someRandomMethod() { } - } - """).indented(), - java( - """ - public class Baz extends Bar { - @Override - public void someRandomMethod() { } - } - """).indented(), - *stubs - ) - .run() - .expectClean() - } - - /* Stubs */ - - // A service with permission annotation on the method. - private val interfaceIFoo: TestFile = java( - """ - public interface IFoo extends android.os.IInterface { - public static abstract class Stub extends android.os.Binder implements IFoo { - } - @Override - @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) - public void testMethod(); - @Override - @android.annotation.RequiresNoPermission - public void testMethodNoPermission(); - @Override - @android.annotation.PermissionManuallyEnforced - public void testMethodManual(); - } - """ - ).indented() - - // A service with no permission annotation. - private val interfaceIBar: TestFile = java( - """ - public interface IBar extends android.os.IInterface { - public static abstract class Stub extends android.os.Binder implements IBar { - } - public void testMethod(); - } - """ - ).indented() - - private val stubs = arrayOf(interfaceIFoo, interfaceIBar) -} diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt index 28eab8f62e74..290e7be9f6c4 100644 --- a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt @@ -20,6 +20,7 @@ import com.android.tools.lint.client.api.IssueRegistry import com.android.tools.lint.client.api.Vendor import com.android.tools.lint.detector.api.CURRENT_API import com.google.android.lint.aidl.EnforcePermissionDetector +import com.google.android.lint.aidl.PermissionAnnotationDetector import com.google.android.lint.aidl.SimpleManualPermissionEnforcementDetector import com.google.auto.service.AutoService @@ -31,6 +32,7 @@ class AndroidGlobalIssueRegistry : IssueRegistry() { EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION, EnforcePermissionDetector.ISSUE_ENFORCE_PERMISSION_HELPER, EnforcePermissionDetector.ISSUE_MISUSING_ENFORCE_PERMISSION, + PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION, SimpleManualPermissionEnforcementDetector.ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT, ) diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfaces.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfaces.kt index 8777712b0f04..675a59e6ae3e 100644 --- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfaces.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfaces.kt @@ -20,12 +20,8 @@ package com.google.android.lint.aidl * The exemptAidlInterfaces set was generated by running ExemptAidlInterfacesGenerator on the * entire source tree. To reproduce the results, run generate-exempt-aidl-interfaces.sh * located in tools/lint/utils. - * - * TODO: b/363248121 - Use the exemptAidlInterfaces set inside PermissionAnnotationDetector when it - * gets migrated to a global lint check. */ val exemptAidlInterfaces = setOf( - "android.accessibilityservice.IAccessibilityServiceConnection", "android.accessibilityservice.IBrailleDisplayConnection", "android.accounts.IAccountAuthenticatorResponse", "android.accounts.IAccountManager", @@ -663,10 +659,6 @@ val exemptAidlInterfaces = setOf( "android.uwb.IUwbOemExtensionCallback", "android.uwb.IUwbRangingCallbacks", "android.uwb.IUwbVendorUciCallback", - "android.view.accessibility.IAccessibilityInteractionConnectionCallback", - "android.view.accessibility.IAccessibilityManager", - "android.view.accessibility.IMagnificationConnectionCallback", - "android.view.accessibility.IRemoteMagnificationAnimationCallback", "android.view.autofill.IAutoFillManager", "android.view.autofill.IAutofillWindowPresenter", "android.view.contentcapture.IContentCaptureManager", diff --git a/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionAnnotationDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/PermissionAnnotationDetector.kt similarity index 92% rename from tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionAnnotationDetector.kt rename to tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/PermissionAnnotationDetector.kt index 6b50cfd9e5ab..d44c271e734c 100644 --- a/tools/lint/framework/checks/src/main/java/com/google/android/lint/PermissionAnnotationDetector.kt +++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/PermissionAnnotationDetector.kt @@ -43,8 +43,14 @@ class PermissionAnnotationDetector : AidlImplementationDetector() { interfaceName: String, body: UBlockExpression ) { + if (!isSystemServicePath(context)) return + if (context.evaluator.isAbstract(node)) return + val fullyQualifiedInterfaceName = + getContainingAidlInterfaceQualified(context, node) ?: return + if (exemptAidlInterfaces.contains(fullyQualifiedInterfaceName)) return + if (AIDL_PERMISSION_ANNOTATIONS.any { node.hasAnnotation(it) }) return context.report( @@ -80,8 +86,7 @@ class PermissionAnnotationDetector : AidlImplementationDetector() { implementation = Implementation( PermissionAnnotationDetector::class.java, Scope.JAVA_FILE_SCOPE - ), - enabledByDefault = false + ) ) } } diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt new file mode 100644 index 000000000000..92d0829911bf --- /dev/null +++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/PermissionAnnotationDetectorTest.kt @@ -0,0 +1,230 @@ +/* + * 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.google.android.lint.aidl + +import com.android.tools.lint.checks.infrastructure.LintDetectorTest +import com.android.tools.lint.checks.infrastructure.TestFile +import com.android.tools.lint.checks.infrastructure.TestLintTask +import com.android.tools.lint.detector.api.Detector +import com.android.tools.lint.detector.api.Issue + +@Suppress("UnstableApiUsage") +class PermissionAnnotationDetectorTest : LintDetectorTest() { + override fun getDetector(): Detector = + PermissionAnnotationDetector() + + override fun getIssues(): List = listOf( + PermissionAnnotationDetector.ISSUE_MISSING_PERMISSION_ANNOTATION, + ) + + override fun lint(): TestLintTask = super.lint().allowMissingSdk(true) + + /** No issue scenario */ + + fun testDoesNotDetectIssuesInCorrectScenario() { + lint() + .files( + java( + createVisitedPath("Foo.java"), + """ + package com.android.server; + public class Foo extends IFoo.Stub { + @Override + @android.annotation.EnforcePermission("android.Manifest.permission.READ_CONTACTS") + public void testMethod() { } + } + """ + ) + .indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testMissingAnnotation() { + lint() + .files( + java( + createVisitedPath("Bar.java"), + """ + package com.android.server; + public class Bar extends IBar.Stub { + public void testMethod() { } + } + """ + ) + .indented(), + *stubs + ) + .run() + .expect( + """ + src/frameworks/base/services/java/com/android/server/Bar.java:3: Error: The method testMethod is not permission-annotated. [MissingPermissionAnnotation] + public void testMethod() { } + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 1 errors, 0 warnings + """ + ) + } + + fun testMissingAnnotationInIgnoredDirectory() { + lint() + .files( + java( + ignoredPath, + """ + package com.android.server; + public class Bar extends IBar.Stub { + public void testMethod() { } + } + """ + ) + .indented(), + *stubs + ) + .run() + .expectClean() + } + + // If this test fails, consider the following steps: + // 1. Pick the first entry (interface) from `exemptAidlInterfaces`. + // 2. Change `interfaceIExempted` to use that interface. + // 3. Change this test's class to extend the interface's Stub. + fun testMissingAnnotationAidlInterfaceExempted() { + lint() + .files( + java( + createVisitedPath("Bar.java"), + """ + package com.android.server; + public class Bar extends android.accessibilityservice.IBrailleDisplayConnection.Stub { + public void testMethod() { } + } + """ + ) + .indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testMissingAnnotationAidlInterfaceAbstractMethod() { + lint() + .files( + java( + createVisitedPath("Bar.java"), + """ + package com.android.server; + public abstract class Bar extends IBar.Stub { + public abstract void testMethod(); + } + """ + ) + .indented(), + *stubs + ) + .run() + .expectClean() + } + + fun testNoIssueWhenExtendingWithAnotherSubclass() { + lint() + .files( + java( + createVisitedPath("Foo.java"), + """ + package com.android.server; + public class Foo extends IFoo.Stub { + @Override + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethod() { } + // not an AIDL method, just another method + public void someRandomMethod() { } + } + """ + ) + .indented(), + java( + createVisitedPath("Baz.java"), + """ + package com.android.server; + public class Baz extends Bar { + @Override + public void someRandomMethod() { } + } + """ + ) + .indented(), + *stubs + ) + .run() + .expectClean() + } + + /* Stubs */ + + // A service with permission annotation on the method. + private val interfaceIFoo: TestFile = java( + """ + public interface IFoo extends android.os.IInterface { + public static abstract class Stub extends android.os.Binder implements IFoo { + } + @Override + @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE) + public void testMethod(); + @Override + @android.annotation.RequiresNoPermission + public void testMethodNoPermission(); + @Override + @android.annotation.PermissionManuallyEnforced + public void testMethodManual(); + } + """ + ).indented() + + // A service with no permission annotation. + private val interfaceIBar: TestFile = java( + """ + public interface IBar extends android.os.IInterface { + public static abstract class Stub extends android.os.Binder implements IBar { + } + public void testMethod(); + } + """ + ).indented() + + // A service whose AIDL Interface is exempted. + private val interfaceIExempted: TestFile = java( + """ + package android.accessibilityservice; + public interface IBrailleDisplayConnection extends android.os.IInterface { + public static abstract class Stub extends android.os.Binder implements IBrailleDisplayConnection { + } + public void testMethod(); + } + """ + ).indented() + + private val stubs = arrayOf(interfaceIFoo, interfaceIBar, interfaceIExempted) + + private fun createVisitedPath(filename: String) = + "src/frameworks/base/services/java/com/android/server/$filename" + + private val ignoredPath = "src/test/pkg/TestClass.java" +} diff --git a/tools/lint/utils/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfacesGenerator.kt b/tools/lint/utils/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfacesGenerator.kt index 6ad223c87a29..57c2e5aa9767 100644 --- a/tools/lint/utils/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfacesGenerator.kt +++ b/tools/lint/utils/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfacesGenerator.kt @@ -33,12 +33,6 @@ import org.jetbrains.uast.UMethod */ class ExemptAidlInterfacesGenerator : AidlImplementationDetector() { private val targetExemptAidlInterfaceNames = mutableSetOf() - private val systemServicePathPrefixes = setOf( - "frameworks/base/services", - "frameworks/base/apex", - "frameworks/opt/wear", - "packages/modules" - ) // We could've improved performance by visiting classes rather than methods, however, this lint // check won't be run regularly, hence we've decided not to add extra overrides to @@ -49,14 +43,7 @@ class ExemptAidlInterfacesGenerator : AidlImplementationDetector() { interfaceName: String, body: UBlockExpression ) { - val filePath = context.file.path - - // We perform `filePath.contains` instead of `filePath.startsWith` since getting the - // relative path of a source file is non-trivial. That is because `context.file.path` - // returns the path to where soong builds the file (i.e. /out/soong/...). Moreover, the - // logic to extract the relative path would need to consider several /out/soong/... - // locations patterns. - if (systemServicePathPrefixes.none { filePath.contains(it) }) return + if (!isSystemServicePath(context)) return val fullyQualifiedInterfaceName = getContainingAidlInterfaceQualified(context, node) ?: return -- GitLab From 66b870635ac3640c5728f9366d2b11b3fc02a645 Mon Sep 17 00:00:00 2001 From: Pablo Gamito Date: Wed, 11 Sep 2024 12:54:21 +0000 Subject: [PATCH 040/388] Move ProtoLog tests to seperate test directory dedicated to tracing tests Bug: 364255103 Flag: TEST_ONLY Test: atest TracingTests Change-Id: I1f53100c03e71647746d2214f1bf23ec30ec3129 --- tests/Tracing/Android.bp | 33 +++++++++++++++ tests/Tracing/AndroidManifest.xml | 33 +++++++++++++++ tests/Tracing/AndroidTest.xml | 40 +++++++++++++++++++ .../internal/protolog => Tracing}/OWNERS | 2 +- tests/Tracing/TEST_MAPPING | 7 ++++ .../res/xml/network_security_config.xml | 21 ++++++++++ .../protolog/LegacyProtoLogImplTest.java | 1 - .../LegacyProtoLogViewerConfigReaderTest.java | 0 .../protolog/PerfettoProtoLogImplTest.java | 0 .../protolog/ProtoLogCommandHandlerTest.java | 27 +++++++------ .../ProtoLogConfigurationServiceTest.java | 0 .../internal/protolog/ProtoLogImplTest.java | 0 .../internal/protolog/ProtoLogTest.java | 0 .../ProtoLogViewerConfigReaderTest.java | 3 +- .../protolog/ProtologDataSourceTest.java | 3 +- .../protolog/common/LogDataTypeTest.java | 0 16 files changed, 153 insertions(+), 17 deletions(-) create mode 100644 tests/Tracing/Android.bp create mode 100644 tests/Tracing/AndroidManifest.xml create mode 100644 tests/Tracing/AndroidTest.xml rename tests/{Internal/src/com/android/internal/protolog => Tracing}/OWNERS (81%) create mode 100644 tests/Tracing/TEST_MAPPING create mode 100644 tests/Tracing/res/xml/network_security_config.xml rename tests/{Internal => Tracing}/src/com/android/internal/protolog/LegacyProtoLogImplTest.java (99%) rename tests/{Internal => Tracing}/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java (100%) rename tests/{Internal => Tracing}/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java (100%) rename tests/{Internal => Tracing}/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java (86%) rename tests/{Internal => Tracing}/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java (100%) rename tests/{Internal => Tracing}/src/com/android/internal/protolog/ProtoLogImplTest.java (100%) rename tests/{Internal => Tracing}/src/com/android/internal/protolog/ProtoLogTest.java (100%) rename tests/{Internal => Tracing}/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java (98%) rename tests/{Internal => Tracing}/src/com/android/internal/protolog/ProtologDataSourceTest.java (98%) rename tests/{Internal => Tracing}/src/com/android/internal/protolog/common/LogDataTypeTest.java (100%) diff --git a/tests/Tracing/Android.bp b/tests/Tracing/Android.bp new file mode 100644 index 000000000000..5a7f12f56655 --- /dev/null +++ b/tests/Tracing/Android.bp @@ -0,0 +1,33 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_team: "trendy_team_windowing_tools", + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "TracingTests", + proto: { + type: "nano", + }, + // Include some source files directly to be able to access package members + srcs: ["src/**/*.java"], + libs: ["android.test.runner"], + static_libs: [ + "junit", + "androidx.test.rules", + "mockito-target-minus-junit4", + "truth", + "platform-test-annotations", + "flickerlib-parsers", + "perfetto_trace_java_protos", + "flickerlib-trace_processor_shell", + ], + java_resource_dirs: ["res"], + certificate: "platform", + platform_apis: true, + test_suites: ["device-tests"], +} diff --git a/tests/Tracing/AndroidManifest.xml b/tests/Tracing/AndroidManifest.xml new file mode 100644 index 000000000000..7254f81307ad --- /dev/null +++ b/tests/Tracing/AndroidManifest.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + diff --git a/tests/Tracing/AndroidTest.xml b/tests/Tracing/AndroidTest.xml new file mode 100644 index 000000000000..9a404203ee18 --- /dev/null +++ b/tests/Tracing/AndroidTest.xml @@ -0,0 +1,40 @@ + + + + + + + + + + \ No newline at end of file diff --git a/tests/Internal/src/com/android/internal/protolog/OWNERS b/tests/Tracing/OWNERS similarity index 81% rename from tests/Internal/src/com/android/internal/protolog/OWNERS rename to tests/Tracing/OWNERS index 18cf2be9f7df..4a5033800b8e 100644 --- a/tests/Internal/src/com/android/internal/protolog/OWNERS +++ b/tests/Tracing/OWNERS @@ -1,3 +1,3 @@ -# ProtoLog owners +# Tracing owners # Bug component: 1157642 include platform/development:/tools/winscope/OWNERS diff --git a/tests/Tracing/TEST_MAPPING b/tests/Tracing/TEST_MAPPING new file mode 100644 index 000000000000..7f58fceee24d --- /dev/null +++ b/tests/Tracing/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "postsubmit": [ + { + "name": "TracingTests" + } + ] +} \ No newline at end of file diff --git a/tests/Tracing/res/xml/network_security_config.xml b/tests/Tracing/res/xml/network_security_config.xml new file mode 100644 index 000000000000..fdf1dbbe7672 --- /dev/null +++ b/tests/Tracing/res/xml/network_security_config.xml @@ -0,0 +1,21 @@ + + + + + localhost + + diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java similarity index 99% rename from tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java rename to tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java index 9657225588b7..8913e8c1996e 100644 --- a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogImplTest.java @@ -180,7 +180,6 @@ public class LegacyProtoLogImplTest { verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq( LogLevel.INFO), eq("test 5")); - verify(mReader, never()).getViewerString(anyLong()); } @Test diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java b/tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java similarity index 100% rename from tests/Internal/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java rename to tests/Tracing/src/com/android/internal/protolog/LegacyProtoLogViewerConfigReaderTest.java diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java similarity index 100% rename from tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java rename to tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java similarity index 86% rename from tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java rename to tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java index aba6722c0813..be0c7daebb57 100644 --- a/tests/Internal/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogCommandHandlerTest.java @@ -22,6 +22,7 @@ import static org.mockito.ArgumentMatchers.endsWith; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.times; +import android.os.Binder; import android.platform.test.annotations.Presubmit; import org.junit.Test; @@ -44,6 +45,8 @@ public class ProtoLogCommandHandlerTest { ProtoLogConfigurationService mProtoLogConfigurationService; @Mock PrintWriter mPrintWriter; + @Mock + Binder mMockBinder; @Test public void printsHelpForAllAvailableCommands() { @@ -70,7 +73,7 @@ public class ProtoLogCommandHandlerTest { final ProtoLogCommandHandler cmdHandler = new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter); - cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out, + cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, new String[] { "groups", "list" }); Mockito.verify(mPrintWriter, times(1)) @@ -84,7 +87,7 @@ public class ProtoLogCommandHandlerTest { final ProtoLogCommandHandler cmdHandler = new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter); - cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out, + cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, new String[] { "groups" }); Mockito.verify(mPrintWriter, times(1)) @@ -99,7 +102,7 @@ public class ProtoLogCommandHandlerTest { final ProtoLogCommandHandler cmdHandler = new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter); - cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out, + cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, new String[] { "groups", "status", "MY_GROUP" }); Mockito.verify(mPrintWriter, times(1)) @@ -114,7 +117,7 @@ public class ProtoLogCommandHandlerTest { final ProtoLogCommandHandler cmdHandler = new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter); - cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out, + cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, new String[] { "groups", "status", "MY_GROUP" }); Mockito.verify(mPrintWriter, times(1)) @@ -128,7 +131,7 @@ public class ProtoLogCommandHandlerTest { final ProtoLogCommandHandler cmdHandler = new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter); - cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out, + cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, new String[] { "groups", "status" }); Mockito.verify(mPrintWriter, times(1)) @@ -140,7 +143,7 @@ public class ProtoLogCommandHandlerTest { final ProtoLogCommandHandler cmdHandler = new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter); - cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out, + cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, new String[] { "logcat" }); Mockito.verify(mPrintWriter, times(1)) @@ -152,11 +155,11 @@ public class ProtoLogCommandHandlerTest { final ProtoLogCommandHandler cmdHandler = new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter); - cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out, + cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, new String[] { "logcat", "enable", "MY_GROUP" }); Mockito.verify(mProtoLogConfigurationService).enableProtoLogToLogcat("MY_GROUP"); - cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out, + cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, new String[] { "logcat", "enable", "MY_GROUP", "MY_OTHER_GROUP" }); Mockito.verify(mProtoLogConfigurationService) @@ -168,11 +171,11 @@ public class ProtoLogCommandHandlerTest { final ProtoLogCommandHandler cmdHandler = new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter); - cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out, + cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, new String[] { "logcat", "disable", "MY_GROUP" }); Mockito.verify(mProtoLogConfigurationService).disableProtoLogToLogcat("MY_GROUP"); - cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out, + cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, new String[] { "logcat", "disable", "MY_GROUP", "MY_OTHER_GROUP" }); Mockito.verify(mProtoLogConfigurationService) @@ -184,7 +187,7 @@ public class ProtoLogCommandHandlerTest { final ProtoLogCommandHandler cmdHandler = new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter); - cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out, + cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, new String[] { "logcat", "enable" }); Mockito.verify(mPrintWriter).println(contains("Incomplete command")); } @@ -194,7 +197,7 @@ public class ProtoLogCommandHandlerTest { final ProtoLogCommandHandler cmdHandler = new ProtoLogCommandHandler(mProtoLogConfigurationService, mPrintWriter); - cmdHandler.exec(mProtoLogConfigurationService, FileDescriptor.in, FileDescriptor.out, + cmdHandler.exec(mMockBinder, FileDescriptor.in, FileDescriptor.out, FileDescriptor.err, new String[] { "logcat", "disable" }); Mockito.verify(mPrintWriter).println(contains("Incomplete command")); } diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java similarity index 100% rename from tests/Internal/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java rename to tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogImplTest.java similarity index 100% rename from tests/Internal/src/com/android/internal/protolog/ProtoLogImplTest.java rename to tests/Tracing/src/com/android/internal/protolog/ProtoLogImplTest.java diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java similarity index 100% rename from tests/Internal/src/com/android/internal/protolog/ProtoLogTest.java rename to tests/Tracing/src/com/android/internal/protolog/ProtoLogTest.java diff --git a/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java similarity index 98% rename from tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java rename to tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java index be0e8bc0fc07..28d7b42764c4 100644 --- a/tests/Internal/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogViewerConfigReaderTest.java @@ -27,7 +27,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import perfetto.protos.Protolog; import perfetto.protos.ProtologCommon; @Presubmit @@ -48,7 +47,7 @@ public class ProtoLogViewerConfigReaderTest { .setTag(TEST_GROUP_TAG) ).addGroups( perfetto.protos.Protolog.ProtoLogViewerConfig.Group.newBuilder() - .setId(1) + .setId(2) .setName(OTHER_TEST_GROUP_NAME) .setTag(OTHER_TEST_GROUP_TAG) ).addMessages( diff --git a/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java similarity index 98% rename from tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java rename to tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java index 9a062e3b2f80..ce519b7a1576 100644 --- a/tests/Internal/src/com/android/internal/protolog/ProtologDataSourceTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/ProtologDataSourceTest.java @@ -67,7 +67,8 @@ public class ProtologDataSourceTest { @Test public void allEnabledTraceMode() { - final ProtoLogDataSource ds = new ProtoLogDataSource((idx, c) -> {}, () -> {}, (idx, c) -> {}); + final ProtoLogDataSource ds = + new ProtoLogDataSource((idx, c) -> {}, () -> {}, (idx, c) -> {}); final ProtoLogDataSource.TlsState tlsState = createTlsState( DataSourceConfigOuterClass.DataSourceConfig.newBuilder().setProtologConfig( diff --git a/tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java b/tests/Tracing/src/com/android/internal/protolog/common/LogDataTypeTest.java similarity index 100% rename from tests/Internal/src/com/android/internal/protolog/common/LogDataTypeTest.java rename to tests/Tracing/src/com/android/internal/protolog/common/LogDataTypeTest.java -- GitLab From 9bc2965a57c71ab41194819da26c6ed70db83501 Mon Sep 17 00:00:00 2001 From: Pablo Gamito Date: Wed, 11 Sep 2024 13:39:54 +0000 Subject: [PATCH 041/388] Add ProtoLogConfigurationService interface for better testability. Test: TracingTests Flag: EXEMPT minor refactor Change-Id: If55ab7159786590b26469ccf2f3278c6534e6b8a --- .../protolog/PerfettoProtoLogImpl.java | 8 +- .../ProtoLogConfigurationService.java | 424 +--------------- .../ProtoLogConfigurationServiceImpl.java | 454 ++++++++++++++++++ .../java/com/android/server/SystemServer.java | 4 +- .../protolog/PerfettoProtoLogImplTest.java | 5 +- .../ProtoLogConfigurationServiceTest.java | 58 +-- 6 files changed, 503 insertions(+), 450 deletions(-) create mode 100644 core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index e440dc9053fd..4db9ddf128da 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -223,17 +223,17 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto "ServiceManager returned a null ProtoLog Configuration Service"); try { - var args = new ProtoLogConfigurationService.RegisterClientArgs(); + var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs(); if (viewerConfigFilePath != null) { args.setViewerConfigFile(viewerConfigFilePath); } final var groupArgs = Stream.of(groups) - .map(group -> new ProtoLogConfigurationService.RegisterClientArgs + .map(group -> new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(group.name(), group.isLogToLogcat())) - .toArray( - ProtoLogConfigurationService.RegisterClientArgs.GroupConfig[]::new); + .toArray(ProtoLogConfigurationServiceImpl + .RegisterClientArgs.GroupConfig[]::new); args.setGroups(groupArgs); mProtoLogConfigurationService.registerClient(this, args); diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java index 7031d694f09c..d65aaae7deaa 100644 --- a/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java +++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationService.java @@ -16,434 +16,32 @@ package com.android.internal.protolog; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.TAG; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE; -import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID; - import android.annotation.NonNull; -import android.annotation.Nullable; -import android.annotation.SystemService; -import android.content.Context; -import android.os.RemoteException; -import android.os.ResultReceiver; -import android.os.ShellCallback; -import android.tracing.perfetto.DataSourceParams; -import android.tracing.perfetto.InitArguments; -import android.tracing.perfetto.Producer; -import android.util.Log; -import android.util.proto.ProtoInputStream; -import android.util.proto.ProtoOutputStream; - -import com.android.internal.annotations.VisibleForTesting; - -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - -/** - * The ProtoLog service is responsible for orchestrating centralized actions of the protolog tracing - * system. Currently this service has the following roles: - * - Handle shell commands to toggle logging ProtoLog messages for specified groups to logcat. - * - Handle viewer config dumping (the mapping from message hash to message string) for all protolog - * clients. This is for two reasons: firstly, because client processes might be frozen so might - * not response to the request to dump their viewer config when the trace is stopped; secondly, - * multiple processes might be running the same code with the same viewer config, this centralized - * service ensures we don't dump the same viewer config multiple times across processes. - *

- * {@link com.android.internal.protolog.IProtoLogClient ProtoLog clients} register themselves to - * this service on initialization. - *

- * This service is intended to run on the system server, such that it never gets frozen. - */ -@SystemService(Context.PROTOLOG_CONFIGURATION_SERVICE) -public final class ProtoLogConfigurationService extends IProtoLogConfigurationService.Stub { - private static final String LOG_TAG = "ProtoLogConfigurationService"; - - private final ProtoLogDataSource mDataSource; - - /** - * Keeps track of how many of each viewer config file is currently registered. - * Use to keep track of which viewer config files are actively being used in tracing and might - * need to be dumped on flush. - */ - private final Map mConfigFileCounts = new HashMap<>(); - /** - * Keeps track of the viewer config file of each client if available. - */ - private final Map mClientConfigFiles = new HashMap<>(); - - /** - * Keeps track of all the protolog groups that have been registered by clients and are still - * being actively traced. - */ - private final Set mRegisteredGroups = new HashSet<>(); - /** - * Keeps track of all the clients that are actively tracing a given protolog group. - */ - private final Map> mGroupToClients = new HashMap<>(); - - /** - * Keeps track of whether or not a given group should be logged to logcat. - * True when logging to logcat, false otherwise. - */ - private final Map mLogGroupToLogcatStatus = new TreeMap<>(); - - /** - * Keeps track of all the tracing instance ids that are actively running for ProtoLog. - */ - private final Set mRunningInstances = new HashSet<>(); - - private final ViewerConfigFileTracer mViewerConfigFileTracer; - - public ProtoLogConfigurationService() { - this(ProtoLogDataSource::new, ProtoLogConfigurationService::dumpTransitionTraceConfig); - } - - @VisibleForTesting - public ProtoLogConfigurationService(@NonNull ProtoLogDataSourceBuilder dataSourceBuilder) { - this(dataSourceBuilder, ProtoLogConfigurationService::dumpTransitionTraceConfig); - } - - @VisibleForTesting - public ProtoLogConfigurationService(@NonNull ViewerConfigFileTracer tracer) { - this(ProtoLogDataSource::new, tracer); - } - - @VisibleForTesting - public ProtoLogConfigurationService( - @NonNull ProtoLogDataSourceBuilder dataSourceBuilder, - @NonNull ViewerConfigFileTracer tracer) { - mDataSource = dataSourceBuilder.build( - this::onTracingInstanceStart, - this::onTracingInstanceFlush, - this::onTracingInstanceStop - ); - - // Initialize the Perfetto producer and register the Perfetto ProtoLog datasource to be - // receive the lifecycle callbacks of the datasource and write the viewer configs if and - // when required to the datasource. - Producer.init(InitArguments.DEFAULTS); - final var params = new DataSourceParams.Builder() - .setBufferExhaustedPolicy(DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP) - .build(); - mDataSource.register(params); - - mViewerConfigFileTracer = tracer; - } - - public static class RegisterClientArgs extends IRegisterClientArgs.Stub { - /** - * The viewer config file to be registered for this client ProtoLog process. - */ - @Nullable - private String mViewerConfigFile = null; - /** - * The list of all groups that this client protolog process supports and might trace. - */ - @NonNull - private String[] mGroups = new String[0]; - /** - * The default logcat status of the ProtoLog client. True is logging to logcat, false - * otherwise. The indices should match the indices in {@link mGroups}. - */ - @NonNull - private boolean[] mLogcatStatus = new boolean[0]; - - public record GroupConfig(@NonNull String group, boolean logToLogcat) {} - - /** - * Specify groups to register with this client that will be used for protologging in this - * process. - * @param groups to register with this client. - * @return self - */ - public RegisterClientArgs setGroups(GroupConfig... groups) { - mGroups = new String[groups.length]; - mLogcatStatus = new boolean[groups.length]; - - for (int i = 0; i < groups.length; i++) { - mGroups[i] = groups[i].group; - mLogcatStatus[i] = groups[i].logToLogcat; - } - - return this; - } - - /** - * Set the viewer config file that the logs in this process are using. - * @param viewerConfigFile The file path of the viewer config. - * @return self - */ - public RegisterClientArgs setViewerConfigFile(@NonNull String viewerConfigFile) { - mViewerConfigFile = viewerConfigFile; - - return this; - } - - @Override - @NonNull - public String[] getGroups() { - return mGroups; - } - - @Override - @NonNull - public boolean[] getGroupsDefaultLogcatStatus() { - return mLogcatStatus; - } - - @Nullable - @Override - public String getViewerConfigFile() { - return mViewerConfigFile; - } - } - - @FunctionalInterface - public interface ViewerConfigFileTracer { - /** - * Write the viewer config data to the trace buffer. - * - * @param dataSource The target datasource to write the viewer config to. - * @param viewerConfigFilePath The path of the viewer config file which contains the data we - * want to write to the trace buffer. - * @throws FileNotFoundException if the viewerConfigFilePath is invalid. - */ - void trace(@NonNull ProtoLogDataSource dataSource, @NonNull String viewerConfigFilePath); - } - - @Override - public void registerClient(@NonNull IProtoLogClient client, @NonNull IRegisterClientArgs args) - throws RemoteException { - client.asBinder().linkToDeath(() -> onClientBinderDeath(client), /* flags */ 0); - - final String viewerConfigFile = args.getViewerConfigFile(); - if (viewerConfigFile != null) { - registerViewerConfigFile(client, viewerConfigFile); - } - - registerGroups(client, args.getGroups(), args.getGroupsDefaultLogcatStatus()); - } - - @Override - public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, - @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback, - @NonNull ResultReceiver resultReceiver) throws RemoteException { - new ProtoLogCommandHandler(this) - .exec(this, in, out, err, args, callback, resultReceiver); - } +public interface ProtoLogConfigurationService extends IProtoLogConfigurationService { /** * Get the list of groups clients have registered to the protolog service. * @return The list of ProtoLog groups registered with this service. */ @NonNull - public String[] getGroups() { - return mRegisteredGroups.toArray(new String[0]); - } + String[] getGroups(); + + /** + * Check if a group is logging to logcat + * @param group The group we want to check for + * @return True iff we are logging this group to logcat. + */ + boolean isLoggingToLogcat(@NonNull String group); /** * Enable logging target groups to logcat. * @param groups we want to enable logging them to logcat for. */ - public void enableProtoLogToLogcat(String... groups) { - toggleProtoLogToLogcat(true, groups); - } + void enableProtoLogToLogcat(@NonNull String... groups); /** * Disable logging target groups to logcat. * @param groups we want to disable from being logged to logcat. */ - public void disableProtoLogToLogcat(String... groups) { - toggleProtoLogToLogcat(false, groups); - } - - /** - * Check if a group is logging to logcat - * @param group The group we want to check for - * @return True iff we are logging this group to logcat. - */ - public boolean isLoggingToLogcat(@NonNull String group) { - final Boolean isLoggingToLogcat = mLogGroupToLogcatStatus.get(group); - - if (isLoggingToLogcat == null) { - throw new RuntimeException( - "Trying to get logcat logging status of non-registered group " + group); - } - - return isLoggingToLogcat; - } - - private void registerViewerConfigFile( - @NonNull IProtoLogClient client, @NonNull String viewerConfigFile) { - final var count = mConfigFileCounts.getOrDefault(viewerConfigFile, 0); - mConfigFileCounts.put(viewerConfigFile, count + 1); - mClientConfigFiles.put(client, viewerConfigFile); - } - - private void registerGroups(@NonNull IProtoLogClient client, @NonNull String[] groups, - @NonNull boolean[] logcatStatuses) throws RemoteException { - if (groups.length != logcatStatuses.length) { - throw new RuntimeException( - "Expected groups and logcatStatuses to have the same length, " - + "but groups has length " + groups.length - + " and logcatStatuses has length " + logcatStatuses.length); - } - - for (int i = 0; i < groups.length; i++) { - String group = groups[i]; - boolean logcatStatus = logcatStatuses[i]; - - mRegisteredGroups.add(group); - - mGroupToClients.putIfAbsent(group, new HashSet<>()); - mGroupToClients.get(group).add(client); - - if (!mLogGroupToLogcatStatus.containsKey(group)) { - mLogGroupToLogcatStatus.put(group, logcatStatus); - } - - boolean requestedLogToLogcat = mLogGroupToLogcatStatus.get(group); - if (requestedLogToLogcat != logcatStatus) { - client.toggleLogcat(requestedLogToLogcat, new String[] { group }); - } - } - } - - private void toggleProtoLogToLogcat(boolean enabled, @NonNull String[] groups) { - final var clientToGroups = new HashMap>(); - - for (String group : groups) { - final var clients = mGroupToClients.get(group); - - if (clients == null) { - // No clients associated to this group - Log.w(LOG_TAG, "Attempting to toggle log to logcat for group " + group - + " with no registered clients."); - continue; - } - - for (IProtoLogClient client : clients) { - clientToGroups.putIfAbsent(client, new HashSet<>()); - clientToGroups.get(client).add(group); - } - } - - for (IProtoLogClient client : clientToGroups.keySet()) { - try { - client.toggleLogcat(enabled, clientToGroups.get(client).toArray(new String[0])); - } catch (RemoteException e) { - throw new RuntimeException( - "Failed to toggle logcat status for groups on client", e); - } - } - - for (String group : groups) { - mLogGroupToLogcatStatus.put(group, enabled); - } - } - - private void onTracingInstanceStart(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) { - mRunningInstances.add(instanceIdx); - } - - private void onTracingInstanceFlush() { - for (String fileName : mConfigFileCounts.keySet()) { - mViewerConfigFileTracer.trace(mDataSource, fileName); - } - } - - private void onTracingInstanceStop(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) { - mRunningInstances.remove(instanceIdx); - } - - private static void dumpTransitionTraceConfig(@NonNull ProtoLogDataSource dataSource, - @NonNull String viewerConfigFilePath) { - Utils.dumpViewerConfig(dataSource, () -> { - try { - return new ProtoInputStream(new FileInputStream(viewerConfigFilePath)); - } catch (FileNotFoundException e) { - throw new RuntimeException( - "Failed to load viewer config file " + viewerConfigFilePath, e); - } - }); - } - - private void onClientBinderDeath(@NonNull IProtoLogClient client) { - // Dump the tracing config now if no other client is going to dump the same config file. - String configFile = mClientConfigFiles.get(client); - if (configFile != null) { - final var newCount = mConfigFileCounts.get(configFile) - 1; - mConfigFileCounts.put(configFile, newCount); - boolean lastProcessWithViewerConfig = newCount == 0; - if (lastProcessWithViewerConfig) { - mViewerConfigFileTracer.trace(mDataSource, configFile); - } - } - } - - private static void writeViewerConfigGroup( - @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException { - final long inGroupToken = pis.start(GROUPS); - final long outGroupToken = os.start(GROUPS); - - while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { - switch (pis.getFieldNumber()) { - case (int) ID -> { - int id = pis.readInt(ID); - os.write(ID, id); - } - case (int) NAME -> { - String name = pis.readString(NAME); - os.write(NAME, name); - } - case (int) TAG -> { - String tag = pis.readString(TAG); - os.write(TAG, tag); - } - default -> - throw new RuntimeException( - "Unexpected field id " + pis.getFieldNumber()); - } - } - - pis.end(inGroupToken); - os.end(outGroupToken); - } - - private static void writeViewerConfigMessage( - @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException { - final long inMessageToken = pis.start(MESSAGES); - final long outMessagesToken = os.start(MESSAGES); - - while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { - switch (pis.getFieldNumber()) { - case (int) MESSAGE_ID -> os.write(MESSAGE_ID, - pis.readLong(MESSAGE_ID)); - case (int) MESSAGE -> os.write(MESSAGE, pis.readString(MESSAGE)); - case (int) LEVEL -> os.write(LEVEL, pis.readInt(LEVEL)); - case (int) GROUP_ID -> os.write(GROUP_ID, pis.readInt(GROUP_ID)); - case (int) LOCATION -> os.write(LOCATION, pis.readString(LOCATION)); - default -> - throw new RuntimeException( - "Unexpected field id " + pis.getFieldNumber()); - } - } - - pis.end(inMessageToken); - os.end(outMessagesToken); - } + void disableProtoLogToLogcat(@NonNull String... groups); } diff --git a/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java new file mode 100644 index 000000000000..e382ac1513e0 --- /dev/null +++ b/core/java/com/android/internal/protolog/ProtoLogConfigurationServiceImpl.java @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.protolog; + +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.GROUPS; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.ID; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.NAME; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.Group.TAG; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MESSAGES; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.GROUP_ID; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LEVEL; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.LOCATION; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE; +import static android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData.MESSAGE_ID; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemService; +import android.content.Context; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.tracing.perfetto.DataSourceParams; +import android.tracing.perfetto.InitArguments; +import android.tracing.perfetto.Producer; +import android.util.Log; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +/** + * The ProtoLog service is responsible for orchestrating centralized actions of the protolog tracing + * system. Currently this service has the following roles: + * - Handle shell commands to toggle logging ProtoLog messages for specified groups to logcat. + * - Handle viewer config dumping (the mapping from message hash to message string) for all protolog + * clients. This is for two reasons: firstly, because client processes might be frozen so might + * not response to the request to dump their viewer config when the trace is stopped; secondly, + * multiple processes might be running the same code with the same viewer config, this centralized + * service ensures we don't dump the same viewer config multiple times across processes. + *

+ * {@link com.android.internal.protolog.IProtoLogClient ProtoLog clients} register themselves to + * this service on initialization. + *

+ * This service is intended to run on the system server, such that it never gets frozen. + */ +@SystemService(Context.PROTOLOG_CONFIGURATION_SERVICE) +public class ProtoLogConfigurationServiceImpl extends IProtoLogConfigurationService.Stub + implements ProtoLogConfigurationService { + private static final String LOG_TAG = "ProtoLogConfigurationService"; + + private final ProtoLogDataSource mDataSource; + + /** + * Keeps track of how many of each viewer config file is currently registered. + * Use to keep track of which viewer config files are actively being used in tracing and might + * need to be dumped on flush. + */ + private final Map mConfigFileCounts = new HashMap<>(); + /** + * Keeps track of the viewer config file of each client if available. + */ + private final Map mClientConfigFiles = new HashMap<>(); + + /** + * Keeps track of all the protolog groups that have been registered by clients and are still + * being actively traced. + */ + private final Set mRegisteredGroups = new HashSet<>(); + /** + * Keeps track of all the clients that are actively tracing a given protolog group. + */ + private final Map> mGroupToClients = new HashMap<>(); + + /** + * Keeps track of whether or not a given group should be logged to logcat. + * True when logging to logcat, false otherwise. + */ + private final Map mLogGroupToLogcatStatus = new TreeMap<>(); + + /** + * Keeps track of all the tracing instance ids that are actively running for ProtoLog. + */ + private final Set mRunningInstances = new HashSet<>(); + + private final ViewerConfigFileTracer mViewerConfigFileTracer; + + public ProtoLogConfigurationServiceImpl() { + this(ProtoLogDataSource::new, ProtoLogConfigurationServiceImpl::dumpTransitionTraceConfig); + } + + @VisibleForTesting + public ProtoLogConfigurationServiceImpl(@NonNull ProtoLogDataSourceBuilder dataSourceBuilder) { + this(dataSourceBuilder, ProtoLogConfigurationServiceImpl::dumpTransitionTraceConfig); + } + + @VisibleForTesting + public ProtoLogConfigurationServiceImpl(@NonNull ViewerConfigFileTracer tracer) { + this(ProtoLogDataSource::new, tracer); + } + + @VisibleForTesting + public ProtoLogConfigurationServiceImpl( + @NonNull ProtoLogDataSourceBuilder dataSourceBuilder, + @NonNull ViewerConfigFileTracer tracer) { + mDataSource = dataSourceBuilder.build( + this::onTracingInstanceStart, + this::onTracingInstanceFlush, + this::onTracingInstanceStop + ); + + // Initialize the Perfetto producer and register the Perfetto ProtoLog datasource to be + // receive the lifecycle callbacks of the datasource and write the viewer configs if and + // when required to the datasource. + Producer.init(InitArguments.DEFAULTS); + final var params = new DataSourceParams.Builder() + .setBufferExhaustedPolicy(DataSourceParams.PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP) + .build(); + mDataSource.register(params); + + mViewerConfigFileTracer = tracer; + } + + public static class RegisterClientArgs extends IRegisterClientArgs.Stub { + /** + * The viewer config file to be registered for this client ProtoLog process. + */ + @Nullable + private String mViewerConfigFile = null; + /** + * The list of all groups that this client protolog process supports and might trace. + */ + @NonNull + private String[] mGroups = new String[0]; + /** + * The default logcat status of the ProtoLog client. True is logging to logcat, false + * otherwise. The indices should match the indices in {@link mGroups}. + */ + @NonNull + private boolean[] mLogcatStatus = new boolean[0]; + + public record GroupConfig(@NonNull String group, boolean logToLogcat) {} + + /** + * Specify groups to register with this client that will be used for protologging in this + * process. + * @param groups to register with this client. + * @return self + */ + public RegisterClientArgs setGroups(GroupConfig... groups) { + mGroups = new String[groups.length]; + mLogcatStatus = new boolean[groups.length]; + + for (int i = 0; i < groups.length; i++) { + mGroups[i] = groups[i].group; + mLogcatStatus[i] = groups[i].logToLogcat; + } + + return this; + } + + /** + * Set the viewer config file that the logs in this process are using. + * @param viewerConfigFile The file path of the viewer config. + * @return self + */ + public RegisterClientArgs setViewerConfigFile(@NonNull String viewerConfigFile) { + mViewerConfigFile = viewerConfigFile; + + return this; + } + + @Override + @NonNull + public String[] getGroups() { + return mGroups; + } + + @Override + @NonNull + public boolean[] getGroupsDefaultLogcatStatus() { + return mLogcatStatus; + } + + @Nullable + @Override + public String getViewerConfigFile() { + return mViewerConfigFile; + } + } + + @FunctionalInterface + public interface ViewerConfigFileTracer { + /** + * Write the viewer config data to the trace buffer. + * + * @param dataSource The target datasource to write the viewer config to. + * @param viewerConfigFilePath The path of the viewer config file which contains the data we + * want to write to the trace buffer. + * @throws FileNotFoundException if the viewerConfigFilePath is invalid. + */ + void trace(@NonNull ProtoLogDataSource dataSource, @NonNull String viewerConfigFilePath); + } + + @Override + public void registerClient(@NonNull IProtoLogClient client, @NonNull IRegisterClientArgs args) + throws RemoteException { + client.asBinder().linkToDeath(() -> onClientBinderDeath(client), /* flags */ 0); + + final String viewerConfigFile = args.getViewerConfigFile(); + if (viewerConfigFile != null) { + registerViewerConfigFile(client, viewerConfigFile); + } + + registerGroups(client, args.getGroups(), args.getGroupsDefaultLogcatStatus()); + } + + @Override + public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, + @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback, + @NonNull ResultReceiver resultReceiver) throws RemoteException { + new ProtoLogCommandHandler(this) + .exec(this, in, out, err, args, callback, resultReceiver); + } + + /** + * Get the list of groups clients have registered to the protolog service. + * @return The list of ProtoLog groups registered with this service. + */ + @Override + @NonNull + public String[] getGroups() { + return mRegisteredGroups.toArray(new String[0]); + } + + /** + * Enable logging target groups to logcat. + * @param groups we want to enable logging them to logcat for. + */ + @Override + public void enableProtoLogToLogcat(@NonNull String... groups) { + toggleProtoLogToLogcat(true, groups); + } + + /** + * Disable logging target groups to logcat. + * @param groups we want to disable from being logged to logcat. + */ + @Override + public void disableProtoLogToLogcat(@NonNull String... groups) { + toggleProtoLogToLogcat(false, groups); + } + + /** + * Check if a group is logging to logcat + * @param group The group we want to check for + * @return True iff we are logging this group to logcat. + */ + @Override + public boolean isLoggingToLogcat(@NonNull String group) { + final Boolean isLoggingToLogcat = mLogGroupToLogcatStatus.get(group); + + if (isLoggingToLogcat == null) { + throw new RuntimeException( + "Trying to get logcat logging status of non-registered group " + group); + } + + return isLoggingToLogcat; + } + + private void registerViewerConfigFile( + @NonNull IProtoLogClient client, @NonNull String viewerConfigFile) { + final var count = mConfigFileCounts.getOrDefault(viewerConfigFile, 0); + mConfigFileCounts.put(viewerConfigFile, count + 1); + mClientConfigFiles.put(client, viewerConfigFile); + } + + private void registerGroups(@NonNull IProtoLogClient client, @NonNull String[] groups, + @NonNull boolean[] logcatStatuses) throws RemoteException { + if (groups.length != logcatStatuses.length) { + throw new RuntimeException( + "Expected groups and logcatStatuses to have the same length, " + + "but groups has length " + groups.length + + " and logcatStatuses has length " + logcatStatuses.length); + } + + for (int i = 0; i < groups.length; i++) { + String group = groups[i]; + boolean logcatStatus = logcatStatuses[i]; + + mRegisteredGroups.add(group); + + mGroupToClients.putIfAbsent(group, new HashSet<>()); + mGroupToClients.get(group).add(client); + + if (!mLogGroupToLogcatStatus.containsKey(group)) { + mLogGroupToLogcatStatus.put(group, logcatStatus); + } + + boolean requestedLogToLogcat = mLogGroupToLogcatStatus.get(group); + if (requestedLogToLogcat != logcatStatus) { + client.toggleLogcat(requestedLogToLogcat, new String[] { group }); + } + } + } + + private void toggleProtoLogToLogcat(boolean enabled, @NonNull String[] groups) { + final var clientToGroups = new HashMap>(); + + for (String group : groups) { + final var clients = mGroupToClients.get(group); + + if (clients == null) { + // No clients associated to this group + Log.w(LOG_TAG, "Attempting to toggle log to logcat for group " + group + + " with no registered clients."); + continue; + } + + for (IProtoLogClient client : clients) { + clientToGroups.putIfAbsent(client, new HashSet<>()); + clientToGroups.get(client).add(group); + } + } + + for (IProtoLogClient client : clientToGroups.keySet()) { + try { + client.toggleLogcat(enabled, clientToGroups.get(client).toArray(new String[0])); + } catch (RemoteException e) { + throw new RuntimeException( + "Failed to toggle logcat status for groups on client", e); + } + } + + for (String group : groups) { + mLogGroupToLogcatStatus.put(group, enabled); + } + } + + private void onTracingInstanceStart(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) { + mRunningInstances.add(instanceIdx); + } + + private void onTracingInstanceFlush() { + for (String fileName : mConfigFileCounts.keySet()) { + mViewerConfigFileTracer.trace(mDataSource, fileName); + } + } + + private void onTracingInstanceStop(int instanceIdx, ProtoLogDataSource.ProtoLogConfig config) { + mRunningInstances.remove(instanceIdx); + } + + private static void dumpTransitionTraceConfig(@NonNull ProtoLogDataSource dataSource, + @NonNull String viewerConfigFilePath) { + Utils.dumpViewerConfig(dataSource, () -> { + try { + return new ProtoInputStream(new FileInputStream(viewerConfigFilePath)); + } catch (FileNotFoundException e) { + throw new RuntimeException( + "Failed to load viewer config file " + viewerConfigFilePath, e); + } + }); + } + + private void onClientBinderDeath(@NonNull IProtoLogClient client) { + // Dump the tracing config now if no other client is going to dump the same config file. + String configFile = mClientConfigFiles.get(client); + if (configFile != null) { + final var newCount = mConfigFileCounts.get(configFile) - 1; + mConfigFileCounts.put(configFile, newCount); + boolean lastProcessWithViewerConfig = newCount == 0; + if (lastProcessWithViewerConfig) { + mViewerConfigFileTracer.trace(mDataSource, configFile); + } + } + } + + private static void writeViewerConfigGroup( + @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException { + final long inGroupToken = pis.start(GROUPS); + final long outGroupToken = os.start(GROUPS); + + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pis.getFieldNumber()) { + case (int) ID -> { + int id = pis.readInt(ID); + os.write(ID, id); + } + case (int) NAME -> { + String name = pis.readString(NAME); + os.write(NAME, name); + } + case (int) TAG -> { + String tag = pis.readString(TAG); + os.write(TAG, tag); + } + default -> + throw new RuntimeException( + "Unexpected field id " + pis.getFieldNumber()); + } + } + + pis.end(inGroupToken); + os.end(outGroupToken); + } + + private static void writeViewerConfigMessage( + @NonNull ProtoInputStream pis, @NonNull ProtoOutputStream os) throws IOException { + final long inMessageToken = pis.start(MESSAGES); + final long outMessagesToken = os.start(MESSAGES); + + while (pis.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (pis.getFieldNumber()) { + case (int) MESSAGE_ID -> os.write(MESSAGE_ID, + pis.readLong(MESSAGE_ID)); + case (int) MESSAGE -> os.write(MESSAGE, pis.readString(MESSAGE)); + case (int) LEVEL -> os.write(LEVEL, pis.readInt(LEVEL)); + case (int) GROUP_ID -> os.write(GROUP_ID, pis.readInt(GROUP_ID)); + case (int) LOCATION -> os.write(LOCATION, pis.readString(LOCATION)); + default -> + throw new RuntimeException( + "Unexpected field id " + pis.getFieldNumber()); + } + } + + pis.end(inMessageToken); + os.end(outMessagesToken); + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index ab459df1cdf6..9fdf088c3d1d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -107,7 +107,7 @@ import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; import com.android.internal.policy.AttributeCache; import com.android.internal.protolog.ProtoLog; -import com.android.internal.protolog.ProtoLogConfigurationService; +import com.android.internal.protolog.ProtoLogConfigurationServiceImpl; import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.EmergencyAffordanceManager; @@ -1097,7 +1097,7 @@ public final class SystemServer implements Dumpable { if (android.tracing.Flags.clientSideProtoLogging()) { t.traceBegin("StartProtoLogConfigurationService"); ServiceManager.addService( - Context.PROTOLOG_CONFIGURATION_SERVICE, new ProtoLogConfigurationService()); + Context.PROTOLOG_CONFIGURATION_SERVICE, new ProtoLogConfigurationServiceImpl()); t.traceEnd(); } diff --git a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java index e841d9ea0880..c882b4e569a1 100644 --- a/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java @@ -42,7 +42,7 @@ import android.util.proto.ProtoInputStream; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.internal.protolog.ProtoLogConfigurationService.ViewerConfigFileTracer; +import com.android.internal.protolog.ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer; import com.android.internal.protolog.common.IProtoLogGroup; import com.android.internal.protolog.common.LogDataType; import com.android.internal.protolog.common.LogLevel; @@ -166,7 +166,8 @@ public class PerfettoProtoLogImplTest { return new ProtoInputStream(sViewerConfigBuilder.build().toByteArray()); }); }; - sProtoLogConfigurationService = new ProtoLogConfigurationService(dataSourceBuilder, tracer); + sProtoLogConfigurationService = + new ProtoLogConfigurationServiceImpl(dataSourceBuilder, tracer); if (android.tracing.Flags.clientSideProtoLogging()) { sProtoLog = new PerfettoProtoLogImpl( diff --git a/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java index e1bdd777dc5f..a3d03a8278ed 100644 --- a/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java +++ b/tests/Tracing/src/com/android/internal/protolog/ProtoLogConfigurationServiceTest.java @@ -150,11 +150,11 @@ public class ProtoLogConfigurationServiceTest { @Test public void canRegisterClientWithGroupsOnly() throws RemoteException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, true)); service.registerClient(mMockClient, args); @@ -165,11 +165,11 @@ public class ProtoLogConfigurationServiceTest { @Test public void willDumpViewerConfigOnlyOnceOnTraceStop() throws RemoteException, InvalidProtocolBufferException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, true)) .setViewerConfigFile(mViewerConfigFile.getAbsolutePath()); service.registerClient(mMockClient, args); @@ -200,13 +200,13 @@ public class ProtoLogConfigurationServiceTest { @Test public void willDumpViewerConfigOnLastClientDisconnected() throws RemoteException, FileNotFoundException { - final ProtoLogConfigurationService.ViewerConfigFileTracer tracer = - Mockito.mock(ProtoLogConfigurationService.ViewerConfigFileTracer.class); - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(tracer); + final ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer tracer = + Mockito.mock(ProtoLogConfigurationServiceImpl.ViewerConfigFileTracer.class); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(tracer); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, true)) .setViewerConfigFile(mViewerConfigFile.getAbsolutePath()); service.registerClient(mMockClient, args); @@ -225,10 +225,10 @@ public class ProtoLogConfigurationServiceTest { @Test public void sendEnableLoggingToLogcatToClient() throws RemoteException { - final var service = new ProtoLogConfigurationService(); + final var service = new ProtoLogConfigurationServiceImpl(); - final var args = new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, false)); service.registerClient(mMockClient, args); @@ -242,11 +242,11 @@ public class ProtoLogConfigurationServiceTest { @Test public void sendDisableLoggingToLogcatToClient() throws RemoteException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, true)); service.registerClient(mMockClient, args); @@ -260,11 +260,11 @@ public class ProtoLogConfigurationServiceTest { @Test public void doNotSendLoggingToLogcatToClientWithoutRegisteredGroup() throws RemoteException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, false)); service.registerClient(mMockClient, args); @@ -277,15 +277,15 @@ public class ProtoLogConfigurationServiceTest { @Test public void handlesToggleToLogcatBeforeClientIsRegistered() throws RemoteException { - final ProtoLogConfigurationService service = new ProtoLogConfigurationService(); + final ProtoLogConfigurationService service = new ProtoLogConfigurationServiceImpl(); Truth.assertThat(service.getGroups()).asList().doesNotContain(TEST_GROUP); service.enableProtoLogToLogcat(TEST_GROUP); Truth.assertThat(service.isLoggingToLogcat(TEST_GROUP)).isTrue(); - final ProtoLogConfigurationService.RegisterClientArgs args = - new ProtoLogConfigurationService.RegisterClientArgs() - .setGroups(new ProtoLogConfigurationService.RegisterClientArgs + final ProtoLogConfigurationServiceImpl.RegisterClientArgs args = + new ProtoLogConfigurationServiceImpl.RegisterClientArgs() + .setGroups(new ProtoLogConfigurationServiceImpl.RegisterClientArgs .GroupConfig(TEST_GROUP, false)); service.registerClient(mMockClient, args); -- GitLab From 57ad2a61364ff6058fe86949bf7ab7c050b32638 Mon Sep 17 00:00:00 2001 From: dakinola Date: Tue, 3 Sep 2024 13:37:30 +0000 Subject: [PATCH 042/388] Flicker Scenarios: MediaProjection Adding necessary framework for Flicker scenarios which set up and tear down a screen sharing scenario (entire screen and single app) Bug: 348585793 Bug: 348585794 Bug: 348585796 Flag: TEST_ONLY Test: atest ShareAppOpenFourApps.kt Test: atest ShareAppOpenFourAppsRsizeAndDrag.kt Test: atest ShareScreenOpenFourApps.kt Change-Id: I6e9cf39cc71f6a3513d13bcfcd3df9c1ca408fe1 --- .../StartAppMediaProjectionResizeAndDrag.kt | 85 ++++++++++ ...AppMediaProjectionWithMaxDesktopWindows.kt | 92 +++++++++++ ...eenMediaProjectionWithMaxDesktopWindows.kt | 85 ++++++++++ .../flicker/utils/MediaProjectionService.kt | 110 ++++++++++++ .../flicker/utils/MediaProjectionUtils.kt | 24 +++ .../helpers/StartMediaProjectionAppHelper.kt | 126 ++++++++++++++ .../test-apps/flickerapp/Android.bp | 1 + .../test-apps/flickerapp/AndroidManifest.xml | 25 +++ .../activity_start_media_projection.xml | 32 ++++ .../wm/flicker/testapp/ActivityOptions.java | 6 + .../testapp/StartMediaProjectionActivity.java | 156 ++++++++++++++++++ 11 files changed, 742 insertions(+) create mode 100644 libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt create mode 100644 libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt create mode 100644 libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt create mode 100644 libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionService.kt create mode 100644 libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionUtils.kt create mode 100644 tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt create mode 100644 tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml create mode 100644 tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/StartMediaProjectionActivity.java diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt new file mode 100644 index 000000000000..f08e50e0d4ee --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionResizeAndDrag.kt @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.scenarios + +import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit +import android.tools.NavBar +import android.tools.Rotation +import android.tools.device.apphelpers.CalculatorAppHelper +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.DesktopModeAppHelper +import com.android.server.wm.flicker.helpers.DesktopModeAppHelper.Corners.LEFT_TOP +import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper +import com.android.window.flags.Flags +import com.android.wm.shell.Utils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class StartAppMediaProjectionResizeAndDrag { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + + private val targetApp = CalculatorAppHelper(instrumentation) + private val mediaProjectionAppHelper = StartMediaProjectionAppHelper(instrumentation) + private val testApp = DesktopModeAppHelper(mediaProjectionAppHelper) + + @Rule + @JvmField + val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) + + @Before + fun setup() { + Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) + tapl.setEnableRotation(true) + tapl.setExpectedRotation(0) + testApp.enterDesktopWithDrag(wmHelper, device) + } + + @Test + open fun startMediaProjectionAndResize() { + mediaProjectionAppHelper.startSingleAppMediaProjection(wmHelper, targetApp) + + with(DesktopModeAppHelper(targetApp)) { + val windowRect = wmHelper.getWindowRegion(this).bounds + // Set start x-coordinate as center of app header. + val startX = windowRect.centerX() + val startY = windowRect.top + + dragWindow(startX, startY, endX = startX + 150, endY = startY + 150, wmHelper, device) + cornerResize(wmHelper, device, LEFT_TOP, -200, -200) + } + } + + @After + fun teardown() { + testApp.exit(wmHelper) + targetApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt new file mode 100644 index 000000000000..717ea306eb77 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.scenarios + +import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit +import android.tools.NavBar +import android.tools.Rotation +import android.tools.device.apphelpers.CalculatorAppHelper +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.DesktopModeAppHelper +import com.android.server.wm.flicker.helpers.ImeAppHelper +import com.android.server.wm.flicker.helpers.MailAppHelper +import com.android.server.wm.flicker.helpers.NewTasksAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper +import com.android.window.flags.Flags +import com.android.wm.shell.Utils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class StartAppMediaProjectionWithMaxDesktopWindows { + + val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + val tapl = LauncherInstrumentation() + val wmHelper = WindowManagerStateHelper(instrumentation) + val device = UiDevice.getInstance(instrumentation) + + private val targetApp = CalculatorAppHelper(instrumentation) + private val mailApp = MailAppHelper(instrumentation) + private val newTasksApp = DesktopModeAppHelper(NewTasksAppHelper(instrumentation)) + private val imeApp = DesktopModeAppHelper(ImeAppHelper(instrumentation)) + private val simpleApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + private val mediaProjectionAppHelper = StartMediaProjectionAppHelper(instrumentation) + private val testApp = DesktopModeAppHelper(mediaProjectionAppHelper) + + @Rule + @JvmField + val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) + + @Before + fun setup() { + Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) + tapl.setEnableRotation(true) + tapl.setExpectedRotation(0) + testApp.enterDesktopWithDrag(wmHelper, device) + } + + @Test + open fun startMediaProjection() { + // TODO(b/366455106) - handle max task Limit + mediaProjectionAppHelper.startSingleAppMediaProjection(wmHelper, targetApp) + mailApp.launchViaIntent(wmHelper) + simpleApp.launchViaIntent(wmHelper) + newTasksApp.launchViaIntent(wmHelper) + imeApp.launchViaIntent(wmHelper) + } + + @After + fun teardown() { + mailApp.exit(wmHelper) + simpleApp.exit(wmHelper) + newTasksApp.exit(wmHelper) + imeApp.exit(wmHelper) + targetApp.exit(wmHelper) + testApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt new file mode 100644 index 000000000000..005195296c62 --- /dev/null +++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithMaxDesktopWindows.kt @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.scenarios + +import android.app.Instrumentation +import android.platform.test.annotations.Postsubmit +import android.tools.NavBar +import android.tools.Rotation +import android.tools.traces.parsers.WindowManagerStateHelper +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.android.launcher3.tapl.LauncherInstrumentation +import com.android.server.wm.flicker.helpers.DesktopModeAppHelper +import com.android.server.wm.flicker.helpers.ImeAppHelper +import com.android.server.wm.flicker.helpers.MailAppHelper +import com.android.server.wm.flicker.helpers.NewTasksAppHelper +import com.android.server.wm.flicker.helpers.SimpleAppHelper +import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper +import com.android.window.flags.Flags +import com.android.wm.shell.Utils +import org.junit.After +import org.junit.Assume +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.BlockJUnit4ClassRunner + +@RunWith(BlockJUnit4ClassRunner::class) +@Postsubmit +open class StartScreenMediaProjectionWithMaxDesktopWindows { + private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() + private val tapl = LauncherInstrumentation() + private val wmHelper = WindowManagerStateHelper(instrumentation) + private val device = UiDevice.getInstance(instrumentation) + + @Rule + @JvmField + val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) + + private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation)) + private val newTasksApp = DesktopModeAppHelper(NewTasksAppHelper(instrumentation)) + private val imeApp = DesktopModeAppHelper(ImeAppHelper(instrumentation)) + private val simpleApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation)) + private val mediaProjectionAppHelper = StartMediaProjectionAppHelper(instrumentation) + private val testApp = DesktopModeAppHelper(mediaProjectionAppHelper) + + @Before + fun setup() { + Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet) + testApp.enterDesktopWithDrag(wmHelper, device) + } + + @Test + open fun startMediaProjection() { + mediaProjectionAppHelper.startEntireScreenMediaProjection(wmHelper) + simpleApp.launchViaIntent(wmHelper) + mailApp.launchViaIntent(wmHelper) + newTasksApp.launchViaIntent(wmHelper) + imeApp.launchViaIntent(wmHelper) + } + + @After + fun teardown() { + testApp.exit(wmHelper) + simpleApp.exit(wmHelper) + mailApp.exit(wmHelper) + newTasksApp.exit(wmHelper) + imeApp.exit(wmHelper) + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionService.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionService.kt new file mode 100644 index 000000000000..aa4e216f01a2 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionService.kt @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.utils + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.Service +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.drawable.Icon +import android.os.IBinder +import android.os.Message +import android.os.Messenger +import android.os.RemoteException +import android.util.Log + +class MediaProjectionService : Service() { + + private var mTestBitmap: Bitmap? = null + + private val notificationId: Int = 1 + private val notificationChannelId: String = "MediaProjectionFlickerTest" + private val notificationChannelName = "FlickerMediaProjectionService" + + var mMessenger: Messenger? = null + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + mMessenger = intent.extras?.getParcelable( + MediaProjectionUtils.EXTRA_MESSENGER, Messenger::class.java) + startForeground() + return super.onStartCommand(intent, flags, startId) + } + + override fun onBind(intent: Intent?): IBinder? = null + + override fun onDestroy() { + mTestBitmap?.recycle() + mTestBitmap = null + sendMessage(MediaProjectionUtils.MSG_SERVICE_DESTROYED) + super.onDestroy() + } + + private fun createNotificationIcon(): Icon { + Log.d(TAG, "createNotification") + + mTestBitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888) + val canvas = Canvas(mTestBitmap!!) + canvas.drawColor(Color.BLUE) + return Icon.createWithBitmap(mTestBitmap) + } + + private fun startForeground() { + Log.d(TAG, "startForeground") + val channel = NotificationChannel( + notificationChannelId, + notificationChannelName, NotificationManager.IMPORTANCE_NONE + ) + channel.lockscreenVisibility = Notification.VISIBILITY_PRIVATE + + val notificationManager: NotificationManager = + getSystemService(NotificationManager::class.java) + notificationManager.createNotificationChannel(channel) + + val notificationBuilder: Notification.Builder = + Notification.Builder(this, notificationChannelId) + + val notification = notificationBuilder.setOngoing(true) + .setContentTitle("App is running") + .setSmallIcon(createNotificationIcon()) + .setCategory(Notification.CATEGORY_SERVICE) + .setContentText("Context") + .build() + + startForeground(notificationId, notification) + sendMessage(MediaProjectionUtils.MSG_START_FOREGROUND_DONE) + } + + fun sendMessage(what: Int) { + Log.d(TAG, "sendMessage") + with(Message.obtain()) { + this.what = what + try { + mMessenger!!.send(this) + } catch (e: RemoteException) { + Log.d(TAG, "Unable to send message", e) + } + } + } + + companion object { + private const val TAG: String = "FlickerMediaProjectionService" + } +} \ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionUtils.kt new file mode 100644 index 000000000000..f9706969ff11 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/utils/MediaProjectionUtils.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.utils + +object MediaProjectionUtils { + const val REQUEST_CODE: Int = 99 + const val MSG_START_FOREGROUND_DONE: Int = 1 + const val MSG_SERVICE_DESTROYED: Int = 2 + const val EXTRA_MESSENGER: String = "messenger" +} \ No newline at end of file diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt new file mode 100644 index 000000000000..69fde0168b14 --- /dev/null +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/StartMediaProjectionAppHelper.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES 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.helpers + +import android.app.Instrumentation +import android.tools.device.apphelpers.StandardAppHelper +import android.tools.helpers.SYSTEMUI_PACKAGE +import android.tools.traces.component.ComponentNameMatcher +import android.tools.traces.parsers.WindowManagerStateHelper +import android.tools.traces.parsers.toFlickerComponent +import android.util.Log +import androidx.test.uiautomator.By +import androidx.test.uiautomator.BySelector +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.UiObjectNotFoundException +import androidx.test.uiautomator.UiScrollable +import androidx.test.uiautomator.UiSelector +import androidx.test.uiautomator.Until +import com.android.server.wm.flicker.testapp.ActivityOptions +import java.util.regex.Pattern + +class StartMediaProjectionAppHelper +@JvmOverloads +constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.StartMediaProjectionActivity.LABEL, + component: ComponentNameMatcher = + ActivityOptions.StartMediaProjectionActivity.COMPONENT.toFlickerComponent() +) : StandardAppHelper(instr, launcherName, component) { + private val packageManager = instr.context.packageManager + + fun startEntireScreenMediaProjection(wmHelper: WindowManagerStateHelper) { + clickStartMediaProjectionButton() + chooseEntireScreenOption() + startScreenSharing() + wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify() + } + + fun startSingleAppMediaProjection( + wmHelper: WindowManagerStateHelper, + targetApp: StandardAppHelper + ) { + clickStartMediaProjectionButton() + chooseSingleAppOption() + startScreenSharing() + selectTargetApp(targetApp.appName) + wmHelper + .StateSyncBuilder() + .withAppTransitionIdle() + .withWindowSurfaceAppeared(targetApp) + .waitForAndVerify() + } + + private fun clickStartMediaProjectionButton() { + findObject(By.res(packageName, START_MEDIA_PROJECTION_BUTTON_ID)).also { it.click() } + } + + private fun chooseEntireScreenOption() { + findObject(By.res(SCREEN_SHARE_OPTIONS_PATTERN)).also { it.click() } + + val entireScreenString = getSysUiResourceString(ENTIRE_SCREEN_STRING_RES_NAME) + findObject(By.text(entireScreenString)).also { it.click() } + } + + private fun selectTargetApp(targetAppName: String) { + // Scroll to to find target app to launch then click app icon it to start capture + val scrollable = UiScrollable(UiSelector().scrollable(true)) + try { + scrollable.scrollForward() + if (!scrollable.scrollIntoView(UiSelector().text(targetAppName))) { + Log.e(TAG, "Didn't find target app when scrolling") + return + } + } catch (e: UiObjectNotFoundException) { + Log.d(TAG, "There was no scrolling (UI may not be scrollable") + } + + findObject(By.text(targetAppName)).also { it.click() } + } + + private fun chooseSingleAppOption() { + findObject(By.res(SCREEN_SHARE_OPTIONS_PATTERN)).also { it.click() } + + val singleAppString = getSysUiResourceString(SINGLE_APP_STRING_RES_NAME) + findObject(By.text(singleAppString)).also { it.click() } + } + + private fun startScreenSharing() { + findObject(By.res(ACCEPT_RESOURCE_ID)).also { it.click() } + } + + private fun findObject(selector: BySelector): UiObject2 = + uiDevice.wait(Until.findObject(selector), TIMEOUT) ?: error("Can't find object $selector") + + private fun getSysUiResourceString(resName: String): String = + with(packageManager.getResourcesForApplication(SYSTEMUI_PACKAGE)) { + getString(getIdentifier(resName, "string", SYSTEMUI_PACKAGE)) + } + + companion object { + const val TAG: String = "StartMediaProjectionAppHelper" + const val TIMEOUT: Long = 5000L + const val ACCEPT_RESOURCE_ID: String = "android:id/button1" + const val START_MEDIA_PROJECTION_BUTTON_ID: String = "button_start_mp" + val SCREEN_SHARE_OPTIONS_PATTERN: Pattern = + Pattern.compile("$SYSTEMUI_PACKAGE:id/screen_share_mode_(options|spinner)") + const val ENTIRE_SCREEN_STRING_RES_NAME: String = + "screen_share_permission_dialog_option_entire_screen" + const val SINGLE_APP_STRING_RES_NAME: String = + "screen_share_permission_dialog_option_single_app" + } +} diff --git a/tests/FlickerTests/test-apps/flickerapp/Android.bp b/tests/FlickerTests/test-apps/flickerapp/Android.bp index e3b23b986c83..5c41b7693ed9 100644 --- a/tests/FlickerTests/test-apps/flickerapp/Android.bp +++ b/tests/FlickerTests/test-apps/flickerapp/Android.bp @@ -46,6 +46,7 @@ android_test { "wm-flicker-common-app-helpers", "wm-flicker-common-assertions", "wm-flicker-window-extensions", + "wm-shell-flicker-utils", ], } diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 45260bddd355..f891606f0066 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -21,6 +21,15 @@ android:targetSdkVersion="35"/> + + + + + + + + + @@ -106,6 +115,17 @@ + + + + + + + + diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml new file mode 100644 index 000000000000..46f01e6c9752 --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_start_media_projection.xml @@ -0,0 +1,32 @@ + + + + +