diff options
187 files changed, 4291 insertions, 1243 deletions
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java index 7dcbebaeba0b..7819e1ef94c6 100644 --- a/core/java/android/app/servertransaction/LaunchActivityItem.java +++ b/core/java/android/app/servertransaction/LaunchActivityItem.java @@ -104,8 +104,8 @@ public class LaunchActivityItem extends ClientTransactionItem { public void execute(@NonNull ClientTransactionHandler client, @NonNull PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); - ActivityClientRecord r = new ActivityClientRecord(mActivityToken, mIntent, mIdent, mInfo, - mOverrideConfig, mReferrer, mVoiceInteractor, mState, mPersistentState, + final ActivityClientRecord r = new ActivityClientRecord(mActivityToken, mIntent, mIdent, + mInfo, mOverrideConfig, mReferrer, mVoiceInteractor, mState, mPersistentState, mPendingResults, mPendingNewIntents, mSceneTransitionInfo, mIsForward, mProfilerInfo, client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble, mTaskFragmentToken, mInitialCallerInfoAccessToken, mActivityWindowInfo); diff --git a/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java b/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java index ee04f8cbe3c7..1c8e497edd0a 100644 --- a/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java +++ b/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java @@ -16,8 +16,6 @@ package android.app.servertransaction; -import static java.util.Objects.requireNonNull; - import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ClientTransactionHandler; @@ -35,23 +33,19 @@ import java.util.Objects; * Message to deliver window insets control change info. * @hide */ -public class WindowStateInsetsControlChangeItem extends ClientTransactionItem { +public class WindowStateInsetsControlChangeItem extends WindowStateTransactionItem { private static final String TAG = "WindowStateInsetsControlChangeItem"; - private IWindow mWindow; private InsetsState mInsetsState; private InsetsSourceControl.Array mActiveControls; @Override - public void execute(@NonNull ClientTransactionHandler client, + public void execute(@NonNull ClientTransactionHandler client, @NonNull IWindow window, @NonNull PendingTransactionActions pendingActions) { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "windowInsetsControlChanged"); - if (mWindow instanceof InsetsControlChangeListener listener) { - listener.onExecutingWindowStateInsetsControlChangeItem(); - } try { - mWindow.insetsControlChanged(mInsetsState, mActiveControls); + window.insetsControlChanged(mInsetsState, mActiveControls); } catch (RemoteException e) { // Should be a local call. // An exception could happen if the process is restarted. It is safe to ignore since @@ -73,7 +67,7 @@ public class WindowStateInsetsControlChangeItem extends ClientTransactionItem { if (instance == null) { instance = new WindowStateInsetsControlChangeItem(); } - instance.mWindow = requireNonNull(window); + instance.setWindow(window); instance.mInsetsState = new InsetsState(insetsState, true /* copySources */); instance.mActiveControls = new InsetsSourceControl.Array(activeControls); @@ -82,7 +76,7 @@ public class WindowStateInsetsControlChangeItem extends ClientTransactionItem { @Override public void recycle() { - mWindow = null; + super.recycle(); mInsetsState = null; mActiveControls = null; ObjectPool.recycle(this); @@ -93,14 +87,14 @@ public class WindowStateInsetsControlChangeItem extends ClientTransactionItem { /** Writes to Parcel. */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeStrongBinder(mWindow.asBinder()); + super.writeToParcel(dest, flags); dest.writeTypedObject(mInsetsState, flags); dest.writeTypedObject(mActiveControls, flags); } /** Reads from Parcel. */ private WindowStateInsetsControlChangeItem(@NonNull Parcel in) { - mWindow = IWindow.Stub.asInterface(in.readStrongBinder()); + super(in); mInsetsState = in.readTypedObject(InsetsState.CREATOR); mActiveControls = in.readTypedObject(InsetsSourceControl.Array.CREATOR); @@ -122,19 +116,18 @@ public class WindowStateInsetsControlChangeItem extends ClientTransactionItem { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!super.equals(o)) { return false; } final WindowStateInsetsControlChangeItem other = (WindowStateInsetsControlChangeItem) o; - return Objects.equals(mWindow, other.mWindow) - && Objects.equals(mInsetsState, other.mInsetsState) + return Objects.equals(mInsetsState, other.mInsetsState) && Objects.equals(mActiveControls, other.mActiveControls); } @Override public int hashCode() { int result = 17; - result = 31 * result + Objects.hashCode(mWindow); + result = 31 * result + super.hashCode(); result = 31 * result + Objects.hashCode(mInsetsState); result = 31 * result + Objects.hashCode(mActiveControls); return result; @@ -142,15 +135,6 @@ public class WindowStateInsetsControlChangeItem extends ClientTransactionItem { @Override public String toString() { - return "WindowStateInsetsControlChangeItem{window=" + mWindow + "}"; - } - - /** The interface for IWindow to perform insets control change directly if possible. */ - public interface InsetsControlChangeListener { - /** - * Notifies that IWindow#insetsControlChanged is going to be called from - * WindowStateInsetsControlChangeItem. - */ - void onExecutingWindowStateInsetsControlChangeItem(); + return "WindowStateInsetsControlChangeItem{" + super.toString() + "}"; } } diff --git a/core/java/android/app/servertransaction/WindowStateResizeItem.java b/core/java/android/app/servertransaction/WindowStateResizeItem.java index da99096f022a..3c1fa4b83340 100644 --- a/core/java/android/app/servertransaction/WindowStateResizeItem.java +++ b/core/java/android/app/servertransaction/WindowStateResizeItem.java @@ -18,8 +18,6 @@ package android.app.servertransaction; import static android.view.Display.INVALID_DISPLAY; -import static java.util.Objects.requireNonNull; - import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ClientTransactionHandler; @@ -39,11 +37,10 @@ import java.util.Objects; * Message to deliver window resize info. * @hide */ -public class WindowStateResizeItem extends ClientTransactionItem { +public class WindowStateResizeItem extends WindowStateTransactionItem { private static final String TAG = "WindowStateResizeItem"; - private IWindow mWindow; private ClientWindowFrames mFrames; private boolean mReportDraw; private MergedConfiguration mConfiguration; @@ -59,15 +56,12 @@ public class WindowStateResizeItem extends ClientTransactionItem { private ActivityWindowInfo mActivityWindowInfo; @Override - public void execute(@NonNull ClientTransactionHandler client, + public void execute(@NonNull ClientTransactionHandler client, @NonNull IWindow window, @NonNull PendingTransactionActions pendingActions) { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, mReportDraw ? "windowResizedReport" : "windowResized"); - if (mWindow instanceof ResizeListener listener) { - listener.onExecutingWindowStateResizeItem(); - } try { - mWindow.resized(mFrames, mReportDraw, mConfiguration, mInsetsState, mForceLayout, + window.resized(mFrames, mReportDraw, mConfiguration, mInsetsState, mForceLayout, mAlwaysConsumeSystemBars, mDisplayId, mSyncSeqId, mDragResizing, mActivityWindowInfo); } catch (RemoteException e) { @@ -94,7 +88,7 @@ public class WindowStateResizeItem extends ClientTransactionItem { if (instance == null) { instance = new WindowStateResizeItem(); } - instance.mWindow = requireNonNull(window); + instance.setWindow(window); instance.mFrames = new ClientWindowFrames(frames); instance.mReportDraw = reportDraw; instance.mConfiguration = new MergedConfiguration(configuration); @@ -113,7 +107,7 @@ public class WindowStateResizeItem extends ClientTransactionItem { @Override public void recycle() { - mWindow = null; + super.recycle(); mFrames = null; mReportDraw = false; mConfiguration = null; @@ -132,7 +126,7 @@ public class WindowStateResizeItem extends ClientTransactionItem { /** Writes to Parcel. */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeStrongBinder(mWindow.asBinder()); + super.writeToParcel(dest, flags); dest.writeTypedObject(mFrames, flags); dest.writeBoolean(mReportDraw); dest.writeTypedObject(mConfiguration, flags); @@ -147,7 +141,7 @@ public class WindowStateResizeItem extends ClientTransactionItem { /** Reads from Parcel. */ private WindowStateResizeItem(@NonNull Parcel in) { - mWindow = IWindow.Stub.asInterface(in.readStrongBinder()); + super(in); mFrames = in.readTypedObject(ClientWindowFrames.CREATOR); mReportDraw = in.readBoolean(); mConfiguration = in.readTypedObject(MergedConfiguration.CREATOR); @@ -175,12 +169,11 @@ public class WindowStateResizeItem extends ClientTransactionItem { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { + if (!super.equals(o)) { return false; } final WindowStateResizeItem other = (WindowStateResizeItem) o; - return Objects.equals(mWindow, other.mWindow) - && Objects.equals(mFrames, other.mFrames) + return Objects.equals(mFrames, other.mFrames) && mReportDraw == other.mReportDraw && Objects.equals(mConfiguration, other.mConfiguration) && Objects.equals(mInsetsState, other.mInsetsState) @@ -195,7 +188,7 @@ public class WindowStateResizeItem extends ClientTransactionItem { @Override public int hashCode() { int result = 17; - result = 31 * result + Objects.hashCode(mWindow); + result = 31 * result + super.hashCode(); result = 31 * result + Objects.hashCode(mFrames); result = 31 * result + (mReportDraw ? 1 : 0); result = 31 * result + Objects.hashCode(mConfiguration); @@ -211,16 +204,10 @@ public class WindowStateResizeItem extends ClientTransactionItem { @Override public String toString() { - return "WindowStateResizeItem{window=" + mWindow + return "WindowStateResizeItem{" + super.toString() + ", reportDrawn=" + mReportDraw + ", configuration=" + mConfiguration + ", activityWindowInfo=" + mActivityWindowInfo + "}"; } - - /** The interface for IWindow to perform resize directly if possible. */ - public interface ResizeListener { - /** Notifies that IWindow#resized is going to be called from WindowStateResizeItem. */ - void onExecutingWindowStateResizeItem(); - } } diff --git a/core/java/android/app/servertransaction/WindowStateTransactionItem.java b/core/java/android/app/servertransaction/WindowStateTransactionItem.java new file mode 100644 index 000000000000..d556363dd947 --- /dev/null +++ b/core/java/android/app/servertransaction/WindowStateTransactionItem.java @@ -0,0 +1,118 @@ +/* + * Copyright 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 android.app.servertransaction; + + +import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; + +import static java.util.Objects.requireNonNull; + +import android.annotation.CallSuper; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ClientTransactionHandler; +import android.os.Parcel; +import android.view.IWindow; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Objects; + +/** + * {@link ClientTransactionItem} to report changes to a window. + * + * @hide + */ +public abstract class WindowStateTransactionItem extends ClientTransactionItem { + + /** The interface for IWindow to perform callback directly if possible. */ + public interface TransactionListener { + /** Notifies that the transaction item is going to be executed. */ + void onExecutingWindowStateTransactionItem(); + } + + /** Target window. */ + private IWindow mWindow; + + WindowStateTransactionItem() {} + + @Override + public final void execute(@NonNull ClientTransactionHandler client, + @NonNull PendingTransactionActions pendingActions) { + if (mWindow instanceof TransactionListener listener) { + listener.onExecutingWindowStateTransactionItem(); + } + execute(client, mWindow, pendingActions); + } + + /** + * Like {@link #execute(ClientTransactionHandler, PendingTransactionActions)}, + * but take non-null {@link IWindow} as a parameter. + */ + @VisibleForTesting(visibility = PACKAGE) + public abstract void execute(@NonNull ClientTransactionHandler client, + @NonNull IWindow window, @NonNull PendingTransactionActions pendingActions); + + void setWindow(@NonNull IWindow window) { + mWindow = requireNonNull(window); + } + + // To be overridden + + WindowStateTransactionItem(@NonNull Parcel in) { + mWindow = IWindow.Stub.asInterface(in.readStrongBinder()); + } + + @CallSuper + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeStrongBinder(mWindow.asBinder()); + } + + @CallSuper + @Override + public void recycle() { + mWindow = null; + } + + // Subclass must override and call super.equals to compare the mActivityToken. + @SuppressWarnings("EqualsGetClass") + @CallSuper + @Override + public boolean equals(@Nullable Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final WindowStateTransactionItem other = (WindowStateTransactionItem) o; + return Objects.equals(mWindow, other.mWindow); + } + + @CallSuper + @Override + public int hashCode() { + return Objects.hashCode(mWindow); + } + + @CallSuper + @Override + public String toString() { + return "mWindow=" + mWindow; + } +} diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig index 006226eb8c31..ed5d66227574 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -52,4 +52,15 @@ flag { description: "Makes MediaDrm APIs device-aware" bug: "303535376" is_fixed_read_only: true -}
\ No newline at end of file +} + +flag { + namespace: "virtual_devices" + name: "virtual_display_multi_window_mode_support" + description: "Add support for WINDOWING_MODE_MULTI_WINDOW to virtual displays by default" + is_fixed_read_only: true + bug: "341151395" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/view/InsetsFlags.java b/core/java/android/view/InsetsFlags.java index ca8a7a8cf175..2fa57688f0cb 100644 --- a/core/java/android/view/InsetsFlags.java +++ b/core/java/android/view/InsetsFlags.java @@ -17,6 +17,7 @@ package android.view; import static android.view.WindowInsetsController.APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS; +import static android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS; @@ -24,6 +25,7 @@ import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_B import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS; import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS; import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS; +import static android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND; import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; @@ -69,7 +71,15 @@ public class InsetsFlags { @ViewDebug.FlagToString( mask = APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS, equals = APPEARANCE_FORCE_LIGHT_NAVIGATION_BARS, - name = "FORCE_LIGHT_NAVIGATION_BARS") + name = "FORCE_LIGHT_NAVIGATION_BARS"), + @ViewDebug.FlagToString( + mask = APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND, + equals = APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND, + name = "APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND"), + @ViewDebug.FlagToString( + mask = APPEARANCE_LIGHT_CAPTION_BARS, + equals = APPEARANCE_LIGHT_CAPTION_BARS, + name = "APPEARANCE_LIGHT_CAPTION_BARS") }) public @Appearance int appearance; diff --git a/core/java/android/view/KeyboardShortcutInfo.java b/core/java/android/view/KeyboardShortcutInfo.java index 118b03ce5504..3f6fd646994c 100644 --- a/core/java/android/view/KeyboardShortcutInfo.java +++ b/core/java/android/view/KeyboardShortcutInfo.java @@ -28,8 +28,8 @@ import android.os.Parcelable; * Information about a Keyboard Shortcut. */ public final class KeyboardShortcutInfo implements Parcelable { - private final CharSequence mLabel; - private final Icon mIcon; + @Nullable private final CharSequence mLabel; + @Nullable private Icon mIcon; private final char mBaseCharacter; private final int mKeycode; private final int mModifiers; @@ -116,6 +116,15 @@ public final class KeyboardShortcutInfo implements Parcelable { } /** + * Removes an icon that was previously set. + * + * @hide + */ + public void clearIcon() { + mIcon = null; + } + + /** * Returns the base keycode that, combined with the modifiers, triggers this shortcut. If the * base character was set instead, returns {@link KeyEvent#KEYCODE_UNKNOWN}. Valid keycodes are * defined as constants in {@link KeyEvent}. @@ -165,4 +174,4 @@ public final class KeyboardShortcutInfo implements Parcelable { return new KeyboardShortcutInfo[size]; } }; -}
\ No newline at end of file +} diff --git a/core/java/android/view/NativeVectorDrawableAnimator.java b/core/java/android/view/NativeVectorDrawableAnimator.java index b0556a3f8a91..e92bd1f5d6b8 100644 --- a/core/java/android/view/NativeVectorDrawableAnimator.java +++ b/core/java/android/view/NativeVectorDrawableAnimator.java @@ -16,6 +16,8 @@ package android.view; +import android.animation.Animator; + /** * Exists just to allow for android.graphics & android.view package separation * @@ -26,4 +28,7 @@ package android.view; public interface NativeVectorDrawableAnimator { /** @hide */ long getAnimatorNativePtr(); + + /** @hide */ + void setThreadedRendererAnimatorListener(Animator.AnimatorListener listener); } diff --git a/core/java/android/view/ViewAnimationHostBridge.java b/core/java/android/view/ViewAnimationHostBridge.java index e0fae21bbdf6..62b2b6c053c4 100644 --- a/core/java/android/view/ViewAnimationHostBridge.java +++ b/core/java/android/view/ViewAnimationHostBridge.java @@ -16,14 +16,19 @@ package android.view; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.graphics.RenderNode; +import androidx.annotation.NonNull; + /** * Maps a View to a RenderNode's AnimationHost * * @hide */ -public class ViewAnimationHostBridge implements RenderNode.AnimationHost { +public class ViewAnimationHostBridge extends AnimatorListenerAdapter + implements RenderNode.AnimationHost { private final View mView; /** @@ -34,17 +39,35 @@ public class ViewAnimationHostBridge implements RenderNode.AnimationHost { } @Override - public void registerAnimatingRenderNode(RenderNode animator) { - mView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(animator); + public void registerAnimatingRenderNode(RenderNode renderNode, Animator animator) { + mView.mAttachInfo.mViewRootImpl.registerAnimatingRenderNode(renderNode); + animator.addListener(this); } @Override public void registerVectorDrawableAnimator(NativeVectorDrawableAnimator animator) { mView.mAttachInfo.mViewRootImpl.registerVectorDrawableAnimator(animator); + animator.setThreadedRendererAnimatorListener(this); } @Override public boolean isAttached() { return mView.mAttachInfo != null; } + + @Override + public void onAnimationStart(@NonNull Animator animation) { + ViewRootImpl viewRoot = mView.getViewRootImpl(); + if (viewRoot != null) { + viewRoot.addThreadedRendererView(mView); + } + } + + @Override + public void onAnimationEnd(@NonNull Animator animation) { + ViewRootImpl viewRoot = mView.getViewRootImpl(); + if (viewRoot != null) { + viewRoot.removeThreadedRendererView(mView); + } + } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 8e2b7f1d4dd2..139285a44817 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -143,7 +143,7 @@ import android.app.ICompatCameraControlCallback; import android.app.ResourcesManager; import android.app.WindowConfiguration; import android.app.compat.CompatChanges; -import android.app.servertransaction.WindowStateResizeItem; +import android.app.servertransaction.WindowStateTransactionItem; import android.compat.annotation.UnsupportedAppUsage; import android.content.ClipData; import android.content.ClipDescription; @@ -427,6 +427,12 @@ public final class ViewRootImpl implements ViewParent, private static final long NANOS_PER_SEC = 1000000000; + // If the ViewRootImpl has been idle for more than 750ms, clear the preferred + // frame rate category and frame rate. + private static final int IDLE_TIME_MILLIS = 750; + + private static final long NANOS_PER_MILLI = 1_000_000; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>(); @@ -659,6 +665,10 @@ public final class ViewRootImpl implements ViewParent, private int mMinusOneFrameIntervalMillis = 0; // VRR interval between the previous and the frame before private int mMinusTwoFrameIntervalMillis = 0; + // VRR has the invalidation idle message been posted? + private boolean mInvalidationIdleMessagePosted = false; + // VRR: List of all Views that are animating with the threaded render + private ArrayList<View> mThreadedRendererViews = new ArrayList(); /** * Update the Choreographer's FrameInfo object with the timing information for the current @@ -1184,6 +1194,8 @@ public final class ViewRootImpl implements ViewParent, toolkitFrameRateVelocityMappingReadOnly(); private static boolean sToolkitEnableInvalidateCheckThreadFlagValue = Flags.enableInvalidateCheckThread(); + private static boolean sSurfaceFlingerBugfixFlagValue = + com.android.graphics.surfaceflinger.flags.Flags.vrrBugfix24q4(); static { sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly(); @@ -4261,8 +4273,13 @@ public final class ViewRootImpl implements ViewParent, // when the values are applicable. if (mDrawnThisFrame) { mDrawnThisFrame = false; + if (!mInvalidationIdleMessagePosted && sSurfaceFlingerBugfixFlagValue) { + mInvalidationIdleMessagePosted = true; + mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS); + } setCategoryFromCategoryCounts(); updateInfrequentCount(); + updateFrameRateFromThreadedRendererViews(); setPreferredFrameRate(mPreferredFrameRate); setPreferredFrameRateCategory(mPreferredFrameRateCategory); if (mPreferredFrameRate > 0 @@ -6499,6 +6516,8 @@ public final class ViewRootImpl implements ViewParent, return "MSG_WINDOW_TOUCH_MODE_CHANGED"; case MSG_KEEP_CLEAR_RECTS_CHANGED: return "MSG_KEEP_CLEAR_RECTS_CHANGED"; + case MSG_CHECK_INVALIDATION_IDLE: + return "MSG_CHECK_INVALIDATION_IDLE"; case MSG_REFRESH_POINTER_ICON: return "MSG_REFRESH_POINTER_ICON"; case MSG_TOUCH_BOOST_TIMEOUT: @@ -6759,6 +6778,31 @@ public final class ViewRootImpl implements ViewParent, mNumPausedForSync = 0; scheduleTraversals(); break; + case MSG_CHECK_INVALIDATION_IDLE: { + long delta; + if (mIsTouchBoosting || mIsFrameRateBoosting || mInsetsAnimationRunning) { + delta = 0; + } else { + delta = System.nanoTime() / NANOS_PER_MILLI - mLastUpdateTimeMillis; + } + if (delta >= IDLE_TIME_MILLIS) { + mFrameRateCategoryHighCount = 0; + mFrameRateCategoryHighHintCount = 0; + mFrameRateCategoryNormalCount = 0; + mFrameRateCategoryLowCount = 0; + mPreferredFrameRate = 0; + mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE; + updateFrameRateFromThreadedRendererViews(); + setPreferredFrameRate(mPreferredFrameRate); + setPreferredFrameRateCategory(mPreferredFrameRateCategory); + mInvalidationIdleMessagePosted = false; + } else { + mInvalidationIdleMessagePosted = true; + mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, + IDLE_TIME_MILLIS - delta); + } + break; + } case MSG_TOUCH_BOOST_TIMEOUT: /** * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME). @@ -7273,7 +7317,8 @@ public final class ViewRootImpl implements ViewParent, if (dispatcher.isBackGestureInProgress()) { return FINISH_NOT_HANDLED; } - if (topCallback instanceof OnBackAnimationCallback) { + if (topCallback instanceof OnBackAnimationCallback + && !(topCallback instanceof ImeBackAnimationController)) { final OnBackAnimationCallback animationCallback = (OnBackAnimationCallback) topCallback; switch (keyEvent.getAction()) { @@ -11201,10 +11246,10 @@ public final class ViewRootImpl implements ViewParent, } } - static class W extends IWindow.Stub implements WindowStateResizeItem.ResizeListener { + static class W extends IWindow.Stub implements WindowStateTransactionItem.TransactionListener { private final WeakReference<ViewRootImpl> mViewAncestor; private final IWindowSession mWindowSession; - private boolean mIsFromResizeItem; + private boolean mIsFromTransactionItem; W(ViewRootImpl viewAncestor) { mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor); @@ -11212,8 +11257,8 @@ public final class ViewRootImpl implements ViewParent, } @Override - public void onExecutingWindowStateResizeItem() { - mIsFromResizeItem = true; + public void onExecutingWindowStateTransactionItem() { + mIsFromTransactionItem = true; } @Override @@ -11221,8 +11266,8 @@ public final class ViewRootImpl implements ViewParent, MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing, @Nullable ActivityWindowInfo activityWindowInfo) { - final boolean isFromResizeItem = mIsFromResizeItem; - mIsFromResizeItem = false; + final boolean isFromResizeItem = mIsFromTransactionItem; + mIsFromTransactionItem = false; // Although this is a AIDL method, it will only be triggered in local process through // either WindowStateResizeItem or WindowlessWindowManager. final ViewRootImpl viewAncestor = mViewAncestor.get(); @@ -11259,10 +11304,13 @@ public final class ViewRootImpl implements ViewParent, @Override public void insetsControlChanged(InsetsState insetsState, InsetsSourceControl.Array activeControls) { + final boolean isFromInsetsControlChangeItem = mIsFromTransactionItem; + mIsFromTransactionItem = false; final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { viewAncestor.dispatchInsetsControlChanged(insetsState, activeControls.get()); } + // TODO(b/339380439): no need to post if the call is from InsetsControlChangeItem } @Override @@ -12577,6 +12625,24 @@ public final class ViewRootImpl implements ViewParent, } /** + * Views that are animating with the ThreadedRenderer don't use the normal invalidation + * path, so the value won't be updated through performTraversals. This reads the votes + * from those views. + */ + private void updateFrameRateFromThreadedRendererViews() { + ArrayList<View> views = mThreadedRendererViews; + for (int i = views.size() - 1; i >= 0; i--) { + View view = views.get(i); + View.AttachInfo attachInfo = view.mAttachInfo; + if (attachInfo == null || attachInfo.mViewRootImpl != this) { + views.remove(i); + } else { + view.votePreferredFrameRate(); + } + } + } + + /** * Sets the mPreferredFrameRateCategory from the high, high_hint, normal, and low counts. */ private void setCategoryFromCategoryCounts() { @@ -12757,6 +12823,31 @@ public final class ViewRootImpl implements ViewParent, } /** + * Mark a View as having an active ThreadedRenderer animation. This is used for + * RenderNodeAnimators and AnimatedVectorDrawables. When the animation stops, + * {@link #removeThreadedRendererView(View)} must be called. + * @param view The View with the ThreadedRenderer animation that started. + */ + public void addThreadedRendererView(View view) { + if (!mThreadedRendererViews.contains(view)) { + mThreadedRendererViews.add(view); + } + } + + /** + * When a ThreadedRenderer animation ends, the View that is associated with it using + * {@link #addThreadedRendererView(View)} must be removed with a call to this method. + * @param view The View whose ThreadedRender animation has stopped. + */ + public void removeThreadedRendererView(View view) { + mThreadedRendererViews.remove(view); + if (!mInvalidationIdleMessagePosted && sSurfaceFlingerBugfixFlagValue) { + mInvalidationIdleMessagePosted = true; + mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE, IDLE_TIME_MILLIS); + } + } + + /** * Returns {@link #INTERMITTENT_STATE_INTERMITTENT} when the ViewRootImpl has only been * updated intermittently, {@link #INTERMITTENT_STATE_NOT_INTERMITTENT} when it is * not updated intermittently, and {@link #INTERMITTENT_STATE_IN_TRANSITION} when it @@ -12979,6 +13070,10 @@ public final class ViewRootImpl implements ViewParent, private void removeVrrMessages() { mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT); mHandler.removeMessages(MSG_FRAME_RATE_SETTING); + if (mInvalidationIdleMessagePosted && sSurfaceFlingerBugfixFlagValue) { + mInvalidationIdleMessagePosted = false; + mHandler.removeMessages(MSG_CHECK_INVALIDATION_IDLE); + } } /** @@ -12997,7 +13092,7 @@ public final class ViewRootImpl implements ViewParent, mMinusOneFrameIntervalMillis = timeIntervalMillis; mLastUpdateTimeMillis = currentTimeMillis; - if (timeIntervalMillis + mMinusTwoFrameIntervalMillis + if (mThreadedRendererViews.isEmpty() && timeIntervalMillis + mMinusTwoFrameIntervalMillis >= INFREQUENT_UPDATE_INTERVAL_MILLIS) { int infrequentUpdateCount = mInfrequentUpdateCount; mInfrequentUpdateCount = infrequentUpdateCount == INFREQUENT_UPDATE_COUNTS diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java index 8d71a8e998bd..9cd2a716e498 100644 --- a/core/java/android/window/DisplayWindowPolicyController.java +++ b/core/java/android/window/DisplayWindowPolicyController.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.WindowConfiguration; +import android.companion.virtualdevice.flags.Flags; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -66,6 +67,9 @@ public abstract class DisplayWindowPolicyController { public DisplayWindowPolicyController() { synchronized (mSupportedWindowingModes) { mSupportedWindowingModes.add(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); + if (Flags.virtualDisplayMultiWindowModeSupport()) { + mSupportedWindowingModes.add(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW); + } } } diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java index 29bb32e6443f..f928f509bdb6 100644 --- a/core/java/android/window/SnapshotDrawerUtils.java +++ b/core/java/android/window/SnapshotDrawerUtils.java @@ -50,7 +50,6 @@ import android.app.ActivityManager; import android.app.ActivityThread; import android.content.Context; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.GraphicBuffer; import android.graphics.Paint; import android.graphics.PixelFormat; @@ -68,6 +67,7 @@ import android.view.WindowManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.policy.DecorView; +import com.android.window.flags.Flags; /** * Utils class to help draw a snapshot on a surface. @@ -181,7 +181,8 @@ public class SnapshotDrawerUtils { // We consider nearly matched dimensions as there can be rounding errors and the user // won't notice very minute differences from scaling one dimension more than the other - boolean aspectRatioMismatch = !isAspectRatioMatch(mFrame, mSnapshotW, mSnapshotH); + boolean aspectRatioMismatch = !isAspectRatioMatch(mFrame, mSnapshotW, mSnapshotH) + && !Flags.drawSnapshotAspectRatioMatch(); // Keep a reference to it such that it doesn't get destroyed when finalized. SurfaceControl childSurfaceControl = new SurfaceControl.Builder(session) @@ -382,8 +383,8 @@ public class SnapshotDrawerUtils { } final SnapshotSurface drawSurface = new SnapshotSurface( rootSurface, snapshot, lp.getTitle()); - - final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams; + final WindowManager.LayoutParams attrs = Flags.drawSnapshotAspectRatioMatch() + ? info.mainWindowLayoutParams : info.topOpaqueWindowLayoutParams; final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo; final ActivityManager.TaskDescription taskDescription = getOrCreateTaskDescription(runningTaskInfo); @@ -400,7 +401,8 @@ public class SnapshotDrawerUtils { public static WindowManager.LayoutParams createLayoutParameters(StartingWindowInfo info, CharSequence title, @WindowManager.LayoutParams.WindowType int windowType, int pixelFormat, IBinder token) { - final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams; + final WindowManager.LayoutParams attrs = Flags.drawSnapshotAspectRatioMatch() + ? info.mainWindowLayoutParams : info.topOpaqueWindowLayoutParams; final WindowManager.LayoutParams mainWindowParams = info.mainWindowLayoutParams; final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState; if (attrs == null || mainWindowParams == null || topWindowInsetsState == null) { @@ -527,7 +529,7 @@ public class SnapshotDrawerUtils { void drawStatusBarBackground(Canvas c, @Nullable Rect alreadyDrawnFrame, int statusBarHeight) { - if (statusBarHeight > 0 && Color.alpha(mStatusBarColor) != 0 + if (statusBarHeight > 0 && alpha(mStatusBarColor) != 0 && (alreadyDrawnFrame == null || c.getWidth() > alreadyDrawnFrame.right)) { final int rightInset = (int) (mSystemBarInsets.right * mScale); final int left = alreadyDrawnFrame != null ? alreadyDrawnFrame.right : 0; @@ -541,7 +543,7 @@ public class SnapshotDrawerUtils { getNavigationBarRect(c.getWidth(), c.getHeight(), mSystemBarInsets, navigationBarRect, mScale); final boolean visible = isNavigationBarColorViewVisible(); - if (visible && Color.alpha(mNavigationBarColor) != 0 + if (visible && alpha(mNavigationBarColor) != 0 && !navigationBarRect.isEmpty()) { c.drawRect(navigationBarRect, mNavigationBarPaint); } diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig index 4b2beb903325..983f46c58c4b 100644 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -2,13 +2,6 @@ package: "com.android.window.flags" container: "system" flag { - name: "disable_thin_letterboxing_reachability" - namespace: "large_screen_experiences_app_compat" - description: "Whether reachability is disabled in case of thin letterboxing" - bug: "334077350" -} - -flag { name: "disable_thin_letterboxing_policy" namespace: "large_screen_experiences_app_compat" description: "Whether reachability is disabled in case of thin letterboxing" diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index f08f5b8fddbe..d6f65f8c9d8b 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -171,4 +171,15 @@ flag { description: "Actively release task snapshot memory" bug: "238206323" is_fixed_read_only: true +} + +flag { + name: "draw_snapshot_aspect_ratio_match" + namespace: "windowing_frontend" + description: "The aspect ratio should always match when drawing snapshot" + bug: "341020277" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } }
\ No newline at end of file diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 6af3d9e50d75..21aa4800237c 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -147,4 +147,15 @@ flag { metadata { purpose: PURPOSE_BUGFIX } +} + +flag { + namespace: "windowing_sdk" + name: "insets_control_seq" + description: "Add seqId to InsetsControls to ensure the stale update is ignored" + bug: "339380439" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } }
\ No newline at end of file diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java index 33b4e4a7dd0b..75ddb58590a1 100644 --- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java +++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java @@ -103,6 +103,8 @@ public class AccessibilityShortcutController { // The component name for the sub setting of Hearing aids in Accessibility settings public static final ComponentName ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME = new ComponentName("com.android.server.accessibility", "HearingAids"); + public static final ComponentName ACCESSIBILITY_HEARING_AIDS_TILE_COMPONENT_NAME = + new ComponentName("com.android.server.accessibility", "HearingDevicesTile"); public static final ComponentName COLOR_INVERSION_TILE_COMPONENT_NAME = new ComponentName("com.android.server.accessibility", "ColorInversionTile"); diff --git a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java index c08968dabc89..6420620adda9 100644 --- a/core/java/com/android/internal/accessibility/common/ShortcutConstants.java +++ b/core/java/com/android/internal/accessibility/common/ShortcutConstants.java @@ -16,6 +16,8 @@ package com.android.internal.accessibility.common; +import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME; +import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_TILE_COMPONENT_NAME; import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME; import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME; import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME; @@ -160,6 +162,8 @@ public final class ShortcutConstants { DALTONIZER_COMPONENT_NAME, DALTONIZER_TILE_COMPONENT_NAME, ONE_HANDED_COMPONENT_NAME, ONE_HANDED_TILE_COMPONENT_NAME, REDUCE_BRIGHT_COLORS_COMPONENT_NAME, REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME, - FONT_SIZE_COMPONENT_NAME, FONT_SIZE_TILE_COMPONENT_NAME + FONT_SIZE_COMPONENT_NAME, FONT_SIZE_TILE_COMPONENT_NAME, + ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME, + ACCESSIBILITY_HEARING_AIDS_TILE_COMPONENT_NAME ); } diff --git a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java index ec6283922807..067e5e8813a7 100644 --- a/core/java/com/android/internal/policy/ScreenDecorationsUtils.java +++ b/core/java/com/android/internal/policy/ScreenDecorationsUtils.java @@ -18,6 +18,9 @@ package com.android.internal.policy; import android.content.Context; import android.content.res.Resources; +import android.util.DisplayUtils; +import android.view.Display; +import android.view.DisplayInfo; import android.view.RoundedCorners; import com.android.internal.R; @@ -57,11 +60,31 @@ public class ScreenDecorationsUtils { bottomRadius = defaultRadius; } + // If the physical pixels are scaled, apply it here + float scale = getPhysicalPixelDisplaySizeRatio(context); + if (scale != 1f) { + topRadius = topRadius * scale; + bottomRadius = bottomRadius * scale; + } + // Always use the smallest radius to make sure the rounded corners will // completely cover the display. return Math.min(topRadius, bottomRadius); } + static float getPhysicalPixelDisplaySizeRatio(Context context) { + DisplayInfo displayInfo = new DisplayInfo(); + context.getDisplay().getDisplayInfo(displayInfo); + final Display.Mode maxDisplayMode = + DisplayUtils.getMaximumResolutionDisplayMode(displayInfo.supportedModes); + if (maxDisplayMode == null) { + return 1f; + } + return DisplayUtils.getPhysicalPixelDisplaySizeRatio( + maxDisplayMode.getPhysicalWidth(), maxDisplayMode.getPhysicalHeight(), + displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight()); + } + /** * If live rounded corners are supported on windows. */ diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 7b9235cdc691..6dbe44b483d2 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4702,6 +4702,11 @@ <permission android:name="android.permission.REQUEST_UNIQUE_ID_ATTESTATION" android:protectionLevel="signature" /> + <!-- Allows an application to use the RemoteKeyProvisioningService. + @hide --> + <permission android:name="android.permission.BIND_RKP_SERVICE" + android:protectionLevel="signature" /> + <!-- Allows an application to get enabled credential manager providers. @hide --> <permission android:name="android.permission.LIST_ENABLED_CREDENTIAL_PROVIDERS" diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index 04f6f5214e74..dcda5d8669a4 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -403,4 +403,10 @@ <integer name="config_wait_for_datagram_sending_response_for_last_message_timeout_millis">60000</integer> <java-symbol type="integer" name="config_wait_for_datagram_sending_response_for_last_message_timeout_millis" /> + <!-- Boolean indicating whether Telephony should force PhoneGlobals creation + regardless of FEATURE_TELEPHONY presence. + --> + <bool name="config_force_phone_globals_creation">false</bool> + <java-symbol type="bool" name="config_force_phone_globals_creation" /> + </resources> diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java index 0b1b40c8ba8b..07446e7617aa 100644 --- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java +++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java @@ -41,11 +41,13 @@ import android.app.Instrumentation; import android.os.Handler; import android.os.Looper; import android.os.SystemClock; +import android.platform.test.annotations.LargeTest; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.util.DisplayMetrics; import android.widget.FrameLayout; +import android.widget.ProgressBar; import androidx.test.annotation.UiThreadTest; import androidx.test.filters.SmallTest; @@ -623,6 +625,162 @@ public class ViewFrameRateTest { assertEquals(FRAME_RATE_CATEGORY_HIGH_HINT, mViewRoot.getLastPreferredFrameRateCategory()); } + @LargeTest + @Test + @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, + FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY, + com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4 + }) + public void idleDetected() throws Throwable { + waitForFrameRateCategoryToSettle(); + mActivityRule.runOnUiThread(() -> { + mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_HIGH); + mMovingView.setFrameContentVelocity(Float.MAX_VALUE); + mMovingView.invalidate(); + runAfterDraw(() -> assertEquals(FRAME_RATE_CATEGORY_HIGH, + mViewRoot.getLastPreferredFrameRateCategory())); + }); + waitForAfterDraw(); + + // Wait for idle timeout + Thread.sleep(1000); + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE, + mViewRoot.getLastPreferredFrameRateCategory()); + } + + @LargeTest + @Test + @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, + FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY, + com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4 + }) + public void vectorDrawableFrameRate() throws Throwable { + final ProgressBar[] progressBars = new ProgressBar[3]; + final ViewGroup[] parents = new ViewGroup[1]; + mActivityRule.runOnUiThread(() -> { + ViewGroup parent = (ViewGroup) mMovingView.getParent(); + parents[0] = parent; + ProgressBar progressBar1 = new ProgressBar(mActivity); + parent.addView(progressBar1); + progressBar1.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW); + progressBar1.setIndeterminate(true); + progressBars[0] = progressBar1; + + ProgressBar progressBar2 = new ProgressBar(mActivity); + parent.addView(progressBar2); + progressBar2.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NORMAL); + progressBar2.setIndeterminate(true); + progressBars[1] = progressBar2; + + ProgressBar progressBar3 = new ProgressBar(mActivity); + parent.addView(progressBar3); + progressBar3.setRequestedFrameRate(45f); + progressBar3.setIndeterminate(true); + progressBars[2] = progressBar3; + }); + waitForFrameRateCategoryToSettle(); + + // Wait for idle timeout + Thread.sleep(1000); + assertEquals(45f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_NORMAL, mViewRoot.getLastPreferredFrameRateCategory()); + + // Removing the vector drawable with NORMAL should drop the category to LOW + mActivityRule.runOnUiThread(() -> parents[0].removeView(progressBars[1])); + Thread.sleep(1000); + assertEquals(45f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_LOW, + mViewRoot.getLastPreferredFrameRateCategory()); + // Removing the one voting for frame rate should leave only the category + mActivityRule.runOnUiThread(() -> parents[0].removeView(progressBars[2])); + Thread.sleep(1000); + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_LOW, + mViewRoot.getLastPreferredFrameRateCategory()); + // Removing the last one should leave it with no preference + mActivityRule.runOnUiThread(() -> parents[0].removeView(progressBars[0])); + Thread.sleep(1000); + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE, + mViewRoot.getLastPreferredFrameRateCategory()); + } + + @LargeTest + @Test + @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, + FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY, + com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4 + }) + public void renderNodeAnimatorFrameRateCanceled() throws Throwable { + mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); + waitForFrameRateCategoryToSettle(); + + RenderNodeAnimator[] renderNodeAnimator = new RenderNodeAnimator[1]; + renderNodeAnimator[0] = new RenderNodeAnimator(RenderNodeAnimator.ALPHA, 0f); + renderNodeAnimator[0].setDuration(100000); + + mActivityRule.runOnUiThread(() -> { + renderNodeAnimator[0].setTarget(mMovingView); + renderNodeAnimator[0].start(); + mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW); + runAfterDraw(() -> { + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_LOW, + mViewRoot.getLastPreferredFrameRateCategory()); + }); + }); + waitForAfterDraw(); + + mActivityRule.runOnUiThread(() -> { + renderNodeAnimator[0].cancel(); + }); + + // Wait for idle timeout + Thread.sleep(1000); + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE, + mViewRoot.getLastPreferredFrameRateCategory()); + } + + @LargeTest + @Test + @RequiresFlagsEnabled({FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY, + FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY, + com.android.graphics.surfaceflinger.flags.Flags.FLAG_VRR_BUGFIX_24Q4 + }) + public void renderNodeAnimatorFrameRateRemoved() throws Throwable { + mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE); + waitForFrameRateCategoryToSettle(); + + RenderNodeAnimator[] renderNodeAnimator = new RenderNodeAnimator[1]; + renderNodeAnimator[0] = new RenderNodeAnimator(RenderNodeAnimator.ALPHA, 0f); + renderNodeAnimator[0].setDuration(100000); + + mActivityRule.runOnUiThread(() -> { + renderNodeAnimator[0].setTarget(mMovingView); + renderNodeAnimator[0].start(); + mMovingView.setRequestedFrameRate(View.REQUESTED_FRAME_RATE_CATEGORY_LOW); + runAfterDraw(() -> { + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_LOW, + mViewRoot.getLastPreferredFrameRateCategory()); + }); + }); + waitForAfterDraw(); + + mActivityRule.runOnUiThread(() -> { + ViewGroup parent = (ViewGroup) mMovingView.getParent(); + assert parent != null; + parent.removeView(mMovingView); + }); + + Thread.sleep(1000); + assertEquals(0f, mViewRoot.getLastPreferredFrameRate()); + assertEquals(FRAME_RATE_CATEGORY_NO_PREFERENCE, + mViewRoot.getLastPreferredFrameRateCategory()); + } + private void runAfterDraw(@NonNull Runnable runnable) { Handler handler = new Handler(Looper.getMainLooper()); mAfterDrawLatch = new CountDownLatch(1); diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml index f9fb84d2b31d..782327713fdc 100644 --- a/data/etc/preinstalled-packages-platform.xml +++ b/data/etc/preinstalled-packages-platform.xml @@ -134,19 +134,4 @@ to pre-existing users, but cannot uninstall pre-existing system packages from pr <install-in-user-type package="com.android.avatarpicker"> <install-in user-type="FULL" /> </install-in-user-type> - - <!-- AiLabs Warp app pre-installed in hardware/google/pixel/common/pixel-common-device.mk --> - <install-in-user-type package="com.google.android.apps.warp"> - <install-in user-type="FULL" /> - <install-in user-type="PROFILE" /> - <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" /> - </install-in-user-type> - - <!-- Google Home app pre-installed on tangor devices in vendor/google/products/tangor_common.mk - --> - <install-in-user-type package="com.google.android.apps.chromecast.app"> - <install-in user-type="FULL" /> - <install-in user-type="PROFILE" /> - <do-not-install-in user-type="android.os.usertype.profile.PRIVATE" /> - </install-in-user-type> </config> diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java index 0650b7817729..211f74a47bdd 100644 --- a/graphics/java/android/graphics/RenderNode.java +++ b/graphics/java/android/graphics/RenderNode.java @@ -16,6 +16,7 @@ package android.graphics; +import android.animation.Animator; import android.annotation.BytesLong; import android.annotation.ColorInt; import android.annotation.FloatRange; @@ -1639,7 +1640,7 @@ public final class RenderNode { */ public interface AnimationHost { /** @hide */ - void registerAnimatingRenderNode(RenderNode animator); + void registerAnimatingRenderNode(RenderNode renderNode, Animator animator); /** @hide */ void registerVectorDrawableAnimator(NativeVectorDrawableAnimator animator); @@ -1654,7 +1655,7 @@ public final class RenderNode { throw new IllegalStateException("Cannot start this animator on a detached view!"); } nAddAnimator(mNativeRenderNode, animator.getNativeAnimator()); - mAnimationHost.registerAnimatingRenderNode(this); + mAnimationHost.registerAnimatingRenderNode(this, animator); } /** @hide */ diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java index 55f205bb14a6..d4bb461c284e 100644 --- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java @@ -1266,6 +1266,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { private final IntArray mPendingAnimationActions = new IntArray(); private final AnimatedVectorDrawable mDrawable; private long mTotalDuration; + private AnimatorListener mThreadedRendererAnimatorListener; VectorDrawableAnimatorRT(AnimatedVectorDrawable drawable) { mDrawable = drawable; @@ -1689,6 +1690,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { if (mListener != null) { mListener.onAnimationStart(null); } + if (mThreadedRendererAnimatorListener != null) { + mThreadedRendererAnimatorListener.onAnimationStart(null); + } } // This should only be called after animator has been added to the RenderNode target. @@ -1717,6 +1721,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { if (mListener != null) { mListener.onAnimationStart(null); } + if (mThreadedRendererAnimatorListener != null) { + mThreadedRendererAnimatorListener.onAnimationStart(null); + } } @Override @@ -1725,6 +1732,11 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { } @Override + public void setThreadedRendererAnimatorListener(AnimatorListener animatorListener) { + mThreadedRendererAnimatorListener = animatorListener; + } + + @Override public boolean canReverse() { return mIsReversible; } @@ -1788,6 +1800,9 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable2 { if (mListener != null) { mListener.onAnimationEnd(null); } + if (mThreadedRendererAnimatorListener != null) { + mThreadedRendererAnimatorListener.onAnimationEnd(null); + } } // onFinished: should be called from native diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java index 29936cc2cac3..6b957114c00b 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java @@ -514,7 +514,12 @@ class DividerPresenter implements View.OnTouchListener { mProperties.mDividerAttributes, mProperties.mIsVerticalSplit, calculateMinPosition(), calculateMaxPosition()); mRenderer.setDividerPosition(mDividerPosition); - switch (event.getAction()) { + + // Convert to use screen-based coordinates to prevent lost track of motion events + // while moving divider bar and calculating dragging velocity. + event.setLocation(event.getRawX(), event.getRawY()); + final int action = event.getAction() & MotionEvent.ACTION_MASK; + switch (action) { case MotionEvent.ACTION_DOWN: onStartDragging(event); break; @@ -713,9 +718,9 @@ class DividerPresenter implements View.OnTouchListener { return snap(dividerPosition, possiblePositions); } if (velocity < 0) { - return 0; + return minPosition; } else { - return fullyExpandedPosition; + return maxPosition; } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 9aa12aa824df..f78e2b5170fc 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -1322,7 +1322,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen mPresenter.expandTaskFragment(wct, container); } else { // Put activity into a new expanded container. - final TaskFragmentContainer newContainer = newContainer(activity, getTaskId(activity)); + final TaskFragmentContainer newContainer = + new TaskFragmentContainer.Builder(this, getTaskId(activity), activity) + .setPendingAppearedActivity(activity).build(); mPresenter.expandActivity(wct, newContainer.getTaskFragmentToken(), activity); } } @@ -1738,9 +1740,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Can't find any activity in the Task that we can use as the owner activity. return null; } - final TaskFragmentContainer container = newContainer(null /* pendingAppearedActivity */, - intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag, - launchOptions, associateLaunchingActivity); + final TaskFragmentContainer container = + new TaskFragmentContainer.Builder(this, taskId, activityInTask) + .setPendingAppearedIntent(intent) + .setOverlayTag(overlayTag) + .setLaunchOptions(launchOptions) + .setAssociatedActivity(associateLaunchingActivity ? activityInTask : null) + .build(); final IBinder taskFragmentToken = container.getTaskFragmentToken(); // Note that taskContainer will not exist before calling #newContainer if the container // is the first embedded TF in the task. @@ -1818,74 +1824,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return null; } - @GuardedBy("mLock") - TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity, int taskId) { - return newContainer(pendingAppearedActivity, pendingAppearedActivity, taskId); - } - - @GuardedBy("mLock") - TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity, - @NonNull Activity activityInTask, int taskId) { - return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */, - activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */, - null /* launchOptions */, false /* associateLaunchingActivity */); - } - - @GuardedBy("mLock") - TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent, - @NonNull Activity activityInTask, int taskId) { - return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent, - activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */, - null /* launchOptions */, false /* associateLaunchingActivity */); - } - - @GuardedBy("mLock") - TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent, - @NonNull Activity activityInTask, int taskId, - @NonNull TaskFragmentContainer pairedPrimaryContainer) { - return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent, - activityInTask, taskId, pairedPrimaryContainer, null /* tag */, - null /* launchOptions */, false /* associateLaunchingActivity */); - } - - /** - * Creates and registers a new organized container with an optional activity that will be - * re-parented to it in a WCT. - * - * @param pendingAppearedActivity the activity that will be reparented to the TaskFragment. - * @param pendingAppearedIntent the Intent that will be started in the TaskFragment. - * @param activityInTask activity in the same Task so that we can get the Task bounds - * if needed. - * @param taskId parent Task of the new TaskFragment. - * @param pairedContainer the paired primary {@link TaskFragmentContainer}. When it is - * set, the new container will be added right above it. - * @param overlayTag The tag for the new created overlay container. It must be - * needed if {@code isOverlay} is {@code true}. Otherwise, - * it should be {@code null}. - * @param launchOptions The launch options bundle to create a container. Must be - * specified for overlay container. - * @param associateLaunchingActivity {@code true} to indicate this overlay container - * should associate with launching activity. - */ - @GuardedBy("mLock") - TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity, - @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId, - @Nullable TaskFragmentContainer pairedContainer, @Nullable String overlayTag, - @Nullable Bundle launchOptions, boolean associateLaunchingActivity) { - if (activityInTask == null) { - throw new IllegalArgumentException("activityInTask must not be null,"); - } - if (!mTaskContainers.contains(taskId)) { - mTaskContainers.put(taskId, new TaskContainer(taskId, activityInTask)); - mDividerPresenters.put(taskId, new DividerPresenter(taskId, this, mExecutor)); - } - final TaskContainer taskContainer = mTaskContainers.get(taskId); - final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity, - pendingAppearedIntent, taskContainer, this, pairedContainer, overlayTag, - launchOptions, associateLaunchingActivity ? activityInTask : null); - return container; - } - /** * Creates and registers a new split with the provided containers and configuration. Finishes * existing secondary containers if found for the given primary container. @@ -2581,6 +2519,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return mTaskContainers.get(taskId); } + @GuardedBy("mLock") + void addTaskContainer(int taskId, TaskContainer taskContainer) { + mTaskContainers.put(taskId, taskContainer); + mDividerPresenters.put(taskId, new DividerPresenter(taskId, this, mExecutor)); + } + Handler getHandler() { return mHandler; } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index 1cb410e90a76..eade86e50659 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -186,8 +186,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // Create new empty task fragment final int taskId = primaryContainer.getTaskId(); - final TaskFragmentContainer secondaryContainer = mController.newContainer( - secondaryIntent, primaryActivity, taskId); + final TaskFragmentContainer secondaryContainer = + new TaskFragmentContainer.Builder(mController, taskId, primaryActivity) + .setPendingAppearedIntent(secondaryIntent).build(); final Rect secondaryRelBounds = getRelBoundsForPosition(POSITION_END, taskProperties, splitAttributes); final int windowingMode = mController.getTaskContainer(taskId) @@ -261,7 +262,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { TaskFragmentContainer container = mController.getContainerWithActivity(activity); final int taskId = container != null ? container.getTaskId() : activity.getTaskId(); if (container == null || container == containerToAvoid) { - container = mController.newContainer(activity, taskId); + container = new TaskFragmentContainer.Builder(mController, taskId, activity) + .setPendingAppearedActivity(activity).build(); final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForTaskFragment(relBounds); final IBinder reparentActivityToken = activity.getActivityToken(); @@ -304,15 +306,19 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { TaskFragmentContainer primaryContainer = mController.getContainerWithActivity( launchingActivity); if (primaryContainer == null) { - primaryContainer = mController.newContainer(launchingActivity, - launchingActivity.getTaskId()); + primaryContainer = new TaskFragmentContainer.Builder(mController, + launchingActivity.getTaskId(), launchingActivity) + .setPendingAppearedActivity(launchingActivity).build(); } final int taskId = primaryContainer.getTaskId(); - final TaskFragmentContainer secondaryContainer = mController.newContainer(activityIntent, - launchingActivity, taskId, - // Pass in the primary container to make sure it is added right above the primary. - primaryContainer); + final TaskFragmentContainer secondaryContainer = + new TaskFragmentContainer.Builder(mController, taskId, launchingActivity) + .setPendingAppearedIntent(activityIntent) + // Pass in the primary container to make sure it is added right above the + // primary. + .setPairedPrimaryContainer(primaryContainer) + .build(); final TaskContainer taskContainer = mController.getTaskContainer(taskId); final int windowingMode = taskContainer.getWindowingModeForTaskFragment( primaryRelBounds); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index d0b6a01bb51e..7173b0c95230 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -195,20 +195,6 @@ class TaskFragmentContainer { private boolean mLastDimOnTask; /** - * @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController, - * TaskFragmentContainer, String, Bundle, Activity) - */ - TaskFragmentContainer(@Nullable Activity pendingAppearedActivity, - @Nullable Intent pendingAppearedIntent, - @NonNull TaskContainer taskContainer, - @NonNull SplitController controller, - @Nullable TaskFragmentContainer pairedPrimaryContainer) { - this(pendingAppearedActivity, pendingAppearedIntent, taskContainer, - controller, pairedPrimaryContainer, null /* overlayTag */, - null /* launchOptions */, null /* associatedActivity */); - } - - /** * Creates a container with an existing activity that will be re-parented to it in a window * container transaction. * @param pairedPrimaryContainer when it is set, the new container will be add right above it @@ -218,7 +204,7 @@ class TaskFragmentContainer { * @param associatedActivity the associated activity of the overlay container. Must be * {@code null} for a non-overlay container. */ - TaskFragmentContainer(@Nullable Activity pendingAppearedActivity, + private TaskFragmentContainer(@Nullable Activity pendingAppearedActivity, @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer, @NonNull SplitController controller, @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag, @@ -232,12 +218,6 @@ class TaskFragmentContainer { mToken = new Binder("TaskFragmentContainer"); mTaskContainer = taskContainer; mOverlayTag = overlayTag; - if (overlayTag != null) { - Objects.requireNonNull(launchOptions); - } else if (associatedActivity != null) { - throw new IllegalArgumentException("Associated activity must be null for " - + "non-overlay activity."); - } mAssociatedActivityToken = associatedActivity != null ? associatedActivity.getActivityToken() : null; @@ -1116,6 +1096,117 @@ class TaskFragmentContainer { return sb.append("]").toString(); } + static final class Builder { + @NonNull + private final SplitController mSplitController; + + // The parent Task id of the new TaskFragment. + private final int mTaskId; + + // The activity in the same Task so that we can get the Task bounds if needed. + @NonNull + private final Activity mActivityInTask; + + // The activity that will be reparented to the TaskFragment. + @Nullable + private Activity mPendingAppearedActivity; + + // The Intent that will be started in the TaskFragment. + @Nullable + private Intent mPendingAppearedIntent; + + // The paired primary {@link TaskFragmentContainer}. When it is set, the new container + // will be added right above it. + @Nullable + private TaskFragmentContainer mPairedPrimaryContainer; + + // The launch options bundle to create a container. Must be specified for overlay container. + @Nullable + private Bundle mLaunchOptions; + + // The tag for the new created overlay container. This is required when creating an + // overlay container. + @Nullable + private String mOverlayTag; + + // The associated activity of the overlay container. Must be {@code null} for a + // non-overlay container. + @Nullable + private Activity mAssociatedActivity; + + Builder(@NonNull SplitController splitController, int taskId, + @Nullable Activity activityInTask) { + if (taskId <= 0) { + throw new IllegalArgumentException("taskId is invalid, " + taskId); + } + + mSplitController = splitController; + mTaskId = taskId; + mActivityInTask = activityInTask; + } + + @NonNull + Builder setPendingAppearedActivity(@Nullable Activity pendingAppearedActivity) { + mPendingAppearedActivity = pendingAppearedActivity; + return this; + } + + @NonNull + Builder setPendingAppearedIntent(@Nullable Intent pendingAppearedIntent) { + mPendingAppearedIntent = pendingAppearedIntent; + return this; + } + + @NonNull + Builder setPairedPrimaryContainer(@Nullable TaskFragmentContainer pairedPrimaryContainer) { + mPairedPrimaryContainer = pairedPrimaryContainer; + return this; + } + + @NonNull + Builder setLaunchOptions(@Nullable Bundle launchOptions) { + mLaunchOptions = launchOptions; + return this; + } + + @NonNull + Builder setOverlayTag(@Nullable String overlayTag) { + mOverlayTag = overlayTag; + return this; + } + + @NonNull + Builder setAssociatedActivity(@Nullable Activity associatedActivity) { + mAssociatedActivity = associatedActivity; + return this; + } + + @NonNull + TaskFragmentContainer build() { + if (mOverlayTag != null) { + Objects.requireNonNull(mLaunchOptions); + } else if (mAssociatedActivity != null) { + throw new IllegalArgumentException("Associated activity must be null for " + + "non-overlay activity."); + } + + TaskContainer taskContainer = mSplitController.getTaskContainer(mTaskId); + if (taskContainer == null && mActivityInTask == null) { + throw new IllegalArgumentException("mActivityInTask must be set."); + } + + if (taskContainer == null) { + // Adding a TaskContainer if no existed one. + taskContainer = new TaskContainer(mTaskId, mActivityInTask); + mSplitController.addTaskContainer(mTaskId, taskContainer); + } + + return new TaskFragmentContainer(mPendingAppearedActivity, mPendingAppearedIntent, + taskContainer, mSplitController, mPairedPrimaryContainer, mOverlayTag, + mLaunchOptions, mAssociatedActivity); + } + } + static class OverlayContainerRestoreParams { /** The token of the overlay container */ @NonNull diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java index 746607c8094c..20626c79714e 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java @@ -720,7 +720,7 @@ public class DividerPresenterTest { // Divider position is greater than minPosition and the velocity is enough for fling assertEquals( - 0, // Closed position + 30, // minPosition DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( 50 /* dividerPosition */, 30 /* minPosition */, @@ -731,7 +731,7 @@ public class DividerPresenterTest { // Divider position is less than maxPosition and the velocity is enough for fling assertEquals( - 1200, // Fully expanded position + 900, // maxPosition DividerPresenter.dividerPositionWithDraggingToFullscreenAllowed( 800 /* dividerPosition */, 30 /* minPosition */, diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java index a069ac7256d6..d649c6d57137 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java @@ -248,4 +248,17 @@ public class EmbeddingTestUtils { return new SplitPlaceholderRule.Builder(placeholderIntent, activityPredicate, intentPredicate, windowMetricsPredicate); } + + @NonNull + static TaskFragmentContainer createTfContainer( + @NonNull SplitController splitController, @NonNull Activity activity) { + return createTfContainer(splitController, TASK_ID, activity); + } + + @NonNull + static TaskFragmentContainer createTfContainer( + @NonNull SplitController splitController, int taskId, @NonNull Activity activity) { + return new TaskFragmentContainer.Builder(splitController, taskId, activity) + .setPendingAppearedActivity(activity).build(); + } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java index 76e6a0ff2c21..7b473b04548c 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java @@ -25,6 +25,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -105,8 +106,11 @@ public class JetpackTaskFragmentOrganizerTest { @Test public void testExpandTaskFragment() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */); + doReturn(taskContainer).when(mSplitController).getTaskContainer(anyInt()); + final TaskFragmentContainer container = new TaskFragmentContainer.Builder(mSplitController, + taskContainer.getTaskId(), null /* activityInTask */) + .setPendingAppearedIntent(new Intent()) + .build(); final TaskFragmentInfo info = createMockInfo(container); mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info); container.setInfo(mTransaction, info); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java index 86b7e88a0c1a..0972d40f33e3 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java @@ -29,6 +29,7 @@ import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMock import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPlaceholderRuleBuilder; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTfContainer; import static androidx.window.extensions.embedding.SplitPresenter.sanitizeBounds; import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK; @@ -530,8 +531,8 @@ public class OverlayPresentationTest { @Test public void testUpdateActivityStackAttributes_nullContainer_earlyReturn() { - final TaskFragmentContainer container = mSplitController.newContainer(mActivity, - mActivity.getTaskId()); + final TaskFragmentContainer container = createTfContainer(mSplitController, + mActivity.getTaskId(), mActivity); mSplitController.updateActivityStackAttributes( ActivityStack.Token.createFromBinder(container.getTaskFragmentToken()), new ActivityStackAttributes.Builder().build()); @@ -837,8 +838,9 @@ public class OverlayPresentationTest { final Intent intent = new Intent(); final IBinder fillTaskActivityToken = new Binder(); final IBinder lastOverlayToken = new Binder(); - final TaskFragmentContainer overlayContainer = mSplitController.newContainer(intent, - mActivity, TASK_ID); + final TaskFragmentContainer overlayContainer = + new TaskFragmentContainer.Builder(mSplitController, TASK_ID, mActivity) + .setPendingAppearedIntent(intent).build(); final TaskFragmentContainer.OverlayContainerRestoreParams params = mock( TaskFragmentContainer.OverlayContainerRestoreParams.class); doReturn(params).when(mSplitController).getOverlayContainerRestoreParams(any(), any()); @@ -884,8 +886,8 @@ public class OverlayPresentationTest { @NonNull private TaskFragmentContainer createMockTaskFragmentContainer( @NonNull Activity activity, boolean isVisible) { - final TaskFragmentContainer container = mSplitController.newContainer(activity, - activity.getTaskId()); + final TaskFragmentContainer container = createTfContainer(mSplitController, + activity.getTaskId(), activity); setupTaskFragmentInfo(container, activity, isVisible); return container; } @@ -918,10 +920,13 @@ public class OverlayPresentationTest { @Nullable Activity launchingActivity) { final Activity activity = launchingActivity != null ? launchingActivity : createMockActivity(); - TaskFragmentContainer overlayContainer = mSplitController.newContainer( - null /* pendingAppearedActivity */, mIntent, activity, taskId, - null /* pairedPrimaryContainer */, tag, Bundle.EMPTY, - associateLaunchingActivity); + TaskFragmentContainer overlayContainer = + new TaskFragmentContainer.Builder(mSplitController, taskId, activity) + .setPendingAppearedIntent(mIntent) + .setOverlayTag(tag) + .setLaunchOptions(Bundle.EMPTY) + .setAssociatedActivity(associateLaunchingActivity ? activity : null) + .build(); setupTaskFragmentInfo(overlayContainer, createMockActivity(), isVisible); return overlayContainer; } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 35353dbe36be..640b1fced455 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -41,6 +41,7 @@ import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSpli import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPlaceholderRuleBuilder; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTfContainer; import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds; import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS; @@ -59,7 +60,6 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.clearInvocations; @@ -198,7 +198,7 @@ public class SplitControllerTest { @Test public void testOnTaskFragmentVanished() { - final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer tf = createTfContainer(mSplitController, mActivity); doReturn(tf.getTaskFragmentToken()).when(mInfo).getFragmentToken(); // The TaskFragment has been removed in the server, we only need to cleanup the reference. @@ -213,7 +213,7 @@ public class SplitControllerTest { public void testOnTaskFragmentAppearEmptyTimeout() { // Setup to make sure a transaction record is started. mTransactionManager.startNewTransaction(); - final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer tf = createTfContainer(mSplitController, mActivity); doCallRealMethod().when(mSplitController).onTaskFragmentAppearEmptyTimeout(any(), any()); mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf); @@ -224,7 +224,7 @@ public class SplitControllerTest { @Test public void testOnActivityDestroyed() { doReturn(new Binder()).when(mActivity).getActivityToken(); - final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer tf = createTfContainer(mSplitController, mActivity); assertTrue(tf.hasActivity(mActivity.getActivityToken())); @@ -245,12 +245,9 @@ public class SplitControllerTest { public void testNewContainer() { // Must pass in a valid activity. assertThrows(IllegalArgumentException.class, () -> - mSplitController.newContainer(null /* activity */, TASK_ID)); - assertThrows(IllegalArgumentException.class, () -> - mSplitController.newContainer(mActivity, null /* launchingActivity */, TASK_ID)); + createTfContainer(mSplitController, null /* activity */)); - final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, mActivity, - TASK_ID); + final TaskFragmentContainer tf = createTfContainer(mSplitController, mActivity); final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID); assertNotNull(tf); @@ -263,7 +260,7 @@ public class SplitControllerTest { public void testUpdateContainer() { // Make SplitController#launchPlaceholderIfNecessary(TaskFragmentContainer) return true // and verify if shouldContainerBeExpanded() not called. - final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer tf = createTfContainer(mSplitController, mActivity); spyOn(tf); doReturn(mActivity).when(tf).getTopNonFinishingActivity(); doReturn(true).when(tf).isEmpty(); @@ -369,8 +366,12 @@ public class SplitControllerTest { public void testOnStartActivityResultError() { final Intent intent = new Intent(); final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - intent, taskContainer, mSplitController, null /* pairedPrimaryContainer */); + final int taskId = taskContainer.getTaskId(); + mSplitController.addTaskContainer(taskId, taskContainer); + final TaskFragmentContainer container = new TaskFragmentContainer.Builder(mSplitController, + taskId, null /* activityInTask */) + .setPendingAppearedIntent(intent) + .build(); final SplitController.ActivityStartMonitor monitor = mSplitController.getActivityStartMonitor(); @@ -410,7 +411,8 @@ public class SplitControllerTest { @Test public void testOnActivityReparentedToTask_diffProcess() { // Create an empty TaskFragment to initialize for the Task. - mSplitController.newContainer(new Intent(), mActivity, TASK_ID); + new TaskFragmentContainer.Builder(mSplitController, TASK_ID, mActivity) + .setPendingAppearedIntent(new Intent()).build(); final IBinder activityToken = new Binder(); final Intent intent = new Intent(); @@ -595,8 +597,9 @@ public class SplitControllerTest { verify(mTransaction, never()).reparentActivityToTaskFragment(any(), any()); // Place in the top container if there is no other rule matched. - final TaskFragmentContainer topContainer = mSplitController - .newContainer(new Intent(), mActivity, TASK_ID); + final TaskFragmentContainer topContainer = + new TaskFragmentContainer.Builder(mSplitController, TASK_ID, mActivity) + .setPendingAppearedIntent(new Intent()).build(); mSplitController.placeActivityInTopContainer(mTransaction, mActivity); verify(mTransaction).reparentActivityToTaskFragment(topContainer.getTaskFragmentToken(), @@ -604,7 +607,7 @@ public class SplitControllerTest { // Not reparent if activity is in a TaskFragment. clearInvocations(mTransaction); - mSplitController.newContainer(mActivity, TASK_ID); + createTfContainer(mSplitController, mActivity); mSplitController.placeActivityInTopContainer(mTransaction, mActivity); verify(mTransaction, never()).reparentActivityToTaskFragment(any(), any()); @@ -616,8 +619,7 @@ public class SplitControllerTest { false /* isOnReparent */); assertFalse(result); - verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(), - anyString(), any(), anyBoolean()); + verify(mSplitController, never()).addTaskContainer(anyInt(), any()); } @Test @@ -632,7 +634,6 @@ public class SplitControllerTest { assertTrue(result); assertNotNull(container); - verify(mSplitController).newContainer(mActivity, TASK_ID); verify(mSplitPresenter).expandActivity(mTransaction, container.getTaskFragmentToken(), mActivity); } @@ -642,7 +643,7 @@ public class SplitControllerTest { setupExpandRule(mActivity); // When the activity is not in any TaskFragment, create a new expanded TaskFragment for it. - final TaskFragmentContainer container = mSplitController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container = createTfContainer(mSplitController, mActivity); final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); @@ -692,8 +693,8 @@ public class SplitControllerTest { // Don't launch placeholder if the activity is not in the topmost active TaskFragment. final Activity activity = createMockActivity(); - mSplitController.newContainer(mActivity, TASK_ID); - mSplitController.newContainer(activity, TASK_ID); + createTfContainer(mSplitController, mActivity); + createTfContainer(mSplitController, activity); final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); @@ -711,7 +712,7 @@ public class SplitControllerTest { (SplitPlaceholderRule) mSplitController.getSplitRules().get(0); // Launch placeholder if the activity is in the topmost expanded TaskFragment. - mSplitController.newContainer(mActivity, TASK_ID); + createTfContainer(mSplitController, mActivity); final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); @@ -763,10 +764,11 @@ public class SplitControllerTest { final SplitPairRule splitRule = (SplitPairRule) mSplitController.getSplitRules().get(0); // Activity is already in primary split, no need to create new split. - final TaskFragmentContainer primaryContainer = mSplitController.newContainer(mActivity, - TASK_ID); - final TaskFragmentContainer secondaryContainer = mSplitController.newContainer( - secondaryIntent, mActivity, TASK_ID); + final TaskFragmentContainer primaryContainer = + createTfContainer(mSplitController, mActivity); + final TaskFragmentContainer secondaryContainer = + new TaskFragmentContainer.Builder(mSplitController, TASK_ID, mActivity) + .setPendingAppearedIntent(secondaryIntent).build(); mSplitController.registerSplit( mTransaction, primaryContainer, @@ -779,8 +781,6 @@ public class SplitControllerTest { false /* isOnReparent */); assertTrue(result); - verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(), - anyString(), any(), anyBoolean()); verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any()); } @@ -792,10 +792,11 @@ public class SplitControllerTest { // The new launched activity is in primary split, but there is no rule for it to split with // the secondary, so return false. - final TaskFragmentContainer primaryContainer = mSplitController.newContainer(mActivity, - TASK_ID); - final TaskFragmentContainer secondaryContainer = mSplitController.newContainer( - secondaryIntent, mActivity, TASK_ID); + final TaskFragmentContainer primaryContainer = + createTfContainer(mSplitController, mActivity); + final TaskFragmentContainer secondaryContainer = + new TaskFragmentContainer.Builder(mSplitController, TASK_ID, mActivity) + .setPendingAppearedIntent(secondaryIntent).build(); mSplitController.registerSplit( mTransaction, primaryContainer, @@ -822,8 +823,6 @@ public class SplitControllerTest { false /* isOnReparent */); assertTrue(result); - verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(), - anyString(), any(), anyBoolean()); verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any()); } @@ -852,10 +851,10 @@ public class SplitControllerTest { doReturn(PLACEHOLDER_INTENT).when(mActivity).getIntent(); // Activity is a placeholder. - final TaskFragmentContainer primaryContainer = mSplitController.newContainer( - primaryActivity, TASK_ID); - final TaskFragmentContainer secondaryContainer = mSplitController.newContainer(mActivity, - TASK_ID); + final TaskFragmentContainer primaryContainer = + createTfContainer(mSplitController, primaryActivity); + final TaskFragmentContainer secondaryContainer = + createTfContainer(mSplitController, mActivity); mSplitController.registerSplit( mTransaction, primaryContainer, @@ -874,8 +873,7 @@ public class SplitControllerTest { final Activity activityBelow = createMockActivity(); setupSplitRule(activityBelow, mActivity); - final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, - TASK_ID); + final TaskFragmentContainer container = createTfContainer(mSplitController, activityBelow); container.addPendingAppearedActivity(mActivity); final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); @@ -890,8 +888,7 @@ public class SplitControllerTest { setupSplitRule(mActivity, activityBelow); // Disallow to split as primary. - final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, - TASK_ID); + final TaskFragmentContainer container = createTfContainer(mSplitController, activityBelow); container.addPendingAppearedActivity(mActivity); boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); @@ -961,8 +958,7 @@ public class SplitControllerTest { doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo(); - final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, - TASK_ID); + final TaskFragmentContainer container = createTfContainer(mSplitController, activityBelow); container.addPendingAppearedActivity(mActivity); // Allow to split as primary. @@ -980,8 +976,7 @@ public class SplitControllerTest { doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo(); - final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, - TASK_ID); + final TaskFragmentContainer container = createTfContainer(mSplitController, activityBelow); container.addPendingAppearedActivity(mActivity); boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, @@ -1044,8 +1039,8 @@ public class SplitControllerTest { public void testResolveActivityToContainer_skipIfNonTopOrPinned() { final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity); final Activity pinnedActivity = createMockActivity(); - final TaskFragmentContainer topContainer = mSplitController.newContainer(pinnedActivity, - TASK_ID); + final TaskFragmentContainer topContainer = + createTfContainer(mSplitController, pinnedActivity); final TaskContainer taskContainer = container.getTaskContainer(); spyOn(taskContainer); doReturn(container).when(taskContainer).getTopNonFinishingTaskFragmentContainer(false); @@ -1351,7 +1346,7 @@ public class SplitControllerTest { // Launch placeholder for activity in top TaskFragment. setupPlaceholderRule(mActivity); mTransactionManager.startNewTransaction(); - final TaskFragmentContainer container = mSplitController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container = createTfContainer(mSplitController, mActivity); mSplitController.launchPlaceholderIfNecessary(mTransaction, mActivity, true /* isOnCreated */); @@ -1365,9 +1360,10 @@ public class SplitControllerTest { // Do not launch placeholder for invisible activity below the top TaskFragment. setupPlaceholderRule(mActivity); mTransactionManager.startNewTransaction(); - final TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID); - final TaskFragmentContainer topTf = mSplitController.newContainer(new Intent(), mActivity, - TASK_ID); + final TaskFragmentContainer bottomTf = createTfContainer(mSplitController, mActivity); + final TaskFragmentContainer topTf = + new TaskFragmentContainer.Builder(mSplitController, TASK_ID, mActivity) + .setPendingAppearedIntent(new Intent()).build(); bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity, false /* isVisible */)); topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity())); @@ -1383,9 +1379,10 @@ public class SplitControllerTest { // Launch placeholder for visible activity below the top TaskFragment. setupPlaceholderRule(mActivity); mTransactionManager.startNewTransaction(); - final TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID); - final TaskFragmentContainer topTf = mSplitController.newContainer(new Intent(), mActivity, - TASK_ID); + final TaskFragmentContainer bottomTf = createTfContainer(mSplitController, mActivity); + final TaskFragmentContainer topTf = + new TaskFragmentContainer.Builder(mSplitController, TASK_ID, mActivity) + .setPendingAppearedIntent(new Intent()).build(); bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity, true /* isVisible */)); topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity())); @@ -1412,7 +1409,7 @@ public class SplitControllerTest { @Test public void testFinishActivityStacks_finishSingleActivityStack() { - TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); + TaskFragmentContainer tf = createTfContainer(mSplitController, mActivity); tf.setInfo(mTransaction, createMockTaskFragmentInfo(tf, mActivity)); final TaskContainer taskContainer = mSplitController.mTaskContainers.get(TASK_ID); @@ -1426,8 +1423,8 @@ public class SplitControllerTest { @Test public void testFinishActivityStacks_finishActivityStacksInOrder() { - TaskFragmentContainer bottomTf = mSplitController.newContainer(mActivity, TASK_ID); - TaskFragmentContainer topTf = mSplitController.newContainer(mActivity, TASK_ID); + TaskFragmentContainer bottomTf = createTfContainer(mSplitController, mActivity); + TaskFragmentContainer topTf = createTfContainer(mSplitController, mActivity); bottomTf.setInfo(mTransaction, createMockTaskFragmentInfo(bottomTf, mActivity)); topTf.setInfo(mTransaction, createMockTaskFragmentInfo(topTf, createMockActivity())); @@ -1687,8 +1684,8 @@ public class SplitControllerTest { /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */ private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) { - final TaskFragmentContainer container = mSplitController.newContainer(activity, - activity.getTaskId()); + final TaskFragmentContainer container = createTfContainer(mSplitController, + activity.getTaskId(), activity); setupTaskFragmentInfo(container, activity); return container; } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java index 3fbce9ec31a5..816e2dae1e5b 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -31,6 +31,7 @@ import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActi import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTfContainer; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createWindowLayoutInfo; import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds; import static androidx.window.extensions.embedding.SplitPresenter.EXPAND_CONTAINERS_ATTRIBUTES; @@ -139,7 +140,7 @@ public class SplitPresenterTest { @Test public void testCreateTaskFragment() { - final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container = createTfContainer(mController, mActivity); mPresenter.createTaskFragment(mTransaction, container.getTaskFragmentToken(), mActivity.getActivityToken(), TASK_BOUNDS, WINDOWING_MODE_MULTI_WINDOW); @@ -150,7 +151,7 @@ public class SplitPresenterTest { @Test public void testResizeTaskFragment() { - final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container = createTfContainer(mController, mActivity); mPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), mTaskFragmentInfo); mPresenter.resizeTaskFragment(mTransaction, container.getTaskFragmentToken(), TASK_BOUNDS); @@ -166,7 +167,7 @@ public class SplitPresenterTest { @Test public void testUpdateWindowingMode() { - final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container = createTfContainer(mController, mActivity); mPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), mTaskFragmentInfo); mPresenter.updateWindowingMode(mTransaction, container.getTaskFragmentToken(), WINDOWING_MODE_MULTI_WINDOW); @@ -184,8 +185,8 @@ public class SplitPresenterTest { @Test public void testSetAdjacentTaskFragments() { - final TaskFragmentContainer container0 = mController.newContainer(mActivity, TASK_ID); - final TaskFragmentContainer container1 = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container0 = createTfContainer(mController, mActivity); + final TaskFragmentContainer container1 = createTfContainer(mController, mActivity); mPresenter.setAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken(), container1.getTaskFragmentToken(), null /* adjacentParams */); @@ -202,8 +203,8 @@ public class SplitPresenterTest { @Test public void testClearAdjacentTaskFragments() { - final TaskFragmentContainer container0 = mController.newContainer(mActivity, TASK_ID); - final TaskFragmentContainer container1 = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container0 = createTfContainer(mController, mActivity); + final TaskFragmentContainer container1 = createTfContainer(mController, mActivity); // No request to clear as it is not set by default. mPresenter.clearAdjacentTaskFragments(mTransaction, container0.getTaskFragmentToken()); @@ -224,8 +225,8 @@ public class SplitPresenterTest { @Test public void testSetCompanionTaskFragment() { - final TaskFragmentContainer container0 = mController.newContainer(mActivity, TASK_ID); - final TaskFragmentContainer container1 = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container0 = createTfContainer(mController, mActivity); + final TaskFragmentContainer container1 = createTfContainer(mController, mActivity); mPresenter.setCompanionTaskFragment(mTransaction, container0.getTaskFragmentToken(), container1.getTaskFragmentToken()); @@ -242,7 +243,7 @@ public class SplitPresenterTest { @Test public void testSetTaskFragmentDimOnTask() { - final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container = createTfContainer(mController, mActivity); mPresenter.setTaskFragmentDimOnTask(mTransaction, container.getTaskFragmentToken(), true); verify(mTransaction).addTaskFragmentOperation(eq(container.getTaskFragmentToken()), any()); @@ -255,7 +256,7 @@ public class SplitPresenterTest { @Test public void testUpdateAnimationParams() { - final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container = createTfContainer(mController, mActivity); // Verify the default. assertTrue(container.areLastRequestedAnimationParamsEqual( @@ -287,7 +288,7 @@ public class SplitPresenterTest { @Test public void testSetTaskFragmentPinned() { - final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer container = createTfContainer(mController, mActivity); // Verify the default. assertFalse(container.isPinned()); @@ -667,8 +668,8 @@ public class SplitPresenterTest { public void testExpandSplitContainerIfNeeded() { Activity secondaryActivity = createMockActivity(); SplitRule splitRule = createSplitRule(mActivity, secondaryActivity); - TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID); - TaskFragmentContainer secondaryTf = mController.newContainer(secondaryActivity, TASK_ID); + TaskFragmentContainer primaryTf = createTfContainer(mController, mActivity); + TaskFragmentContainer secondaryTf = createTfContainer(mController, secondaryActivity); SplitContainer splitContainer = new SplitContainer(primaryTf, secondaryActivity, secondaryTf, splitRule, SPLIT_ATTRIBUTES); @@ -710,8 +711,8 @@ public class SplitPresenterTest { @Test public void testCreateNewSplitContainer_secondaryAbovePrimary() { final Activity secondaryActivity = createMockActivity(); - final TaskFragmentContainer bottomTf = mController.newContainer(secondaryActivity, TASK_ID); - final TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID); + final TaskFragmentContainer bottomTf = createTfContainer(mController, secondaryActivity); + final TaskFragmentContainer primaryTf = createTfContainer(mController, mActivity); final SplitPairRule rule = createSplitPairRuleBuilder(pair -> pair.first == mActivity && pair.second == secondaryActivity, pair -> false, metrics -> true) diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java index 8913b22115e9..284723279b80 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java @@ -29,6 +29,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -57,6 +58,9 @@ import java.util.List; * Build/Install/Run: * atest WMJetpackUnitTests:TaskContainerTest */ + +// Suppress GuardedBy warning on unit tests +@SuppressWarnings("GuardedBy") @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) @@ -126,8 +130,11 @@ public class TaskContainerTest { assertTrue(taskContainer.isEmpty()); - final TaskFragmentContainer tf = new TaskFragmentContainer(null /* activity */, - new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */); + doReturn(taskContainer).when(mController).getTaskContainer(anyInt()); + final TaskFragmentContainer tf = new TaskFragmentContainer.Builder(mController, + taskContainer.getTaskId(), null /* activityInTask */) + .setPendingAppearedIntent(new Intent()) + .build(); assertFalse(taskContainer.isEmpty()); @@ -142,12 +149,17 @@ public class TaskContainerTest { final TaskContainer taskContainer = createTestTaskContainer(); assertNull(taskContainer.getTopNonFinishingTaskFragmentContainer()); - final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */, - new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */); + doReturn(taskContainer).when(mController).getTaskContainer(anyInt()); + final TaskFragmentContainer tf0 = new TaskFragmentContainer.Builder(mController, + taskContainer.getTaskId(), null /* activityInTask */) + .setPendingAppearedIntent(new Intent()) + .build(); assertEquals(tf0, taskContainer.getTopNonFinishingTaskFragmentContainer()); - final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */, - new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer tf1 = new TaskFragmentContainer.Builder(mController, + taskContainer.getTaskId(), null /* activityInTask */) + .setPendingAppearedIntent(new Intent()) + .build(); assertEquals(tf1, taskContainer.getTopNonFinishingTaskFragmentContainer()); } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java index 44ab2c458e39..7fab371cb790 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -100,24 +100,27 @@ public class TaskFragmentContainerTest { @Test public void testNewContainer() { final TaskContainer taskContainer = createTestTaskContainer(); + mController.addTaskContainer(taskContainer.getTaskId(), taskContainer); // One of the activity and the intent must be non-null assertThrows(IllegalArgumentException.class, - () -> new TaskFragmentContainer(null, null, taskContainer, mController, - null /* pairedPrimaryContainer */)); + () -> new TaskFragmentContainer.Builder(mController, taskContainer.getTaskId(), + null /* activityInTask */).build()); // One of the activity and the intent must be null. assertThrows(IllegalArgumentException.class, - () -> new TaskFragmentContainer(mActivity, mIntent, taskContainer, mController, - null /* pairedPrimaryContainer */)); + () -> new TaskFragmentContainer.Builder(mController, taskContainer.getTaskId(), + null /* activityInTask */) + .setPendingAppearedActivity(createMockActivity()) + .setPendingAppearedIntent(mIntent) + .build()); } @Test public void testFinish() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(mActivity, - null /* pendingAppearedIntent */, taskContainer, mController, - null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer(taskContainer, + mActivity, null /* pendingAppearedIntent */); doReturn(container).when(mController).getContainerWithActivity(mActivity); // Only remove the activity, but not clear the reference until appeared. @@ -148,15 +151,13 @@ public class TaskFragmentContainerTest { @Test public void testFinish_notFinishActivityThatIsReparenting() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity, - null /* pendingAppearedIntent */, taskContainer, mController, - null /* pairedPrimaryContainer */); + final TaskFragmentContainer container0 = createTaskFragmentContainer(taskContainer, + mActivity, null /* pendingAppearedIntent */); final TaskFragmentInfo info = createMockTaskFragmentInfo(container0, mActivity); container0.setInfo(mTransaction, info); // Request to reparent the activity to a new TaskFragment. - final TaskFragmentContainer container1 = new TaskFragmentContainer(mActivity, - null /* pendingAppearedIntent */, taskContainer, mController, - null /* pairedPrimaryContainer */); + final TaskFragmentContainer container1 = createTaskFragmentContainer(taskContainer, + mActivity, null /* pendingAppearedIntent */); doReturn(container1).when(mController).getContainerWithActivity(mActivity); // The activity is requested to be reparented, so don't finish it. @@ -171,15 +172,13 @@ public class TaskFragmentContainerTest { public void testFinish_alwaysFinishPlaceholder() { // Register container1 as a placeholder final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity, - null /* pendingAppearedIntent */, taskContainer, mController, - null /* pairedPrimaryContainer */); + final TaskFragmentContainer container0 = createTaskFragmentContainer(taskContainer, + mActivity, null /* pendingAppearedIntent */); final TaskFragmentInfo info0 = createMockTaskFragmentInfo(container0, mActivity); container0.setInfo(mTransaction, info0); final Activity placeholderActivity = createMockActivity(); - final TaskFragmentContainer container1 = new TaskFragmentContainer(placeholderActivity, - null /* pendingAppearedIntent */, taskContainer, mController, - null /* pairedPrimaryContainer */); + final TaskFragmentContainer container1 = createTaskFragmentContainer(taskContainer, + placeholderActivity, null /* pendingAppearedIntent */); final TaskFragmentInfo info1 = createMockTaskFragmentInfo(container1, placeholderActivity); container1.setInfo(mTransaction, info1); final SplitAttributes splitAttributes = new SplitAttributes.Builder().build(); @@ -207,9 +206,8 @@ public class TaskFragmentContainerTest { public void testSetInfo() { final TaskContainer taskContainer = createTestTaskContainer(); // Pending activity should be cleared when it has appeared on server side. - final TaskFragmentContainer pendingActivityContainer = new TaskFragmentContainer(mActivity, - null /* pendingAppearedIntent */, taskContainer, mController, - null /* pairedPrimaryContainer */); + final TaskFragmentContainer pendingActivityContainer = createTaskFragmentContainer( + taskContainer, mActivity, null /* pendingAppearedIntent */); assertTrue(pendingActivityContainer.mPendingAppearedActivities.contains( mActivity.getActivityToken())); @@ -221,9 +219,8 @@ public class TaskFragmentContainerTest { assertTrue(pendingActivityContainer.mPendingAppearedActivities.isEmpty()); // Pending intent should be cleared when the container becomes non-empty. - final TaskFragmentContainer pendingIntentContainer = new TaskFragmentContainer( - null /* pendingAppearedActivity */, mIntent, taskContainer, mController, - null /* pairedPrimaryContainer */); + final TaskFragmentContainer pendingIntentContainer = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); assertEquals(mIntent, pendingIntentContainer.getPendingAppearedIntent()); @@ -237,8 +234,8 @@ public class TaskFragmentContainerTest { @Test public void testIsWaitingActivityAppear() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); assertTrue(container.isWaitingActivityAppear()); @@ -259,8 +256,8 @@ public class TaskFragmentContainerTest { public void testAppearEmptyTimeout() { doNothing().when(mController).onTaskFragmentAppearEmptyTimeout(any(), any()); final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); assertNull(container.mAppearEmptyTimeout); @@ -299,8 +296,8 @@ public class TaskFragmentContainerTest { @Test public void testCollectNonFinishingActivities() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); List<Activity> activities = container.collectNonFinishingActivities(); assertTrue(activities.isEmpty()); @@ -327,8 +324,8 @@ public class TaskFragmentContainerTest { @Test public void testCollectNonFinishingActivities_checkIfStable() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); // In case mInfo is null, collectNonFinishingActivities(true) should return null. List<Activity> activities = @@ -353,8 +350,8 @@ public class TaskFragmentContainerTest { @Test public void testAddPendingActivity() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); container.addPendingAppearedActivity(mActivity); assertEquals(1, container.collectNonFinishingActivities().size()); @@ -367,10 +364,10 @@ public class TaskFragmentContainerTest { @Test public void testIsAbove() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container0 = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); - final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container0 = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); + final TaskFragmentContainer container1 = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); assertTrue(container1.isAbove(container0)); assertFalse(container0.isAbove(container1)); @@ -379,8 +376,8 @@ public class TaskFragmentContainerTest { @Test public void testGetBottomMostActivity() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); container.addPendingAppearedActivity(mActivity); assertEquals(mActivity, container.getBottomMostActivity()); @@ -396,8 +393,8 @@ public class TaskFragmentContainerTest { @Test public void testOnActivityDestroyed() { final TaskContainer taskContainer = createTestTaskContainer(mController); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); container.addPendingAppearedActivity(mActivity); final List<IBinder> activities = new ArrayList<>(); activities.add(mActivity.getActivityToken()); @@ -416,8 +413,8 @@ public class TaskFragmentContainerTest { public void testIsInIntermediateState() { // True if no info set. final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); spyOn(taskContainer); doReturn(true).when(taskContainer).isVisible(); @@ -479,8 +476,8 @@ public class TaskFragmentContainerTest { @Test public void testHasAppearedActivity() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); container.addPendingAppearedActivity(mActivity); assertFalse(container.hasAppearedActivity(mActivity.getActivityToken())); @@ -496,8 +493,8 @@ public class TaskFragmentContainerTest { @Test public void testHasPendingAppearedActivity() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); container.addPendingAppearedActivity(mActivity); assertTrue(container.hasPendingAppearedActivity(mActivity.getActivityToken())); @@ -513,10 +510,10 @@ public class TaskFragmentContainerTest { @Test public void testHasActivity() { final TaskContainer taskContainer = createTestTaskContainer(mController); - final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); - final TaskFragmentContainer container2 = new TaskFragmentContainer(null /* activity */, - mIntent, taskContainer, mController, null /* pairedPrimaryContainer */); + final TaskFragmentContainer container1 = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); + final TaskFragmentContainer container2 = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, mIntent); // Activity is pending appeared on container2. container2.addPendingAppearedActivity(mActivity); @@ -550,17 +547,19 @@ public class TaskFragmentContainerTest { @Test public void testNewContainerWithPairedPrimaryContainer() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer tf0 = new TaskFragmentContainer( - null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, - null /* pairedPrimaryTaskFragment */); - final TaskFragmentContainer tf1 = new TaskFragmentContainer( - null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, - null /* pairedPrimaryTaskFragment */); + mController.addTaskContainer(taskContainer.getTaskId(), taskContainer); + final TaskFragmentContainer tf0 = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, new Intent()); + final TaskFragmentContainer tf1 = createTaskFragmentContainer( + taskContainer, null /* pendingAppearedActivity */, new Intent()); // When tf2 is created with using tf0 as pairedPrimaryContainer, tf2 should be inserted // right above tf0. - final TaskFragmentContainer tf2 = new TaskFragmentContainer( - null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, tf0); + final TaskFragmentContainer tf2 = new TaskFragmentContainer.Builder(mController, + taskContainer.getTaskId(), null /* activityInTask */) + .setPendingAppearedIntent(new Intent()) + .setPairedPrimaryContainer(tf0) + .build(); assertEquals(0, taskContainer.indexOf(tf0)); assertEquals(1, taskContainer.indexOf(tf2)); assertEquals(2, taskContainer.indexOf(tf1)); @@ -569,18 +568,15 @@ public class TaskFragmentContainerTest { @Test public void testNewContainerWithPairedPendingAppearedActivity() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer tf0 = new TaskFragmentContainer( - createMockActivity(), null /* pendingAppearedIntent */, taskContainer, mController, - null /* pairedPrimaryTaskFragment */); - final TaskFragmentContainer tf1 = new TaskFragmentContainer( - null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, - null /* pairedPrimaryTaskFragment */); + final TaskFragmentContainer tf0 = createTaskFragmentContainer(taskContainer, + createMockActivity(), null /* pendingAppearedIntent */); + final TaskFragmentContainer tf1 = createTaskFragmentContainer(taskContainer, + null /* pendingAppearedActivity */, new Intent()); // When tf2 is created with pendingAppearedActivity, tf2 should be inserted below any // TaskFragment without any Activity. - final TaskFragmentContainer tf2 = new TaskFragmentContainer( - createMockActivity(), null /* pendingAppearedIntent */, taskContainer, mController, - null /* pairedPrimaryTaskFragment */); + final TaskFragmentContainer tf2 = createTaskFragmentContainer(taskContainer, + createMockActivity(), null /* pendingAppearedIntent */); assertEquals(0, taskContainer.indexOf(tf0)); assertEquals(1, taskContainer.indexOf(tf2)); assertEquals(2, taskContainer.indexOf(tf1)); @@ -589,9 +585,8 @@ public class TaskFragmentContainerTest { @Test public void testIsVisible() { final TaskContainer taskContainer = createTestTaskContainer(); - final TaskFragmentContainer container = new TaskFragmentContainer( - null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, - null /* pairedPrimaryTaskFragment */); + final TaskFragmentContainer container = createTaskFragmentContainer(taskContainer, + null /* pendingAppearedActivity */, new Intent()); // Not visible when there is not appeared. assertFalse(container.isVisible()); @@ -617,4 +612,14 @@ public class TaskFragmentContainerTest { doReturn(activity).when(mController).getActivity(activityToken); return activity; } + + private TaskFragmentContainer createTaskFragmentContainer(TaskContainer taskContainer, + Activity pendingAppearedActivity, Intent pendingAppearedIntent) { + final int taskId = taskContainer.getTaskId(); + mController.addTaskContainer(taskId, taskContainer); + return new TaskFragmentContainer.Builder(mController, taskId, pendingAppearedActivity) + .setPendingAppearedActivity(pendingAppearedActivity) + .setPendingAppearedIntent(pendingAppearedIntent) + .build(); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index f2095b130989..3ded7d246499 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -175,6 +175,7 @@ public class ShellTaskOrganizer extends TaskOrganizer implements .setName("home_task_overlay_container") .setContainerLayer() .setHidden(false) + .setCallsite("ShellTaskOrganizer.mHomeTaskOverlayContainer") .build(); /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index fb0a1ab3062e..12bbd51b968d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -522,14 +522,16 @@ public abstract class WMShellModule { RecentsTransitionHandler recentsTransitionHandler, MultiInstanceHelper multiInstanceHelper, @ShellMainThread ShellExecutor mainExecutor, - Optional<DesktopTasksLimiter> desktopTasksLimiter) { + Optional<DesktopTasksLimiter> desktopTasksLimiter, + Optional<RecentTasksController> recentTasksController) { return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController, displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, dragAndDropController, transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler, dragToDesktopTransitionHandler, desktopModeTaskRepository, desktopModeLoggerTransitionObserver, launchAdjacentController, - recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter); + recentsTransitionHandler, multiInstanceHelper, + mainExecutor, desktopTasksLimiter, recentTasksController.orElse(null)); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java index 38db1ebdc47b..ed0d2b87b03f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java @@ -223,6 +223,7 @@ public class DesktopModeVisualIndicator { mLeash = builder .setName("Desktop Mode Visual Indicator") .setContainerLayer() + .setCallsite("DesktopModeVisualIndicator.createView") .build(); t.show(mLeash); final WindowManager.LayoutParams lp = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 6e45397411d7..ef384c74cb5e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -70,6 +70,7 @@ import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksLi import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener import com.android.wm.shell.draganddrop.DragAndDropController import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import com.android.wm.shell.recents.RecentTasksController import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener import com.android.wm.shell.shared.DesktopModeStatus @@ -118,6 +119,7 @@ class DesktopTasksController( private val multiInstanceHelper: MultiInstanceHelper, @ShellMainThread private val mainExecutor: ShellExecutor, private val desktopTasksLimiter: Optional<DesktopTasksLimiter>, + private val recentTasksController: RecentTasksController? ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler, @@ -293,24 +295,49 @@ class DesktopTasksController( taskId: Int, wct: WindowContainerTransaction = WindowContainerTransaction() ): Boolean { - shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToDesktop(task, wct) } - ?: return false + shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { + moveToDesktop(it, wct) + } ?: moveToDesktopFromNonRunningTask(taskId, wct) return true } - /** Move a task to desktop */ + private fun moveToDesktopFromNonRunningTask( + taskId: Int, + wct: WindowContainerTransaction + ): Boolean { + recentTasksController?.findTaskInBackground(taskId)?.let { + KtProtoLog.v( + WM_SHELL_DESKTOP_MODE, + "DesktopTasksController: moveToDesktopFromNonRunningTask taskId=%d", + taskId + ) + // TODO(342378842): Instead of using default display, support multiple displays + val taskToMinimize = + bringDesktopAppsToFrontBeforeShowingNewTask(DEFAULT_DISPLAY, wct, taskId) + addMoveToDesktopChangesNonRunningTask(wct, taskId) + // TODO(343149901): Add DPI changes for task launch + val transition = enterDesktopTaskTransitionHandler.moveToDesktop(wct) + addPendingMinimizeTransition(transition, taskToMinimize) + return true + } ?: return false + } + + private fun addMoveToDesktopChangesNonRunningTask( + wct: WindowContainerTransaction, + taskId: Int + ) { + val options = ActivityOptions.makeBasic() + options.launchWindowingMode = WINDOWING_MODE_FREEFORM + wct.startTask(taskId, options.toBundle()) + } + + /** + * Move a task to desktop + */ fun moveToDesktop( task: RunningTaskInfo, wct: WindowContainerTransaction = WindowContainerTransaction() ) { - if (!DesktopModeStatus.canEnterDesktopMode(context)) { - KtProtoLog.w( - WM_SHELL_DESKTOP_MODE, - "DesktopTasksController: Cannot enter desktop, " + - "display does not meet minimum size requirements" - ) - return - } if (Flags.enableDesktopWindowingModalsPolicy() && isSingleTopActivityTranslucent(task)) { KtProtoLog.w( WM_SHELL_DESKTOP_MODE, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index c7f693d8de50..6fcea1fe5560 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -675,6 +675,7 @@ public class PipTransition extends PipTransitionController { .setContainerLayer() .setHidden(false) .setParent(root.getLeash()) + .setCallsite("PipTransition.startExitAnimation") .build(); startTransaction.reparent(activitySurface, pipLeash); // Put the activity at local position with offset in case it is letterboxed. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java index c2f4d72a1ddf..ca0d61f8fc9b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTransition.java @@ -233,6 +233,7 @@ public class TvPipTransition extends PipTransitionController { .setContainerLayer() .setHidden(false) .setParent(root.getLeash()) + .setCallsite("TvPipTransition.startAnimation") .build(); startTransaction.reparent(activitySurface, pipLeash); // Put the activity at local position with offset in case it is letterboxed. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index d8f2c02b5399..863202d5e1c3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -446,6 +446,25 @@ public class RecentTasksController implements TaskStackListenerCallback, return null; } + /** + * Find the background task that match the given taskId. + */ + @Nullable + public ActivityManager.RecentTaskInfo findTaskInBackground(int taskId) { + List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks( + Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE, + ActivityManager.getCurrentUser()); + for (int i = 0; i < tasks.size(); i++) { + final ActivityManager.RecentTaskInfo task = tasks.get(i); + if (task.isVisible) { + continue; + } + if (taskId == task.taskId) { + return task; + } + } + return null; + } public void dump(@NonNull PrintWriter pw, String prefix) { final String innerPrefix = prefix + " "; pw.println(prefix + TAG); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java index 1be85d05c16e..ad4f02d13cc6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java @@ -280,6 +280,7 @@ public class TransitionAnimationHelper { .setParent(rootLeash) .setColorLayer() .setOpaque(true) + .setCallsite("TransitionAnimationHelper.addBackgroundToTransition") .build(); startTransaction .setLayer(animationBackgroundSurface, Integer.MIN_VALUE) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index 5379ca6cd51d..badce6e93d67 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -132,6 +132,7 @@ class DragResizeInputListener implements AutoCloseable { .setName("TaskInputSink of " + decorationSurface) .setContainerLayer() .setParent(mDecorationSurface) + .setCallsite("DragResizeInputListener.constructor") .build(); mSurfaceControlTransactionSupplier.get() .setLayer(mInputSinkSurface, WindowDecoration.INPUT_SINK_Z_ORDER) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index 2cbe47212c63..0dc512835d65 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -268,6 +268,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .setName("Decor container of Task=" + mTaskInfo.taskId) .setContainerLayer() .setParent(mTaskSurface) + .setCallsite("WindowDecoration.relayout_1") .build(); startT.setTrustedOverlay(mDecorationContainerSurface, true) @@ -285,6 +286,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .setName("Caption container of Task=" + mTaskInfo.taskId) .setContainerLayer() .setParent(mDecorationContainerSurface) + .setCallsite("WindowDecoration.relayout_2") .build(); } @@ -575,6 +577,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .setName(namePrefix + " of Task=" + mTaskInfo.taskId) .setContainerLayer() .setParent(mDecorationContainerSurface) + .setCallsite("WindowDecoration.addWindow") .build(); View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index ac67bd1fedd8..cf6cea2b34a7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.desktopmode +import android.app.ActivityManager.RecentTaskInfo import android.app.ActivityManager.RunningTaskInfo import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD @@ -47,13 +48,16 @@ import android.view.WindowManager.TRANSIT_OPEN import android.view.WindowManager.TRANSIT_TO_BACK import android.view.WindowManager.TRANSIT_TO_FRONT import android.window.DisplayAreaInfo +import android.window.IWindowContainerToken import android.window.RemoteTransition import android.window.TransitionRequestInfo import android.window.WindowContainerToken import android.window.WindowContainerTransaction +import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_LAUNCH_TASK import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER +import android.window.WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession @@ -78,6 +82,7 @@ import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFulls import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createSplitScreenTask import com.android.wm.shell.draganddrop.DragAndDropController +import com.android.wm.shell.recents.RecentTasksController import com.android.wm.shell.recents.RecentsTransitionHandler import com.android.wm.shell.recents.RecentsTransitionStateListener import com.android.wm.shell.shared.DesktopModeStatus @@ -93,6 +98,7 @@ import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_DESKTOP_MODE import com.android.wm.shell.transition.Transitions.TransitionHandler import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage +import java.util.Optional import org.junit.After import org.junit.Assume.assumeTrue import org.junit.Before @@ -115,7 +121,6 @@ import org.mockito.kotlin.anyOrNull import org.mockito.kotlin.atLeastOnce import org.mockito.kotlin.capture import org.mockito.quality.Strictness -import java.util.Optional import junit.framework.Assert.assertFalse import junit.framework.Assert.assertTrue import org.mockito.Mockito.`when` as whenever @@ -154,6 +159,7 @@ class DesktopTasksControllerTest : ShellTestCase() { @Mock lateinit var multiInstanceHelper: MultiInstanceHelper @Mock lateinit var desktopModeLoggerTransitionObserver: DesktopModeLoggerTransitionObserver @Mock lateinit var desktopModeVisualIndicator: DesktopModeVisualIndicator + @Mock lateinit var recentTasksController: RecentTasksController private lateinit var mockitoSession: StaticMockitoSession private lateinit var controller: DesktopTasksController @@ -233,6 +239,7 @@ class DesktopTasksControllerTest : ShellTestCase() { multiInstanceHelper, shellExecutor, Optional.of(desktopTasksLimiter), + recentTasksController ) } @@ -622,7 +629,7 @@ class DesktopTasksControllerTest : ShellTestCase() { controller.moveToDesktop(task) val wct = getLatestMoveToDesktopWct() assertThat(wct.changes[task.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FREEFORM) + .isEqualTo(WINDOWING_MODE_FREEFORM) } @Test @@ -643,14 +650,17 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test - fun moveToDesktop_deviceNotSupported_doesNothing() { - val task = setUpFullscreenTask() + fun moveToDesktop_nonRunningTask_launchesInFreeform() { + whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null) - // Simulate non compatible device - doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + val task = createTaskInfo(1) - controller.moveToDesktop(task) - verifyWCTNotExecuted() + whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task) + + controller.moveToDesktop(task.taskId) + with(getLatestMoveToDesktopWct()){ + assertLaunchTaskAt(0, task.taskId, WINDOWING_MODE_FREEFORM) + } } @Test @@ -666,6 +676,17 @@ class DesktopTasksControllerTest : ShellTestCase() { } @Test + fun moveToDesktop_deviceNotSupported_doesNothing() { + val task = setUpFullscreenTask() + + // Simulate non compatible device + doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) } + + controller.moveToDesktop(task) + verifyWCTNotExecuted() + } + + @Test fun moveToDesktop_deviceNotSupported_deviceRestrictionsOverridden_taskIsMovedToDesktop() { val task = setUpFullscreenTask() @@ -1834,6 +1855,20 @@ private fun WindowContainerTransaction.assertPendingIntentAt(index: Int, intent: assertThat(op.pendingIntent?.intent?.component).isEqualTo(intent.component) } +private fun WindowContainerTransaction.assertLaunchTaskAt( + index: Int, + taskId: Int, + windowingMode: Int +) { + val keyLaunchWindowingMode = "android.activity.windowingMode" + + assertIndexInBounds(index) + val op = hierarchyOps[index] + assertThat(op.type).isEqualTo(HIERARCHY_OP_TYPE_LAUNCH_TASK) + assertThat(op.launchOptions?.getInt(LAUNCH_KEY_TASK_ID)).isEqualTo(taskId) + assertThat(op.launchOptions?.getInt(keyLaunchWindowingMode, WINDOWING_MODE_UNDEFINED)) + .isEqualTo(windowingMode) +} private fun WindowContainerTransaction?.anyDensityConfigChange( token: WindowContainerToken ): Boolean { @@ -1841,3 +1876,7 @@ private fun WindowContainerTransaction?.anyDensityConfigChange( change.key == token.asBinder() && ((change.value.configSetMask and CONFIG_DENSITY) != 0) } ?: false } +private fun createTaskInfo(id: Int) = RecentTaskInfo().apply { + taskId = id + token = WindowContainerToken(mock(IWindowContainerToken::class.java)) +} diff --git a/media/tests/MediaRouter/Android.bp b/media/tests/MediaRouter/Android.bp index 61b18c88e734..d21cb9319885 100644 --- a/media/tests/MediaRouter/Android.bp +++ b/media/tests/MediaRouter/Android.bp @@ -9,6 +9,7 @@ package { android_test { name: "mediaroutertest", + team: "trendy_team_android_media_solutions", srcs: ["**/*.java"], diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig index 55edff6d9518..d201071620e4 100644 --- a/packages/SystemUI/aconfig/accessibility.aconfig +++ b/packages/SystemUI/aconfig/accessibility.aconfig @@ -81,3 +81,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "hearing_devices_dialog_related_tools" + namespace: "accessibility" + description: "Shows the related tools for hearing devices dialog." + bug: "341648471" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 36bad5e622cf..1df9c88e48ac 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -184,6 +184,13 @@ flag { } flag { + name: "notification_avalanche_throttle_hun" + namespace: "systemui" + description: "(currently unused) During notification avalanche, throttle HUNs showing in fast succession." + bug: "307288824" +} + +flag { name: "notification_avalanche_suppression" namespace: "systemui" description: "After notification avalanche floodgate event, suppress HUNs completely." @@ -990,6 +997,13 @@ flag { } flag { + name: "glanceable_hub_shortcut_button" + namespace: "systemui" + description: "Shows a button over the dream and lock screen to open the glanceable hub" + bug: "339667383" +} + +flag { name: "glanceable_hub_gesture_handle" namespace: "systemui" description: "Shows a vertical bar at the right edge to indicate the user can swipe to open the glanceable hub" diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt index b1240252796f..978943ae7f7c 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt @@ -179,8 +179,15 @@ class TextAnimator( private val fontVariationUtils = FontVariationUtils() - fun updateLayout(layout: Layout) { + fun updateLayout(layout: Layout, textSize: Float = -1f) { textInterpolator.layout = layout + + if (textSize >= 0) { + textInterpolator.targetPaint.textSize = textSize + textInterpolator.basePaint.textSize = textSize + textInterpolator.onTargetPaintModified() + textInterpolator.onBasePaintModified() + } } fun isRunning(): Boolean { diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt index c19c08e09349..b8f9ca82f072 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt @@ -66,6 +66,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity @@ -137,7 +138,7 @@ fun BouncerContent( // Despite the keyboard only being part of the password bouncer, adding it at this level is // both necessary to properly handle the keyboard in all layouts and harmless in cases when // the keyboard isn't used (like the PIN or pattern auth methods). - modifier = modifier.imePadding(), + modifier = modifier.imePadding().onKeyEvent(viewModel::onKeyEvent), ) { when (layout) { BouncerSceneLayout.STANDARD_BOUNCER -> diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationHeadsUpHeight.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationHeadsUpHeight.kt new file mode 100644 index 000000000000..75a565b2d2a0 --- /dev/null +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationHeadsUpHeight.kt @@ -0,0 +1,87 @@ +/* + * 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.notifications.ui.composable + +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.node.LayoutModifierNode +import androidx.compose.ui.node.ModifierNodeElement +import androidx.compose.ui.node.invalidateMeasurement +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.IntOffset +import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView + +/** + * Modify element, which updates the height to the height of current top heads up notification, or + * to 0 if there is none. + * + * @param view Notification stack scroll view + */ +fun Modifier.notificationHeadsUpHeight(view: NotificationScrollView) = + this then HeadsUpLayoutElement(view) + +private data class HeadsUpLayoutElement( + val view: NotificationScrollView, +) : ModifierNodeElement<HeadsUpLayoutNode>() { + + override fun create(): HeadsUpLayoutNode = HeadsUpLayoutNode(view) + + override fun update(node: HeadsUpLayoutNode) { + check(view == node.view) { "Trying to reuse the node with a new View." } + } +} + +private class HeadsUpLayoutNode(val view: NotificationScrollView) : + LayoutModifierNode, Modifier.Node() { + + private val headsUpHeightChangedListener = Runnable { invalidateMeasureIfAttached() } + + override fun onAttach() { + super.onAttach() + view.addHeadsUpHeightChangedListener(headsUpHeightChangedListener) + } + + override fun onDetach() { + super.onDetach() + view.removeHeadsUpHeightChangedListener(headsUpHeightChangedListener) + } + + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints + ): MeasureResult { + // TODO(b/339181697) make sure, that the row is already measured. + val contentHeight = view.topHeadsUpHeight + val placeable = + measurable.measure( + constraints.copy(minHeight = contentHeight, maxHeight = contentHeight) + ) + return layout(placeable.width, placeable.height) { placeable.place(IntOffset.Zero) } + } + + override fun toString(): String { + return "HeadsUpLayoutNode(view=$view)" + } + + fun invalidateMeasureIfAttached() { + if (isAttached) { + this.invalidateMeasurement() + } + } +} diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index e6132c6a2a59..c26259f3287c 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -67,7 +67,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.NestedScrollBehavior import com.android.compose.animation.scene.SceneScope -import com.android.compose.modifiers.height import com.android.compose.modifiers.thenIf import com.android.systemui.common.ui.compose.windowinsets.LocalRawScreenHeight import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius @@ -108,18 +107,17 @@ object Notifications { */ @Composable fun SceneScope.HeadsUpNotificationSpace( + stackScrollView: NotificationScrollView, viewModel: NotificationsPlaceholderViewModel, modifier: Modifier = Modifier, isPeekFromBottom: Boolean = false, ) { - val headsUpHeight = viewModel.headsUpHeight.collectAsStateWithLifecycle() - Element( Notifications.Elements.HeadsUpNotificationPlaceholder, modifier = modifier - .height { headsUpHeight.value.roundToInt() } .fillMaxWidth() + .notificationHeadsUpHeight(stackScrollView) .debugBackground(viewModel, DEBUG_HUN_COLOR) .onGloballyPositioned { coordinates: LayoutCoordinates -> val boundsInWindow = coordinates.boundsInWindow() @@ -152,6 +150,7 @@ fun SceneScope.ConstrainedNotificationStack( modifier = Modifier.fillMaxSize(), ) HeadsUpNotificationSpace( + stackScrollView = stackScrollView, viewModel = viewModel, modifier = Modifier.align(Alignment.TopCenter), ) @@ -358,7 +357,7 @@ fun SceneScope.NotificationScrollingStack( .onSizeChanged { size -> stackHeight.intValue = size.height }, ) } - HeadsUpNotificationSpace(viewModel = viewModel) + HeadsUpNotificationSpace(stackScrollView = stackScrollView, viewModel = viewModel) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt index d76b19f3fa82..0ee485c496be 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt @@ -42,7 +42,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.rememberScrollState @@ -60,7 +59,6 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.colorResource -import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope @@ -79,14 +77,13 @@ import com.android.systemui.media.controls.ui.composable.MediaCarousel import com.android.systemui.media.controls.ui.controller.MediaCarouselController import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.media.dagger.MediaModule -import com.android.systemui.notifications.ui.composable.NotificationScrollingStack +import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace import com.android.systemui.qs.footer.ui.compose.FooterActionsWithAnimatedVisibility import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel import com.android.systemui.res.R import com.android.systemui.scene.session.ui.composable.SaveableSession import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene -import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.shade.ui.composable.CollapsedShadeHeader import com.android.systemui.shade.ui.composable.ExpandedShadeHeader import com.android.systemui.shade.ui.composable.Shade @@ -99,7 +96,6 @@ import com.android.systemui.statusbar.phone.ui.TintedIconManager import dagger.Lazy import javax.inject.Inject import javax.inject.Named -import kotlin.math.roundToInt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.stateIn @@ -368,15 +364,11 @@ private fun SceneScope.QuickSettingsScene( Modifier.align(Alignment.CenterHorizontally).sysuiResTag("qs_footer_actions"), ) } - NotificationScrollingStack( + HeadsUpNotificationSpace( stackScrollView = notificationStackScrollView, viewModel = notificationsPlaceholderViewModel, - shadeSession = shadeSession, - maxScrimTop = { screenHeight }, - shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim, - shadeMode = ShadeMode.Single, - modifier = - Modifier.fillMaxWidth().offset { IntOffset(x = 0, y = screenHeight.roundToInt()) }, + modifier = Modifier.align(Alignment.BottomCenter), + isPeekFromBottom = true, ) } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt index d2a1de3e4695..f0fb9f62fdad 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt @@ -371,96 +371,57 @@ private fun prepareInterruption( transition: TransitionState.Transition, previousTransition: TransitionState.Transition, ) { - val previousUniqueState = reconcileStates(element, previousTransition) - if (previousUniqueState == null) { - reconcileStates(element, transition) - return - } - - val fromSceneState = element.sceneStates[transition.fromScene] - val toSceneState = element.sceneStates[transition.toScene] - - if ( - fromSceneState == null || - toSceneState == null || - sharedElementTransformation(element.key, transition)?.enabled != false - ) { - // If there is only one copy of the element or if the element is shared, animate deltas in - // both scenes. - fromSceneState?.updateValuesBeforeInterruption(previousUniqueState) - toSceneState?.updateValuesBeforeInterruption(previousUniqueState) - } + val sceneStates = element.sceneStates + sceneStates[previousTransition.fromScene]?.selfUpdateValuesBeforeInterruption() + sceneStates[previousTransition.toScene]?.selfUpdateValuesBeforeInterruption() + sceneStates[transition.fromScene]?.selfUpdateValuesBeforeInterruption() + sceneStates[transition.toScene]?.selfUpdateValuesBeforeInterruption() + + reconcileStates(element, previousTransition) + reconcileStates(element, transition) } /** * Reconcile the state of [element] in the fromScene and toScene of [transition] so that the values * before interruption have their expected values, taking shared transitions into account. - * - * If the element had a unique state, i.e. it is shared in [transition] or it is only present in one - * of the scenes, return it. */ private fun reconcileStates( element: Element, transition: TransitionState.Transition, -): Element.SceneState? { - val fromSceneState = element.sceneStates[transition.fromScene] - val toSceneState = element.sceneStates[transition.toScene] - when { - // Element is in both scenes. - fromSceneState != null && toSceneState != null -> { - val isSharedTransformationDisabled = - sharedElementTransformation(element.key, transition)?.enabled == false - when { - // Element shared transition is disabled so the element is placed in both scenes. - isSharedTransformationDisabled -> { - fromSceneState.updateValuesBeforeInterruption(fromSceneState) - toSceneState.updateValuesBeforeInterruption(toSceneState) - return null - } - - // Element is shared and placed in fromScene only. - fromSceneState.lastOffset != Offset.Unspecified -> { - fromSceneState.updateValuesBeforeInterruption(fromSceneState) - toSceneState.updateValuesBeforeInterruption(fromSceneState) - return fromSceneState - } - - // Element is shared and placed in toScene only. - toSceneState.lastOffset != Offset.Unspecified -> { - fromSceneState.updateValuesBeforeInterruption(toSceneState) - toSceneState.updateValuesBeforeInterruption(toSceneState) - return toSceneState - } - - // Element is in none of the scenes. - else -> { - fromSceneState.updateValuesBeforeInterruption(null) - toSceneState.updateValuesBeforeInterruption(null) - return null - } - } - } - - // Element is only in fromScene. - fromSceneState != null -> { - fromSceneState.updateValuesBeforeInterruption(fromSceneState) - return fromSceneState - } +) { + val fromSceneState = element.sceneStates[transition.fromScene] ?: return + val toSceneState = element.sceneStates[transition.toScene] ?: return + if (!isSharedElementEnabled(element.key, transition)) { + return + } - // Element is only in toScene. - toSceneState != null -> { - toSceneState.updateValuesBeforeInterruption(toSceneState) - return toSceneState - } - else -> return null + if ( + fromSceneState.offsetBeforeInterruption != Offset.Unspecified && + toSceneState.offsetBeforeInterruption == Offset.Unspecified + ) { + // Element is shared and placed in fromScene only. + toSceneState.updateValuesBeforeInterruption(fromSceneState) + } else if ( + toSceneState.offsetBeforeInterruption != Offset.Unspecified && + fromSceneState.offsetBeforeInterruption == Offset.Unspecified + ) { + // Element is shared and placed in toScene only. + fromSceneState.updateValuesBeforeInterruption(toSceneState) } } -private fun Element.SceneState.updateValuesBeforeInterruption(lastState: Element.SceneState?) { - offsetBeforeInterruption = lastState?.lastOffset ?: Offset.Unspecified - sizeBeforeInterruption = lastState?.lastSize ?: Element.SizeUnspecified - scaleBeforeInterruption = lastState?.lastScale ?: Scale.Unspecified - alphaBeforeInterruption = lastState?.lastAlpha ?: Element.AlphaUnspecified +private fun Element.SceneState.selfUpdateValuesBeforeInterruption() { + offsetBeforeInterruption = lastOffset + sizeBeforeInterruption = lastSize + scaleBeforeInterruption = lastScale + alphaBeforeInterruption = lastAlpha +} + +private fun Element.SceneState.updateValuesBeforeInterruption(lastState: Element.SceneState) { + offsetBeforeInterruption = lastState.offsetBeforeInterruption + sizeBeforeInterruption = lastState.sizeBeforeInterruption + scaleBeforeInterruption = lastState.scaleBeforeInterruption + alphaBeforeInterruption = lastState.alphaBeforeInterruption clearInterruptionDeltas() } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index bdeab797d165..079c1d922275 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -17,7 +17,6 @@ package com.android.systemui.shared.clocks import android.animation.TimeInterpolator import android.annotation.ColorInt -import android.annotation.FloatRange import android.annotation.IntRange import android.annotation.SuppressLint import android.content.Context @@ -27,7 +26,7 @@ import android.text.TextUtils import android.text.format.DateFormat import android.util.AttributeSet import android.util.MathUtils.constrainedMap -import android.util.TypedValue +import android.util.TypedValue.COMPLEX_UNIT_PX import android.view.View import android.view.View.MeasureSpec.EXACTLY import android.widget.TextView @@ -219,9 +218,7 @@ constructor( override fun setTextSize(type: Int, size: Float) { super.setTextSize(type, size) - if (type == TypedValue.COMPLEX_UNIT_PX) { - lastUnconstrainedTextSize = size - } + lastUnconstrainedTextSize = if (type == COMPLEX_UNIT_PX) size else Float.MAX_VALUE } @SuppressLint("DrawAllocation") @@ -234,23 +231,19 @@ constructor( MeasureSpec.getMode(heightMeasureSpec) == EXACTLY ) { // Call straight into TextView.setTextSize to avoid setting lastUnconstrainedTextSize - super.setTextSize( - TypedValue.COMPLEX_UNIT_PX, - min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F) - ) + val size = min(lastUnconstrainedTextSize, MeasureSpec.getSize(heightMeasureSpec) / 2F) + super.setTextSize(COMPLEX_UNIT_PX, size) } super.onMeasure(widthMeasureSpec, heightMeasureSpec) - val animator = textAnimator - if (animator == null) { - textAnimator = - textAnimatorFactory(layout, ::invalidate)?.also { - onTextAnimatorInitialized?.invoke(it) - onTextAnimatorInitialized = null - } - } else { - animator.updateLayout(layout) - } + textAnimator?.let { animator -> animator.updateLayout(layout, textSize) } + ?: run { + textAnimator = + textAnimatorFactory(layout, ::invalidate).also { + onTextAnimatorInitialized?.invoke(it) + onTextAnimatorInitialized = null + } + } if (migratedClocks && hasCustomPositionUpdatedAnimation) { // Expand width to avoid clock being clipped during stepping animation @@ -307,18 +300,18 @@ constructor( logger.d("animateColorChange") setTextStyle( weight = lockScreenWeight, - textSize = -1f, color = null, /* using current color */ animate = false, + interpolator = null, duration = 0, delay = 0, onAnimationEnd = null ) setTextStyle( weight = lockScreenWeight, - textSize = -1f, color = lockScreenColor, animate = true, + interpolator = null, duration = COLOR_ANIM_DURATION, delay = 0, onAnimationEnd = null @@ -329,16 +322,15 @@ constructor( logger.d("animateAppearOnLockscreen") setTextStyle( weight = dozingWeight, - textSize = -1f, color = lockScreenColor, animate = false, + interpolator = null, duration = 0, delay = 0, onAnimationEnd = null ) setTextStyle( weight = lockScreenWeight, - textSize = -1f, color = lockScreenColor, animate = true, duration = APPEAR_ANIM_DURATION, @@ -356,16 +348,15 @@ constructor( logger.d("animateFoldAppear") setTextStyle( weight = lockScreenWeightInternal, - textSize = -1f, color = lockScreenColor, animate = false, + interpolator = null, duration = 0, delay = 0, onAnimationEnd = null ) setTextStyle( weight = dozingWeightInternal, - textSize = -1f, color = dozingColor, animate = animate, interpolator = Interpolators.EMPHASIZED_DECELERATE, @@ -385,9 +376,9 @@ constructor( val startAnimPhase2 = Runnable { setTextStyle( weight = if (isDozing()) dozingWeight else lockScreenWeight, - textSize = -1f, color = null, animate = true, + interpolator = null, duration = CHARGE_ANIM_DURATION_PHASE_1, delay = 0, onAnimationEnd = null @@ -395,9 +386,9 @@ constructor( } setTextStyle( weight = if (isDozing()) lockScreenWeight else dozingWeight, - textSize = -1f, color = null, animate = true, + interpolator = null, duration = CHARGE_ANIM_DURATION_PHASE_0, delay = chargeAnimationDelay.toLong(), onAnimationEnd = startAnimPhase2 @@ -408,9 +399,9 @@ constructor( logger.d("animateDoze") setTextStyle( weight = if (isDozing) dozingWeight else lockScreenWeight, - textSize = -1f, color = if (isDozing) dozingColor else lockScreenColor, animate = animate, + interpolator = null, duration = DOZE_ANIM_DURATION, delay = 0, onAnimationEnd = null @@ -448,7 +439,6 @@ constructor( */ private fun setTextStyle( @IntRange(from = 0, to = 1000) weight: Int, - @FloatRange(from = 0.0) textSize: Float, color: Int?, animate: Boolean, interpolator: TimeInterpolator?, @@ -459,7 +449,6 @@ constructor( textAnimator?.let { it.setTextStyle( weight = weight, - textSize = textSize, color = color, animate = animate && isAnimationEnabled, duration = duration, @@ -474,7 +463,6 @@ constructor( onTextAnimatorInitialized = { textAnimator -> textAnimator.setTextStyle( weight = weight, - textSize = textSize, color = color, animate = false, duration = duration, @@ -487,27 +475,6 @@ constructor( } } - private fun setTextStyle( - @IntRange(from = 0, to = 1000) weight: Int, - @FloatRange(from = 0.0) textSize: Float, - color: Int?, - animate: Boolean, - duration: Long, - delay: Long, - onAnimationEnd: Runnable? - ) { - setTextStyle( - weight = weight, - textSize = textSize, - color = color, - animate = animate, - interpolator = null, - duration = duration, - delay = delay, - onAnimationEnd = onAnimationEnd - ) - } - fun refreshFormat() = refreshFormat(DateFormat.is24HourFormat(context)) fun refreshFormat(use24HourFormat: Boolean) { Patterns.update(context) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 256687b56f4e..89bafb952211 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -16,6 +16,12 @@ package com.android.systemui.bouncer.ui.viewmodel +import android.view.KeyEvent.KEYCODE_0 +import android.view.KeyEvent.KEYCODE_4 +import android.view.KeyEvent.KEYCODE_A +import android.view.KeyEvent.KEYCODE_DEL +import android.view.KeyEvent.KEYCODE_NUMPAD_0 +import androidx.compose.ui.input.key.KeyEventType import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.SceneKey @@ -34,6 +40,8 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat +import kotlin.random.Random +import kotlin.random.nextInt import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -444,6 +452,44 @@ class PinBouncerViewModelTest : SysuiTestCase() { assertThat(pin).hasSize(FakeAuthenticationRepository.HINTING_PIN_LENGTH + 1) } + @Test + fun onKeyboardInput_pinInput_isUpdated() = + testScope.runTest { + val pin by collectLastValue(underTest.pinInput.map { it.getPin() }) + lockDeviceAndOpenPinBouncer() + val random = Random(System.currentTimeMillis()) + // Generate a random 4 digit PIN + val expectedPin = + with(random) { arrayOf(nextInt(0..9), nextInt(0..9), nextInt(0..9), nextInt(0..9)) } + + // Enter the PIN using NUM pad and normal number keyboard events + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_0 + expectedPin[0]) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_0 + expectedPin[0]) + + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_NUMPAD_0 + expectedPin[1]) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_NUMPAD_0 + expectedPin[1]) + + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_0 + expectedPin[2]) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_0 + expectedPin[2]) + + // Enter an additional digit in between and delete it + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_4) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_4) + + // Delete that additional digit + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_DEL) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_DEL) + + // Try entering a non digit character, this should be ignored. + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_A) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_A) + + underTest.onKeyEvent(KeyEventType.KeyDown, KEYCODE_NUMPAD_0 + expectedPin[3]) + underTest.onKeyEvent(KeyEventType.KeyUp, KEYCODE_NUMPAD_0 + expectedPin[3]) + + assertThat(pin).containsExactly(*expectedPin) + } + private fun TestScope.switchToScene(toScene: SceneKey) { val currentScene by collectLastValue(sceneInteractor.currentScene) val bouncerHidden = currentScene == Scenes.Bouncer && toScene != Scenes.Bouncer diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java index 29fbee01a18b..7936ccc1ddd1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/CommunalTouchHandlerTest.java @@ -108,7 +108,7 @@ public class CommunalTouchHandlerTest extends SysuiTestCase { mTouchHandler.onSessionStart(mTouchSession); verify(mTouchSession).registerInputListener(inputEventListenerArgumentCaptor.capture()); inputEventListenerArgumentCaptor.getValue().onInputEvent(motionEvent); - verify(mCentralSurfaces).handleDreamTouch(motionEvent); + verify(mCentralSurfaces).handleCommunalHubTouch(motionEvent); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt index 26fcb234843d..49d039970a24 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt @@ -90,6 +90,7 @@ class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = FakeUserTracker(), + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) settings = FakeSettings() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt index 99a01858471c..9ab1ac116b0d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt @@ -22,13 +22,14 @@ import android.content.SharedPreferences import android.content.pm.UserInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.backup.BackupHelper +import com.android.systemui.res.R import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.UserFileManager import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -80,6 +81,7 @@ class KeyguardQuickAffordanceLocalUserSelectionManagerTest : SysuiTestCase() { context = context, userFileManager = userFileManager, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt index 567e0a9717fc..159ce36bea2d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt @@ -21,7 +21,6 @@ import android.content.pm.UserInfo import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig @@ -32,6 +31,7 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation +import com.android.systemui.res.R import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.UserFileManager import com.android.systemui.shared.customization.data.content.FakeCustomizationProviderClient @@ -91,6 +91,7 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) client1 = FakeCustomizationProviderClient() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index 2d77f4f1c436..78a116737349 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -148,6 +148,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt index 2b8a644162c7..9dc930babc10 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt @@ -27,18 +27,22 @@ import com.android.systemui.coroutines.collectValues import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.flags.DisableSceneContainer import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.data.repository.setSceneTransition import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage import junit.framework.Assert.assertEquals import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf @@ -192,6 +196,175 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() { } @Test + @EnableSceneContainer + fun surfaceBehindVisibility_fromLockscreenToGone_trueThroughout() = + testScope.runTest { + val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility) + val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) + + // Before the transition, we start on Lockscreen so the surface should start invisible. + kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Lockscreen)) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(isSurfaceBehindVisible).isFalse() + + // Unlocked with fingerprint. + kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + + // Start the transition to Gone, the surface should become immediately visible. + kosmos.setSceneTransition( + ObservableTransitionState.Transition( + fromScene = Scenes.Lockscreen, + toScene = Scenes.Gone, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + progress = flowOf(0.3f), + currentScene = flowOf(Scenes.Lockscreen), + ) + ) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(isSurfaceBehindVisible).isTrue() + + // Towards the end of the transition, the surface should continue to be visible. + kosmos.setSceneTransition( + ObservableTransitionState.Transition( + fromScene = Scenes.Lockscreen, + toScene = Scenes.Gone, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + progress = flowOf(0.9f), + currentScene = flowOf(Scenes.Gone), + ) + ) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + assertThat(isSurfaceBehindVisible).isTrue() + + // After the transition, settles on Gone. Surface behind should stay visible now. + kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Gone)) + kosmos.sceneInteractor.changeScene(Scenes.Gone, "") + assertThat(currentScene).isEqualTo(Scenes.Gone) + assertThat(isSurfaceBehindVisible).isTrue() + } + + @Test + @EnableSceneContainer + fun surfaceBehindVisibility_fromBouncerToGone_becomesTrue() = + testScope.runTest { + val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility) + val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) + + // Before the transition, we start on Bouncer so the surface should start invisible. + kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Bouncer)) + kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "") + assertThat(currentScene).isEqualTo(Scenes.Bouncer) + assertThat(isSurfaceBehindVisible).isFalse() + + // Unlocked with fingerprint. + kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + + // Start the transition to Gone, the surface should remain invisible prior to hitting + // the + // threshold. + kosmos.setSceneTransition( + ObservableTransitionState.Transition( + fromScene = Scenes.Bouncer, + toScene = Scenes.Gone, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + progress = + flowOf( + FromPrimaryBouncerTransitionInteractor + .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD + ), + currentScene = flowOf(Scenes.Bouncer), + ) + ) + assertThat(currentScene).isEqualTo(Scenes.Bouncer) + assertThat(isSurfaceBehindVisible).isFalse() + + // Once the transition passes the threshold, the surface should become visible. + kosmos.setSceneTransition( + ObservableTransitionState.Transition( + fromScene = Scenes.Bouncer, + toScene = Scenes.Gone, + isInitiatedByUserInput = false, + isUserInputOngoing = flowOf(false), + progress = + flowOf( + FromPrimaryBouncerTransitionInteractor + .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD + 0.01f + ), + currentScene = flowOf(Scenes.Gone), + ) + ) + assertThat(currentScene).isEqualTo(Scenes.Bouncer) + assertThat(isSurfaceBehindVisible).isTrue() + + // After the transition, settles on Gone. Surface behind should stay visible now. + kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Gone)) + kosmos.sceneInteractor.changeScene(Scenes.Gone, "") + assertThat(currentScene).isEqualTo(Scenes.Gone) + assertThat(isSurfaceBehindVisible).isTrue() + } + + @Test + @EnableSceneContainer + fun surfaceBehindVisibility_idleWhileUnlocked_alwaysTrue() = + testScope.runTest { + val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility) + val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) + + // Unlocked with fingerprint. + kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Gone)) + kosmos.sceneInteractor.changeScene(Scenes.Gone, "") + assertThat(currentScene).isEqualTo(Scenes.Gone) + + listOf( + Scenes.Shade, + Scenes.QuickSettings, + Scenes.Shade, + Scenes.Gone, + ) + .forEach { scene -> + kosmos.setSceneTransition(ObservableTransitionState.Idle(scene)) + kosmos.sceneInteractor.changeScene(scene, "") + assertThat(currentScene).isEqualTo(scene) + assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"") + .that(isSurfaceBehindVisible) + .isTrue() + } + } + + @Test + @EnableSceneContainer + fun surfaceBehindVisibility_idleWhileLocked_alwaysFalse() = + testScope.runTest { + val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility) + val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) + assertThat(currentScene).isEqualTo(Scenes.Lockscreen) + + listOf( + Scenes.Shade, + Scenes.QuickSettings, + Scenes.Shade, + Scenes.Lockscreen, + ) + .forEach { scene -> + kosmos.setSceneTransition(ObservableTransitionState.Idle(scene)) + kosmos.sceneInteractor.changeScene(scene, "") + assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"") + .that(isSurfaceBehindVisible) + .isFalse() + } + } + + @Test @DisableSceneContainer fun testUsingGoingAwayAnimation_duringTransitionToGone() = testScope.runTest { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt index 311122d7f8d5..16f30feb7e3b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.ColorCorrectionTile import com.android.systemui.qs.tiles.ColorInversionTile +import com.android.systemui.qs.tiles.HearingDevicesTile import com.android.systemui.qs.tiles.OneHandedModeTile import com.android.systemui.qs.tiles.ReduceBrightColorsTile import com.android.systemui.util.mockito.mock @@ -77,6 +78,10 @@ class A11yShortcutAutoAddableListTest : SysuiTestCase() { TileSpec.create(ReduceBrightColorsTile.TILE_SPEC), AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME ), + factory.create( + TileSpec.create(HearingDevicesTile.TILE_SPEC), + AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME + ), ) val autoAddables = A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(factory) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java index 0f66a93bcbec..3bfc046e46b4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java @@ -38,6 +38,7 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.PendingIntent; import android.app.Person; +import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.FlagsParameterization; import android.testing.TestableLooper; @@ -150,6 +151,62 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { } @Test + public void testHasNotifications_headsUpManagerMapNotEmpty_true() { + final BaseHeadsUpManager bhum = createHeadsUpManager(); + final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext); + bhum.showNotification(entry); + + assertThat(bhum.mHeadsUpEntryMap).isNotEmpty(); + assertThat(bhum.hasNotifications()).isTrue(); + } + + @Test + @EnableFlags(NotificationThrottleHun.FLAG_NAME) + public void testHasNotifications_avalancheMapNotEmpty_true() { + final BaseHeadsUpManager bhum = createHeadsUpManager(); + final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, + mContext); + final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = bhum.createHeadsUpEntry(notifEntry); + mAvalancheController.addToNext(headsUpEntry, () -> {}); + + assertThat(mAvalancheController.getWaitingEntryList()).isNotEmpty(); + assertThat(bhum.hasNotifications()).isTrue(); + } + + @Test + @EnableFlags(NotificationThrottleHun.FLAG_NAME) + public void testHasNotifications_false() { + final BaseHeadsUpManager bhum = createHeadsUpManager(); + assertThat(bhum.mHeadsUpEntryMap).isEmpty(); + assertThat(mAvalancheController.getWaitingEntryList()).isEmpty(); + assertThat(bhum.hasNotifications()).isFalse(); + } + + @Test + @EnableFlags(NotificationThrottleHun.FLAG_NAME) + public void testGetHeadsUpEntryList_includesAvalancheEntryList() { + final BaseHeadsUpManager bhum = createHeadsUpManager(); + final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, + mContext); + final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = bhum.createHeadsUpEntry(notifEntry); + mAvalancheController.addToNext(headsUpEntry, () -> {}); + + assertThat(bhum.getHeadsUpEntryList()).contains(headsUpEntry); + } + + @Test + @EnableFlags(NotificationThrottleHun.FLAG_NAME) + public void testGetHeadsUpEntry_returnsAvalancheEntry() { + final BaseHeadsUpManager bhum = createHeadsUpManager(); + final NotificationEntry notifEntry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, + mContext); + final BaseHeadsUpManager.HeadsUpEntry headsUpEntry = bhum.createHeadsUpEntry(notifEntry); + mAvalancheController.addToNext(headsUpEntry, () -> {}); + + assertThat(bhum.getHeadsUpEntry(notifEntry.getKey())).isEqualTo(headsUpEntry); + } + + @Test public void testShowNotification_addsEntry() { final BaseHeadsUpManager alm = createHeadsUpManager(); final NotificationEntry entry = HeadsUpManagerTestUtil.createEntry(/* id = */ 0, mContext); diff --git a/packages/SystemUI/res/drawable/ic_widgets.xml b/packages/SystemUI/res/drawable/ic_widgets.xml new file mode 100644 index 000000000000..b21d047f9386 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_widgets.xml @@ -0,0 +1,27 @@ +<!-- + ~ 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. + --> + +<!-- go/gm2-icons, from gs_widgets_vd_theme_24.xml --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/black" + android:pathData="M666,520L440,294L666,68L892,294L666,520ZM120,440L120,120L440,120L440,440L120,440ZM520,840L520,520L840,520L840,840L520,840ZM120,840L120,520L440,520L440,840L120,840ZM200,360L360,360L360,200L200,200L200,360ZM667,408L780,295L667,182L554,295L667,408ZM600,760L760,760L760,600L600,600L600,760ZM200,760L360,760L360,600L200,600L200,760ZM360,360L360,360L360,360L360,360L360,360ZM554,295L554,295L554,295L554,295L554,295ZM360,600L360,600L360,600L360,600L360,600ZM600,600L600,600L600,600L600,600L600,600Z" /> +</vector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/qs_hearing_devices_related_tools_background.xml b/packages/SystemUI/res/drawable/qs_hearing_devices_related_tools_background.xml new file mode 100644 index 000000000000..627b92b8a779 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_hearing_devices_related_tools_background.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2021 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + +<ripple + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:color="?android:attr/colorControlHighlight"> + <item> + <shape android:shape="rectangle"> + <solid android:color="@android:color/transparent"/> + <corners android:radius="@dimen/hearing_devices_preset_spinner_background_radius"/> + <stroke + android:width="1dp" + android:color="?androidprv:attr/textColorTertiary" /> + </shape> + </item> +</ripple> diff --git a/packages/SystemUI/res/layout/dream_overlay_open_hub_chip.xml b/packages/SystemUI/res/layout/dream_overlay_open_hub_chip.xml new file mode 100644 index 000000000000..be063a9631e8 --- /dev/null +++ b/packages/SystemUI/res/layout/dream_overlay_open_hub_chip.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. +--> +<com.android.systemui.animation.view.LaunchableImageView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_height="@dimen/dream_overlay_bottom_affordance_height" + android:layout_width="@dimen/dream_overlay_bottom_affordance_width" + android:layout_gravity="bottom|start" + android:padding="@dimen/dream_overlay_bottom_affordance_padding" + android:scaleType="fitCenter" + android:tint="?android:attr/textColorPrimary" + android:src="@drawable/ic_widgets" + android:contentDescription="@string/accessibility_action_open_communal_hub" /> diff --git a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml index 2bf6f80c32d0..4a7bef9f48b9 100644 --- a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml +++ b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml @@ -36,9 +36,8 @@ style="@style/BluetoothTileDialog.Device" android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="@dimen/hearing_devices_preset_spinner_height" android:layout_marginTop="@dimen/hearing_devices_layout_margin" - android:layout_marginBottom="@dimen/hearing_devices_layout_margin" + android:minHeight="@dimen/hearing_devices_preset_spinner_height" android:gravity="center_vertical" android:background="@drawable/hearing_devices_preset_spinner_background" android:popupBackground="@drawable/hearing_devices_preset_spinner_popup_background" @@ -54,9 +53,10 @@ android:visibility="gone"/> <androidx.constraintlayout.widget.Barrier - android:id="@+id/device_barrier" + android:id="@+id/device_function_barrier" android:layout_width="wrap_content" android:layout_height="wrap_content" + app:barrierAllowsGoneWidgets="false" app:barrierDirection="bottom" app:constraint_referenced_ids="device_list,preset_spinner" /> @@ -71,7 +71,8 @@ android:contentDescription="@string/accessibility_hearing_device_pair_new_device" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/device_barrier" + app:layout_constraintTop_toBottomOf="@id/device_function_barrier" + app:layout_constraintBottom_toTopOf="@id/related_tools_scroll" android:drawableStart="@drawable/ic_add" android:drawablePadding="20dp" android:drawableTint="?android:attr/textColorPrimary" @@ -83,4 +84,32 @@ android:maxLines="1" android:ellipsize="end" /> + <androidx.constraintlayout.widget.Barrier + android:id="@+id/device_barrier" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:barrierAllowsGoneWidgets="false" + app:barrierDirection="bottom" + app:constraint_referenced_ids="device_function_barrier, pair_new_device_button" /> + + <HorizontalScrollView + android:id="@+id/related_tools_scroll" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin" + android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin" + android:layout_marginTop="@dimen/hearing_devices_layout_margin" + android:scrollbars="none" + android:fillViewport="true" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/preset_spinner"> + <LinearLayout + android:id="@+id/related_tools_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal"> + </LinearLayout> + </HorizontalScrollView> + </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/hearing_tool_item.xml b/packages/SystemUI/res/layout/hearing_tool_item.xml new file mode 100644 index 000000000000..84462d08d4a0 --- /dev/null +++ b/packages/SystemUI/res/layout/hearing_tool_item.xml @@ -0,0 +1,53 @@ +<!-- + 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. +--> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/tool_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:gravity="center" + android:focusable="true" + android:clickable="true" + android:layout_weight="1"> + <FrameLayout + android:id="@+id/icon_frame" + android:layout_width="@dimen/hearing_devices_tool_icon_frame_width" + android:layout_height="@dimen/hearing_devices_tool_icon_frame_height" + android:background="@drawable/qs_hearing_devices_related_tools_background" + android:focusable="false" > + <ImageView + android:id="@+id/tool_icon" + android:layout_width="@dimen/hearing_devices_tool_icon_size" + android:layout_height="@dimen/hearing_devices_tool_icon_size" + android:layout_gravity="center" + android:scaleType="fitCenter" + android:focusable="false" /> + </FrameLayout> + <TextView + android:id="@+id/tool_name" + android:textDirection="locale" + android:textAlignment="center" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/hearing_devices_layout_margin" + android:ellipsize="end" + android:maxLines="1" + android:textSize="12sp" + android:textAppearance="@style/TextAppearance.Dialog.Body.Message" + android:focusable="false" /> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 638785402055..fb883640c9a9 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -125,6 +125,20 @@ <!-- Use collapsed layout for media player in landscape QQS --> <bool name="config_quickSettingsMediaLandscapeCollapsed">true</bool> + <!-- For hearing devices related tool list. Need to be in ComponentName format (package/class). + Should be activity to be launched. + Already contains tool that holds intent: "com.android.settings.action.live_caption". + Maximum number is 3. --> + <string-array name="config_quickSettingsHearingDevicesRelatedToolName" translatable="false"> + </string-array> + + <!-- The drawable resource names. If provided, it will replace the corresponding icons in + config_quickSettingsHearingDevicesRelatedToolName. Can be empty to use original icons. + Already contains tool that holds intent: "com.android.settings.action.live_caption". + Maximum number is 3. --> + <string-array name="config_quickSettingsHearingDevicesRelatedToolIcon" translatable="false"> + </string-array> + <!-- Show indicator for Wifi on but not connected. --> <bool name="config_showWifiIndicatorWhenEnabled">false</bool> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 7d7a5d4dbf14..edd3d77555f7 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1778,6 +1778,9 @@ <dimen name="hearing_devices_preset_spinner_text_padding_vertical">15dp</dimen> <dimen name="hearing_devices_preset_spinner_arrow_size">24dp</dimen> <dimen name="hearing_devices_preset_spinner_background_radius">28dp</dimen> + <dimen name="hearing_devices_tool_icon_frame_width">94dp</dimen> + <dimen name="hearing_devices_tool_icon_frame_height">64dp</dimen> + <dimen name="hearing_devices_tool_icon_size">28dp</dimen> <!-- Height percentage of the parent container occupied by the communal view --> <item name="communal_source_height_percentage" format="float" type="dimen">0.80</item> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 6f2806d80ef3..c038a8207d43 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -917,6 +917,8 @@ <string name="hearing_devices_presets_error">Couldn\'t update preset</string> <!-- QuickSettings: Title for hearing aids presets. Preset is a set of hearing aid settings. User can apply different settings in different environments (e.g. Outdoor, Restaurant, Home) [CHAR LIMIT=40]--> <string name="hearing_devices_preset_label">Preset</string> + <!-- QuickSettings: Tool name for hearing devices dialog related tools [CHAR LIMIT=40]--> + <string name="live_caption_title">Live Caption</string> <!--- Title of dialog triggered if the microphone is disabled but an app tried to access it. [CHAR LIMIT=150] --> <string name="sensor_privacy_start_use_mic_dialog_title">Unblock device microphone?</string> diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 3f3bb0bc94b6..86c807bf9d07 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -15,12 +15,12 @@ */ package com.android.keyguard -import android.os.Trace import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.res.Resources +import android.os.Trace import android.text.format.DateFormat import android.util.Log import android.util.TypedValue @@ -466,15 +466,11 @@ constructor( largeRegionSampler?.stopRegionSampler() smallTimeListener?.stop() largeTimeListener?.stop() - clock - ?.smallClock - ?.view - ?.removeOnAttachStateChangeListener(smallClockOnAttachStateChangeListener) + clock?.apply { + smallClock.view.removeOnAttachStateChangeListener(smallClockOnAttachStateChangeListener) + largeClock.view.removeOnAttachStateChangeListener(largeClockOnAttachStateChangeListener) + } smallClockFrame?.viewTreeObserver?.removeOnGlobalLayoutListener(onGlobalLayoutListener) - clock - ?.largeClock - ?.view - ?.removeOnAttachStateChangeListener(largeClockOnAttachStateChangeListener) } /** @@ -505,15 +501,17 @@ constructor( } } - private fun updateFontSizes() { + fun updateFontSizes() { clock?.run { - smallClock.events.onFontSettingChanged( - resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat() - ) + smallClock.events.onFontSettingChanged(getSmallClockSizePx()) largeClock.events.onFontSettingChanged(getLargeClockSizePx()) } } + private fun getSmallClockSizePx(): Float { + return resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat() + } + private fun getLargeClockSizePx(): Float { return if (largeClockOnSecondaryDisplay) { resources.getDimensionPixelSize(R.dimen.presentation_clock_text_size).toFloat() @@ -549,9 +547,8 @@ constructor( it.copy(value = 1f - it.value) }, keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, AOD)), - ).filter { - it.transitionState != TransitionState.FINISHED - } + ) + .filter { it.transitionState != TransitionState.FINISHED } .collect { handleDoze(it.value) } } } @@ -574,28 +571,27 @@ constructor( internal fun listenForAnyStateToLockscreenTransition(scope: CoroutineScope): Job { return scope.launch { keyguardTransitionInteractor - .transitionStepsToState(LOCKSCREEN) - .filter { it.transitionState == TransitionState.STARTED } - .filter { it.from != AOD } - .collect { handleDoze(0f) } + .transitionStepsToState(LOCKSCREEN) + .filter { it.transitionState == TransitionState.STARTED } + .filter { it.from != AOD } + .collect { handleDoze(0f) } } } /** - * When keyguard is displayed due to pulsing notifications when AOD is off, - * we should make sure clock is in dozing state instead of LS state + * When keyguard is displayed due to pulsing notifications when AOD is off, we should make sure + * clock is in dozing state instead of LS state */ @VisibleForTesting internal fun listenForAnyStateToDozingTransition(scope: CoroutineScope): Job { return scope.launch { keyguardTransitionInteractor - .transitionStepsToState(DOZING) - .filter { it.transitionState == TransitionState.FINISHED } - .collect { handleDoze(1f) } + .transitionStepsToState(DOZING) + .filter { it.transitionState == TransitionState.FINISHED } + .collect { handleDoze(1f) } } } - @VisibleForTesting internal fun listenForDozing(scope: CoroutineScope): Job { return scope.launch { diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index 27b2b92ab899..63ad41a808dc 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -20,7 +20,6 @@ import static androidx.dynamicanimation.animation.DynamicAnimation.TRANSLATION_X import static androidx.dynamicanimation.animation.FloatPropertyCompat.createFloatPropertyCompat; import static com.android.systemui.classifier.Classifier.NOTIFICATION_DISMISS; -import static com.android.systemui.flags.Flags.SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX; import static com.android.systemui.statusbar.notification.NotificationUtils.logKey; import android.animation.Animator; @@ -481,16 +480,11 @@ public class SwipeHelper implements Gefingerpoken, Dumpable { updateSwipeProgressFromOffset(animView, canBeDismissed); mDismissPendingMap.remove(animView); boolean wasRemoved = false; - if (animView instanceof ExpandableNotificationRow) { - ExpandableNotificationRow row = (ExpandableNotificationRow) animView; - if (mFeatureFlags.isEnabled(SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX)) { - // If the view is already removed from its parent and added as Transient, - // we need to clean the transient view upon animation end - wasRemoved = row.getTransientContainer() != null - || row.getParent() == null || row.isRemoved(); - } else { - wasRemoved = row.isRemoved(); - } + if (animView instanceof ExpandableNotificationRow row) { + // If the view is already removed from its parent and added as Transient, + // we need to clean the transient view upon animation end + wasRemoved = row.getTransientContainer() != null + || row.getParent() == null || row.isRemoved(); } if (!mCancelled || wasRemoved) { mCallback.onChildDismissed(animView); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt index 4069cece7790..63791f9228c0 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt @@ -30,6 +30,7 @@ import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.ColorCorrectionTile import com.android.systemui.qs.tiles.ColorInversionTile import com.android.systemui.qs.tiles.FontScalingTile +import com.android.systemui.qs.tiles.HearingDevicesTile import com.android.systemui.qs.tiles.OneHandedModeTile import com.android.systemui.qs.tiles.ReduceBrightColorsTile import javax.inject.Inject @@ -74,6 +75,8 @@ constructor( .REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME, FontScalingTile.TILE_SPEC to AccessibilityShortcutController.FONT_SIZE_TILE_COMPONENT_NAME, + HearingDevicesTile.TILE_SPEC to + AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_TILE_COMPONENT_NAME ) } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java index 28dd2338ff2b..961d6aa1b821 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java @@ -25,10 +25,14 @@ import android.bluetooth.BluetoothHapClient; import android.bluetooth.BluetoothHapPresetInfo; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; import android.media.AudioManager; import android.os.Bundle; import android.os.Handler; import android.provider.Settings; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.Visibility; @@ -36,7 +40,10 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.Spinner; +import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; @@ -69,6 +76,7 @@ import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -78,12 +86,15 @@ import java.util.stream.Collectors; */ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, HearingDeviceItemCallback, BluetoothCallback { - + private static final String TAG = "HearingDevicesDialogDelegate"; @VisibleForTesting static final String ACTION_BLUETOOTH_DEVICE_DETAILS = "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS"; private static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args"; private static final String KEY_BLUETOOTH_ADDRESS = "device_address"; + @VisibleForTesting + static final Intent LIVE_CAPTION_INTENT = new Intent( + "com.android.settings.action.live_caption"); private final SystemUIDialog.Factory mSystemUIDialogFactory; private final DialogTransitionAnimator mDialogTransitionAnimator; private final ActivityStarter mActivityStarter; @@ -102,6 +113,7 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, private Spinner mPresetSpinner; private ArrayAdapter<String> mPresetInfoAdapter; private Button mPairButton; + private LinearLayout mRelatedToolsContainer; private final HearingDevicesPresetsController.PresetCallback mPresetCallback = new HearingDevicesPresetsController.PresetCallback() { @Override @@ -253,10 +265,14 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, mPairButton = dialog.requireViewById(R.id.pair_new_device_button); mDeviceList = dialog.requireViewById(R.id.device_list); mPresetSpinner = dialog.requireViewById(R.id.preset_spinner); + mRelatedToolsContainer = dialog.requireViewById(R.id.related_tools_container); setupDeviceListView(dialog); setupPresetSpinner(dialog); setupPairNewDeviceButton(dialog, mShowPairNewDevice ? VISIBLE : GONE); + if (com.android.systemui.Flags.hearingDevicesDialogRelatedTools()) { + setupRelatedToolsView(dialog); + } } @Override @@ -351,6 +367,34 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, } } + private void setupRelatedToolsView(SystemUIDialog dialog) { + final Context context = dialog.getContext(); + final List<ToolItem> toolItemList = new ArrayList<>(); + final String[] toolNameArray; + final String[] toolIconArray; + + ToolItem preInstalledItem = getLiveCaption(context); + if (preInstalledItem != null) { + toolItemList.add(preInstalledItem); + } + try { + toolNameArray = context.getResources().getStringArray( + R.array.config_quickSettingsHearingDevicesRelatedToolName); + toolIconArray = context.getResources().getStringArray( + R.array.config_quickSettingsHearingDevicesRelatedToolIcon); + toolItemList.addAll( + HearingDevicesToolItemParser.parseStringArray(context, toolNameArray, + toolIconArray)); + } catch (Resources.NotFoundException e) { + Log.i(TAG, "No hearing devices related tool config resource"); + } + final int listSize = toolItemList.size(); + for (int i = 0; i < listSize; i++) { + View view = createHearingToolView(context, toolItemList.get(i)); + mRelatedToolsContainer.addView(view); + } + } + private void refreshPresetInfoAdapter(List<BluetoothHapPresetInfo> presetInfos, int activePresetIndex) { mPresetInfoAdapter.clear(); @@ -400,6 +444,40 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, return null; } + @NonNull + private View createHearingToolView(Context context, ToolItem item) { + View view = LayoutInflater.from(context).inflate(R.layout.hearing_tool_item, + mRelatedToolsContainer, false); + ImageView icon = view.requireViewById(R.id.tool_icon); + TextView text = view.requireViewById(R.id.tool_name); + view.setContentDescription(item.getToolName()); + icon.setImageDrawable(item.getToolIcon()); + text.setText(item.getToolName()); + Intent intent = item.getToolIntent(); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + view.setOnClickListener( + v -> { + dismissDialogIfExists(); + mActivityStarter.postStartActivityDismissingKeyguard(intent, /* delay= */ 0, + mDialogTransitionAnimator.createActivityTransitionController(view)); + }); + return view; + } + + private ToolItem getLiveCaption(Context context) { + final PackageManager packageManager = context.getPackageManager(); + LIVE_CAPTION_INTENT.setPackage(packageManager.getSystemCaptionsServicePackageName()); + final List<ResolveInfo> resolved = packageManager.queryIntentActivities(LIVE_CAPTION_INTENT, + /* flags= */ 0); + if (!resolved.isEmpty()) { + return new ToolItem(context.getString(R.string.live_caption_title), + context.getDrawable(R.drawable.ic_volume_odi_captions), + LIVE_CAPTION_INTENT); + } + + return null; + } + private void dismissDialogIfExists() { if (mDialog != null) { mDialog.dismiss(); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParser.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParser.java new file mode 100644 index 000000000000..2006726e6847 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParser.java @@ -0,0 +1,129 @@ +/* + * 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.accessibility.hearingaid; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.util.Log; + +import androidx.annotation.VisibleForTesting; + +import com.google.common.collect.ImmutableList; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Utility class for managing and parsing tool items related to hearing devices. + */ +public class HearingDevicesToolItemParser { + private static final String TAG = "HearingDevicesToolItemParser"; + private static final String SPLIT_DELIMITER = "/"; + private static final String RES_TYPE = "drawable"; + @VisibleForTesting + static final int MAX_NUM = 3; + + /** + * Parses the string arrays to create a list of {@link ToolItem}. + * + * This method validates the structure of {@code toolNameArray} and {@code toolIconArray}. + * If {@code toolIconArray} is empty or mismatched in length with {@code toolNameArray}, the + * icon from {@link ActivityInfo#loadIcon(PackageManager)} will be used instead. + * + * @param context A valid context. + * @param toolNameArray An array of tool names in the format of {@link ComponentName}. + * @param toolIconArray An optional array of resource names for tool icons (can be empty). + * @return A list of {@link ToolItem} or an empty list if there are errors during parsing. + */ + public static ImmutableList<ToolItem> parseStringArray(Context context, String[] toolNameArray, + String[] toolIconArray) { + if (toolNameArray.length == 0) { + Log.i(TAG, "Empty hearing device related tool name in array."); + return ImmutableList.of(); + } + // For the performance concern, especially `getIdentifier` in `parseValidIcon`, we will + // limit the maximum number. + String[] nameArrayCpy = Arrays.copyOfRange(toolNameArray, 0, + Math.min(toolNameArray.length, MAX_NUM)); + String[] iconArrayCpy = Arrays.copyOfRange(toolIconArray, 0, + Math.min(toolIconArray.length, MAX_NUM)); + + final PackageManager packageManager = context.getPackageManager(); + final ImmutableList.Builder<ToolItem> toolItemList = ImmutableList.builder(); + final List<ActivityInfo> activityInfoList = parseValidActivityInfo(context, nameArrayCpy); + final List<Drawable> iconList = parseValidIcon(context, iconArrayCpy); + final int size = activityInfoList.size(); + // Only use custom icon if provided icon's list size is equal to provided name's list size. + final boolean useCustomIcons = (size == iconList.size()); + + for (int i = 0; i < size; i++) { + toolItemList.add(new ToolItem( + activityInfoList.get(i).loadLabel(packageManager).toString(), + useCustomIcons ? iconList.get(i) + : activityInfoList.get(i).loadIcon(packageManager), + new Intent(Intent.ACTION_MAIN).setComponent( + activityInfoList.get(i).getComponentName()) + )); + } + + return toolItemList.build(); + } + + private static List<ActivityInfo> parseValidActivityInfo(Context context, + String[] toolNameArray) { + final PackageManager packageManager = context.getPackageManager(); + final List<ActivityInfo> activityInfoList = new ArrayList<>(); + for (String toolName : toolNameArray) { + String[] nameParts = toolName.split(SPLIT_DELIMITER); + if (nameParts.length == 2) { + ComponentName componentName = ComponentName.unflattenFromString(toolName); + try { + ActivityInfo activityInfo = packageManager.getActivityInfo( + componentName, /* flags= */ 0); + activityInfoList.add(activityInfo); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "Unable to find hearing device related tool: " + + componentName.flattenToString()); + } + } else { + Log.e(TAG, "Malformed hearing device related tool name item in array: " + + toolName); + } + } + return activityInfoList; + } + + private static List<Drawable> parseValidIcon(Context context, String[] toolIconArray) { + final List<Drawable> drawableList = new ArrayList<>(); + for (String icon : toolIconArray) { + int resId = context.getResources().getIdentifier(icon, RES_TYPE, + context.getPackageName()); + try { + drawableList.add(context.getDrawable(resId)); + } catch (Resources.NotFoundException e) { + Log.e(TAG, "Resource does not exist: " + icon); + } + } + return drawableList; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/ToolItem.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/ToolItem.kt new file mode 100644 index 000000000000..66bb2b5e2328 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/ToolItem.kt @@ -0,0 +1,26 @@ +/* + * 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.accessibility.hearingaid + +import android.content.Intent +import android.graphics.drawable.Drawable + +data class ToolItem( + var toolName: String = "", + var toolIcon: Drawable, + var toolIntent: Intent, +) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index 7c41b75d7105..40a141dcec77 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -20,6 +20,8 @@ import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyResources import android.content.Context import android.graphics.Bitmap +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.type import androidx.core.graphics.drawable.toBitmap import com.android.compose.animation.scene.Back import com.android.compose.animation.scene.SceneKey @@ -326,7 +328,8 @@ class BouncerViewModel( { message }, failedAttempts, remainingAttempts, - ) ?: message + ) + ?: message } else { message } @@ -343,7 +346,8 @@ class BouncerViewModel( .KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE, { message }, failedAttempts, - ) ?: message + ) + ?: message } else { message } @@ -377,6 +381,19 @@ class BouncerViewModel( Swipe(SwipeDirection.Down) to UserActionResult(prevScene), ) + /** + * Notifies that a key event has occurred. + * + * @return `true` when the [KeyEvent] was consumed as user input on bouncer; `false` otherwise. + */ + fun onKeyEvent(keyEvent: KeyEvent): Boolean { + return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent( + keyEvent.type, + keyEvent.nativeKeyEvent.keyCode + ) + ?: false + } + data class DialogViewModel( val text: String, diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index 4c2380c5e4db..aa447ffac154 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -19,6 +19,14 @@ package com.android.systemui.bouncer.ui.viewmodel import android.content.Context +import android.view.KeyEvent.KEYCODE_0 +import android.view.KeyEvent.KEYCODE_9 +import android.view.KeyEvent.KEYCODE_DEL +import android.view.KeyEvent.KEYCODE_NUMPAD_0 +import android.view.KeyEvent.KEYCODE_NUMPAD_9 +import android.view.KeyEvent.isConfirmKey +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.KeyEventType import com.android.keyguard.PinShapeAdapter import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor @@ -196,6 +204,44 @@ class PinBouncerViewModel( else -> ActionButtonAppearance.Shown } } + + /** + * Notifies that a key event has occurred. + * + * @return `true` when the [KeyEvent] was consumed as user input on bouncer; `false` otherwise. + */ + fun onKeyEvent(type: KeyEventType, keyCode: Int): Boolean { + return when (type) { + KeyEventType.KeyUp -> { + if (isConfirmKey(keyCode)) { + onAuthenticateButtonClicked() + true + } else { + false + } + } + KeyEventType.KeyDown -> { + when (keyCode) { + KEYCODE_DEL -> { + onBackspaceButtonClicked() + true + } + in KEYCODE_0..KEYCODE_9 -> { + onPinButtonClicked(keyCode - KEYCODE_0) + true + } + in KEYCODE_NUMPAD_0..KEYCODE_NUMPAD_9 -> { + onPinButtonClicked(keyCode - KEYCODE_NUMPAD_0) + true + } + else -> { + false + } + } + } + else -> false + } + } } /** Appearance of pin-pad action buttons. */ diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java index afa23755d937..e284bc7752cf 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java +++ b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java @@ -18,6 +18,7 @@ package com.android.systemui.complication; import static com.android.systemui.complication.dagger.DreamHomeControlsComplicationComponent.DreamHomeControlsModule.DREAM_HOME_CONTROLS_CHIP_VIEW; import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS; +import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS; import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE; import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK; import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE; @@ -35,6 +36,7 @@ import androidx.annotation.Nullable; import com.android.internal.logging.UiEventLogger; import com.android.settingslib.Utils; import com.android.systemui.CoreStartable; +import com.android.systemui.Flags; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.complication.dagger.DreamHomeControlsComplicationComponent; import com.android.systemui.controls.ControlsServiceInfo; @@ -89,6 +91,7 @@ public class DreamHomeControlsComplication implements Complication { private final DreamHomeControlsComplication mComplication; private final DreamOverlayStateController mDreamOverlayStateController; private final ControlsComponent mControlsComponent; + private final boolean mReplacedByOpenHub; private boolean mOverlayActive = false; @@ -116,11 +119,13 @@ public class DreamHomeControlsComplication implements Complication { public Registrant(DreamHomeControlsComplication complication, DreamOverlayStateController dreamOverlayStateController, ControlsComponent controlsComponent, - @SystemUser Monitor monitor) { + @SystemUser Monitor monitor, + @Named(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS) boolean replacedByOpenHub) { super(monitor); mComplication = complication; mControlsComponent = controlsComponent; mDreamOverlayStateController = dreamOverlayStateController; + mReplacedByOpenHub = replacedByOpenHub; } @Override @@ -132,7 +137,9 @@ public class DreamHomeControlsComplication implements Complication { private void updateHomeControlsComplication() { mControlsComponent.getControlsListingController().ifPresent(c -> { - if (isHomeControlsAvailable(c.getCurrentServices())) { + final boolean replacedWithOpenHub = + Flags.glanceableHubShortcutButton() && mReplacedByOpenHub; + if (isHomeControlsAvailable(c.getCurrentServices()) && !replacedWithOpenHub) { mDreamOverlayStateController.addComplication(mComplication); } else { mDreamOverlayStateController.removeComplication(mComplication); diff --git a/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java b/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java new file mode 100644 index 000000000000..3cf22b1b55e4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java @@ -0,0 +1,212 @@ +/* + * 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.complication; + +import static com.android.systemui.complication.dagger.OpenHubComplicationComponent.OpenHubModule.OPEN_HUB_CHIP_VIEW; +import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.OPEN_HUB_CHIP_LAYOUT_PARAMS; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; + +import com.android.settingslib.Utils; +import com.android.systemui.CoreStartable; +import com.android.systemui.Flags; +import com.android.systemui.communal.domain.interactor.CommunalInteractor; +import com.android.systemui.communal.shared.model.CommunalScenes; +import com.android.systemui.complication.dagger.OpenHubComplicationComponent; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.dagger.qualifiers.SystemUser; +import com.android.systemui.dreams.DreamOverlayStateController; +import com.android.systemui.shared.condition.Monitor; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.util.ViewController; +import com.android.systemui.util.condition.ConditionalCoreStartable; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * A dream complication that shows a chip to open the glanceable hub. + */ +// TODO(b/339667383): delete or properly implement this once a product decision is made +public class OpenHubComplication implements Complication { + private final Resources mResources; + private final OpenHubComplicationComponent.Factory mComponentFactory; + + @Inject + public OpenHubComplication( + @Main Resources resources, + OpenHubComplicationComponent.Factory componentFactory) { + mResources = resources; + mComponentFactory = componentFactory; + } + + @Override + public ViewHolder createView(ComplicationViewModel model) { + return mComponentFactory.create(mResources).getViewHolder(); + } + + @Override + public int getRequiredTypeAvailability() { + // TODO(b/339667383): create a new complication type if we decide to productionize this + return COMPLICATION_TYPE_HOME_CONTROLS; + } + + /** + * {@link CoreStartable} for registering the complication with SystemUI on startup. + */ + public static class Registrant extends ConditionalCoreStartable { + private final OpenHubComplication mComplication; + private final DreamOverlayStateController mDreamOverlayStateController; + + private boolean mOverlayActive = false; + + private final DreamOverlayStateController.Callback mOverlayStateCallback = + new DreamOverlayStateController.Callback() { + @Override + public void onStateChanged() { + if (mOverlayActive == mDreamOverlayStateController.isOverlayActive()) { + return; + } + + mOverlayActive = !mOverlayActive; + + if (mOverlayActive) { + updateOpenHubComplication(); + } + } + }; + + @Inject + public Registrant(OpenHubComplication complication, + DreamOverlayStateController dreamOverlayStateController, + @SystemUser Monitor monitor) { + super(monitor); + mComplication = complication; + mDreamOverlayStateController = dreamOverlayStateController; + } + + @Override + public void onStart() { + mDreamOverlayStateController.addCallback(mOverlayStateCallback); + } + + private void updateOpenHubComplication() { + // TODO(b/339667383): don't show the complication if glanceable hub is disabled + if (Flags.glanceableHubShortcutButton()) { + mDreamOverlayStateController.addComplication(mComplication); + } else { + mDreamOverlayStateController.removeComplication(mComplication); + } + } + } + + /** + * Contains values/logic associated with the dream complication view. + */ + public static class OpenHubChipViewHolder implements ViewHolder { + private final ImageView mView; + private final ComplicationLayoutParams mLayoutParams; + private final OpenHubChipViewController mViewController; + + @Inject + OpenHubChipViewHolder( + OpenHubChipViewController dreamOpenHubChipViewController, + @Named(OPEN_HUB_CHIP_VIEW) ImageView view, + @Named(OPEN_HUB_CHIP_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams + ) { + mView = view; + mLayoutParams = layoutParams; + mViewController = dreamOpenHubChipViewController; + mViewController.init(); + } + + @Override + public ImageView getView() { + return mView; + } + + @Override + public ComplicationLayoutParams getLayoutParams() { + return mLayoutParams; + } + } + + /** + * Controls behavior of the dream complication. + */ + static class OpenHubChipViewController extends ViewController<ImageView> { + private static final String TAG = "OpenHubCtrl"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final Context mContext; + private final ConfigurationController mConfigurationController; + + private final ConfigurationController.ConfigurationListener mConfigurationListener = + new ConfigurationController.ConfigurationListener() { + @Override + public void onUiModeChanged() { + reloadResources(); + } + }; + private final CommunalInteractor mCommunalInteractor; + + @Inject + OpenHubChipViewController( + @Named(OPEN_HUB_CHIP_VIEW) ImageView view, + Context context, + ConfigurationController configurationController, + CommunalInteractor communalInteractor) { + super(view); + + mContext = context; + mConfigurationController = configurationController; + mCommunalInteractor = communalInteractor; + } + + @Override + protected void onViewAttached() { + reloadResources(); + mView.setOnClickListener(this::onClickOpenHub); + mConfigurationController.addCallback(mConfigurationListener); + } + + @Override + protected void onViewDetached() { + mConfigurationController.removeCallback(mConfigurationListener); + } + + private void reloadResources() { + mView.setImageTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary)); + final Drawable background = mView.getBackground(); + if (background != null) { + background.setTintList( + Utils.getColorAttr(mContext, com.android.internal.R.attr.colorSurface)); + } + } + + private void onClickOpenHub(View v) { + if (DEBUG) Log.d(TAG, "open hub complication tapped"); + + mCommunalInteractor.changeScene(CommunalScenes.Communal, null); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/OpenHubComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/OpenHubComplicationComponent.java new file mode 100644 index 000000000000..501601ee32ba --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/OpenHubComplicationComponent.java @@ -0,0 +1,138 @@ +/* + * 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.complication.dagger; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.widget.ImageView; + +import com.android.systemui.complication.OpenHubComplication; +import com.android.systemui.res.R; +import com.android.systemui.shared.shadow.DoubleShadowIconDrawable; +import com.android.systemui.shared.shadow.DoubleShadowTextHelper; + +import dagger.BindsInstance; +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import javax.inject.Named; +import javax.inject.Scope; + +/** + * Responsible for generating dependencies for the {@link OpenHubComplication}. + */ +@Subcomponent(modules = OpenHubComplicationComponent.OpenHubModule.class) +@OpenHubComplicationComponent.OpenHubComplicationScope +public interface OpenHubComplicationComponent { + /** + * Creates a view holder for the open hub complication. + */ + OpenHubComplication.OpenHubChipViewHolder getViewHolder(); + + /** + * Scope of the open hub complication. + */ + @Documented + @Retention(RUNTIME) + @Scope + @interface OpenHubComplicationScope { + } + + /** + * Factory that generates a {@link OpenHubComplicationComponent}. + */ + @Subcomponent.Factory + interface Factory { + /** + * Creates an instance of {@link OpenHubComplicationComponent}. + */ + OpenHubComplicationComponent create(@BindsInstance Resources resources); + } + + /** + * Scoped injected values for the {@link OpenHubComplicationComponent}. + */ + @Module + interface OpenHubModule { + String OPEN_HUB_CHIP_VIEW = "open_hub_chip_view"; + String OPEN_HUB_BACKGROUND_DRAWABLE = "open_hub_background_drawable"; + + /** + * Provides the dream open hub chip view. + */ + @Provides + @OpenHubComplicationScope + @Named(OPEN_HUB_CHIP_VIEW) + static ImageView provideOpenHubChipView( + LayoutInflater layoutInflater, + @Named(OPEN_HUB_BACKGROUND_DRAWABLE) Drawable backgroundDrawable) { + final ImageView chip = + (ImageView) layoutInflater.inflate(R.layout.dream_overlay_open_hub_chip, + null, false); + chip.setBackground(backgroundDrawable); + + return chip; + } + + /** + * Provides the background drawable for the open hub chip. + */ + @Provides + @OpenHubComplicationScope + @Named(OPEN_HUB_BACKGROUND_DRAWABLE) + static Drawable providesOpenHubBackground(Context context, Resources resources) { + return new DoubleShadowIconDrawable(createShadowInfo( + resources, + R.dimen.dream_overlay_bottom_affordance_key_text_shadow_radius, + R.dimen.dream_overlay_bottom_affordance_key_text_shadow_dx, + R.dimen.dream_overlay_bottom_affordance_key_text_shadow_dy, + R.dimen.dream_overlay_bottom_affordance_key_shadow_alpha + ), + createShadowInfo( + resources, + R.dimen.dream_overlay_bottom_affordance_ambient_text_shadow_radius, + R.dimen.dream_overlay_bottom_affordance_ambient_text_shadow_dx, + R.dimen.dream_overlay_bottom_affordance_ambient_text_shadow_dy, + R.dimen.dream_overlay_bottom_affordance_ambient_shadow_alpha + ), + resources.getDrawable(R.drawable.dream_overlay_bottom_affordance_bg), + resources.getDimensionPixelOffset( + R.dimen.dream_overlay_bottom_affordance_width), + resources.getDimensionPixelSize(R.dimen.dream_overlay_bottom_affordance_inset) + ); + } + + private static DoubleShadowTextHelper.ShadowInfo createShadowInfo(Resources resources, + int blurId, int offsetXId, int offsetYId, int alphaId) { + + return new DoubleShadowTextHelper.ShadowInfo( + resources.getDimension(blurId), + resources.getDimension(offsetXId), + resources.getDimension(offsetYId), + resources.getFloat(alphaId) + ); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java index 6f1b09829671..edb5ff7799be 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java @@ -25,6 +25,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.res.R; +import com.android.systemui.util.settings.SystemSettings; import dagger.Module; import dagger.Provides; @@ -39,6 +40,7 @@ import javax.inject.Named; subcomponents = { DreamClockTimeComplicationComponent.class, DreamHomeControlsComplicationComponent.class, + OpenHubComplicationComponent.class, DreamMediaEntryComplicationComponent.class }) public interface RegisteredComplicationsModule { @@ -46,6 +48,8 @@ public interface RegisteredComplicationsModule { String DREAM_SMARTSPACE_LAYOUT_PARAMS = "smartspace_layout_params"; String DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS = "home_controls_chip_layout_params"; String DREAM_MEDIA_ENTRY_LAYOUT_PARAMS = "media_entry_layout_params"; + String OPEN_HUB_CHIP_LAYOUT_PARAMS = "open_hub_chip_layout_params"; + String OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS = "open_hub_chip_replace_home_controls"; int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT = 1; int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT_NO_SMARTSPACE = 2; @@ -109,6 +113,26 @@ public interface RegisteredComplicationsModule { } /** + * Provides layout parameters for the open hub complication. + */ + @Provides + @Named(OPEN_HUB_CHIP_LAYOUT_PARAMS) + static ComplicationLayoutParams provideOpenHubLayoutParams( + @Named(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS) boolean replaceHomeControls) { + int position = ComplicationLayoutParams.POSITION_BOTTOM | (replaceHomeControls + ? ComplicationLayoutParams.POSITION_START + : ComplicationLayoutParams.POSITION_END); + int direction = replaceHomeControls ? ComplicationLayoutParams.DIRECTION_END + : ComplicationLayoutParams.DIRECTION_START; + return new ComplicationLayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, + position, + direction, + DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT); + } + + /** * Provides layout parameters for the smartspace complication. */ @Provides @@ -124,4 +148,14 @@ public interface RegisteredComplicationsModule { res.getDimensionPixelSize(R.dimen.dream_overlay_complication_smartspace_padding), res.getDimensionPixelSize(R.dimen.dream_overlay_complication_smartspace_max_width)); } + + /** + * If true, the home controls chip should not be shown and the open hub chip should be shown in + * its place. + */ + @Provides + @Named(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS) + static boolean providesOpenHubChipReplaceHomeControls(SystemSettings systemSettings) { + return systemSettings.getBool(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS, false); + } } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index 3e98fc1b4a2a..7aab37e12b8c 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -32,6 +32,8 @@ import com.android.systemui.dock.DockManager; import com.android.systemui.dock.DockManagerImpl; import com.android.systemui.doze.DozeHost; import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule; +import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule; +import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule; import com.android.systemui.media.dagger.MediaModule; import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionCli; import com.android.systemui.media.nearby.NearbyMediaDevicesManager; @@ -112,6 +114,8 @@ import javax.inject.Named; GestureModule.class, HeadsUpModule.class, KeyboardShortcutsModule.class, + KeyguardBlueprintModule.class, + KeyguardSectionsModule.class, MediaModule.class, MediaMuteAwaitConnectionCli.StartableModule.class, MultiUserUtilsModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 339e8f06e853..2ebb94f8bcf4 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -70,8 +70,6 @@ import com.android.systemui.inputmethod.InputMethodModule; import com.android.systemui.keyboard.KeyboardModule; import com.android.systemui.keyevent.data.repository.KeyEventRepositoryModule; import com.android.systemui.keyguard.ui.composable.LockscreenContent; -import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule; -import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule; import com.android.systemui.log.dagger.LogModule; import com.android.systemui.log.dagger.MonitorLog; import com.android.systemui.log.table.TableLogBuffer; @@ -222,8 +220,6 @@ import javax.inject.Named; InputMethodModule.class, KeyEventRepositoryModule.class, KeyboardModule.class, - KeyguardBlueprintModule.class, - KeyguardSectionsModule.class, LetterboxModule.class, LogModule.class, MediaProjectionActivitiesModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java index 1c047ddcd3d8..04fda3313df6 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java @@ -98,7 +98,7 @@ public class CommunalTouchHandler implements TouchHandler { // Notification shade window has its own logic to be visible if the hub is open, no need to // do anything here other than send touch events over. session.registerInputListener(ev -> { - surfaces.handleDreamTouch((MotionEvent) ev); + surfaces.handleCommunalHubTouch((MotionEvent) ev); if (ev != null && ((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) { var unused = session.pop(); } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 2e49919d489b..c08434015ab1 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -42,14 +42,6 @@ object Flags { @JvmField val NULL_FLAG = unreleasedFlag("null_flag") // 100 - notification - // TODO(b/297792660): Tracking Bug - @JvmField val UNCLEARED_TRANSIENT_HUN_FIX = - releasedFlag("uncleared_transient_hun_fix") - - // TODO(b/298308067): Tracking Bug - @JvmField val SWIPE_UNCLEARED_TRANSIENT_VIEW_FIX = - releasedFlag("swipe_uncleared_transient_view_fix") - // TODO(b/254512751): Tracking Bug val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING = unreleasedFlag("notification_pipeline_developer_logging") @@ -170,12 +162,6 @@ object Flags { val WALLPAPER_PICKER_GRID_APPLY_BUTTON = unreleasedFlag("wallpaper_picker_grid_apply_button") - /** Keyguard Migration */ - - // TODO(b/297037052): Tracking bug. - @JvmField - val REMOVE_NPVC_BOTTOM_AREA_USAGE = unreleasedFlag("remove_npvc_bottom_area_usage") - /** Flag meant to guard the talkback fix for the KeyguardIndicationTextView */ // TODO(b/286563884): Tracking bug @JvmField val KEYGUARD_TALKBACK_FIX = unreleasedFlag("keyguard_talkback_fix") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index c32c226441fe..a50cc8fbacb3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -53,7 +53,6 @@ import com.android.systemui.keyguard.ui.composable.LockscreenContent import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea import com.android.systemui.keyguard.ui.view.KeyguardRootView -import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel @@ -89,7 +88,6 @@ constructor( private val screenOffAnimationController: ScreenOffAnimationController, private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel, private val chipbarCoordinator: ChipbarCoordinator, - private val keyguardBlueprintCommandListener: KeyguardBlueprintCommandListener, private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel, private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory, private val configuration: ConfigurationState, @@ -160,7 +158,6 @@ constructor( ) } } - keyguardBlueprintCommandListener.start() } fun bindIndicationArea() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt index 80675d373b8e..0863cd737529 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt @@ -28,6 +28,8 @@ object BuiltInKeyguardQuickAffordanceKeys { const val CREATE_NOTE = "create_note" const val DO_NOT_DISTURB = "do_not_disturb" const val FLASHLIGHT = "flashlight" + // TODO(b/339667383): delete or properly implement this once a product decision is made + const val GLANCEABLE_HUB = "glanceable_hub" const val HOME_CONTROLS = "home" const val MUTE = "mute" const val QR_CODE_SCANNER = "qr_code_scanner" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt new file mode 100644 index 000000000000..d09b9f68ea60 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt @@ -0,0 +1,61 @@ +/* + * 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.keyguard.data.quickaffordance + +import com.android.systemui.Flags +import com.android.systemui.animation.Expandable +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.communal.data.repository.CommunalSceneRepository +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +/** Shortcut that opens the glanceable hub. */ +// TODO(b/339667383): delete or properly implement this once a product decision is made +@SysUISingleton +class GlanceableHubQuickAffordanceConfig +@Inject +constructor( + private val communalRepository: CommunalSceneRepository, +) : KeyguardQuickAffordanceConfig { + + override val key: String = BuiltInKeyguardQuickAffordanceKeys.GLANCEABLE_HUB + override fun pickerName(): String = "Glanceable hub" + + override val pickerIconResourceId = R.drawable.ic_widgets + + override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> by lazy { + if (Flags.glanceableHubShortcutButton()) { + val contentDescription = ContentDescription.Loaded(pickerName()) + val icon = Icon.Resource(pickerIconResourceId, contentDescription) + flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon)) + } else { + flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden) + } + } + + override fun onTriggered( + expandable: Expandable? + ): KeyguardQuickAffordanceConfig.OnTriggeredResult { + communalRepository.changeScene(CommunalScenes.Communal, null) + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt index 45561959a7df..93296f0ca24b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt @@ -36,6 +36,7 @@ interface KeyguardDataQuickAffordanceModule { camera: CameraQuickAffordanceConfig, doNotDisturb: DoNotDisturbQuickAffordanceConfig, flashlight: FlashlightQuickAffordanceConfig, + glanceableHub: GlanceableHubQuickAffordanceConfig, home: HomeControlsKeyguardQuickAffordanceConfig, mute: MuteQuickAffordanceConfig, quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig, @@ -46,6 +47,7 @@ interface KeyguardDataQuickAffordanceModule { camera, doNotDisturb, flashlight, + glanceableHub, home, mute, quickAccessWallet, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt index deedbdb9e72b..0748979a2465 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt @@ -20,15 +20,17 @@ package com.android.systemui.keyguard.data.quickaffordance import android.content.Context import android.content.IntentFilter import android.content.SharedPreferences -import com.android.systemui.res.R +import com.android.systemui.Flags import com.android.systemui.backup.BackupHelper import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker +import com.android.systemui.util.settings.SystemSettings import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose @@ -50,6 +52,7 @@ constructor( @Application private val context: Context, private val userFileManager: UserFileManager, private val userTracker: UserTracker, + private val systemSettings: SystemSettings, broadcastDispatcher: BroadcastDispatcher, ) : KeyguardQuickAffordanceSelectionManager { @@ -70,6 +73,22 @@ constructor( } private val defaults: Map<String, List<String>> by lazy { + // Quick hack to allow testing out a lock screen shortcut to open the glanceable hub. This + // flag will not be rolled out and is only used for local testing. + // TODO(b/339667383): delete or properly implement this once a product decision is made + if (Flags.glanceableHubShortcutButton()) { + if (systemSettings.getBool("open_hub_chip_replace_home_controls", false)) { + return@lazy mapOf( + "bottom_start" to listOf("glanceable_hub"), + "bottom_end" to listOf("create_note") + ) + } else { + return@lazy mapOf( + "bottom_start" to listOf("home"), + "bottom_end" to listOf("glanceable_hub") + ) + } + } context.resources .getStringArray(R.array.config_keyguardQuickAffordanceDefaults) .associate { item -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt index c11c49c7a8a0..b826a002b9d9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt @@ -91,9 +91,9 @@ constructor( */ fun refreshBlueprint(config: Config = Config.DEFAULT) { fun scheduleCallback() { - // We use a handler here instead of a CoroutineDipsatcher because the one provided by + // We use a handler here instead of a CoroutineDispatcher because the one provided by // @Main CoroutineDispatcher is currently Dispatchers.Main.immediate, which doesn't - // delay the callback, and instead runs it imemdiately. + // delay the callback, and instead runs it immediately. handler.post { assert.isMainThread() targetTransitionConfig?.let { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt index 53a0c3200f3d..76a822369b0c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt @@ -86,7 +86,7 @@ constructor( return@combine null } - fromBouncerStep.value > 0.5f + fromBouncerStep.value > TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD } .onStart { // Default to null ("don't care, use a reasonable default"). @@ -232,5 +232,6 @@ constructor( val TO_AOD_DURATION = DEFAULT_DURATION val TO_LOCKSCREEN_DURATION = DEFAULT_DURATION val TO_DOZING_DURATION = DEFAULT_DURATION + val TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD = 0.5f } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt index 7cee258dc39f..41c39597dc02 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard.domain.interactor import android.content.Context +import com.android.systemui.CoreStartable import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.dagger.SysUISingleton @@ -31,17 +32,17 @@ import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBl import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Type +import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection +import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.shared.model.ShadeMode import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch @SysUISingleton @@ -53,9 +54,11 @@ constructor( private val context: Context, private val shadeInteractor: ShadeInteractor, private val clockInteractor: KeyguardClockInteractor, - configurationInteractor: ConfigurationInteractor, - fingerprintPropertyInteractor: FingerprintPropertyInteractor, -) { + private val configurationInteractor: ConfigurationInteractor, + private val fingerprintPropertyInteractor: FingerprintPropertyInteractor, + private val smartspaceSection: SmartspaceSection, + private val clockSection: ClockSection, +) : CoreStartable { /** The current blueprint for the lockscreen. */ val blueprint: StateFlow<KeyguardBlueprint> = keyguardBlueprintRepository.blueprint @@ -75,15 +78,23 @@ constructor( } } - private val refreshEvents: Flow<Unit> = - merge( - configurationInteractor.onAnyConfigurationChange, - fingerprintPropertyInteractor.propertiesInitialized.filter { it }.map {}, - ) - - init { + override fun start() { applicationScope.launch { blueprintId.collect { transitionToBlueprint(it) } } - applicationScope.launch { refreshEvents.collect { refreshBlueprint() } } + applicationScope.launch { + fingerprintPropertyInteractor.propertiesInitialized + .filter { it } + .collect { refreshBlueprint() } + } + applicationScope.launch { + val refreshConfig = + Config( + Type.NoTransition, + rebuildSections = listOf(smartspaceSection), + ) + configurationInteractor.onAnyConfigurationChange.collect { + refreshBlueprint(refreshConfig) + } + } } /** @@ -120,4 +131,8 @@ constructor( fun getCurrentBlueprint(): KeyguardBlueprint { return keyguardBlueprintRepository.blueprint.value } + + companion object { + private val TAG = "KeyguardBlueprintInteractor" + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt index fb65a6dacbe3..88e6602e56b7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.keyguard.shared.model.BiometricUnlockMode @@ -29,6 +30,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor import com.android.systemui.util.kotlin.sample +import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -84,25 +86,52 @@ constructor( } .distinctUntilChanged() + private val isDeviceEntered: Flow<Boolean> by lazy { + deviceEntryInteractor.get().isDeviceEntered + } + + private val isDeviceNotEntered: Flow<Boolean> by lazy { isDeviceEntered.map { !it } } + /** - * Surface visibility, which is either determined by the default visibility in the FINISHED - * KeyguardState, or the transition-specific visibility used during certain RUNNING transitions. + * Surface visibility, which is either determined by the default visibility when not + * transitioning between [KeyguardState]s or [Scenes] or the transition-specific visibility used + * during certain ongoing transitions. */ @OptIn(ExperimentalCoroutinesApi::class) val surfaceBehindVisibility: Flow<Boolean> = - transitionInteractor.isInTransitionToAnyState - .flatMapLatest { isInTransition -> - if (!isInTransition) { - defaultSurfaceBehindVisibility - } else { - combine( - transitionSpecificSurfaceBehindVisibility, - defaultSurfaceBehindVisibility, - ) { transitionVisibility, defaultVisibility -> - // Defer to the transition-specific visibility since we're RUNNING a - // transition, but fall back to the default visibility if the current - // transition's interactor did not specify a visibility. - transitionVisibility ?: defaultVisibility + if (SceneContainerFlag.isEnabled) { + sceneInteractor.get().transitionState.flatMapLatestConflated { transitionState -> + when (transitionState) { + is ObservableTransitionState.Transition -> + when { + transitionState.fromScene == Scenes.Lockscreen && + transitionState.toScene == Scenes.Gone -> flowOf(true) + transitionState.fromScene == Scenes.Bouncer && + transitionState.toScene == Scenes.Gone -> + transitionState.progress.map { progress -> + progress > + FromPrimaryBouncerTransitionInteractor + .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD + } + else -> isDeviceEntered + } + is ObservableTransitionState.Idle -> isDeviceEntered + } + } + } else { + transitionInteractor.isInTransitionToAnyState.flatMapLatest { isInTransition -> + if (!isInTransition) { + defaultSurfaceBehindVisibility + } else { + combine( + transitionSpecificSurfaceBehindVisibility, + defaultSurfaceBehindVisibility, + ) { transitionVisibility, defaultVisibility -> + // Defer to the transition-specific visibility since we're RUNNING a + // transition, but fall back to the default visibility if the current + // transition's interactor did not specify a visibility. + transitionVisibility ?: defaultVisibility + } } } } @@ -162,7 +191,7 @@ constructor( */ val lockscreenVisibility: Flow<Boolean> = if (SceneContainerFlag.isEnabled) { - deviceEntryInteractor.get().isDeviceEntered.map { !it } + isDeviceNotEntered } else { transitionInteractor.currentKeyguardState .sample(transitionInteractor.startedStepWithPrecedingStep, ::Pair) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt index 7ca2ebaeab20..6d579f3b2513 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt @@ -37,16 +37,32 @@ interface KeyguardBlueprint { fun replaceViews( constraintLayout: ConstraintLayout, previousBlueprint: KeyguardBlueprint? = null, + rebuildSections: List<KeyguardSection> = listOf(), bindData: Boolean = true ) { - val prevSections = - previousBlueprint?.let { prev -> - prev.sections.subtract(sections).forEach { it.removeViews(constraintLayout) } - prev.sections + val prevSections = previousBlueprint?.sections ?: listOf() + val skipSections = sections.intersect(prevSections).subtract(rebuildSections) + prevSections.subtract(skipSections).forEach { it.removeViews(constraintLayout) } + sections.subtract(skipSections).forEach { + it.addViews(constraintLayout) + if (bindData) { + it.bindData(constraintLayout) } - ?: listOf() + } + } + + /** Rebuilds views for the target sections, or all of them if unspecified. */ + fun rebuildViews( + constraintLayout: ConstraintLayout, + rebuildSections: List<KeyguardSection> = sections, + bindData: Boolean = true + ) { + if (rebuildSections.isEmpty()) { + return + } - sections.subtract(prevSections).forEach { + rebuildSections.forEach { it.removeViews(constraintLayout) } + rebuildSections.forEach { it.addViews(constraintLayout) if (bindData) { it.bindData(constraintLayout) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt index 52d7519c2e3c..816033561e94 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt @@ -95,17 +95,8 @@ constructor( null as KeyguardBlueprint?, ) .collect { (prevBlueprint, blueprint) -> - val cs = - ConstraintSet().apply { - clone(constraintLayout) - val emptyLayout = ConstraintSet.Layout() - knownIds.forEach { - getConstraint(it).layout.copyFrom(emptyLayout) - } - blueprint.applyConstraints(this) - } - - var transition = + val config = Config.DEFAULT + val transition = if ( !KeyguardBottomAreaRefactor.isEnabled && prevBlueprint != null && @@ -114,23 +105,37 @@ constructor( BaseBlueprintTransition(clockViewModel) .addTransition( IntraBlueprintTransition( - Config.DEFAULT, + config, clockViewModel, smartspaceViewModel ) ) } else { IntraBlueprintTransition( - Config.DEFAULT, + config, clockViewModel, smartspaceViewModel ) } - runTransition(constraintLayout, transition, Config.DEFAULT) { - // Add and remove views of sections that are not contained by the - // other. - blueprint.replaceViews(constraintLayout, prevBlueprint) + runTransition(constraintLayout, transition, config) { + // Replace sections from the previous blueprint with the new ones + blueprint.replaceViews( + constraintLayout, + prevBlueprint, + config.rebuildSections + ) + + val cs = + ConstraintSet().apply { + clone(constraintLayout) + val emptyLayout = ConstraintSet.Layout() + knownIds.forEach { + getConstraint(it).layout.copyFrom(emptyLayout) + } + blueprint.applyConstraints(this) + } + logAlphaVisibilityOfAppliedConstraintSet(cs, clockViewModel) cs.applyTo(constraintLayout) } @@ -138,22 +143,21 @@ constructor( } launch("$TAG#viewModel.refreshTransition") { - viewModel.refreshTransition.collect { transition -> - val cs = - ConstraintSet().apply { - clone(constraintLayout) - viewModel.blueprint.value.applyConstraints(this) - } + viewModel.refreshTransition.collect { config -> + val blueprint = viewModel.blueprint.value runTransition( constraintLayout, - IntraBlueprintTransition( - transition, - clockViewModel, - smartspaceViewModel - ), - transition, + IntraBlueprintTransition(config, clockViewModel, smartspaceViewModel), + config, ) { + blueprint.rebuildViews(constraintLayout, config.rebuildSections) + + val cs = + ConstraintSet().apply { + clone(constraintLayout) + blueprint.applyConstraints(this) + } logAlphaVisibilityOfAppliedConstraintSet(cs, clockViewModel) cs.applyTo(constraintLayout) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt index 962cdf10cf86..c0266567a63f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.view.layout import androidx.core.text.isDigitsOnly +import com.android.systemui.CoreStartable import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.statusbar.commandline.Command @@ -31,10 +32,10 @@ constructor( private val commandRegistry: CommandRegistry, private val keyguardBlueprintRepository: KeyguardBlueprintRepository, private val keyguardBlueprintInteractor: KeyguardBlueprintInteractor, -) { +) : CoreStartable { private val layoutCommand = KeyguardLayoutManagerCommand() - fun start() { + override fun start() { commandRegistry.registerCommand(COMMAND) { layoutCommand } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt index 04ac7bf1178e..2dc930121a71 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/KeyguardBlueprintModule.kt @@ -17,9 +17,14 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints +import com.android.systemui.CoreStartable +import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.shared.model.KeyguardBlueprint +import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener import dagger.Binds import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap import dagger.multibindings.IntoSet @Module @@ -41,4 +46,18 @@ abstract class KeyguardBlueprintModule { abstract fun bindShortcutsBesideUdfpsLockscreenBlueprint( shortcutsBesideUdfpsLockscreenBlueprint: ShortcutsBesideUdfpsKeyguardBlueprint ): KeyguardBlueprint + + @Binds + @IntoMap + @ClassKey(KeyguardBlueprintInteractor::class) + abstract fun bindsKeyguardBlueprintInteractor( + keyguardBlueprintInteractor: KeyguardBlueprintInteractor + ): CoreStartable + + @Binds + @IntoMap + @ClassKey(KeyguardBlueprintCommandListener::class) + abstract fun bindsKeyguardBlueprintCommandListener( + keyguardBlueprintCommandListener: KeyguardBlueprintCommandListener + ): CoreStartable } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt index c69d868866d0..02e9ca5b6821 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.ui.view.layout.blueprints.transitions import android.transition.TransitionSet +import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.keyguard.ui.view.layout.sections.transitions.ClockSizeTransition import com.android.systemui.keyguard.ui.view.layout.sections.transitions.DefaultClockSteppingTransition import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel @@ -46,6 +47,7 @@ class IntraBlueprintTransition( val type: Type, val checkPriority: Boolean = true, val terminatePrevious: Boolean = true, + val rebuildSections: List<KeyguardSection> = listOf(), ) { companion object { val DEFAULT = Config(Type.NoTransition) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt index b367715f529e..34a1da54c123 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt @@ -32,6 +32,7 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP import androidx.constraintlayout.widget.ConstraintSet.VISIBLE import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT import com.android.systemui.customization.R as custR +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor @@ -57,6 +58,7 @@ internal fun ConstraintSet.setAlpha( alpha: Float, ) = views.forEach { view -> this.setAlpha(view.id, alpha) } +@SysUISingleton class ClockSection @Inject constructor( @@ -72,6 +74,7 @@ constructor( if (!MigrateClocksToBlueprint.isEnabled) { return } + KeyguardClockViewBinder.bind( this, constraintLayout, @@ -86,6 +89,7 @@ constructor( if (!MigrateClocksToBlueprint.isEnabled) { return } + keyguardClockViewModel.currentClock.value?.let { clock -> constraintSet.applyDeltaFrom(buildConstraints(clock, constraintSet)) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt index 0b8376af811c..c5fab8f57822 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt @@ -219,5 +219,37 @@ constructor( sensorRect.left ) } + + // This is only intended to be here until the KeyguardBottomAreaRefactor flag is enabled + // Without this logic, the lock icon location changes but the KeyguardBottomAreaView is not + // updated and visible ui layout jank occurs. This is due to AmbientIndicationContainer + // being in NPVC and laying out prior to the KeyguardRootView. + // Remove when both DeviceEntryUdfpsRefactor and KeyguardBottomAreaRefactor are enabled. + if (DeviceEntryUdfpsRefactor.isEnabled && !KeyguardBottomAreaRefactor.isEnabled) { + with(notificationPanelView) { + val isUdfpsSupported = deviceEntryIconViewModel.get().isUdfpsSupported.value + val bottomAreaViewRight = findViewById<View>(R.id.keyguard_bottom_area)?.right ?: 0 + findViewById<View>(R.id.ambient_indication_container)?.let { + val (ambientLeft, ambientTop) = it.locationOnScreen + if (isUdfpsSupported) { + // make top of ambient indication view the bottom of the lock icon + it.layout( + ambientLeft, + sensorRect.bottom, + bottomAreaViewRight - ambientLeft, + ambientTop + it.measuredHeight + ) + } else { + // make bottom of ambient indication view the top of the lock icon + it.layout( + ambientLeft, + sensorRect.top - it.measuredHeight, + bottomAreaViewRight - ambientLeft, + sensorRect.top + ) + } + } + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt index 487c2e918e5f..2d6690fbbc99 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt @@ -22,6 +22,7 @@ import android.view.ViewTreeObserver.OnGlobalLayoutListener import androidx.constraintlayout.widget.Barrier import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.KeyguardUnlockAnimationController import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor @@ -36,6 +37,7 @@ import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController import dagger.Lazy import javax.inject.Inject +@SysUISingleton open class SmartspaceSection @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt index 486d4d46c767..aa93df7f1474 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManager.kt @@ -23,6 +23,7 @@ import android.graphics.drawable.Drawable import android.media.MediaRouter2Manager import android.media.RoutingSessionInfo import android.media.session.MediaController +import android.media.session.MediaController.PlaybackInfo import android.text.TextUtils import android.util.Log import androidx.annotation.AnyThread @@ -74,6 +75,11 @@ constructor( private val listeners: MutableSet<Listener> = mutableSetOf() private val entries: MutableMap<String, Entry> = mutableMapOf() + companion object { + private val EMPTY_AND_DISABLED_MEDIA_DEVICE_DATA = + MediaDeviceData(enabled = false, icon = null, name = null, showBroadcastButton = false) + } + /** Add a listener for changes to the media route (ie. device). */ fun addListener(listener: Listener) = listeners.add(listener) @@ -333,28 +339,32 @@ constructor( @WorkerThread private fun updateCurrent() { if (isLeAudioBroadcastEnabled()) { - if (enableLeAudioSharing()) { - current = - MediaDeviceData( - enabled = false, - icon = - context.getDrawable( - com.android.settingslib.R.drawable.ic_bt_le_audio_sharing - ), - name = context.getString(R.string.audio_sharing_description), - intent = null, - showBroadcastButton = false - ) + current = getLeAudioBroadcastDeviceData() + } else if (Flags.usePlaybackInfoForRoutingControls()) { + val activeDevice: MediaDeviceData? + + // LocalMediaManager provides the connected device based on PlaybackInfo. + // TODO (b/342197065): Simplify nullability once we make currentConnectedDevice + // non-null. + val connectedDevice = localMediaManager.currentConnectedDevice?.toMediaDeviceData() + + if (controller?.playbackInfo?.playbackType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) { + val routingSession = + mr2manager.get().getRoutingSessionForMediaController(controller) + + activeDevice = + routingSession?.let { + // For a remote session, always use the current device from + // LocalMediaManager. Override with routing session name if available to + // show dynamic group name. + connectedDevice?.copy(name = it.name ?: connectedDevice.name) + } } else { - current = - MediaDeviceData( - /* enabled */ true, - /* icon */ context.getDrawable(R.drawable.settings_input_antenna), - /* name */ broadcastDescription, - /* intent */ null, - /* showBroadcastButton */ showBroadcastButton = true - ) + // Prefer SASS if available when playback is local. + activeDevice = getSassDevice() ?: connectedDevice } + + current = activeDevice ?: EMPTY_AND_DISABLED_MEDIA_DEVICE_DATA } else { val aboutToConnect = aboutToConnectDeviceOverride if ( @@ -389,6 +399,43 @@ constructor( } } + private fun getSassDevice(): MediaDeviceData? { + val sassDevice = aboutToConnectDeviceOverride ?: return null + return sassDevice.fullMediaDevice?.toMediaDeviceData() + ?: sassDevice.backupMediaDeviceData + } + + private fun MediaDevice.toMediaDeviceData() = + MediaDeviceData( + enabled = true, + icon = iconWithoutBackground, + name = name, + id = id, + showBroadcastButton = false + ) + + private fun getLeAudioBroadcastDeviceData(): MediaDeviceData { + return if (enableLeAudioSharing()) { + MediaDeviceData( + enabled = false, + icon = + context.getDrawable( + com.android.settingslib.R.drawable.ic_bt_le_audio_sharing + ), + name = context.getString(R.string.audio_sharing_description), + intent = null, + showBroadcastButton = false + ) + } else { + MediaDeviceData( + enabled = true, + icon = context.getDrawable(R.drawable.settings_input_antenna), + name = broadcastDescription, + intent = null, + showBroadcastButton = true + ) + } + } /** Return a display name for the current device / route, or null if not possible */ private fun getDeviceName( device: MediaDevice?, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt new file mode 100644 index 000000000000..f3e5b8f0c214 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt @@ -0,0 +1,73 @@ +/* + * 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.data.repository + +import android.content.Context +import android.content.SharedPreferences +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.settings.UserFileManager +import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.util.kotlin.SharedPreferencesExt.observe +import com.android.systemui.util.kotlin.emitOnStart +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map + +/** Repository for QS user preferences. */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton +class QSPreferencesRepository +@Inject +constructor( + private val userFileManager: UserFileManager, + private val userRepository: UserRepository, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) { + /** Whether to show the labels on icon tiles for the current user. */ + val showLabels: Flow<Boolean> = + userRepository.selectedUserInfo + .flatMapLatest { userInfo -> + val prefs = getSharedPrefs(userInfo.id) + prefs.observe().emitOnStart().map { prefs.getBoolean(ICON_LABELS_KEY, false) } + } + .flowOn(backgroundDispatcher) + + /** Sets for the current user whether to show the labels on icon tiles. */ + fun setShowLabels(showLabels: Boolean) { + with(getSharedPrefs(userRepository.getSelectedUserInfo().id)) { + edit().putBoolean(ICON_LABELS_KEY, showLabels).apply() + } + } + + private fun getSharedPrefs(userId: Int): SharedPreferences { + return userFileManager.getSharedPreferences( + FILE_NAME, + Context.MODE_PRIVATE, + userId, + ) + } + + companion object { + private const val ICON_LABELS_KEY = "show_icon_labels" + const val FILE_NAME = "quick_settings_prefs" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt index a871531f283a..6a899b07b2a0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractor.kt @@ -20,7 +20,6 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel -import com.android.systemui.qs.panels.data.repository.IconLabelVisibilityRepository import com.android.systemui.qs.panels.shared.model.IconLabelVisibilityLog import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -33,17 +32,17 @@ import kotlinx.coroutines.flow.stateIn class IconLabelVisibilityInteractor @Inject constructor( - private val repo: IconLabelVisibilityRepository, + private val preferencesInteractor: QSPreferencesInteractor, @IconLabelVisibilityLog private val logBuffer: LogBuffer, @Application scope: CoroutineScope, ) { val showLabels: StateFlow<Boolean> = - repo.showLabels + preferencesInteractor.showLabels .onEach { logChange(it) } - .stateIn(scope, SharingStarted.WhileSubscribed(), repo.showLabels.value) + .stateIn(scope, SharingStarted.WhileSubscribed(), false) fun setShowLabels(showLabels: Boolean) { - repo.setShowLabels(showLabels) + preferencesInteractor.setShowLabels(showLabels) } private fun logChange(showLabels: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt index 686e5f49442b..811be80d23fa 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractor.kt @@ -14,22 +14,18 @@ * limitations under the License. */ -package com.android.systemui.qs.panels.data.repository +package com.android.systemui.qs.panels.domain.interactor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.qs.panels.data.repository.QSPreferencesRepository import javax.inject.Inject -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.Flow -/** Repository for whether to show the labels of icon tiles. */ @SysUISingleton -class IconLabelVisibilityRepository @Inject constructor() { - // TODO(b/341735914): Persist and back up showLabels - private val _showLabels = MutableStateFlow(false) - val showLabels: StateFlow<Boolean> = _showLabels.asStateFlow() +class QSPreferencesInteractor @Inject constructor(private val repo: QSPreferencesRepository) { + val showLabels: Flow<Boolean> = repo.showLabels fun setShowLabels(showLabels: Boolean) { - _showLabels.value = showLabels + repo.setShowLabels(showLabels) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModel.kt index 5d4b8f1773f2..12cbde2cbc91 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconLabelVisibilityViewModel.kt @@ -30,7 +30,9 @@ interface IconLabelVisibilityViewModel { @SysUISingleton class IconLabelVisibilityViewModelImpl @Inject -constructor(private val interactor: IconLabelVisibilityInteractor) : IconLabelVisibilityViewModel { +constructor( + private val interactor: IconLabelVisibilityInteractor, +) : IconLabelVisibilityViewModel { override val showLabels: StateFlow<Boolean> = interactor.showLabels override fun setShowLabels(showLabels: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt index 08e39204386e..a0c9737de0ee 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt @@ -22,6 +22,7 @@ import com.android.systemui.qs.pipeline.domain.model.AutoAddable import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.ColorCorrectionTile import com.android.systemui.qs.tiles.ColorInversionTile +import com.android.systemui.qs.tiles.HearingDevicesTile import com.android.systemui.qs.tiles.OneHandedModeTile import com.android.systemui.qs.tiles.ReduceBrightColorsTile @@ -50,6 +51,10 @@ object A11yShortcutAutoAddableList { TileSpec.create(ReduceBrightColorsTile.TILE_SPEC), AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME ), + factory.create( + TileSpec.create(HearingDevicesTile.TILE_SPEC), + AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME + ) ) } else { emptySet() diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index 7bc76af68c6b..c091ac3dea50 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -936,6 +936,10 @@ public class InternetDialogController implements AccessPointController.AccessPoi mHasActiveSubIdOnDds = false; Log.e(TAG, "Can't get DDS subscriptionInfo"); return; + } else if (ddsSubInfo.isOnlyNonTerrestrialNetwork()) { + mHasActiveSubIdOnDds = false; + Log.d(TAG, "This is NTN, so do not show mobile data"); + return; } mHasActiveSubIdOnDds = isEmbeddedSubscriptionVisible(ddsSubInfo); diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index 22aa492dbfe8..1d8b7e5b6155 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -43,6 +43,7 @@ import com.android.systemui.communal.ui.compose.CommunalContainer import com.android.systemui.communal.ui.compose.CommunalContent import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.communal.util.CommunalColors +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -64,6 +65,7 @@ import kotlinx.coroutines.launch * * This will be used until the glanceable hub is integrated into Flexiglass. */ +@SysUISingleton class GlanceableHubContainerController @Inject constructor( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java index 0d8030f02948..526800954427 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java @@ -19,9 +19,14 @@ package com.android.systemui.statusbar; import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; +import static com.android.systemui.Flags.validateKeyboardShortcutHelperIconUri; + import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.app.AppGlobals; +import android.app.SynchronousUserSwitchObserver; +import android.app.UserSwitchObserver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -37,6 +42,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.hardware.input.InputManagerGlobal; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.os.RemoteException; import android.text.Editable; @@ -136,6 +142,8 @@ public final class KeyboardShortcutListSearch { }; private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final HandlerThread mHandlerThread = new HandlerThread("KeyboardShortcutHelper"); + @VisibleForTesting Handler mBackgroundHandler; @VisibleForTesting public Context mContext; private final IPackageManager mPackageManager; @@ -143,6 +151,13 @@ public final class KeyboardShortcutListSearch { private KeyCharacterMap mKeyCharacterMap; private KeyCharacterMap mBackupKeyCharacterMap; + private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() { + @Override + public void onUserSwitching(int newUserId) throws RemoteException { + dismiss(); + } + }; + @VisibleForTesting KeyboardShortcutListSearch(Context context, WindowManager windowManager) { this.mContext = new ContextThemeWrapper( @@ -413,36 +428,75 @@ public final class KeyboardShortcutListSearch { private boolean mAppShortcutsReceived; private boolean mImeShortcutsReceived; - @VisibleForTesting - public void showKeyboardShortcuts(int deviceId) { - retrieveKeyCharacterMap(deviceId); - mAppShortcutsReceived = false; - mImeShortcutsReceived = false; - mWindowManager.requestAppKeyboardShortcuts(result -> { - // Add specific app shortcuts + private void onAppSpecificShortcutsReceived(List<KeyboardShortcutGroup> result) { + // Add specific app shortcuts + if (result != null) { if (result.isEmpty()) { mCurrentAppPackageName = null; mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, false); } else { mCurrentAppPackageName = result.get(0).getPackageName(); - mSpecificAppGroup.addAll(reMapToKeyboardShortcutMultiMappingGroup(result)); + if (validateKeyboardShortcutHelperIconUri()) { + KeyboardShortcuts.sanitiseShortcuts(result); + } + mSpecificAppGroup.addAll( + reMapToKeyboardShortcutMultiMappingGroup(result)); mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, true); } - mAppShortcutsReceived = true; - if (mImeShortcutsReceived) { - mergeAndShowKeyboardShortcutsGroups(); - } - }, deviceId); - mWindowManager.requestImeKeyboardShortcuts(result -> { - // Add specific Ime shortcuts + } + mAppShortcutsReceived = true; + if (mImeShortcutsReceived) { + mergeAndShowKeyboardShortcutsGroups(); + } + } + + private void onImeSpecificShortcutsReceived(List<KeyboardShortcutGroup> result) { + // Add specific Ime shortcuts + if (result != null) { if (!result.isEmpty()) { - mInputGroup.addAll(reMapToKeyboardShortcutMultiMappingGroup(result)); + if (validateKeyboardShortcutHelperIconUri()) { + KeyboardShortcuts.sanitiseShortcuts(result); + } + mInputGroup.addAll( + reMapToKeyboardShortcutMultiMappingGroup(result)); } - mImeShortcutsReceived = true; - if (mAppShortcutsReceived) { - mergeAndShowKeyboardShortcutsGroups(); + } + mImeShortcutsReceived = true; + if (mAppShortcutsReceived) { + mergeAndShowKeyboardShortcutsGroups(); + } + } + + @VisibleForTesting + public void showKeyboardShortcuts(int deviceId) { + if (mBackgroundHandler == null) { + mHandlerThread.start(); + mBackgroundHandler = new Handler(mHandlerThread.getLooper()); + } + + if (validateKeyboardShortcutHelperIconUri()) { + try { + ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG); + } catch (RemoteException e) { + Log.e(TAG, "could not register user switch observer", e); } - }, deviceId); + } + + retrieveKeyCharacterMap(deviceId); + mAppShortcutsReceived = false; + mImeShortcutsReceived = false; + mWindowManager.requestAppKeyboardShortcuts( + result -> { + mBackgroundHandler.post(() -> { + onAppSpecificShortcutsReceived(result); + }); + }, deviceId); + mWindowManager.requestImeKeyboardShortcuts( + result -> { + mBackgroundHandler.post(() -> { + onImeSpecificShortcutsReceived(result); + }); + }, deviceId); } private void mergeAndShowKeyboardShortcutsGroups() { @@ -508,6 +562,14 @@ public final class KeyboardShortcutListSearch { mKeyboardShortcutsBottomSheetDialog.dismiss(); mKeyboardShortcutsBottomSheetDialog = null; } + mHandlerThread.quit(); + if (validateKeyboardShortcutHelperIconUri()) { + try { + ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver); + } catch (RemoteException e) { + Log.e(TAG, "Could not unregister user switch observer", e); + } + } } private KeyboardShortcutMultiMappingGroup getMultiMappingSystemShortcuts(Context context) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java index 21f608e13f5c..d00916a1c1a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java @@ -20,11 +20,16 @@ import static android.content.Context.LAYOUT_INFLATER_SERVICE; import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; +import static com.android.systemui.Flags.validateKeyboardShortcutHelperIconUri; + import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityManager; import android.app.AlertDialog; import android.app.AppGlobals; import android.app.Dialog; +import android.app.SynchronousUserSwitchObserver; +import android.app.UserSwitchObserver; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; @@ -39,6 +44,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.hardware.input.InputManager; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; import android.os.RemoteException; import android.util.Log; @@ -93,6 +99,8 @@ public final class KeyboardShortcuts { }; private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final HandlerThread mHandlerThread = new HandlerThread("KeyboardShortcutHelper"); + @VisibleForTesting Handler mBackgroundHandler; @VisibleForTesting public Context mContext; private final IPackageManager mPackageManager; private final OnClickListener mDialogCloseListener = new DialogInterface.OnClickListener() { @@ -129,6 +137,13 @@ public final class KeyboardShortcuts { @Nullable private List<KeyboardShortcutGroup> mReceivedAppShortcutGroups = null; @Nullable private List<KeyboardShortcutGroup> mReceivedImeShortcutGroups = null; + private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() { + @Override + public void onUserSwitching(int newUserId) throws RemoteException { + dismiss(); + } + }; + @VisibleForTesting KeyboardShortcuts(Context context, WindowManager windowManager) { this.mContext = new ContextThemeWrapper( @@ -374,21 +389,68 @@ public final class KeyboardShortcuts { @VisibleForTesting public void showKeyboardShortcuts(int deviceId) { + if (mBackgroundHandler == null) { + mHandlerThread.start(); + mBackgroundHandler = new Handler(mHandlerThread.getLooper()); + } + + if (validateKeyboardShortcutHelperIconUri()) { + try { + ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG); + } catch (RemoteException e) { + Log.e(TAG, "could not register user switch observer", e); + } + } + retrieveKeyCharacterMap(deviceId); + mReceivedAppShortcutGroups = null; mReceivedImeShortcutGroups = null; + mWindowManager.requestAppKeyboardShortcuts( result -> { - mReceivedAppShortcutGroups = result; - maybeMergeAndShowKeyboardShortcuts(); + mBackgroundHandler.post(() -> { + onAppSpecificShortcutsReceived(result); + }); }, deviceId); mWindowManager.requestImeKeyboardShortcuts( result -> { - mReceivedImeShortcutGroups = result; - maybeMergeAndShowKeyboardShortcuts(); + mBackgroundHandler.post(() -> { + onImeSpecificShortcutsReceived(result); + }); }, deviceId); } + private void onAppSpecificShortcutsReceived(List<KeyboardShortcutGroup> result) { + mReceivedAppShortcutGroups = + result == null ? Collections.emptyList() : result; + + if (validateKeyboardShortcutHelperIconUri()) { + sanitiseShortcuts(mReceivedAppShortcutGroups); + } + + maybeMergeAndShowKeyboardShortcuts(); + } + + private void onImeSpecificShortcutsReceived(List<KeyboardShortcutGroup> result) { + mReceivedImeShortcutGroups = + result == null ? Collections.emptyList() : result; + + if (validateKeyboardShortcutHelperIconUri()) { + sanitiseShortcuts(mReceivedImeShortcutGroups); + } + + maybeMergeAndShowKeyboardShortcuts(); + } + + static void sanitiseShortcuts(List<KeyboardShortcutGroup> shortcutGroups) { + for (KeyboardShortcutGroup group : shortcutGroups) { + for (KeyboardShortcutInfo info : group.getItems()) { + info.clearIcon(); + } + } + } + private void maybeMergeAndShowKeyboardShortcuts() { if (mReceivedAppShortcutGroups == null || mReceivedImeShortcutGroups == null) { return; @@ -413,6 +475,14 @@ public final class KeyboardShortcuts { mKeyboardShortcutsDialog.dismiss(); mKeyboardShortcutsDialog = null; } + mHandlerThread.quit(); + if (validateKeyboardShortcutHelperIconUri()) { + try { + ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver); + } catch (RemoteException e) { + Log.e(TAG, "Could not unregister user switch observer", e); + } + } } private KeyboardShortcutGroup getSystemShortcuts() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index edd2961fe119..99ce454126cf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -2843,14 +2843,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mIsSystemChildExpanded = expanded; } - public void setLayoutListener(LayoutListener listener) { + public void setLayoutListener(@Nullable LayoutListener listener) { mLayoutListener = listener; } - public void removeListener() { - mLayoutListener = null; - } - @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { Trace.beginSection(appendTraceStyleTag("ExpNotRow#onLayout")); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java index c10c09c49c6b..bdfbc4b53943 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java @@ -242,7 +242,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl public void onLayout() { mIconsPlaced = false; // Force icons to be re-placed setMenuLocation(); - mParent.removeListener(); + mParent.setLayoutListener(null); } private void createMenuViews(boolean resetState) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index a9d7cc003b79..fe22cc628b5f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -24,7 +24,6 @@ import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_ import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL; import static com.android.systemui.Flags.newAodTransition; import static com.android.systemui.Flags.notificationOverExpansionClippingFix; -import static com.android.systemui.flags.Flags.UNCLEARED_TRANSIENT_HUN_FIX; import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE; import static com.android.systemui.util.DumpUtilsKt.println; @@ -255,7 +254,8 @@ public class NotificationStackScrollLayout * The raw amount of the overScroll on the bottom, which is not rubber-banded. */ private float mOverScrolledBottomPixels; - private ListenerSet<Runnable> mStackHeightChangedListeners = new ListenerSet<>(); + private final ListenerSet<Runnable> mStackHeightChangedListeners = new ListenerSet<>(); + private final ListenerSet<Runnable> mHeadsUpHeightChangedListeners = new ListenerSet<>(); private NotificationLogger.OnChildLocationsChangedListener mListener; private OnNotificationLocationsChangedListener mLocationsChangedListener; private OnOverscrollTopChangedListener mOverscrollTopChangedListener; @@ -1114,6 +1114,28 @@ public class NotificationStackScrollLayout mStackHeightChangedListeners.remove(runnable); } + private void notifyHeadsUpHeightChangedForView(View view) { + if (mTopHeadsUpRow == view) { + notifyHeadsUpHeightChangedListeners(); + } + } + + private void notifyHeadsUpHeightChangedListeners() { + for (Runnable listener : mHeadsUpHeightChangedListeners) { + listener.run(); + } + } + + @Override + public void addHeadsUpHeightChangedListener(@NonNull Runnable runnable) { + mHeadsUpHeightChangedListeners.addIfAbsent(runnable); + } + + @Override + public void removeHeadsUpHeightChangedListener(@NonNull Runnable runnable) { + mHeadsUpHeightChangedListeners.remove(runnable); + } + @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { if (!mSuppressChildrenMeasureAndLayout) { @@ -2444,6 +2466,11 @@ public class NotificationStackScrollLayout return mScrollViewFields.getIntrinsicStackHeight(); } + @Override + public int getTopHeadsUpHeight() { + return getTopHeadsUpPinnedHeight(); + } + /** * Calculate the gap height between two different views * @@ -2816,23 +2843,15 @@ public class NotificationStackScrollLayout mAddedHeadsUpChildren.remove(child); return false; } - if (mFeatureFlags.isEnabled(UNCLEARED_TRANSIENT_HUN_FIX)) { - // Skip adding animation for clicked heads up notifications when the - // Shade is closed, because the animation event is generated in - // generateHeadsUpAnimationEvents. Only report that an animation was - // actually generated (thus requesting the transient view be added) - // if a removal animation is in progress. - if (!isExpanded() && isClickedHeadsUp(child)) { - // An animation is already running, add it transiently - mClearTransientViewsWhenFinished.add(child); - return child.inRemovalAnimation(); - } - } else { - if (isClickedHeadsUp(child)) { - // An animation is already running, add it transiently - mClearTransientViewsWhenFinished.add(child); - return true; - } + // Skip adding animation for clicked heads up notifications when the + // Shade is closed, because the animation event is generated in + // generateHeadsUpAnimationEvents. Only report that an animation was + // actually generated (thus requesting the transient view be added) + // if a removal animation is in progress. + if (!isExpanded() && isClickedHeadsUp(child)) { + // An animation is already running, add it transiently + mClearTransientViewsWhenFinished.add(child); + return child.inRemovalAnimation(); } if (mDebugRemoveAnimation) { Log.d(TAG, "generateRemove " + key @@ -4193,12 +4212,14 @@ public class NotificationStackScrollLayout requestAnimationOnViewResize(row); } requestChildrenUpdate(); + notifyHeadsUpHeightChangedForView(view); mAnimateStackYForContentHeightChange = previouslyNeededAnimation; } void onChildHeightReset(ExpandableView view) { updateAnimationState(view); updateChronometerForChild(view); + notifyHeadsUpHeightChangedForView(view); } private void updateScrollPositionOnExpandInBottom(ExpandableView view) { @@ -5573,6 +5594,7 @@ public class NotificationStackScrollLayout */ public void setTopHeadsUpRow(@Nullable ExpandableNotificationRow topHeadsUpRow) { mTopHeadsUpRow = topHeadsUpRow; + notifyHeadsUpHeightChangedListeners(); } public boolean getIsExpanded() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt index 463c631db32f..f6d9351952f1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationViewHeightRepository.kt @@ -27,9 +27,6 @@ import kotlinx.coroutines.flow.MutableStateFlow @SysUISingleton class NotificationViewHeightRepository @Inject constructor() { - /** The height in px of the current heads up notification. */ - val headsUpHeight = MutableStateFlow(0f) - /** * The amount in px that the notification stack should scroll due to internal expansion. This * should only happen when a notification expansion hits the bottom of the screen, so it is diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt index e7acbe3ab0b0..afcf3ae7d5b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt @@ -65,9 +65,6 @@ constructor( } .distinctUntilChanged() - /** The height in px of the contents of the HUN. */ - val headsUpHeight: StateFlow<Float> = viewHeightRepository.headsUpHeight.asStateFlow() - /** The alpha of the Notification Stack for the brightness mirror */ val alphaForBrightnessMirror: StateFlow<Float> = placeholderRepository.alphaForBrightnessMirror.asStateFlow() @@ -110,11 +107,6 @@ constructor( placeholderRepository.shadeScrimBounds.value = bounds } - /** Sets the height of heads up notification. */ - fun setHeadsUpHeight(height: Float) { - viewHeightRepository.headsUpHeight.value = height - } - /** Sets whether the notification stack is scrolled to the top. */ fun setScrolledToTop(scrolledToTop: Boolean) { placeholderRepository.scrolledToTop.value = scrolledToTop diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt index 14b882f974d2..eaaa9a1523c2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt @@ -32,6 +32,9 @@ interface NotificationScrollView { */ val intrinsicStackHeight: Int + /** Height in pixels required to display the top HeadsUp Notification. */ + val topHeadsUpHeight: Int + /** * Since this is an interface rather than a literal View, this provides cast-like access to the * underlying view. @@ -72,9 +75,18 @@ interface NotificationScrollView { /** Sets whether the view is displayed in doze mode. */ fun setDozing(dozing: Boolean) - /** Sets a listener to be notified, when the stack height might have changed. */ + /** Adds a listener to be notified, when the stack height might have changed. */ fun addStackHeightChangedListener(runnable: Runnable) /** @see addStackHeightChangedListener */ fun removeStackHeightChangedListener(runnable: Runnable) + + /** + * Adds a listener to be notified, when the height of the top heads up notification might have + * changed. + */ + fun addHeadsUpHeightChangedListener(runnable: Runnable) + + /** @see addHeadsUpHeightChangedListener */ + fun removeHeadsUpHeightChangedListener(runnable: Runnable) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt index 622d8e7b2307..fd08e898fce3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt @@ -80,7 +80,6 @@ constructor( launch { viewModel.maxAlpha.collect { view.setMaxAlpha(it) } } launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } } - launch { viewModel.headsUpTop.collect { view.setHeadsUpTop(it) } } launch { viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) } } launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } } launch { viewModel.isDozing.collect { isDozing -> view.setDozing(isDozing) } } @@ -88,11 +87,9 @@ constructor( launchAndDispose { view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer) view.setCurrentGestureOverscrollConsumer(viewModel.currentGestureOverscrollConsumer) - view.setHeadsUpHeightConsumer(viewModel.headsUpHeightConsumer) DisposableHandle { view.setSyntheticScrollConsumer(null) view.setCurrentGestureOverscrollConsumer(null) - view.setHeadsUpHeightConsumer(null) } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt index c2ce1144fe1b..a99fbfcc7907 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt @@ -150,8 +150,6 @@ constructor( */ val currentGestureOverscrollConsumer: (Boolean) -> Unit = stackAppearanceInteractor::setCurrentGestureOverscroll - /** Receives the height of the heads up notification. */ - val headsUpHeightConsumer: (Float) -> Unit = stackAppearanceInteractor::setHeadsUpHeight /** Whether the notification stack is scrollable or not. */ val isScrollable: Flow<Boolean> = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index 97b86e3371f5..ea33be0ea4ed 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -29,7 +29,6 @@ import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrim import com.android.systemui.util.kotlin.FlowDumperImpl import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.StateFlow /** * ViewModel used by the Notification placeholders inside the scene container to update the @@ -74,9 +73,6 @@ constructor( val shadeScrimRounding: Flow<ShadeScrimRounding> = interactor.shadeScrimRounding.dumpWhileCollecting("shadeScrimRounding") - /** The height in px of the contents of the HUN. */ - val headsUpHeight: StateFlow<Float> = interactor.headsUpHeight.dumpValue("headsUpHeight") - /** * The amount [0-1] that the shade or quick settings has been opened. At 0, the shade is closed; * at 1, either the shade or quick settings is open. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java index 7b7a35b4928f..05a43917f7e0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -327,6 +327,11 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner, CoreStartable @Deprecated float getDisplayDensity(); + /** + * Forwards touch events to communal hub + */ + void handleCommunalHubTouch(MotionEvent event); + public static class KeyboardShortcutsMessage { final int mDeviceId; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt index 5ab56ae4be4d..a7b54847cdf9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt @@ -81,6 +81,7 @@ abstract class CentralSurfacesEmptyImpl : CentralSurfaces { override fun shouldIgnoreTouch() = false override fun isDeviceInteractive() = false override fun handleDreamTouch(event: MotionEvent?) {} + override fun handleCommunalHubTouch(event: MotionEvent?) {} override fun awakenDreams() {} override fun isBouncerShowing() = false override fun isBouncerShowingScrimmed() = false diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index e0da2fe584b6..78a803618c8b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -172,6 +172,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor; import com.android.systemui.shade.CameraLauncher; +import com.android.systemui.shade.GlanceableHubContainerController; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.NotificationShadeWindowViewController; import com.android.systemui.shade.QuickSettingsController; @@ -595,6 +596,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener = (extractor, which) -> updateTheme(); private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor; + private final GlanceableHubContainerController mGlanceableHubContainerController; /** * Public constructor for CentralSurfaces. @@ -707,7 +709,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { UserTracker userTracker, Provider<FingerprintManager> fingerprintManager, ActivityStarter activityStarter, - BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor + BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor, + GlanceableHubContainerController glanceableHubContainerController ) { mContext = context; mNotificationsController = notificationsController; @@ -802,6 +805,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mFingerprintManager = fingerprintManager; mActivityStarter = activityStarter; mBrightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor; + mGlanceableHubContainerController = glanceableHubContainerController; mLockscreenShadeTransitionController = lockscreenShadeTransitionController; mStartingSurfaceOptional = startingSurfaceOptional; @@ -2951,6 +2955,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @Override + public void handleCommunalHubTouch(MotionEvent event) { + mGlanceableHubContainerController.onTouchEvent(event); + } + + @Override public void awakenDreams() { mUiBgExecutor.execute(() -> { try { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index ffc859eb9197..4bf122dd3b6a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -86,6 +86,8 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements private final List<OnHeadsUpPhoneListenerChange> mHeadsUpPhoneListeners = new ArrayList<>(); private final VisualStabilityProvider mVisualStabilityProvider; + private final AvalancheController mAvalancheController; + // TODO(b/328393698) move the topHeadsUpRow logic to an interactor private final MutableStateFlow<HeadsUpRowRepository> mTopHeadsUpRow = StateFlowKt.MutableStateFlow(null); @@ -155,6 +157,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements mBypassController = bypassController; mGroupMembershipManager = groupMembershipManager; mVisualStabilityProvider = visualStabilityProvider; + mAvalancheController = avalancheController; updateResources(); configurationController.addCallback(new ConfigurationController.ConfigurationListener() { @@ -653,9 +656,10 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements boolean wasKeyguard = mStatusBarState == StatusBarState.KEYGUARD; boolean isKeyguard = newState == StatusBarState.KEYGUARD; mStatusBarState = newState; + if (wasKeyguard && !isKeyguard && mBypassController.getBypassEnabled()) { ArrayList<String> keysToRemove = new ArrayList<>(); - for (HeadsUpEntry entry : mHeadsUpEntryMap.values()) { + for (HeadsUpEntry entry : getHeadsUpEntryList()) { if (entry.mEntry != null && entry.mEntry.isBubble() && !entry.isSticky()) { keysToRemove.add(entry.mEntry.getKey()); } @@ -671,7 +675,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements if (!isDozing) { // Let's make sure all huns we got while dozing time out within the normal timeout // duration. Otherwise they could get stuck for a very long time - for (HeadsUpEntry entry : mHeadsUpEntryMap.values()) { + for (HeadsUpEntry entry : getHeadsUpEntryList()) { entry.updateEntry(true /* updatePostTime */, "onDozingChanged(false)"); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt index 21691540fc56..eb09e6ef39cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt @@ -254,6 +254,13 @@ constructor( return null } + fun getWaitingEntryList(): List<HeadsUpEntry> { + if (!NotificationThrottleHun.isEnabled) { + return mutableListOf() + } + return nextMap.keys.toList() + } + private fun isShowing(entry: HeadsUpEntry): Boolean { return headsUpEntryShowing != null && entry.mEntry?.key == headsUpEntryShowing?.mEntry?.key } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java index 2ee98bb61d80..4bd868179faf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java @@ -279,7 +279,6 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { * Returns the entry if it is managed by this manager. * @param key key of notification * @return the entry - * TODO(b/315362456) See if caller needs to check AvalancheController waiting entries */ @Nullable public NotificationEntry getEntry(@NonNull String key) { @@ -294,8 +293,13 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { @NonNull @Override public Stream<NotificationEntry> getAllEntries() { - // TODO(b/315362456) See if callers need to check AvalancheController - return mHeadsUpEntryMap.values().stream().map(headsUpEntry -> headsUpEntry.mEntry); + return getHeadsUpEntryList().stream().map(headsUpEntry -> headsUpEntry.mEntry); + } + + public List<HeadsUpEntry> getHeadsUpEntryList() { + List<HeadsUpEntry> entryList = new ArrayList<>(mHeadsUpEntryMap.values()); + entryList.addAll(mAvalancheController.getWaitingEntryList()); + return entryList; } /** @@ -304,7 +308,8 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { */ @Override public boolean hasNotifications() { - return !mHeadsUpEntryMap.isEmpty(); + return !mHeadsUpEntryMap.isEmpty() + || !mAvalancheController.getWaitingEntryList().isEmpty(); } /** @@ -507,8 +512,10 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { @Nullable protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) { - // TODO(b/315362456) See if callers need to check AvalancheController - return mHeadsUpEntryMap.get(key); + if (mHeadsUpEntryMap.containsKey(key)) { + return mHeadsUpEntryMap.get(key); + } + return mAvalancheController.getWaitingEntry(key); } /** @@ -688,8 +695,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { */ @Override public boolean isSticky(String key) { - // TODO(b/315362456) See if callers need to check AvalancheController - HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key); + HeadsUpEntry headsUpEntry = getHeadsUpEntry(key); if (headsUpEntry != null) { return headsUpEntry.isSticky(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java index 58b82f166623..da928a364984 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java @@ -32,6 +32,7 @@ import android.util.SparseBooleanArray; import androidx.annotation.NonNull; import com.android.internal.camera.flags.Flags; +import com.android.systemui.util.ListenerSet; import java.util.Set; @@ -43,7 +44,7 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr private final SparseBooleanArray mSoftwareToggleState = new SparseBooleanArray(); private final SparseBooleanArray mHardwareToggleState = new SparseBooleanArray(); private Boolean mRequiresAuthentication; - private final Set<Callback> mCallbacks = new ArraySet<>(); + private final ListenerSet<Callback> mCallbacks = new ListenerSet<>(); public IndividualSensorPrivacyControllerImpl( @NonNull SensorPrivacyManager sensorPrivacyManager) { @@ -115,7 +116,7 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr @Override public void addCallback(@NonNull Callback listener) { - mCallbacks.add(listener); + mCallbacks.addIfAbsent(listener); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt index 03c8af9020e1..99f956489bc3 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt @@ -97,7 +97,14 @@ constructor( } private fun showNewVolumePanel() { - volumePanelGlobalStateInteractor.setVisible(true) + activityStarter.dismissKeyguardThenExecute( + { + volumePanelGlobalStateInteractor.setVisible(true) + false + }, + {}, + true + ) } private fun createNewVolumePanelDialog(): Dialog { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt index bd446b9ed190..1d1329ac550c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt @@ -29,6 +29,7 @@ import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.ColorCorrectionTile import com.android.systemui.qs.tiles.ColorInversionTile import com.android.systemui.qs.tiles.FontScalingTile +import com.android.systemui.qs.tiles.HearingDevicesTile import com.android.systemui.qs.tiles.OneHandedModeTile import com.android.systemui.qs.tiles.ReduceBrightColorsTile import com.android.systemui.util.mockito.whenever @@ -94,7 +95,7 @@ class AccessibilityQsShortcutsRepositoryImplForDeviceTest : SysuiTestCase() { fun testTileSpecToComponentMappingContent() { val mapping = AccessibilityQsShortcutsRepositoryImpl.TILE_SPEC_TO_COMPONENT_MAPPING - assertThat(mapping.size).isEqualTo(5) + assertThat(mapping.size).isEqualTo(6) assertThat(mapping[ColorCorrectionTile.TILE_SPEC]) .isEqualTo(AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME) assertThat(mapping[ColorInversionTile.TILE_SPEC]) @@ -107,6 +108,10 @@ class AccessibilityQsShortcutsRepositoryImplForDeviceTest : SysuiTestCase() { ) assertThat(mapping[FontScalingTile.TILE_SPEC]) .isEqualTo(AccessibilityShortcutController.FONT_SIZE_TILE_COMPONENT_NAME) + assertThat(mapping[HearingDevicesTile.TILE_SPEC]) + .isEqualTo( + AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_TILE_COMPONENT_NAME + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java index 8895a5e1a97c..0db0de2bcd7e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java @@ -16,6 +16,7 @@ package com.android.systemui.accessibility.hearingaid; +import static com.android.systemui.accessibility.hearingaid.HearingDevicesDialogDelegate.LIVE_CAPTION_INTENT; import static com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK; import static com.google.common.truth.Truth.assertThat; @@ -24,16 +25,24 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.ComponentName; import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.os.Handler; +import android.platform.test.annotations.EnableFlags; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; +import android.widget.LinearLayout; import androidx.test.filters.SmallTest; @@ -44,6 +53,7 @@ import com.android.settingslib.bluetooth.HapClientProfile; import com.android.settingslib.bluetooth.LocalBluetoothAdapter; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.DialogTransitionAnimator; import com.android.systemui.bluetooth.qsdialog.DeviceItem; @@ -54,6 +64,7 @@ import com.android.systemui.res.R; import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.phone.SystemUIDialogManager; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -75,6 +86,10 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { public MockitoRule mockito = MockitoJUnit.rule(); private static final String DEVICE_ADDRESS = "AA:BB:CC:DD:EE:FF"; + private static final String TEST_PKG = "pkg"; + private static final String TEST_CLS = "cls"; + private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PKG, TEST_CLS); + private static final String TEST_LABEL = "label"; @Mock private SystemUIDialog.Factory mSystemUIDialogFactory; @@ -104,6 +119,12 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { private CachedBluetoothDevice mCachedDevice; @Mock private DeviceItem mHearingDeviceItem; + @Mock + private PackageManager mPackageManager; + @Mock + private ActivityInfo mActivityInfo; + @Mock + private Drawable mDrawable; private SystemUIDialog mDialog; private HearingDevicesDialogDelegate mDialogDelegate; private TestableLooper mTestableLooper; @@ -122,6 +143,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { when(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState); when(mCachedDevice.getAddress()).thenReturn(DEVICE_ADDRESS); when(mHearingDeviceItem.getCachedBluetoothDevice()).thenReturn(mCachedDevice); + mContext.setMockPackageManager(mPackageManager); setUpPairNewDeviceDialog(); @@ -170,6 +192,45 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { verify(mCachedDevice).disconnect(); } + @Test + @EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS) + public void showDialog_hasLiveCaption_noRelatedToolsInConfig_showOneRelatedTool() { + when(mPackageManager.queryIntentActivities( + eq(LIVE_CAPTION_INTENT), anyInt())).thenReturn( + List.of(new ResolveInfo())); + mContext.getOrCreateTestableResources().addOverride( + R.array.config_quickSettingsHearingDevicesRelatedToolName, new String[]{}); + + setUpPairNewDeviceDialog(); + mDialog.show(); + + LinearLayout relatedToolsView = (LinearLayout) getRelatedToolsView(mDialog); + assertThat(relatedToolsView.getChildCount()).isEqualTo(1); + } + + @Test + @EnableFlags(Flags.FLAG_HEARING_DEVICES_DIALOG_RELATED_TOOLS) + public void showDialog_hasLiveCaption_oneRelatedToolInConfig_showTwoRelatedTools() + throws PackageManager.NameNotFoundException { + when(mPackageManager.queryIntentActivities( + eq(LIVE_CAPTION_INTENT), anyInt())).thenReturn( + List.of(new ResolveInfo())); + mContext.getOrCreateTestableResources().addOverride( + R.array.config_quickSettingsHearingDevicesRelatedToolName, + new String[]{TEST_PKG + "/" + TEST_CLS}); + when(mPackageManager.getActivityInfo(eq(TEST_COMPONENT), anyInt())).thenReturn( + mActivityInfo); + when(mActivityInfo.loadLabel(mPackageManager)).thenReturn(TEST_LABEL); + when(mActivityInfo.loadIcon(mPackageManager)).thenReturn(mDrawable); + when(mActivityInfo.getComponentName()).thenReturn(TEST_COMPONENT); + + setUpPairNewDeviceDialog(); + mDialog.show(); + + LinearLayout relatedToolsView = (LinearLayout) getRelatedToolsView(mDialog); + assertThat(relatedToolsView.getChildCount()).isEqualTo(2); + } + private void setUpPairNewDeviceDialog() { mDialogDelegate = new HearingDevicesDialogDelegate( mContext, @@ -219,4 +280,18 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { private View getPairNewDeviceButton(SystemUIDialog dialog) { return dialog.requireViewById(R.id.pair_new_device_button); } + + private View getRelatedToolsView(SystemUIDialog dialog) { + return dialog.requireViewById(R.id.related_tools_container); + } + + @After + public void reset() { + if (mDialogDelegate != null) { + mDialogDelegate = null; + } + if (mDialog != null) { + mDialog.dismiss(); + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java new file mode 100644 index 000000000000..717292378913 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java @@ -0,0 +1,134 @@ +/* + * 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.accessibility.hearingaid; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import static java.util.Collections.emptyList; + +import android.content.ComponentName; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.List; + +/** + * Tests for {@link HearingDevicesToolItemParser}. + */ +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +public class HearingDevicesToolItemParserTest extends SysuiTestCase { + @Rule + public MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private PackageManager mPackageManager; + @Mock + private ActivityInfo mActivityInfo; + @Mock + private Drawable mDrawable; + private static final String TEST_PKG = "pkg"; + private static final String TEST_CLS = "cls"; + private static final ComponentName TEST_COMPONENT = new ComponentName(TEST_PKG, TEST_CLS); + private static final String TEST_NO_EXIST_PKG = "NoPkg"; + private static final String TEST_NO_EXIST_CLS = "NoCls"; + private static final ComponentName TEST_NO_EXIST_COMPONENT = new ComponentName( + TEST_NO_EXIST_PKG, TEST_NO_EXIST_CLS); + + private static final String TEST_LABEL = "label"; + + @Before + public void setUp() throws PackageManager.NameNotFoundException { + mContext.setMockPackageManager(mPackageManager); + + when(mPackageManager.getActivityInfo(eq(TEST_COMPONENT), anyInt())).thenReturn( + mActivityInfo); + when(mPackageManager.getActivityInfo(eq(TEST_NO_EXIST_COMPONENT), anyInt())).thenThrow( + new PackageManager.NameNotFoundException()); + when(mActivityInfo.loadLabel(mPackageManager)).thenReturn(TEST_LABEL); + when(mActivityInfo.loadIcon(mPackageManager)).thenReturn(mDrawable); + when(mActivityInfo.getComponentName()).thenReturn(TEST_COMPONENT); + } + + @Test + public void parseStringArray_noString_emptyResult() { + assertThat(HearingDevicesToolItemParser.parseStringArray(mContext, new String[]{}, + new String[]{})).isEqualTo(emptyList()); + } + + @Test + public void parseStringArray_oneToolName_oneExpectedToolItem() { + String[] toolName = new String[]{TEST_PKG + "/" + TEST_CLS}; + + List<ToolItem> toolItemList = HearingDevicesToolItemParser.parseStringArray(mContext, + toolName, new String[]{}); + + assertThat(toolItemList.size()).isEqualTo(1); + assertThat(toolItemList.get(0).getToolName()).isEqualTo(TEST_LABEL); + assertThat(toolItemList.get(0).getToolIntent().getComponent()).isEqualTo(TEST_COMPONENT); + } + + @Test + public void parseStringArray_fourToolName_maxThreeToolItem() { + String componentNameString = TEST_PKG + "/" + TEST_CLS; + String[] fourToolName = + new String[]{componentNameString, componentNameString, componentNameString, + componentNameString}; + + List<ToolItem> toolItemList = HearingDevicesToolItemParser.parseStringArray(mContext, + fourToolName, new String[]{}); + assertThat(toolItemList.size()).isEqualTo(HearingDevicesToolItemParser.MAX_NUM); + } + + @Test + public void parseStringArray_oneWrongFormatToolName_noToolItem() { + String[] wrongFormatToolName = new String[]{TEST_PKG}; + + List<ToolItem> toolItemList = HearingDevicesToolItemParser.parseStringArray(mContext, + wrongFormatToolName, new String[]{}); + assertThat(toolItemList.size()).isEqualTo(0); + } + + @Test + public void parseStringArray_oneNotExistToolName_noToolItem() { + String[] notExistToolName = new String[]{TEST_NO_EXIST_PKG + "/" + TEST_NO_EXIST_CLS}; + + List<ToolItem> toolItemList = HearingDevicesToolItemParser.parseStringArray(mContext, + notExistToolName, new String[]{}); + assertThat(toolItemList.size()).isEqualTo(0); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java index 07c980bb6656..18bd960b30a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java @@ -132,7 +132,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceNotAvailable_noFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor); + mDreamOverlayStateController, mControlsComponent, mMonitor, false); registrant.start(); setHaveFavorites(false); @@ -145,7 +145,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceAvailable_noFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor); + mDreamOverlayStateController, mControlsComponent, mMonitor, false); registrant.start(); setHaveFavorites(false); @@ -158,7 +158,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceAvailable_noFavorites_panel_addComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor); + mDreamOverlayStateController, mControlsComponent, mMonitor, false); registrant.start(); setHaveFavorites(false); @@ -171,7 +171,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceNotAvailable_haveFavorites_doNotAddComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor); + mDreamOverlayStateController, mControlsComponent, mMonitor, false); registrant.start(); setHaveFavorites(true); @@ -184,7 +184,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_serviceAvailable_haveFavorites_addComplication() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor); + mDreamOverlayStateController, mControlsComponent, mMonitor, false); registrant.start(); setHaveFavorites(true); @@ -197,7 +197,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void complicationAvailability_checkAvailabilityWhenDreamOverlayBecomesActive() { final DreamHomeControlsComplication.Registrant registrant = new DreamHomeControlsComplication.Registrant(mComplication, - mDreamOverlayStateController, mControlsComponent, mMonitor); + mDreamOverlayStateController, mControlsComponent, mMonitor, false); registrant.start(); setServiceAvailable(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt index 36bfa092c042..90ac05fc1b2e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt @@ -135,6 +135,7 @@ class CustomizationProviderTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt index 0bdf47a51670..f0ad5103e9a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.plugins.clocks.ClockController import com.android.systemui.shade.data.repository.shadeRepository import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -48,6 +49,9 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.reset +import org.mockito.Mockito.times +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @ExperimentalCoroutinesApi @@ -57,6 +61,7 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val underTest = kosmos.keyguardBlueprintInteractor + private val keyguardBlueprintRepository = kosmos.keyguardBlueprintRepository private val clockRepository by lazy { kosmos.fakeKeyguardClockRepository } private val configurationRepository by lazy { kosmos.fakeConfigurationRepository } private val fingerprintPropertyRepository by lazy { kosmos.fakeFingerprintPropertyRepository } @@ -103,44 +108,46 @@ class KeyguardBlueprintInteractorTest : SysuiTestCase() { } @Test - @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) - fun fingerprintPropertyInitialized_updatesBlueprint() { + @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) + fun testDoesNotApplySplitShadeBlueprint() { testScope.runTest { - assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNull() - - fingerprintPropertyRepository.supportsUdfps() // initialize properties + val blueprintId by collectLastValue(underTest.blueprintId) + kosmos.shadeRepository.setShadeMode(ShadeMode.Split) + clockRepository.setCurrentClock(clockController) + configurationRepository.onConfigurationChange() runCurrent() advanceUntilIdle() - assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNotNull() + assertThat(blueprintId).isEqualTo(DefaultKeyguardBlueprint.DEFAULT) } } @Test - @EnableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) - fun testDoesNotApplySplitShadeBlueprint() { + @DisableFlags(Flags.FLAG_COMPOSE_LOCKSCREEN) + fun fingerprintPropertyInitialized_updatesBlueprint() { testScope.runTest { - val blueprintId by collectLastValue(underTest.blueprintId) - kosmos.shadeRepository.setShadeMode(ShadeMode.Split) - clockRepository.setCurrentClock(clockController) - configurationRepository.onConfigurationChange() + underTest.start() + reset(keyguardBlueprintRepository) + + fingerprintPropertyRepository.supportsUdfps() // initialize properties runCurrent() advanceUntilIdle() - assertThat(blueprintId).isEqualTo(DefaultKeyguardBlueprint.DEFAULT) + verify(keyguardBlueprintRepository, times(2)).refreshBlueprint(any()) } } @Test fun testRefreshFromConfigChange() { testScope.runTest { - assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNull() + underTest.start() + reset(keyguardBlueprintRepository) configurationRepository.onConfigurationChange() runCurrent() advanceUntilIdle() - assertThat(kosmos.keyguardBlueprintRepository.targetTransitionConfig).isNotNull() + verify(keyguardBlueprintRepository, times(2)).refreshBlueprint(any()) } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index 35659c12fad5..14d954873f0f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -281,6 +281,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt index ef3183a8891f..ced3526f40be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt @@ -281,6 +281,7 @@ class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 616aac7ce460..344e0fc2bc6a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -44,6 +44,7 @@ import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import com.android.systemui.util.mockito.whenever import java.util.Optional +import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -55,6 +56,7 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @RunWithLooper(setAsMainLooper = true) +@ExperimentalCoroutinesApi @SmallTest class DefaultKeyguardBlueprintTest : SysuiTestCase() { private lateinit var underTest: DefaultKeyguardBlueprint @@ -112,17 +114,66 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { @Test fun replaceViews_withPrevBlueprint() { val prevBlueprint = mock(KeyguardBlueprint::class.java) - val someSection = mock(KeyguardSection::class.java) - whenever(prevBlueprint.sections) - .thenReturn(underTest.sections.minus(mDefaultDeviceEntrySection).plus(someSection)) + val removedSection = mock(KeyguardSection::class.java) + val addedSection = mDefaultDeviceEntrySection + val rebuildSection = clockSection + val prevSections = underTest.sections.minus(addedSection).plus(removedSection) + val unchangedSections = underTest.sections.subtract(listOf(addedSection, rebuildSection)) + whenever(prevBlueprint.sections).thenReturn(prevSections) + val constraintLayout = ConstraintLayout(context, null) underTest.replaceViews(constraintLayout, prevBlueprint) - underTest.sections.minus(mDefaultDeviceEntrySection).forEach { - verify(it, never())?.addViews(constraintLayout) + + unchangedSections.forEach { + verify(it, never()).addViews(constraintLayout) + verify(it, never()).removeViews(constraintLayout) + } + + verify(addedSection).addViews(constraintLayout) + verify(removedSection).removeViews(constraintLayout) + } + + @Test + fun replaceViews_withPrevBlueprint_withRebuildTargets() { + val prevBlueprint = mock(KeyguardBlueprint::class.java) + val removedSection = mock(KeyguardSection::class.java) + val addedSection = mDefaultDeviceEntrySection + val rebuildSection = clockSection + val prevSections = underTest.sections.minus(addedSection).plus(removedSection) + val unchangedSections = underTest.sections.subtract(listOf(addedSection, rebuildSection)) + whenever(prevBlueprint.sections).thenReturn(prevSections) + + val constraintLayout = ConstraintLayout(context, null) + underTest.replaceViews(constraintLayout, prevBlueprint, listOf(rebuildSection)) + + unchangedSections.forEach { + verify(it, never()).addViews(constraintLayout) + verify(it, never()).removeViews(constraintLayout) + } + + verify(addedSection).addViews(constraintLayout) + verify(rebuildSection).addViews(constraintLayout) + verify(rebuildSection).removeViews(constraintLayout) + verify(removedSection).removeViews(constraintLayout) + } + + @Test + fun rebuildViews() { + val rebuildSections = listOf(mDefaultDeviceEntrySection, clockSection) + val unchangedSections = underTest.sections.subtract(rebuildSections) + + val constraintLayout = ConstraintLayout(context, null) + underTest.rebuildViews(constraintLayout, rebuildSections) + + unchangedSections.forEach { + verify(it, never()).addViews(constraintLayout) + verify(it, never()).removeViews(constraintLayout) } - verify(mDefaultDeviceEntrySection).addViews(constraintLayout) - verify(someSection).removeViews(constraintLayout) + rebuildSections.forEach { + verify(it).addViews(constraintLayout) + verify(it).removeViews(constraintLayout) + } } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 16421a0f83b4..bdc5fc34158f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -178,6 +178,7 @@ class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestC .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt index 833822076620..8a12a90efc4c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt @@ -221,6 +221,7 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { .thenReturn(FakeSharedPreferences()) }, userTracker = userTracker, + systemSettings = FakeSettings(), broadcastDispatcher = fakeBroadcastDispatcher, ) val remoteUserSelectionManager = diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt index d2701dd0d3a3..16d8819f13fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt @@ -29,6 +29,7 @@ import android.media.session.MediaController.PlaybackInfo import android.media.session.MediaSession import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import android.platform.test.annotations.RequiresFlagsDisabled import android.platform.test.flag.junit.DeviceFlagsValueProvider import android.testing.AndroidTestingRunner import android.testing.TestableLooper @@ -40,6 +41,7 @@ import com.android.settingslib.flags.Flags import com.android.settingslib.media.LocalMediaManager import com.android.settingslib.media.MediaDevice import com.android.settingslib.media.PhoneMediaDevice +import com.android.settingslib.media.flags.Flags.FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS import com.android.systemui.SysuiTestCase import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.shared.model.MediaData @@ -101,7 +103,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Mock private lateinit var listener: MediaDeviceManager.Listener @Mock private lateinit var device: MediaDevice @Mock private lateinit var icon: Drawable - @Mock private lateinit var route: RoutingSessionInfo + @Mock private lateinit var routingSession: RoutingSessionInfo @Mock private lateinit var selectedRoute: MediaRoute2Info @Mock private lateinit var controller: MediaController @Mock private lateinit var playbackInfo: PlaybackInfo @@ -141,7 +143,10 @@ public class MediaDeviceManagerTest : SysuiTestCase() { whenever(lmmFactory.create(PACKAGE)).thenReturn(lmm) whenever(muteAwaitFactory.create(lmm)).thenReturn(muteAwaitManager) whenever(lmm.getCurrentConnectedDevice()).thenReturn(device) - whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(route) + whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(routingSession) + + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL) + whenever(controller.playbackInfo).thenReturn(playbackInfo) // Create a media sesssion and notification for testing. session = MediaSession(context, SESSION_KEY) @@ -235,6 +240,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { reset(listener) // WHEN media data is loaded with a different token // AND that token results in a null route + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() @@ -394,9 +400,10 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun deviceNameFromMR2RouteInfo() { + fun onMediaDataLoaded_withRemotePlaybackType_usesNonNullRoutingSessionName() { // GIVEN that MR2Manager returns a valid routing session - whenever(route.name).thenReturn(REMOTE_DEVICE_NAME) + whenever(routingSession.name).thenReturn(REMOTE_DEVICE_NAME) + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() @@ -408,8 +415,9 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun deviceDisabledWhenMR2ReturnsNullRouteInfo() { + fun onMediaDataLoaded_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() { // GIVEN that MR2Manager returns null for routing session + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) @@ -422,13 +430,14 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun deviceDisabledWhenMR2ReturnsNullRouteInfoOnDeviceChanged() { + fun onSelectedDeviceStateChanged_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() { // GIVEN a notif is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() reset(listener) // AND MR2Manager returns null for routing session + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) // WHEN the selected device changes state val deviceCallback = captureCallback() @@ -442,13 +451,14 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun deviceDisabledWhenMR2ReturnsNullRouteInfoOnDeviceListUpdate() { + fun onDeviceListUpdate_withRemotePlaybackInfo_noMatchingRoutingSession_setsDisabledDevice() { // GIVEN a notif is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() fakeFgExecutor.runAllReady() reset(listener) // GIVEN that MR2Manager returns null for routing session + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null) // WHEN the selected device changes state val deviceCallback = captureCallback() @@ -461,15 +471,17 @@ public class MediaDeviceManagerTest : SysuiTestCase() { assertThat(data.name).isNull() } + // With the flag enabled, MediaDeviceManager no longer gathers device name information directly. + @RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS) @Test fun mr2ReturnsSystemRouteWithNullName_isPhone_usePhoneName() { // When the routing session name is null, and is a system session for a PhoneMediaDevice val phoneDevice = mock(PhoneMediaDevice::class.java) whenever(phoneDevice.iconWithoutBackground).thenReturn(icon) whenever(lmm.currentConnectedDevice).thenReturn(phoneDevice) - whenever(route.isSystemSession).thenReturn(true) + whenever(routingSession.isSystemSession).thenReturn(true) - whenever(route.name).thenReturn(null) + whenever(routingSession.name).thenReturn(null) whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) whenever(selectedRoute.type).thenReturn(MediaRoute2Info.TYPE_BUILTIN_SPEAKER) @@ -483,13 +495,15 @@ public class MediaDeviceManagerTest : SysuiTestCase() { assertThat(data.name).isEqualTo(PhoneMediaDevice.getMediaTransferThisDeviceName(context)) } + // With the flag enabled, MediaDeviceManager no longer gathers device name information directly. + @RequiresFlagsDisabled(FLAG_USE_PLAYBACK_INFO_FOR_ROUTING_CONTROLS) @Test fun mr2ReturnsSystemRouteWithNullName_useSelectedRouteName() { // When the routing session does not have a name, and is a system session - whenever(route.name).thenReturn(null) + whenever(routingSession.name).thenReturn(null) whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute)) whenever(selectedRoute.name).thenReturn(REMOTE_DEVICE_NAME) - whenever(route.isSystemSession).thenReturn(true) + whenever(routingSession.isSystemSession).thenReturn(true) manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() @@ -503,8 +517,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName() { // GIVEN that MR2Manager returns a routing session that does not have a name - whenever(route.name).thenReturn(null) - whenever(route.isSystemSession).thenReturn(false) + whenever(routingSession.name).thenReturn(null) + whenever(routingSession.isSystemSession).thenReturn(false) // WHEN a notification is added manager.onMediaDataLoaded(KEY, null, mediaData) fakeBgExecutor.runAllReady() @@ -534,7 +548,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { } @Test - fun audioInfoVolumeControlIdChanged() { + fun onAudioInfoChanged_withRemotePlaybackInfo_queriesRoutingSession() { whenever(playbackInfo.getPlaybackType()).thenReturn(PlaybackInfo.PLAYBACK_TYPE_LOCAL) whenever(playbackInfo.getVolumeControlId()).thenReturn(null) whenever(controller.getPlaybackInfo()).thenReturn(playbackInfo) @@ -545,10 +559,11 @@ public class MediaDeviceManagerTest : SysuiTestCase() { reset(mr2) // WHEN onAudioInfoChanged fires with a volume control id change whenever(playbackInfo.getVolumeControlId()).thenReturn("placeholder id") + whenever(playbackInfo.playbackType).thenReturn(PlaybackInfo.PLAYBACK_TYPE_REMOTE) val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java) verify(controller).registerCallback(captor.capture()) captor.value.onAudioInfoChanged(playbackInfo) - // THEN the route is checked + // THEN the routing session is checked verify(mr2).getRoutingSessionForMediaController(eq(controller)) } @@ -654,7 +669,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() { @Test fun testRemotePlaybackDeviceOverride() { - whenever(route.name).thenReturn(DEVICE_NAME) + whenever(routingSession.name).thenReturn(DEVICE_NAME) val deviceData = MediaDeviceData(false, null, REMOTE_DEVICE_NAME, null, showBroadcastButton = false) val mediaDataWithDevice = mediaData.copy(device = deviceData) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/data/QSPreferencesRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/data/QSPreferencesRepositoryTest.kt new file mode 100644 index 000000000000..b0aa6ddf44ce --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/data/QSPreferencesRepositoryTest.kt @@ -0,0 +1,130 @@ +/* + * 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.data + +import android.content.Context +import android.content.SharedPreferences +import android.content.pm.UserInfo +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.panels.data.repository.QSPreferencesRepository +import com.android.systemui.qs.panels.data.repository.qsPreferencesRepository +import com.android.systemui.settings.userFileManager +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.user.data.repository.userRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class QSPreferencesRepositoryTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val underTest = with(kosmos) { qsPreferencesRepository } + + @Test + fun showLabels_updatesFromSharedPreferences() = + with(kosmos) { + testScope.runTest { + val latest by collectLastValue(underTest.showLabels) + assertThat(latest).isFalse() + + setShowLabelsInSharedPreferences(true) + assertThat(latest).isTrue() + + setShowLabelsInSharedPreferences(false) + assertThat(latest).isFalse() + } + } + + @Test + fun showLabels_updatesFromUserChange() = + with(kosmos) { + testScope.runTest { + fakeUserRepository.setUserInfos(USERS) + val latest by collectLastValue(underTest.showLabels) + + fakeUserRepository.setSelectedUserInfo(PRIMARY_USER) + setShowLabelsInSharedPreferences(false) + + fakeUserRepository.setSelectedUserInfo(ANOTHER_USER) + setShowLabelsInSharedPreferences(true) + + fakeUserRepository.setSelectedUserInfo(PRIMARY_USER) + assertThat(latest).isFalse() + } + } + + @Test + fun setShowLabels_inSharedPreferences() { + underTest.setShowLabels(false) + assertThat(getShowLabelsFromSharedPreferences(true)).isFalse() + + underTest.setShowLabels(true) + assertThat(getShowLabelsFromSharedPreferences(false)).isTrue() + } + + @Test + fun setShowLabels_forDifferentUser() = + with(kosmos) { + testScope.runTest { + fakeUserRepository.setUserInfos(USERS) + + fakeUserRepository.setSelectedUserInfo(PRIMARY_USER) + underTest.setShowLabels(false) + assertThat(getShowLabelsFromSharedPreferences(true)).isFalse() + + fakeUserRepository.setSelectedUserInfo(ANOTHER_USER) + underTest.setShowLabels(true) + assertThat(getShowLabelsFromSharedPreferences(false)).isTrue() + + fakeUserRepository.setSelectedUserInfo(PRIMARY_USER) + assertThat(getShowLabelsFromSharedPreferences(true)).isFalse() + } + } + + private fun getSharedPreferences(): SharedPreferences = + with(kosmos) { + return userFileManager.getSharedPreferences( + QSPreferencesRepository.FILE_NAME, + Context.MODE_PRIVATE, + userRepository.getSelectedUserInfo().id, + ) + } + + private fun setShowLabelsInSharedPreferences(value: Boolean) { + getSharedPreferences().edit().putBoolean(ICON_LABELS_KEY, value).apply() + } + + private fun getShowLabelsFromSharedPreferences(defaultValue: Boolean): Boolean { + return getSharedPreferences().getBoolean(ICON_LABELS_KEY, defaultValue) + } + + companion object { + private const val ICON_LABELS_KEY = "show_icon_labels" + private const val PRIMARY_USER_ID = 0 + private val PRIMARY_USER = UserInfo(PRIMARY_USER_ID, "user 0", UserInfo.FLAG_MAIN) + private const val ANOTHER_USER_ID = 1 + private val ANOTHER_USER = UserInfo(ANOTHER_USER_ID, "user 1", UserInfo.FLAG_FULL) + private val USERS = listOf(PRIMARY_USER, ANOTHER_USER) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorTest.kt new file mode 100644 index 000000000000..9b08432e290f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorTest.kt @@ -0,0 +1,90 @@ +/* + * 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.domain.interactor + +import android.content.pm.UserInfo +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidTestingRunner::class) +class IconLabelVisibilityInteractorTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val underTest = with(kosmos) { iconLabelVisibilityInteractor } + + @Before + fun setUp() { + with(kosmos) { fakeUserRepository.setUserInfos(USERS) } + } + + @Test + fun changingShowLabels_receivesCorrectShowLabels() = + with(kosmos) { + testScope.runTest { + val showLabels by collectLastValue(underTest.showLabels) + + underTest.setShowLabels(false) + runCurrent() + assertThat(showLabels).isFalse() + + underTest.setShowLabels(true) + runCurrent() + assertThat(showLabels).isTrue() + } + } + + @Test + fun changingUser_receivesCorrectShowLabels() = + with(kosmos) { + testScope.runTest { + val showLabels by collectLastValue(underTest.showLabels) + + fakeUserRepository.setSelectedUserInfo(PRIMARY_USER) + underTest.setShowLabels(false) + runCurrent() + assertThat(showLabels).isFalse() + + fakeUserRepository.setSelectedUserInfo(ANOTHER_USER) + underTest.setShowLabels(true) + runCurrent() + assertThat(showLabels).isTrue() + + fakeUserRepository.setSelectedUserInfo(PRIMARY_USER) + runCurrent() + assertThat(showLabels).isFalse() + } + } + + companion object { + private val PRIMARY_USER = UserInfo(0, "user 0", UserInfo.FLAG_MAIN) + private val ANOTHER_USER = UserInfo(1, "user 1", UserInfo.FLAG_FULL) + private val USERS = listOf(PRIMARY_USER, ANOTHER_USER) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java index 9798562ab5a8..29487cdace2d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java @@ -1116,6 +1116,34 @@ public class InternetDialogDelegateControllerTest extends SysuiTestCase { assertThat(mInternetDialogController.hasActiveSubIdOnDds()).isFalse(); } + @Test + public void hasActiveSubIdOnDds_activeDdsAndIsOnlyNonTerrestrialNetwork_returnFalse() { + when(SubscriptionManager.getDefaultDataSubscriptionId()) + .thenReturn(SUB_ID); + SubscriptionInfo info = mock(SubscriptionInfo.class); + when(info.isEmbedded()).thenReturn(true); + when(info.isOnlyNonTerrestrialNetwork()).thenReturn(true); + when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info); + + mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged(); + + assertFalse(mInternetDialogController.hasActiveSubIdOnDds()); + } + + @Test + public void hasActiveSubIdOnDds_activeDdsAndIsNotOnlyNonTerrestrialNetwork_returnTrue() { + when(SubscriptionManager.getDefaultDataSubscriptionId()) + .thenReturn(SUB_ID); + SubscriptionInfo info = mock(SubscriptionInfo.class); + when(info.isEmbedded()).thenReturn(true); + when(info.isOnlyNonTerrestrialNetwork()).thenReturn(false); + when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(info); + + mInternetDialogController.mOnSubscriptionsChangedListener.onSubscriptionsChanged(); + + assertTrue(mInternetDialogController.hasActiveSubIdOnDds()); + } + private String getResourcesString(String name) { return mContext.getResources().getString(getResourcesId(name)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java index 22c9e45d48af..6985a27a59c8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java @@ -20,14 +20,21 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.graphics.drawable.Icon; +import android.os.Handler; +import android.platform.test.annotations.EnableFlags; +import android.view.KeyboardShortcutGroup; +import android.view.KeyboardShortcutInfo; import android.view.WindowManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.google.android.material.bottomsheet.BottomSheetDialog; @@ -36,10 +43,14 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.Arrays; +import java.util.Collections; + @SmallTest @RunWith(AndroidJUnit4.class) public class KeyboardShortcutListSearchTest extends SysuiTestCase { @@ -51,6 +62,7 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase { @Mock private BottomSheetDialog mBottomSheetDialog; @Mock WindowManager mWindowManager; + @Mock Handler mHandler; @Before public void setUp() { @@ -58,6 +70,7 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase { mKeyboardShortcutListSearch.sInstance = mKeyboardShortcutListSearch; mKeyboardShortcutListSearch.mKeyboardShortcutsBottomSheetDialog = mBottomSheetDialog; mKeyboardShortcutListSearch.mContext = mContext; + mKeyboardShortcutListSearch.mBackgroundHandler = mHandler; } @Test @@ -78,4 +91,59 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase { verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt()); verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt()); } + + @Test + @EnableFlags(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI) + public void requestAppKeyboardShortcuts_callback_sanitisesIcons() { + KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); + + mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID); + + ArgumentCaptor<WindowManager.KeyboardShortcutsReceiver> callbackCaptor = + ArgumentCaptor.forClass(WindowManager.KeyboardShortcutsReceiver.class); + ArgumentCaptor<Runnable> handlerRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mWindowManager).requestAppKeyboardShortcuts(callbackCaptor.capture(), anyInt()); + callbackCaptor.getValue().onKeyboardShortcutsReceived(Collections.singletonList(group)); + verify(mHandler).post(handlerRunnableCaptor.capture()); + handlerRunnableCaptor.getValue().run(); + + verify(group.getItems().get(0)).clearIcon(); + verify(group.getItems().get(1)).clearIcon(); + } + + @Test + @EnableFlags(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI) + public void requestImeKeyboardShortcuts_callback_sanitisesIcons() { + KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); + + mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID); + + ArgumentCaptor<WindowManager.KeyboardShortcutsReceiver> callbackCaptor = + ArgumentCaptor.forClass(WindowManager.KeyboardShortcutsReceiver.class); + ArgumentCaptor<Runnable> handlerRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mWindowManager).requestImeKeyboardShortcuts(callbackCaptor.capture(), anyInt()); + callbackCaptor.getValue().onKeyboardShortcutsReceived(Collections.singletonList(group)); + verify(mHandler).post(handlerRunnableCaptor.capture()); + handlerRunnableCaptor.getValue().run(); + + verify(group.getItems().get(0)).clearIcon(); + verify(group.getItems().get(1)).clearIcon(); + + } + + private KeyboardShortcutGroup createKeyboardShortcutGroupForIconTests() { + Icon icon = mock(Icon.class); + + KeyboardShortcutInfo info1 = mock(KeyboardShortcutInfo.class); + KeyboardShortcutInfo info2 = mock(KeyboardShortcutInfo.class); + when(info1.getIcon()).thenReturn(icon); + when(info2.getIcon()).thenReturn(icon); + when(info1.getLabel()).thenReturn("label"); + when(info2.getLabel()).thenReturn("label"); + + KeyboardShortcutGroup group = new KeyboardShortcutGroup("label", + Arrays.asList(new KeyboardShortcutInfo[]{ info1, info2})); + group.setPackageName("com.example"); + return group; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java index a3ecde0fe976..2b3f13986113 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java @@ -20,25 +20,36 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Dialog; +import android.graphics.drawable.Icon; +import android.os.Handler; +import android.platform.test.annotations.EnableFlags; +import android.view.KeyboardShortcutGroup; +import android.view.KeyboardShortcutInfo; import android.view.WindowManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.Arrays; +import java.util.Collections; + @SmallTest @RunWith(AndroidJUnit4.class) public class KeyboardShortcutsTest extends SysuiTestCase { @@ -50,6 +61,7 @@ public class KeyboardShortcutsTest extends SysuiTestCase { @Mock private Dialog mDialog; @Mock WindowManager mWindowManager; + @Mock Handler mHandler; @Before public void setUp() { @@ -57,6 +69,7 @@ public class KeyboardShortcutsTest extends SysuiTestCase { mKeyboardShortcuts.sInstance = mKeyboardShortcuts; mKeyboardShortcuts.mKeyboardShortcutsDialog = mDialog; mKeyboardShortcuts.mContext = mContext; + mKeyboardShortcuts.mBackgroundHandler = mHandler; } @Test @@ -77,4 +90,78 @@ public class KeyboardShortcutsTest extends SysuiTestCase { verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt()); verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt()); } + + @Test + public void sanitiseShortcuts_clearsIcons() { + KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); + + KeyboardShortcuts.sanitiseShortcuts(Collections.singletonList(group)); + + verify(group.getItems().get(0)).clearIcon(); + verify(group.getItems().get(1)).clearIcon(); + } + + @Test + public void sanitiseShortcuts_nullPackage_clearsIcons() { + KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); + group.setPackageName(null); + + KeyboardShortcuts.sanitiseShortcuts(Collections.singletonList(group)); + + verify(group.getItems().get(0)).clearIcon(); + verify(group.getItems().get(1)).clearIcon(); + } + + @Test + @EnableFlags(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI) + public void requestAppKeyboardShortcuts_callback_sanitisesIcons() { + KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); + + mKeyboardShortcuts.toggle(mContext, DEVICE_ID); + + ArgumentCaptor<WindowManager.KeyboardShortcutsReceiver> callbackCaptor = + ArgumentCaptor.forClass(WindowManager.KeyboardShortcutsReceiver.class); + ArgumentCaptor<Runnable> handlerRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mWindowManager).requestAppKeyboardShortcuts(callbackCaptor.capture(), anyInt()); + callbackCaptor.getValue().onKeyboardShortcutsReceived(Collections.singletonList(group)); + verify(mHandler).post(handlerRunnableCaptor.capture()); + handlerRunnableCaptor.getValue().run(); + + verify(group.getItems().get(0)).clearIcon(); + verify(group.getItems().get(1)).clearIcon(); + } + + @Test + @EnableFlags(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI) + public void requestImeKeyboardShortcuts_callback_sanitisesIcons() { + KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); + + mKeyboardShortcuts.toggle(mContext, DEVICE_ID); + + ArgumentCaptor<WindowManager.KeyboardShortcutsReceiver> callbackCaptor = + ArgumentCaptor.forClass(WindowManager.KeyboardShortcutsReceiver.class); + ArgumentCaptor<Runnable> handlerRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mWindowManager).requestImeKeyboardShortcuts(callbackCaptor.capture(), anyInt()); + callbackCaptor.getValue().onKeyboardShortcutsReceived(Collections.singletonList(group)); + verify(mHandler).post(handlerRunnableCaptor.capture()); + handlerRunnableCaptor.getValue().run(); + + verify(group.getItems().get(0)).clearIcon(); + verify(group.getItems().get(1)).clearIcon(); + + } + + private KeyboardShortcutGroup createKeyboardShortcutGroupForIconTests() { + Icon icon = mock(Icon.class); + + KeyboardShortcutInfo info1 = mock(KeyboardShortcutInfo.class); + KeyboardShortcutInfo info2 = mock(KeyboardShortcutInfo.class); + when(info1.getIcon()).thenReturn(icon); + when(info2.getIcon()).thenReturn(icon); + + KeyboardShortcutGroup group = new KeyboardShortcutGroup("label", + Arrays.asList(new KeyboardShortcutInfo[]{ info1, info2})); + group.setPackageName("com.example"); + return group; + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java index f461e2f67d20..12f3ef3cf553 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java @@ -171,7 +171,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase { // and then we would test both configurations, but currently they are all read // in the constructor. mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION); - mFeatureFlags.setDefault(Flags.UNCLEARED_TRANSIENT_HUN_FIX); // Inject dependencies before initializing the layout mDependency.injectTestDependency(FeatureFlags.class, mFeatureFlags); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 62804ed1412e..cb9790b2495a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -138,6 +138,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor; import com.android.systemui.shade.CameraLauncher; +import com.android.systemui.shade.GlanceableHubContainerController; import com.android.systemui.shade.NotificationPanelView; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowViewController; @@ -337,6 +338,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private KeyboardShortcuts mKeyboardShortcuts; @Mock private KeyboardShortcutListSearch mKeyboardShortcutListSearch; @Mock private PackageManager mPackageManager; + @Mock private GlanceableHubContainerController mGlanceableHubContainerController; private ShadeController mShadeController; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); @@ -590,7 +592,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mUserTracker, () -> mFingerprintManager, mActivityStarter, - mBrightnessMirrorShowingInteractor + mBrightnessMirrorShowingInteractor, + mGlanceableHubContainerController ); mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver); mCentralSurfaces.initShadeVisibilityListener(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt index a6b40df8e81b..fb12897ead19 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt @@ -23,12 +23,14 @@ import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.blueprints.SplitShadeKeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection +import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection import com.android.systemui.keyguard.ui.viewmodel.keyguardClockViewModel import com.android.systemui.keyguard.ui.viewmodel.keyguardRootViewModel import com.android.systemui.keyguard.ui.viewmodel.keyguardSmartspaceViewModel import com.android.systemui.kosmos.Kosmos import com.android.systemui.util.mockito.mock import java.util.Optional +import org.mockito.Mockito.spy val Kosmos.keyguardClockSection: ClockSection by Kosmos.Fixture { @@ -42,6 +44,9 @@ val Kosmos.keyguardClockSection: ClockSection by ) } +val Kosmos.keyguardSmartspaceSection: SmartspaceSection by + Kosmos.Fixture { mock<SmartspaceSection>() } + val Kosmos.defaultKeyguardBlueprint by Kosmos.Fixture { DefaultKeyguardBlueprint( @@ -57,7 +62,7 @@ val Kosmos.defaultKeyguardBlueprint by aodBurnInSection = mock(), communalTutorialIndicatorSection = mock(), clockSection = keyguardClockSection, - smartspaceSection = mock(), + smartspaceSection = keyguardSmartspaceSection, keyguardSliceViewSection = mock(), udfpsAccessibilityOverlaySection = mock(), accessibilityActionsSection = mock(), @@ -80,7 +85,7 @@ val Kosmos.splitShadeBlueprint by aodBurnInSection = mock(), communalTutorialIndicatorSection = mock(), clockSection = keyguardClockSection, - smartspaceSection = mock(), + smartspaceSection = keyguardSmartspaceSection, mediaSection = mock(), accessibilityActionsSection = mock(), ) @@ -88,13 +93,15 @@ val Kosmos.splitShadeBlueprint by val Kosmos.keyguardBlueprintRepository by Kosmos.Fixture { - KeyguardBlueprintRepository( - blueprints = - setOf( - defaultKeyguardBlueprint, - splitShadeBlueprint, - ), - handler = fakeExecutorHandler, - assert = mock(), + spy( + KeyguardBlueprintRepository( + blueprints = + setOf( + defaultKeyguardBlueprint, + splitShadeBlueprint, + ), + handler = fakeExecutorHandler, + assert = mock(), + ) ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt index 5256ce4b9adf..4328ca153374 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractorKosmos.kt @@ -20,6 +20,8 @@ import android.content.applicationContext import com.android.systemui.biometrics.domain.interactor.fingerprintPropertyInteractor import com.android.systemui.common.ui.domain.interactor.configurationInteractor import com.android.systemui.keyguard.data.repository.keyguardBlueprintRepository +import com.android.systemui.keyguard.data.repository.keyguardClockSection +import com.android.systemui.keyguard.data.repository.keyguardSmartspaceSection import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.shade.domain.interactor.shadeInteractor @@ -34,5 +36,7 @@ val Kosmos.keyguardBlueprintInteractor by clockInteractor = keyguardClockInteractor, configurationInteractor = configurationInteractor, fingerprintPropertyInteractor = fingerprintPropertyInteractor, + clockSection = keyguardClockSection, + smartspaceSection = keyguardSmartspaceSection, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt new file mode 100644 index 000000000000..39ae5eb44c65 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt @@ -0,0 +1,25 @@ +/* + * 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.data.repository + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.settings.userFileManager +import com.android.systemui.user.data.repository.userRepository + +val Kosmos.qsPreferencesRepository by + Kosmos.Fixture { QSPreferencesRepository(userFileManager, userRepository, testDispatcher) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorKosmos.kt index 7b9e4a17e998..954084b874a0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/IconLabelVisibilityInteractorKosmos.kt @@ -19,12 +19,11 @@ package com.android.systemui.qs.panels.domain.interactor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.log.core.FakeLogBuffer -import com.android.systemui.qs.panels.data.repository.iconLabelVisibilityRepository val Kosmos.iconLabelVisibilityInteractor by Kosmos.Fixture { IconLabelVisibilityInteractor( - iconLabelVisibilityRepository, + qsPreferencesInteractor, FakeLogBuffer.Factory.create(), applicationCoroutineScope ) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractorKosmos.kt index 277dbb7016ad..eb83e325d79b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/IconLabelVisibilityRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/domain/interactor/QSPreferencesInteractorKosmos.kt @@ -14,8 +14,10 @@ * limitations under the License. */ -package com.android.systemui.qs.panels.data.repository +package com.android.systemui.qs.panels.domain.interactor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.panels.data.repository.qsPreferencesRepository -val Kosmos.iconLabelVisibilityRepository by Kosmos.Fixture { IconLabelVisibilityRepository() } +val Kosmos.qsPreferencesInteractor by + Kosmos.Fixture { QSPreferencesInteractor(qsPreferencesRepository) } diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index dc1cfb92c3b8..ddccb3731cc1 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -4008,20 +4008,10 @@ public class UserBackupManagerService { } private PackageInfo getPackageInfoForBMMLogging(String packageName) { - try { - return mPackageManager.getPackageInfoAsUser(packageName, 0, mUserId); - } catch (NameNotFoundException e) { - Slog.w( - TAG, - addUserIdToLogMessage( - mUserId, "Asked to get PackageInfo for BMM logging of nonexistent pkg " - + packageName)); - - PackageInfo packageInfo = new PackageInfo(); - packageInfo.packageName = packageName; + PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = packageName; - return packageInfo; - } + return packageInfo; } /** Hand off a restore session. */ diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 4dd3a8f67b0d..b35959f1a6e8 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -3980,7 +3980,7 @@ class StorageManagerService extends IStorageManager.Stub if (resUuids.contains(rec.fsUuid)) continue; // Treat as recent if mounted within the last week - if (rec.lastSeenMillis > 0 && rec.lastSeenMillis < lastWeek) { + if (rec.lastSeenMillis > 0 && rec.lastSeenMillis >= lastWeek) { final StorageVolume userVol = rec.buildStorageVolume(mContext); res.add(userVol); resUuids.add(userVol.getUuid()); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 72c52544a44a..2addf6f9ec96 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -16,7 +16,20 @@ package com.android.server.audio; +import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; +import static android.Manifest.permission.BLUETOOTH_STACK; +import static android.Manifest.permission.CALL_AUDIO_INTERCEPTION; +import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; +import static android.Manifest.permission.CAPTURE_AUDIO_OUTPUT; +import static android.Manifest.permission.CAPTURE_MEDIA_OUTPUT; +import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.Manifest.permission.MODIFY_AUDIO_ROUTING; +import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS; import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED; +import static android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS; +import static android.Manifest.permission.MODIFY_PHONE_STATE; +import static android.Manifest.permission.QUERY_AUDIO_STATE; +import static android.Manifest.permission.WRITE_SETTINGS; import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT; import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET; import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER; @@ -1888,7 +1901,6 @@ public class AudioService extends IAudioService.Stub } mSpatializerHelper.reset(/* featureEnabled */ mHasSpatializerEffect); - mSoundDoseHelper.reset(); // Restore rotation information. if (mMonitorRotation) { @@ -1899,6 +1911,8 @@ public class AudioService extends IAudioService.Stub // indicate the end of reconfiguration phase to audio HAL AudioSystem.setParameters("restarting=false"); + mSoundDoseHelper.reset(/*resetISoundDose=*/true); + sendMsg(mAudioHandler, MSG_DISPATCH_AUDIO_SERVER_STATE, SENDMSG_QUEUE, 1, 0, null, 0); @@ -2075,7 +2089,7 @@ public class AudioService extends IAudioService.Stub } } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** * @see AudioManager#setSupportedSystemUsages(int[]) */ @@ -2090,7 +2104,7 @@ public class AudioService extends IAudioService.Stub } } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** * @see AudioManager#getSupportedSystemUsages() */ @@ -2110,7 +2124,7 @@ public class AudioService extends IAudioService.Stub } } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** * @return the {@link android.media.audiopolicy.AudioProductStrategy} discovered from the * platform configuration file. @@ -2124,9 +2138,7 @@ public class AudioService extends IAudioService.Stub } @android.annotation.EnforcePermission(anyOf = { - MODIFY_AUDIO_SETTINGS_PRIVILEGED, - android.Manifest.permission.MODIFY_AUDIO_ROUTING - }) + MODIFY_AUDIO_SETTINGS_PRIVILEGED, MODIFY_AUDIO_ROUTING }) /** * @return the List of {@link android.media.audiopolicy.AudioVolumeGroup} discovered from the * platform configuration file. @@ -2584,7 +2596,7 @@ public class AudioService extends IAudioService.Stub Log.w(TAG, "audioFormat to enable is not a surround format."); return false; } - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.WRITE_SETTINGS) + if (mContext.checkCallingOrSelfPermission(WRITE_SETTINGS) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Missing WRITE_SETTINGS permission"); } @@ -2608,7 +2620,7 @@ public class AudioService extends IAudioService.Stub return true; } - @android.annotation.EnforcePermission(android.Manifest.permission.WRITE_SETTINGS) + @android.annotation.EnforcePermission(WRITE_SETTINGS) /** @see AudioManager#setEncodedSurroundMode(int) */ @Override public boolean setEncodedSurroundMode(@AudioManager.EncodedSurroundOutputMode int mode) { @@ -2786,7 +2798,7 @@ public class AudioService extends IAudioService.Stub if (!TextUtils.isEmpty(packageName)) { PackageManager pm = mContext.getPackageManager(); - if (pm.checkPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD, packageName) + if (pm.checkPermission(CAPTURE_AUDIO_HOTWORD, packageName) == PackageManager.PERMISSION_GRANTED) { try { assistantUid = pm.getPackageUidAsUser(packageName, getCurrentUserId()); @@ -2973,7 +2985,7 @@ public class AudioService extends IAudioService.Stub * @see AudioManager#setPreferredDevicesForStrategy(AudioProductStrategy, * List<AudioDeviceAttributes>) */ - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) public int setPreferredDevicesForStrategy(int strategy, List<AudioDeviceAttributes> devices) { super.setPreferredDevicesForStrategy_enforcePermission(); if (devices == null) { @@ -3001,7 +3013,7 @@ public class AudioService extends IAudioService.Stub return status; } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#removePreferredDeviceForStrategy(AudioProductStrategy) */ public int removePreferredDevicesForStrategy(int strategy) { super.removePreferredDevicesForStrategy_enforcePermission(); @@ -3017,7 +3029,7 @@ public class AudioService extends IAudioService.Stub return status; } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** * @see AudioManager#getPreferredDeviceForStrategy(AudioProductStrategy) * @see AudioManager#getPreferredDevicesForStrategy(AudioProductStrategy) @@ -3049,7 +3061,7 @@ public class AudioService extends IAudioService.Stub * @see AudioManager#setDeviceAsNonDefaultForStrategy(AudioProductStrategy, * List<AudioDeviceAttributes>) */ - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) public int setDeviceAsNonDefaultForStrategy(int strategy, @NonNull AudioDeviceAttributes device) { super.setDeviceAsNonDefaultForStrategy_enforcePermission(); @@ -3078,7 +3090,7 @@ public class AudioService extends IAudioService.Stub * @see AudioManager#removeDeviceAsNonDefaultForStrategy(AudioProductStrategy, * AudioDeviceAttributes) */ - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) public int removeDeviceAsNonDefaultForStrategy(int strategy, AudioDeviceAttributes device) { super.removeDeviceAsNonDefaultForStrategy_enforcePermission(); @@ -3104,7 +3116,7 @@ public class AudioService extends IAudioService.Stub /** * @see AudioManager#getNonDefaultDevicesForStrategy(AudioProductStrategy) */ - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) public List<AudioDeviceAttributes> getNonDefaultDevicesForStrategy(int strategy) { super.getNonDefaultDevicesForStrategy_enforcePermission(); List<AudioDeviceAttributes> devices = new ArrayList<>(); @@ -3205,7 +3217,7 @@ public class AudioService extends IAudioService.Stub return status; } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#clearPreferredDevicesForCapturePreset(int) */ public int clearPreferredDevicesForCapturePreset(int capturePreset) { super.clearPreferredDevicesForCapturePreset_enforcePermission(); @@ -3221,7 +3233,7 @@ public class AudioService extends IAudioService.Stub return status; } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** * @see AudioManager#getPreferredDevicesForCapturePreset(int) */ @@ -3555,7 +3567,7 @@ public class AudioService extends IAudioService.Stub if (isMuteAdjust && (streamType == AudioSystem.STREAM_VOICE_CALL || streamType == AudioSystem.STREAM_BLUETOOTH_SCO) && - mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid) + mContext.checkPermission(MODIFY_PHONE_STATE, pid, uid) != PackageManager.PERMISSION_GRANTED) { Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: adjustStreamVolume from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); @@ -3566,7 +3578,7 @@ public class AudioService extends IAudioService.Stub // make sure that the calling app have the MODIFY_AUDIO_ROUTING permission. if (streamType == AudioSystem.STREAM_ASSISTANT && mContext.checkPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING, pid, uid) + MODIFY_AUDIO_ROUTING, pid, uid) != PackageManager.PERMISSION_GRANTED) { Log.w(TAG, "MODIFY_AUDIO_ROUTING Permission Denial: adjustStreamVolume from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); @@ -3997,50 +4009,25 @@ public class AudioService extends IAudioService.Stub } private void enforceModifyAudioRoutingPermission() { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + if (mContext.checkCallingOrSelfPermission(MODIFY_AUDIO_ROUTING) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Missing MODIFY_AUDIO_ROUTING permission"); } } - private void enforceAccessUltrasoundPermission() { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_ULTRASOUND) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Missing ACCESS_ULTRASOUND permission"); - } - } - - private void enforceQueryStatePermission() { - if (mContext.checkCallingOrSelfPermission(Manifest.permission.QUERY_AUDIO_STATE) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Missing QUERY_AUDIO_STATE permissions"); - } - } - private void enforceQueryStateOrModifyRoutingPermission() { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + if (mContext.checkCallingOrSelfPermission(MODIFY_AUDIO_ROUTING) != PackageManager.PERMISSION_GRANTED - && mContext.checkCallingOrSelfPermission(Manifest.permission.QUERY_AUDIO_STATE) + && mContext.checkCallingOrSelfPermission(QUERY_AUDIO_STATE) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException( "Missing MODIFY_AUDIO_ROUTING or QUERY_AUDIO_STATE permissions"); } } - private void enforceCallAudioInterceptionPermission() { - if (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.CALL_AUDIO_INTERCEPTION) - != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Missing CALL_AUDIO_INTERCEPTION permission"); - } - } - - @Override @android.annotation.EnforcePermission(anyOf = { - MODIFY_AUDIO_SETTINGS_PRIVILEGED, - android.Manifest.permission.MODIFY_AUDIO_ROUTING - }) + MODIFY_AUDIO_SETTINGS_PRIVILEGED, MODIFY_AUDIO_ROUTING }) /** @see AudioManager#setVolumeGroupVolumeIndex(int, int, int) */ public void setVolumeGroupVolumeIndex(int groupId, int index, int flags, String callingPackage, String attributionTag) { @@ -4074,9 +4061,7 @@ public class AudioService extends IAudioService.Stub @Override @android.annotation.EnforcePermission(anyOf = { - MODIFY_AUDIO_SETTINGS_PRIVILEGED, - android.Manifest.permission.MODIFY_AUDIO_ROUTING - }) + MODIFY_AUDIO_SETTINGS_PRIVILEGED, MODIFY_AUDIO_ROUTING }) /** @see AudioManager#getVolumeGroupVolumeIndex(int) */ public int getVolumeGroupVolumeIndex(int groupId) { super.getVolumeGroupVolumeIndex_enforcePermission(); @@ -4093,9 +4078,7 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#getVolumeGroupMaxVolumeIndex(int) */ @android.annotation.EnforcePermission(anyOf = { - MODIFY_AUDIO_SETTINGS_PRIVILEGED, - android.Manifest.permission.MODIFY_AUDIO_ROUTING - }) + MODIFY_AUDIO_SETTINGS_PRIVILEGED, MODIFY_AUDIO_ROUTING }) public int getVolumeGroupMaxVolumeIndex(int groupId) { super.getVolumeGroupMaxVolumeIndex_enforcePermission(); synchronized (VolumeStreamState.class) { @@ -4109,9 +4092,7 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#getVolumeGroupMinVolumeIndex(int) */ @android.annotation.EnforcePermission(anyOf = { - MODIFY_AUDIO_SETTINGS_PRIVILEGED, - android.Manifest.permission.MODIFY_AUDIO_ROUTING - }) + MODIFY_AUDIO_SETTINGS_PRIVILEGED, MODIFY_AUDIO_ROUTING }) public int getVolumeGroupMinVolumeIndex(int groupId) { super.getVolumeGroupMinVolumeIndex_enforcePermission(); synchronized (VolumeStreamState.class) { @@ -4125,9 +4106,7 @@ public class AudioService extends IAudioService.Stub @Override @android.annotation.EnforcePermission(anyOf = { - android.Manifest.permission.MODIFY_AUDIO_ROUTING, - android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED - }) + MODIFY_AUDIO_ROUTING, MODIFY_AUDIO_SETTINGS_PRIVILEGED }) /** @see AudioDeviceVolumeManager#setDeviceVolume(VolumeInfo, AudioDeviceAttributes) * Part of service interface, check permissions and parameters here * Note calling package is for logging purposes only, not to be trusted @@ -4253,7 +4232,7 @@ public class AudioService extends IAudioService.Stub } /** @see AudioManager#getLastAudibleVolumeForVolumeGroup(int) */ - @android.annotation.EnforcePermission(android.Manifest.permission.QUERY_AUDIO_STATE) + @android.annotation.EnforcePermission(QUERY_AUDIO_STATE) public int getLastAudibleVolumeForVolumeGroup(int groupId) { super.getLastAudibleVolumeForVolumeGroup_enforcePermission(); synchronized (VolumeStreamState.class) { @@ -4318,16 +4297,14 @@ public class AudioService extends IAudioService.Stub return; } if ((streamType == AudioManager.STREAM_VOICE_CALL) && (index == 0) - && (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE) + && (mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE) != PackageManager.PERMISSION_GRANTED)) { Log.w(TAG, "Trying to call setStreamVolume() for STREAM_VOICE_CALL and index 0 without" + " MODIFY_PHONE_STATE callingPackage=" + callingPackage); return; } if ((streamType == AudioManager.STREAM_ASSISTANT) - && (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING) + && (mContext.checkCallingOrSelfPermission(MODIFY_AUDIO_ROUTING) != PackageManager.PERMISSION_GRANTED)) { Log.w(TAG, "Trying to call setStreamVolume() for STREAM_ASSISTANT without" + " MODIFY_AUDIO_ROUTING callingPackage=" + callingPackage); @@ -4348,7 +4325,7 @@ public class AudioService extends IAudioService.Stub canChangeMuteAndUpdateController); } - @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_ULTRASOUND) + @android.annotation.EnforcePermission(Manifest.permission.ACCESS_ULTRASOUND) /** @see AudioManager#isUltrasoundSupported() */ public boolean isUltrasoundSupported() { super.isUltrasoundSupported_enforcePermission(); @@ -4356,8 +4333,8 @@ public class AudioService extends IAudioService.Stub return AudioSystem.isUltrasoundSupported(); } - /** @see AudioManager#isHotwordStreamSupported() */ - @android.annotation.EnforcePermission(android.Manifest.permission.CAPTURE_AUDIO_HOTWORD) + /** @see AudioManager#isHotwordStreamSupported(boolean) */ + @android.annotation.EnforcePermission(CAPTURE_AUDIO_HOTWORD) public boolean isHotwordStreamSupported(boolean lookbackAudio) { super.isHotwordStreamSupported_enforcePermission(); try { @@ -4373,7 +4350,7 @@ public class AudioService extends IAudioService.Stub private boolean canChangeAccessibilityVolume() { synchronized (mAccessibilityServiceUidsLock) { if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( - android.Manifest.permission.CHANGE_ACCESSIBILITY_VOLUME)) { + Manifest.permission.CHANGE_ACCESSIBILITY_VOLUME)) { return true; } if (mAccessibilityServiceUids != null) { @@ -4874,7 +4851,7 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#forceVolumeControlStream(int) */ public void forceVolumeControlStream(int streamType, IBinder cb) { - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + if (mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { return; } @@ -5127,7 +5104,7 @@ public class AudioService extends IAudioService.Stub return; } if ((PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission( - android.Manifest.permission.CAPTURE_AUDIO_OUTPUT))) { + CAPTURE_AUDIO_OUTPUT))) { Log.w(TAG, "Trying to call forceRemoteSubmixFullVolume() without CAPTURE_AUDIO_OUTPUT"); return; } @@ -5174,8 +5151,7 @@ public class AudioService extends IAudioService.Stub return; } if (userId != UserHandle.getCallingUserId() && - mContext.checkPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, - pid, uid) + mContext.checkPermission(INTERACT_ACROSS_USERS_FULL, pid, uid) != PackageManager.PERMISSION_GRANTED) { return; } @@ -5216,7 +5192,7 @@ public class AudioService extends IAudioService.Stub return mMasterMute.get(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#setMasterMute(boolean, int) */ public void setMasterMute(boolean mute, int flags, String callingPackage, int userId, String attributionTag) { @@ -5252,9 +5228,7 @@ public class AudioService extends IAudioService.Stub @Override @android.annotation.EnforcePermission(anyOf = { - android.Manifest.permission.MODIFY_AUDIO_ROUTING, - android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED - }) + MODIFY_AUDIO_ROUTING, MODIFY_AUDIO_SETTINGS_PRIVILEGED }) /** * @see AudioDeviceVolumeManager#getDeviceVolume(VolumeInfo, AudioDeviceAttributes) */ @@ -5302,12 +5276,12 @@ public class AudioService extends IAudioService.Stub final boolean isPrivileged = Binder.getCallingUid() == Process.SYSTEM_UID || callingHasAudioSettingsPermission() - || (mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_ROUTING) + || (mContext.checkCallingPermission(MODIFY_AUDIO_ROUTING) == PackageManager.PERMISSION_GRANTED); return (mStreamStates[streamType].getMinIndex(isPrivileged) + 5) / 10; } - @android.annotation.EnforcePermission(android.Manifest.permission.QUERY_AUDIO_STATE) + @android.annotation.EnforcePermission(QUERY_AUDIO_STATE) /** Get last audible volume before stream was muted. */ public int getLastAudibleStreamVolume(int streamType) { super.getLastAudibleStreamVolume_enforcePermission(); @@ -5469,8 +5443,7 @@ public class AudioService extends IAudioService.Stub return; } if (userId != UserHandle.getCallingUserId() && - mContext.checkCallingOrSelfPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + mContext.checkCallingOrSelfPermission(INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED) { mmi.set(MediaMetrics.Property.EARLY_RETURN, "permission").record(); return; @@ -6060,7 +6033,7 @@ public class AudioService extends IAudioService.Stub } final boolean hasModifyPhoneStatePermission = mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE) + MODIFY_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; if ((mode == AudioSystem.MODE_IN_CALL || mode == AudioSystem.MODE_CALL_REDIRECT @@ -6267,7 +6240,7 @@ public class AudioService extends IAudioService.Stub mModeDispatchers.unregister(dispatcher); } - @android.annotation.EnforcePermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) + @android.annotation.EnforcePermission(CALL_AUDIO_INTERCEPTION) /** @see AudioManager#isPstnCallAudioInterceptable() */ public boolean isPstnCallAudioInterceptable() { @@ -6293,7 +6266,7 @@ public class AudioService extends IAudioService.Stub @Override public void setRttEnabled(boolean rttEnabled) { if (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE) + MODIFY_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: setRttEnabled from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); @@ -6585,8 +6558,7 @@ public class AudioService extends IAudioService.Stub ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED) .record(); } - final boolean isPrivileged = mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE) + final boolean isPrivileged = mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; final long ident = Binder.clearCallingIdentity(); try { @@ -6636,8 +6608,7 @@ public class AudioService extends IAudioService.Stub if (!checkAudioSettingsPermission("setSpeakerphoneOn()")) { return; } - final boolean isPrivileged = mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE) + final boolean isPrivileged = mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; // for logging only @@ -6704,7 +6675,7 @@ public class AudioService extends IAudioService.Stub } /** @see AudioManager#setA2dpSuspended(boolean) */ - @android.annotation.EnforcePermission(android.Manifest.permission.BLUETOOTH_STACK) + @android.annotation.EnforcePermission(BLUETOOTH_STACK) public void setA2dpSuspended(boolean enable) { super.setA2dpSuspended_enforcePermission(); final String eventSource = new StringBuilder("setA2dpSuspended(").append(enable) @@ -6714,7 +6685,7 @@ public class AudioService extends IAudioService.Stub } /** @see AudioManager#setA2dpSuspended(boolean) */ - @android.annotation.EnforcePermission(android.Manifest.permission.BLUETOOTH_STACK) + @android.annotation.EnforcePermission(BLUETOOTH_STACK) public void setLeAudioSuspended(boolean enable) { super.setLeAudioSuspended_enforcePermission(); final String eventSource = new StringBuilder("setLeAudioSuspended(").append(enable) @@ -6819,8 +6790,7 @@ public class AudioService extends IAudioService.Stub mmi.set(MediaMetrics.Property.EARLY_RETURN, "permission or systemReady").record(); return; } - final boolean isPrivileged = mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE) + final boolean isPrivileged = mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; final long ident = Binder.clearCallingIdentity(); try { @@ -6843,8 +6813,7 @@ public class AudioService extends IAudioService.Stub final String eventSource = new StringBuilder("stopBluetoothSco()") .append(") from u/pid:").append(uid).append("/") .append(pid).toString(); - final boolean isPrivileged = mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE) + final boolean isPrivileged = mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; final long ident = Binder.clearCallingIdentity(); try { @@ -7325,17 +7294,17 @@ public class AudioService extends IAudioService.Stub } private boolean callingOrSelfHasAudioSettingsPermission() { - return mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) + return mContext.checkCallingOrSelfPermission(MODIFY_AUDIO_SETTINGS) == PackageManager.PERMISSION_GRANTED; } private boolean callingHasAudioSettingsPermission() { - return mContext.checkCallingPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS) + return mContext.checkCallingPermission(MODIFY_AUDIO_SETTINGS) == PackageManager.PERMISSION_GRANTED; } private boolean hasAudioSettingsPermission(int uid, int pid) { - return mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS, pid, uid) + return mContext.checkPermission(MODIFY_AUDIO_SETTINGS, pid, uid) == PackageManager.PERMISSION_GRANTED; } @@ -7527,17 +7496,16 @@ public class AudioService extends IAudioService.Stub * @param register Whether the listener is to be registered or unregistered. If false, the * device adopts variable volume behavior. */ - @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING, - android.Manifest.permission.BLUETOOTH_PRIVILEGED }) + @RequiresPermission(anyOf = { MODIFY_AUDIO_ROUTING, BLUETOOTH_PRIVILEGED }) public void registerDeviceVolumeDispatcherForAbsoluteVolume(boolean register, IAudioDeviceVolumeDispatcher cb, String packageName, AudioDeviceAttributes device, List<VolumeInfo> volumes, boolean handlesVolumeAdjustment, @AudioManager.AbsoluteDeviceVolumeBehavior int deviceVolumeBehavior) { // verify permissions - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + if (mContext.checkCallingOrSelfPermission(MODIFY_AUDIO_ROUTING) != PackageManager.PERMISSION_GRANTED - && mContext.checkCallingOrSelfPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + && mContext.checkCallingOrSelfPermission(BLUETOOTH_PRIVILEGED) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException( "Missing MODIFY_AUDIO_ROUTING or BLUETOOTH_PRIVILEGED permissions"); @@ -7595,9 +7563,7 @@ public class AudioService extends IAudioService.Stub * @param deviceVolumeBehavior one of the device behaviors */ @android.annotation.EnforcePermission(anyOf = { - android.Manifest.permission.MODIFY_AUDIO_ROUTING, - android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED - }) + MODIFY_AUDIO_ROUTING, MODIFY_AUDIO_SETTINGS_PRIVILEGED }) public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device, @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior, @Nullable String pkgName) { // verify permissions @@ -7680,9 +7646,7 @@ public class AudioService extends IAudioService.Stub * @return the volume behavior for the device */ @android.annotation.EnforcePermission(anyOf = { - android.Manifest.permission.MODIFY_AUDIO_ROUTING, - android.Manifest.permission.QUERY_AUDIO_STATE, - android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED + MODIFY_AUDIO_ROUTING, QUERY_AUDIO_STATE, MODIFY_AUDIO_SETTINGS_PRIVILEGED }) public @AudioManager.DeviceVolumeBehavior int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) { @@ -7770,7 +7734,7 @@ public class AudioService extends IAudioService.Stub */ private static final byte[] DEFAULT_ARC_AUDIO_DESCRIPTOR = new byte[]{0x09, 0x7f, 0x07}; - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** * see AudioManager.setWiredDeviceConnectionState() */ @@ -7880,7 +7844,7 @@ public class AudioService extends IAudioService.Stub public @interface BtProfile {} - @android.annotation.EnforcePermission(android.Manifest.permission.BLUETOOTH_STACK) + @android.annotation.EnforcePermission(BLUETOOTH_STACK) /** * See AudioManager.handleBluetoothActiveDeviceChanged(...) */ @@ -10156,8 +10120,8 @@ public class AudioService extends IAudioService.Stub if (AudioAttributes.isSystemUsage(usage)) { if ((usage == AudioAttributes.USAGE_CALL_ASSISTANT && (audioAttributes.getAllFlags() & AudioAttributes.FLAG_CALL_REDIRECTION) != 0 - && callerHasPermission(Manifest.permission.CALL_AUDIO_INTERCEPTION)) - || callerHasPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)) { + && callerHasPermission(CALL_AUDIO_INTERCEPTION)) + || callerHasPermission(MODIFY_AUDIO_ROUTING)) { if (!isSupportedSystemUsage(usage)) { throw new IllegalArgumentException( "Unsupported usage " + AudioAttributes.usageToString(usage)); @@ -10175,8 +10139,8 @@ public class AudioService extends IAudioService.Stub && ((usage == AudioAttributes.USAGE_CALL_ASSISTANT && (audioAttributes.getAllFlags() & AudioAttributes.FLAG_CALL_REDIRECTION) != 0 - && callerHasPermission(Manifest.permission.CALL_AUDIO_INTERCEPTION)) - || callerHasPermission(Manifest.permission.MODIFY_AUDIO_ROUTING)); + && callerHasPermission(CALL_AUDIO_INTERCEPTION)) + || callerHasPermission(MODIFY_AUDIO_ROUTING)); } return true; } @@ -10207,7 +10171,7 @@ public class AudioService extends IAudioService.Stub if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) { if (AudioSystem.IN_VOICE_COMM_FOCUS_ID.equals(clientId)) { if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE)) { + MODIFY_PHONE_STATE)) { final String reason = "Invalid permission to (un)lock audio focus"; Log.e(TAG, reason, new Exception()); mmi.set(MediaMetrics.Property.EARLY_RETURN, reason) @@ -10239,10 +10203,9 @@ public class AudioService extends IAudioService.Stub // does caller have system privileges to bypass HardeningEnforcer boolean permissionOverridesCheck = false; - if ((mContext.checkCallingOrSelfPermission( - Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + if ((mContext.checkCallingOrSelfPermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) == PackageManager.PERMISSION_GRANTED) - || (mContext.checkCallingOrSelfPermission(Manifest.permission.MODIFY_AUDIO_ROUTING) + || (mContext.checkCallingOrSelfPermission(MODIFY_AUDIO_ROUTING) == PackageManager.PERMISSION_GRANTED)) { permissionOverridesCheck = true; } else if (uid < UserHandle.AID_APP_START) { @@ -10376,7 +10339,7 @@ public class AudioService extends IAudioService.Stub * such as another freeze currently used. */ @Override - @EnforcePermission("android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED") + @EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) public boolean enterAudioFocusFreezeForTest(IBinder cb, int[] exemptedUids) { super.enterAudioFocusFreezeForTest_enforcePermission(); Objects.requireNonNull(exemptedUids); @@ -10392,7 +10355,7 @@ public class AudioService extends IAudioService.Stub * such as the freeze already having ended, or not started. */ @Override - @EnforcePermission("android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED") + @EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) public boolean exitAudioFocusFreezeForTest(IBinder cb) { super.exitAudioFocusFreezeForTest_enforcePermission(); Objects.requireNonNull(cb); @@ -10438,8 +10401,7 @@ public class AudioService extends IAudioService.Stub private static final boolean SPATIAL_AUDIO_ENABLED_DEFAULT = true; private void enforceModifyDefaultAudioEffectsPermission() { - if (mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + if (mContext.checkCallingOrSelfPermission(MODIFY_DEFAULT_AUDIO_EFFECTS) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Missing MODIFY_DEFAULT_AUDIO_EFFECTS permission"); } @@ -10463,7 +10425,7 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.isAvailable(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#isAvailableForDevice(AudioDeviceAttributes) */ public boolean isSpatializerAvailableForDevice(@NonNull AudioDeviceAttributes device) { super.isSpatializerAvailableForDevice_enforcePermission(); @@ -10471,7 +10433,7 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.isAvailableForDevice(Objects.requireNonNull(device)); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#hasHeadTracker(AudioDeviceAttributes) */ public boolean hasHeadTracker(@NonNull AudioDeviceAttributes device) { super.hasHeadTracker_enforcePermission(); @@ -10479,7 +10441,7 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.hasHeadTracker(Objects.requireNonNull(device)); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setHeadTrackerEnabled(boolean, AudioDeviceAttributes) */ public void setHeadTrackerEnabled(boolean enabled, @NonNull AudioDeviceAttributes device) { super.setHeadTrackerEnabled_enforcePermission(); @@ -10487,7 +10449,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.setHeadTrackerEnabled(enabled, Objects.requireNonNull(device)); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#isHeadTrackerEnabled(AudioDeviceAttributes) */ public boolean isHeadTrackerEnabled(@NonNull AudioDeviceAttributes device) { super.isHeadTrackerEnabled_enforcePermission(); @@ -10500,7 +10462,7 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.isHeadTrackerAvailable(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setSpatializerEnabled(boolean) */ public void setSpatializerEnabled(boolean enabled) { super.setSpatializerEnabled_enforcePermission(); @@ -10530,7 +10492,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.unregisterStateCallback(cb); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#SpatializerHeadTrackingDispatcherStub */ public void registerSpatializerHeadTrackingCallback( @NonNull ISpatializerHeadTrackingModeCallback cb) { @@ -10540,7 +10502,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.registerHeadTrackingModeCallback(cb); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#SpatializerHeadTrackingDispatcherStub */ public void unregisterSpatializerHeadTrackingCallback( @NonNull ISpatializerHeadTrackingModeCallback cb) { @@ -10557,7 +10519,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.registerHeadTrackerAvailableCallback(cb, register); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setOnHeadToSoundstagePoseUpdatedListener */ public void registerHeadToSoundstagePoseCallback( @NonNull ISpatializerHeadToSoundStagePoseCallback cb) { @@ -10567,7 +10529,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.registerHeadToSoundstagePoseCallback(cb); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#clearOnHeadToSoundstagePoseUpdatedListener */ public void unregisterHeadToSoundstagePoseCallback( @NonNull ISpatializerHeadToSoundStagePoseCallback cb) { @@ -10577,7 +10539,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.unregisterHeadToSoundstagePoseCallback(cb); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#getSpatializerCompatibleAudioDevices() */ public @NonNull List<AudioDeviceAttributes> getSpatializerCompatibleAudioDevices() { super.getSpatializerCompatibleAudioDevices_enforcePermission(); @@ -10585,7 +10547,7 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.getCompatibleAudioDevices(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#addSpatializerCompatibleAudioDevice(AudioDeviceAttributes) */ public void addSpatializerCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { super.addSpatializerCompatibleAudioDevice_enforcePermission(); @@ -10594,7 +10556,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.addCompatibleAudioDevice(ada); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#removeSpatializerCompatibleAudioDevice(AudioDeviceAttributes) */ public void removeSpatializerCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) { super.removeSpatializerCompatibleAudioDevice_enforcePermission(); @@ -10603,7 +10565,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.removeCompatibleAudioDevice(ada); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#getSupportedHeadTrackingModes() */ public int[] getSupportedHeadTrackingModes() { super.getSupportedHeadTrackingModes_enforcePermission(); @@ -10611,7 +10573,7 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.getSupportedHeadTrackingModes(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#getHeadTrackingMode() */ public int getActualHeadTrackingMode() { super.getActualHeadTrackingMode_enforcePermission(); @@ -10619,7 +10581,7 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.getActualHeadTrackingMode(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#getDesiredHeadTrackingMode() */ public int getDesiredHeadTrackingMode() { super.getDesiredHeadTrackingMode_enforcePermission(); @@ -10627,7 +10589,7 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.getDesiredHeadTrackingMode(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setGlobalTransform */ public void setSpatializerGlobalTransform(@NonNull float[] transform) { super.setSpatializerGlobalTransform_enforcePermission(); @@ -10636,7 +10598,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.setGlobalTransform(transform); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#recenterHeadTracker() */ public void recenterHeadTracker() { super.recenterHeadTracker_enforcePermission(); @@ -10644,7 +10606,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.recenterHeadTracker(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setDesiredHeadTrackingMode */ public void setDesiredHeadTrackingMode(@Spatializer.HeadTrackingModeSet int mode) { super.setDesiredHeadTrackingMode_enforcePermission(); @@ -10660,7 +10622,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.setDesiredHeadTrackingMode(mode); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setEffectParameter */ public void setSpatializerParameter(int key, @NonNull byte[] value) { super.setSpatializerParameter_enforcePermission(); @@ -10669,7 +10631,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.setEffectParameter(key, value); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#getEffectParameter */ public void getSpatializerParameter(int key, @NonNull byte[] value) { super.getSpatializerParameter_enforcePermission(); @@ -10678,7 +10640,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.getEffectParameter(key, value); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#getOutput */ public int getSpatializerOutput() { super.getSpatializerOutput_enforcePermission(); @@ -10686,7 +10648,7 @@ public class AudioService extends IAudioService.Stub return mSpatializerHelper.getOutput(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#setOnSpatializerOutputChangedListener */ public void registerSpatializerOutputCallback(ISpatializerOutputCallback cb) { super.registerSpatializerOutputCallback_enforcePermission(); @@ -10695,7 +10657,7 @@ public class AudioService extends IAudioService.Stub mSpatializerHelper.registerSpatializerOutputCallback(cb); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS) + @android.annotation.EnforcePermission(MODIFY_DEFAULT_AUDIO_EFFECTS) /** @see Spatializer#clearOnSpatializerOutputChangedListener */ public void unregisterSpatializerOutputCallback(ISpatializerOutputCallback cb) { super.unregisterSpatializerOutputCallback_enforcePermission(); @@ -10742,7 +10704,7 @@ public class AudioService extends IAudioService.Stub private boolean isBluetoothPrividged() { return PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( - android.Manifest.permission.BLUETOOTH_CONNECT) + Manifest.permission.BLUETOOTH_CONNECT) || Binder.getCallingUid() == Process.SYSTEM_UID; } @@ -10949,7 +10911,7 @@ public class AudioService extends IAudioService.Stub }); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#getMutingExpectedDevice */ public @Nullable AudioDeviceAttributes getMutingExpectedDevice() { super.getMutingExpectedDevice_enforcePermission(); @@ -11000,7 +10962,7 @@ public class AudioService extends IAudioService.Stub final RemoteCallbackList<IMuteAwaitConnectionCallback> mMuteAwaitConnectionDispatchers = new RemoteCallbackList<IMuteAwaitConnectionCallback>(); - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#registerMuteAwaitConnectionCallback */ public void registerMuteAwaitConnectionDispatcher(@NonNull IMuteAwaitConnectionCallback cb, boolean register) { @@ -11175,7 +11137,7 @@ public class AudioService extends IAudioService.Stub } } - @android.annotation.EnforcePermission(android.Manifest.permission.REMOTE_AUDIO_PLAYBACK) + @android.annotation.EnforcePermission(Manifest.permission.REMOTE_AUDIO_PLAYBACK) @Override public void setRingtonePlayer(IRingtonePlayer player) { setRingtonePlayer_enforcePermission(); @@ -11376,7 +11338,6 @@ public class AudioService extends IAudioService.Stub @Override @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) - @AudioDeviceCategory public boolean isBluetoothAudioDeviceCategoryFixed(@NonNull String address) { super.isBluetoothAudioDeviceCategoryFixed_enforcePermission(); if (!automaticBtDeviceType()) { @@ -11892,7 +11853,7 @@ public class AudioService extends IAudioService.Stub } private void enforceVolumeController(String action) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE, + mContext.enforceCallingOrSelfPermission(Manifest.permission.STATUS_BAR_SERVICE, "Only SystemUI can " + action); } @@ -12396,8 +12357,8 @@ public class AudioService extends IAudioService.Stub } if (requireCaptureAudioOrMediaOutputPerm - && !callerHasPermission(android.Manifest.permission.CAPTURE_MEDIA_OUTPUT) - && !callerHasPermission(android.Manifest.permission.CAPTURE_AUDIO_OUTPUT)) { + && !callerHasPermission(CAPTURE_MEDIA_OUTPUT) + && !callerHasPermission(CAPTURE_AUDIO_OUTPUT)) { Log.e(TAG, "Privileged audio capture requires CAPTURE_MEDIA_OUTPUT or " + "CAPTURE_AUDIO_OUTPUT system permission"); return false; @@ -12405,7 +12366,7 @@ public class AudioService extends IAudioService.Stub if (voiceCommunicationCaptureMixes != null && voiceCommunicationCaptureMixes.size() > 0) { if (!callerHasPermission( - android.Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT)) { + Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT)) { Log.e(TAG, "Audio capture for voice communication requires " + "CAPTURE_VOICE_COMMUNICATION_OUTPUT system permission"); return false; @@ -12422,13 +12383,12 @@ public class AudioService extends IAudioService.Stub } if (requireModifyRouting - && !callerHasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)) { + && !callerHasPermission(MODIFY_AUDIO_ROUTING)) { Log.e(TAG, "Can not capture audio without MODIFY_AUDIO_ROUTING"); return false; } - if (requireCallAudioInterception - && !callerHasPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION)) { + if (requireCallAudioInterception && !callerHasPermission(CALL_AUDIO_INTERCEPTION)) { Log.e(TAG, "Can not capture audio without CALL_AUDIO_INTERCEPTION"); return false; } @@ -12541,7 +12501,7 @@ public class AudioService extends IAudioService.Stub // permission check final boolean hasPermissionForPolicy = (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING)); + MODIFY_AUDIO_ROUTING)); if (!hasPermissionForPolicy) { Slog.w(TAG, errorMsg + " for pid " + + Binder.getCallingPid() + " / uid " @@ -12625,7 +12585,7 @@ public class AudioService extends IAudioService.Stub * @return {@link AudioManager#SUCCESS} iff the mixing rules were updated successfully, * {@link AudioManager#ERROR} otherwise. */ - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) public int updateMixingRulesForPolicy( @NonNull AudioMix[] mixesToUpdate, @NonNull AudioMixingRule[] updatedMixingRules, @@ -12753,7 +12713,7 @@ public class AudioService extends IAudioService.Stub return AudioManager.SUCCESS; } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioPolicy#getFocusStack() */ public List<AudioFocusInfo> getFocusStack() { super.getFocusStack_enforcePermission(); @@ -12779,8 +12739,7 @@ public class AudioService extends IAudioService.Stub /** * see {@link AudioPolicy#setFadeManagerConfigurationForFocusLoss(FadeManagerConfiguration)} */ - @android.annotation.EnforcePermission( - android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int setFadeManagerConfigurationForFocusLoss( @NonNull FadeManagerConfiguration fmcForFocusLoss) { super.setFadeManagerConfigurationForFocusLoss_enforcePermission(); @@ -12796,8 +12755,7 @@ public class AudioService extends IAudioService.Stub /** * see {@link AudioPolicy#clearFadeManagerConfigurationForFocusLoss()} */ - @android.annotation.EnforcePermission( - android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int clearFadeManagerConfigurationForFocusLoss() { super.clearFadeManagerConfigurationForFocusLoss_enforcePermission(); ensureFadeManagerConfigIsEnabled(); @@ -12808,8 +12766,7 @@ public class AudioService extends IAudioService.Stub /** * see {@link AudioPolicy#getFadeManagerConfigurationForFocusLoss()} */ - @android.annotation.EnforcePermission( - android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) public FadeManagerConfiguration getFadeManagerConfigurationForFocusLoss() { super.getFadeManagerConfigurationForFocusLoss_enforcePermission(); ensureFadeManagerConfigIsEnabled(); @@ -12970,7 +12927,7 @@ public class AudioService extends IAudioService.Stub /** @see AudioManager#supportsBluetoothVariableLatency() */ - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) public boolean supportsBluetoothVariableLatency() { super.supportsBluetoothVariableLatency_enforcePermission(); try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { @@ -12979,7 +12936,7 @@ public class AudioService extends IAudioService.Stub } /** @see AudioManager#setBluetoothVariableLatencyEnabled(boolean) */ - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) public void setBluetoothVariableLatencyEnabled(boolean enabled) { super.setBluetoothVariableLatencyEnabled_enforcePermission(); try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { @@ -12988,7 +12945,7 @@ public class AudioService extends IAudioService.Stub } /** @see AudioManager#isBluetoothVariableLatencyEnabled(boolean) */ - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) public boolean isBluetoothVariableLatencyEnabled() { super.isBluetoothVariableLatencyEnabled_enforcePermission(); try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { @@ -13075,7 +13032,7 @@ public class AudioService extends IAudioService.Stub public void registerRecordingCallback(IRecordingConfigDispatcher rcdb) { final boolean isPrivileged = (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING)); + MODIFY_AUDIO_ROUTING)); mRecordMonitor.registerRecordingCallback(rcdb, isPrivileged); } @@ -13086,7 +13043,7 @@ public class AudioService extends IAudioService.Stub public List<AudioRecordingConfiguration> getActiveRecordingConfigurations() { final boolean isPrivileged = Binder.getCallingUid() == Process.SYSTEM_UID || (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING)); + MODIFY_AUDIO_ROUTING)); return mRecordMonitor.getActiveRecordingConfigurations(isPrivileged); } @@ -13122,7 +13079,7 @@ public class AudioService extends IAudioService.Stub public void registerPlaybackCallback(IPlaybackConfigDispatcher pcdb) { final boolean isPrivileged = (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING)); + MODIFY_AUDIO_ROUTING)); mPlaybackMonitor.registerPlaybackCallback(pcdb, isPrivileged); } @@ -13133,7 +13090,7 @@ public class AudioService extends IAudioService.Stub public List<AudioPlaybackConfiguration> getActivePlaybackConfigurations() { final boolean isPrivileged = (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING)); + MODIFY_AUDIO_ROUTING)); return mPlaybackMonitor.getActivePlaybackConfigurations(isPrivileged); } @@ -13724,8 +13681,7 @@ public class AudioService extends IAudioService.Stub * see {@link AudioManager#dispatchAudioFocusChangeWithFade(AudioFocusInfo, int, AudioPolicy, * List, FadeManagerConfiguration)} */ - @android.annotation.EnforcePermission( - android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) public int dispatchFocusChangeWithFade(AudioFocusInfo afi, int focusChange, IAudioPolicyCallback pcb, List<AudioFocusInfo> otherActiveAfis, FadeManagerConfiguration transientFadeMgrConfig) { @@ -13762,8 +13718,7 @@ public class AudioService extends IAudioService.Stub /** * @see AudioManager#shouldNotificationSoundPlay(AudioAttributes) */ - @android.annotation.EnforcePermission( - android.Manifest.permission.QUERY_AUDIO_STATE) + @android.annotation.EnforcePermission(QUERY_AUDIO_STATE) public boolean shouldNotificationSoundPlay(@NonNull final AudioAttributes aa) { super.shouldNotificationSoundPlay_enforcePermission(); Objects.requireNonNull(aa); @@ -13823,12 +13778,10 @@ public class AudioService extends IAudioService.Stub new HashMap<IBinder, AsdProxy>(); private void checkMonitorAudioServerStatePermission() { - if (!(mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_PHONE_STATE) == - PackageManager.PERMISSION_GRANTED || - mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MODIFY_AUDIO_ROUTING) == - PackageManager.PERMISSION_GRANTED)) { + if (!(mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE) + == PackageManager.PERMISSION_GRANTED + || mContext.checkCallingOrSelfPermission(MODIFY_AUDIO_ROUTING) + == PackageManager.PERMISSION_GRANTED)) { throw new SecurityException("Not allowed to monitor audioserver state"); } } @@ -13923,7 +13876,7 @@ public class AudioService extends IAudioService.Stub AudioSystem.setAudioHalPids(pidsArray); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) //====================== // Multi Audio Focus //====================== @@ -13964,7 +13917,7 @@ public class AudioService extends IAudioService.Stub * or the delay is not in range of {@link #getMaxAdditionalOutputDeviceDelay()}. */ @Override - //@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + //@RequiresPermission(MODIFY_AUDIO_ROUTING) public boolean setAdditionalOutputDeviceDelay( @NonNull AudioDeviceAttributes device, @IntRange(from = 0) long delayMillis) { Objects.requireNonNull(device, "device must not be null"); @@ -14037,7 +13990,7 @@ public class AudioService extends IAudioService.Stub return delayMillis; } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#addAssistantServicesUids(int []) */ @Override public void addAssistantServicesUids(int [] assistantUids) { @@ -14050,7 +14003,7 @@ public class AudioService extends IAudioService.Stub } } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#removeAssistantServicesUids(int []) */ @Override public void removeAssistantServicesUids(int [] assistantUids) { @@ -14062,7 +14015,7 @@ public class AudioService extends IAudioService.Stub } } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#getAssistantServicesUids() */ @Override public int[] getAssistantServicesUids() { @@ -14075,7 +14028,7 @@ public class AudioService extends IAudioService.Stub return assistantUids; } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#setActiveAssistantServiceUids(int []) */ @Override public void setActiveAssistantServiceUids(int [] activeAssistantUids) { @@ -14088,7 +14041,7 @@ public class AudioService extends IAudioService.Stub updateActiveAssistantServiceUids(); } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + @android.annotation.EnforcePermission(MODIFY_AUDIO_ROUTING) /** @see AudioManager#getActiveAssistantServiceUids() */ @Override public int[] getActiveAssistantServiceUids() { diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index 9610034caf01..e28ae952e65a 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -856,11 +856,12 @@ public class SoundDoseHelper { pw.println(); } - /*package*/void reset() { + /*package*/void reset(boolean resetISoundDose) { Log.d(TAG, "Reset the sound dose helper"); - mSoundDose.compareAndExchange(/*expectedValue=*/null, - AudioSystem.getSoundDoseInterface(mSoundDoseCallback)); + if (resetISoundDose) { + mSoundDose.set(AudioSystem.getSoundDoseInterface(mSoundDoseCallback)); + } synchronized (mCsdStateLock) { try { @@ -972,7 +973,7 @@ public class SoundDoseHelper { } } - reset(); + reset(/*resetISoundDose=*/false); } private void onConfigureSafeMedia(boolean force, String caller) { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java index d876a381ca7a..3c3bdd5b69f6 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecMessageValidator.java @@ -269,8 +269,7 @@ public class HdmiCecMessageValidator { addValidationInfo(Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR, oneByteValidator, ADDR_NOT_UNREGISTERED, ADDR_DIRECT); addValidationInfo(Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE, - new MinimumOneByteRangeValidator(0x00, 0x01), - ADDR_NOT_UNREGISTERED, ADDR_ALL); + new SingleByteRangeValidator(0x00, 0x01), ADDR_AUDIO_SYSTEM, ADDR_ALL); addValidationInfo(Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS, new SingleByteRangeValidator(0x00, 0x01), ADDR_NOT_UNREGISTERED, ADDR_DIRECT); diff --git a/services/core/java/com/android/server/hdmi/HdmiUtils.java b/services/core/java/com/android/server/hdmi/HdmiUtils.java index 5646e1b9a9ef..0688fbf358ad 100644 --- a/services/core/java/com/android/server/hdmi/HdmiUtils.java +++ b/services/core/java/com/android/server/hdmi/HdmiUtils.java @@ -175,14 +175,15 @@ final class HdmiUtils { * * @param logicalAddress the logical address to verify * @param deviceType the device type to check - * @throws IllegalArgumentException */ - static void verifyAddressType(int logicalAddress, int deviceType) { + static boolean verifyAddressType(int logicalAddress, int deviceType) { List<Integer> actualDeviceTypes = getTypeFromAddress(logicalAddress); if (!actualDeviceTypes.contains(deviceType)) { - throw new IllegalArgumentException("Device type missmatch:[Expected:" + deviceType - + ", Actual:" + actualDeviceTypes); + Slog.w(TAG,"Device type mismatch:[Expected:" + deviceType + + ", Actual:" + actualDeviceTypes + "]"); + return false; } + return true; } /** diff --git a/services/core/java/com/android/server/hdmi/RequestArcAction.java b/services/core/java/com/android/server/hdmi/RequestArcAction.java index 54c8c00b8889..58e146ecaa78 100644 --- a/services/core/java/com/android/server/hdmi/RequestArcAction.java +++ b/services/core/java/com/android/server/hdmi/RequestArcAction.java @@ -19,6 +19,7 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; +import android.util.Slog; /** * Base feature action class for <Request ARC Initiation>/<Request ARC Termination>. @@ -38,13 +39,14 @@ abstract class RequestArcAction extends HdmiCecFeatureAction { * @param source {@link HdmiCecLocalDevice} instance * @param avrAddress address of AV receiver. It should be AUDIO_SYSTEM type * @param callback callback to inform about the status of the action - * @throws IllegalArgumentException if device type of sourceAddress and avrAddress - * is invalid */ RequestArcAction(HdmiCecLocalDevice source, int avrAddress, IHdmiControlCallback callback) { super(source, callback); - HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV); - HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + if (!HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV) || + !HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)) { + Slog.w(TAG, "Device type mismatch, stop the action."); + finish(); + } mAvrAddress = avrAddress; } diff --git a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java index 32e274ece9ab..5ab22e1dcd61 100644 --- a/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java +++ b/services/core/java/com/android/server/hdmi/SetArcTransmissionStateAction.java @@ -47,8 +47,11 @@ final class SetArcTransmissionStateAction extends HdmiCecFeatureAction { SetArcTransmissionStateAction(HdmiCecLocalDevice source, int avrAddress, boolean enabled) { super(source); - HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV); - HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + if (!HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV) || + !HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)) { + Slog.w(TAG, "Device type mismatch, stop the action."); + finish(); + } mAvrAddress = avrAddress; mEnabled = enabled; } diff --git a/services/core/java/com/android/server/hdmi/SystemAudioAction.java b/services/core/java/com/android/server/hdmi/SystemAudioAction.java index e96963b9ae3f..f14cda1e6509 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioAction.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioAction.java @@ -20,6 +20,7 @@ import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; import android.hardware.tv.cec.V1_0.SendMessageResult; +import android.util.Slog; import java.util.List; @@ -56,12 +57,14 @@ abstract class SystemAudioAction extends HdmiCecFeatureAction { * @param avrAddress logical address of AVR device * @param targetStatus Whether to enable the system audio mode or not * @param callback callback interface to be notified when it's done - * @throws IllegalArgumentException if device type of sourceAddress and avrAddress is invalid */ SystemAudioAction(HdmiCecLocalDevice source, int avrAddress, boolean targetStatus, IHdmiControlCallback callback) { super(source, callback); - HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); + if (!HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)) { + Slog.w(TAG, "Device type mismatch, stop the action."); + finish(); + } mAvrLogicalAddress = avrAddress; mTargetAudioStatus = targetStatus; } diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java index 99148c4ea114..08a938731dae 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromAvr.java @@ -19,12 +19,14 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiControlManager; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; +import android.util.Slog; /** * Feature action that handles System Audio initiated by AVR devices. */ // Seq #33 final class SystemAudioActionFromAvr extends SystemAudioAction { + private static final String TAG = "SystemAudioActionFromAvr"; /** * Constructor * @@ -32,12 +34,14 @@ final class SystemAudioActionFromAvr extends SystemAudioAction { * @param avrAddress logical address of AVR device * @param targetStatus Whether to enable the system audio mode or not * @param callback callback interface to be notified when it's done - * @throws IllegalArgumentException if device type of tvAddress and avrAddress is invalid */ SystemAudioActionFromAvr(HdmiCecLocalDevice source, int avrAddress, boolean targetStatus, IHdmiControlCallback callback) { super(source, avrAddress, targetStatus, callback); - HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV); + if (!HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV)) { + Slog.w(TAG, "Device type mismatch, stop the action."); + finish(); + } } @Override diff --git a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java index 5c0c272f59e0..675aa3171fbd 100644 --- a/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java +++ b/services/core/java/com/android/server/hdmi/SystemAudioActionFromTv.java @@ -18,13 +18,14 @@ package com.android.server.hdmi; import android.hardware.hdmi.HdmiDeviceInfo; import android.hardware.hdmi.IHdmiControlCallback; +import android.util.Slog; /** * Feature action that handles System Audio initiated by TV devices. */ final class SystemAudioActionFromTv extends SystemAudioAction { - + private static final String TAG = "SystemAudioActionFromTv"; /** * Constructor * @@ -32,12 +33,14 @@ final class SystemAudioActionFromTv extends SystemAudioAction { * @param avrAddress logical address of AVR device * @param targetStatus Whether to enable the system audio mode or not * @param callback callback interface to be notified when it's done - * @throws IllegalArgumentException if device type of tvAddress is invalid */ SystemAudioActionFromTv(HdmiCecLocalDevice sourceAddress, int avrAddress, boolean targetStatus, IHdmiControlCallback callback) { super(sourceAddress, avrAddress, targetStatus, callback); - HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV); + if (!HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV)) { + Slog.w(TAG, "Device type mismatch, stop the action."); + finish(); + } } @Override diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 976399d3917a..5843d72f346a 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -4098,17 +4098,17 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (additionalSubtypeMap != newAdditionalSubtypeMap) { AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap, settings.getMethodMap()); - final InputMethodSettings newSettings = queryInputMethodServicesInternal(mContext, - userId, AdditionalSubtypeMapRepository.get(userId), - DirectBootAwareness.AUTO); - InputMethodSettingsRepository.put(userId, newSettings); - if (isCurrentUser) { - final long ident = Binder.clearCallingIdentity(); - try { + final long ident = Binder.clearCallingIdentity(); + try { + final InputMethodSettings newSettings = queryInputMethodServicesInternal( + mContext, userId, AdditionalSubtypeMapRepository.get(userId), + DirectBootAwareness.AUTO); + InputMethodSettingsRepository.put(userId, newSettings); + if (isCurrentUser) { postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */); - } finally { - Binder.restoreCallingIdentity(ident); } + } finally { + Binder.restoreCallingIdentity(ident); } } } @@ -4969,6 +4969,12 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. final int flags = PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS | directBootAwarenessFlags; + + // Beware that package visibility filtering will be enforced based on the effective calling + // identity (Binder.getCallingUid()), but our use case always expect Binder.getCallingUid() + // to return Process.SYSTEM_UID here. The actual filtering is implemented separately with + // canCallerAccessInputMethod(). + // TODO(b/343108534): Use PackageManagerInternal#queryIntentServices() to pass SYSTEM_UID. final List<ResolveInfo> services = userAwareContext.getPackageManager().queryIntentServices( new Intent(InputMethod.SERVICE_INTERFACE), PackageManager.ResolveInfoFlags.of(flags)); diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java index d932bd4e6d20..563f93e96331 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java +++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java @@ -434,7 +434,7 @@ class LocaleManagerBackupHelper { ATTR_PACKAGE_NAME); String languageTags = parser.getAttributeValue(/* namespace= */ null, ATTR_LOCALES); boolean delegateSelector = parser.getAttributeBoolean(/* namespace= */ null, - ATTR_DELEGATE_SELECTOR); + ATTR_DELEGATE_SELECTOR, false); if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(languageTags)) { LocalesInfo localesInfo = new LocalesInfo(languageTags, delegateSelector); diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java index 1bc2a5eb1351..363684f618cc 100644 --- a/services/core/java/com/android/server/media/MediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.content.ComponentName; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; +import android.media.MediaRouter2; import android.media.RouteDiscoveryPreference; import android.media.RoutingSessionInfo; import android.os.Bundle; @@ -172,4 +173,59 @@ abstract class MediaRoute2Provider { @NonNull RoutingSessionInfo sessionInfo); void onRequestFailed(@NonNull MediaRoute2Provider provider, long requestId, int reason); } + + /** + * Holds session creation or transfer initiation information for a transfer in flight. + * + * <p>The initiator app is typically also the {@link RoutingSessionInfo#getClientPackageName() + * client app}, with the exception of the {@link MediaRouter2#getSystemController() system + * routing session} which is exceptional in that it's shared among all apps. + * + * <p>For the system routing session, the initiator app is the one that programmatically + * triggered the transfer (for example, via {@link MediaRouter2#transferTo}), or the target app + * of the proxy router that did the transfer. + * + * @see MediaRouter2.RoutingController#wasTransferInitiatedBySelf() + * @see RoutingSessionInfo#getTransferInitiatorPackageName() + * @see RoutingSessionInfo#getTransferInitiatorUserHandle() + */ + protected static class SessionCreationOrTransferRequest { + + /** + * The id of the request, or {@link + * android.media.MediaRoute2ProviderService#REQUEST_ID_NONE} if unknown. + */ + public final long mRequestId; + + /** The {@link MediaRoute2Info#getId() id} of the target route. */ + @NonNull public final String mTargetRouteId; + + @RoutingSessionInfo.TransferReason public final int mTransferReason; + + /** The {@link android.os.UserHandle} on which the initiator app is running. */ + @NonNull public final UserHandle mTransferInitiatorUserHandle; + + @NonNull public final String mTransferInitiatorPackageName; + + SessionCreationOrTransferRequest( + long requestId, + @NonNull String routeId, + @RoutingSessionInfo.TransferReason int transferReason, + @NonNull UserHandle transferInitiatorUserHandle, + @NonNull String transferInitiatorPackageName) { + mRequestId = requestId; + mTargetRouteId = routeId; + mTransferReason = transferReason; + mTransferInitiatorUserHandle = transferInitiatorUserHandle; + mTransferInitiatorPackageName = transferInitiatorPackageName; + } + + public boolean isTargetRoute(@Nullable MediaRoute2Info route2Info) { + return route2Info != null && mTargetRouteId.equals(route2Info.getId()); + } + + public boolean isTargetRouteIdInList(@NonNull List<String> routesList) { + return routesList.stream().anyMatch(mTargetRouteId::equals); + } + } } diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 6ce3ab4b2d65..76930a003e46 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -79,12 +79,15 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { new AudioManagerBroadcastReceiver(); private final Object mRequestLock = new Object(); + @GuardedBy("mRequestLock") - private volatile SessionCreationRequest mPendingSessionCreationRequest; + private volatile SessionCreationOrTransferRequest mPendingSessionCreationOrTransferRequest; private final Object mTransferLock = new Object(); + @GuardedBy("mTransferLock") - @Nullable private volatile SessionCreationRequest mPendingTransferRequest; + @Nullable + private volatile SessionCreationOrTransferRequest mPendingTransferRequest; SystemMediaRoute2Provider(Context context, UserHandle user, Looper looper) { super(COMPONENT_NAME); @@ -180,12 +183,14 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { synchronized (mRequestLock) { // Handle the previous request as a failure if exists. - if (mPendingSessionCreationRequest != null) { - mCallback.onRequestFailed(this, mPendingSessionCreationRequest.mRequestId, + if (mPendingSessionCreationOrTransferRequest != null) { + mCallback.onRequestFailed( + /* provider= */ this, + mPendingSessionCreationOrTransferRequest.mRequestId, MediaRoute2ProviderService.REASON_UNKNOWN_ERROR); } - mPendingSessionCreationRequest = - new SessionCreationRequest( + mPendingSessionCreationOrTransferRequest = + new SessionCreationOrTransferRequest( requestId, routeId, RoutingSessionInfo.TRANSFER_REASON_FALLBACK, @@ -247,7 +252,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) { synchronized (mTransferLock) { mPendingTransferRequest = - new SessionCreationRequest( + new SessionCreationOrTransferRequest( requestId, routeId, transferReason, @@ -438,7 +443,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { boolean isTransferringToTheSelectedRoute = mPendingTransferRequest.isTargetRoute(selectedRoute); boolean canBePotentiallyTransferred = - mPendingTransferRequest.isInsideOfRoutesList(transferableRoutes); + mPendingTransferRequest.isTargetRouteIdInList(transferableRoutes); if (isTransferringToTheSelectedRoute) { transferReason = mPendingTransferRequest.mTransferReason; @@ -492,20 +497,20 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { @GuardedBy("mRequestLock") private void reportPendingSessionRequestResultLockedIfNeeded( RoutingSessionInfo newSessionInfo) { - if (mPendingSessionCreationRequest == null) { + if (mPendingSessionCreationOrTransferRequest == null) { // No pending request, nothing to report. return; } - long pendingRequestId = mPendingSessionCreationRequest.mRequestId; - if (TextUtils.equals(mSelectedRouteId, mPendingSessionCreationRequest.mRouteId)) { + long pendingRequestId = mPendingSessionCreationOrTransferRequest.mRequestId; + if (mPendingSessionCreationOrTransferRequest.mTargetRouteId.equals(mSelectedRouteId)) { if (DEBUG) { Slog.w( TAG, "Session creation success to route " - + mPendingSessionCreationRequest.mRouteId); + + mPendingSessionCreationOrTransferRequest.mTargetRouteId); } - mPendingSessionCreationRequest = null; + mPendingSessionCreationOrTransferRequest = null; mCallback.onSessionCreated(this, pendingRequestId, newSessionInfo); } else { boolean isRequestedRouteConnectedBtRoute = isRequestedRouteConnectedBtRoute(); @@ -515,16 +520,16 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { Slog.w( TAG, "Session creation failed to route " - + mPendingSessionCreationRequest.mRouteId); + + mPendingSessionCreationOrTransferRequest.mTargetRouteId); } - mPendingSessionCreationRequest = null; + mPendingSessionCreationOrTransferRequest = null; mCallback.onRequestFailed( this, pendingRequestId, MediaRoute2ProviderService.REASON_UNKNOWN_ERROR); } else if (DEBUG) { Slog.w( TAG, "Session creation waiting state to route " - + mPendingSessionCreationRequest.mRouteId); + + mPendingSessionCreationOrTransferRequest.mTargetRouteId); } } } @@ -535,7 +540,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { // where two BT routes are active so the transferable routes list is empty. // See b/307723189 for context for (MediaRoute2Info btRoute : mBluetoothRouteController.getAllBluetoothRoutes()) { - if (TextUtils.equals(btRoute.getId(), mPendingSessionCreationRequest.mRouteId)) { + if (TextUtils.equals( + btRoute.getId(), mPendingSessionCreationOrTransferRequest.mTargetRouteId)) { return true; } } @@ -585,51 +591,6 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { mBluetoothRouteController.getClass().getSimpleName()); } - private static class SessionCreationRequest { - private final long mRequestId; - @NonNull private final String mRouteId; - - @RoutingSessionInfo.TransferReason private final int mTransferReason; - - @NonNull private final UserHandle mTransferInitiatorUserHandle; - @NonNull private final String mTransferInitiatorPackageName; - - SessionCreationRequest( - long requestId, - @NonNull String routeId, - @RoutingSessionInfo.TransferReason int transferReason, - @NonNull UserHandle transferInitiatorUserHandle, - @NonNull String transferInitiatorPackageName) { - mRequestId = requestId; - mRouteId = routeId; - mTransferReason = transferReason; - mTransferInitiatorUserHandle = transferInitiatorUserHandle; - mTransferInitiatorPackageName = transferInitiatorPackageName; - } - - private boolean isTargetRoute(@Nullable MediaRoute2Info route2Info) { - if (route2Info == null) { - return false; - } - - return isTargetRoute(route2Info.getId()); - } - - private boolean isTargetRoute(@Nullable String routeId) { - return mRouteId.equals(routeId); - } - - private boolean isInsideOfRoutesList(@NonNull List<String> routesList) { - for (String routeId : routesList) { - if (isTargetRoute(routeId)) { - return true; - } - } - - return false; - } - } - void updateVolume() { int devices = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC); int volume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 57f6d2789dc5..a90473865ce5 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -5153,6 +5153,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } // Okay to proceed synchronized (mLock) { + assertCallerIsOwnerOrRoot(); + assertPreparedAndNotSealedLocked("setPreVerifiedDomains"); mPreVerifiedDomains = preVerifiedDomains; } } diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java index fd4b06148c5f..0e0b78fb3206 100644 --- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java +++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java @@ -466,17 +466,17 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer, } /** - * @return The {@link WindowInsetsController.Appearance} flags for the top fullscreen opaque - * window in the given {@param TYPE}. + * @return The {@link WindowInsetsController.Appearance} flags for the top main app window in + * the given {@param TYPE}. */ @WindowInsetsController.Appearance private int getAppearance(TYPE source) { final ActivityRecord topFullscreenActivity = getTopFullscreenActivity(source); - final WindowState topFullscreenOpaqueWindow = topFullscreenActivity != null - ? topFullscreenActivity.getTopFullscreenOpaqueWindow() + final WindowState topFullscreenWindow = topFullscreenActivity != null + ? topFullscreenActivity.findMainWindow() : null; - if (topFullscreenOpaqueWindow != null) { - return topFullscreenOpaqueWindow.mAttrs.insetsFlags.appearance; + if (topFullscreenWindow != null) { + return topFullscreenWindow.mAttrs.insetsFlags.appearance; } return 0; } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index eb6262c95d84..434e92f7978a 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7660,20 +7660,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - /** - * @return The to top most child window for which {@link LayoutParams#isFullscreen()} returns - * true and isn't fully transparent. - */ - WindowState getTopFullscreenOpaqueWindow() { - for (int i = mChildren.size() - 1; i >= 0; i--) { - final WindowState win = mChildren.get(i); - if (win != null && win.mAttrs.isFullscreen() && !win.isFullyTransparent()) { - return win; - } - } - return null; - } - WindowState findMainWindow() { return findMainWindow(true); } diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 0e4f0335118d..f91ef1d41a0c 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -1099,10 +1099,6 @@ class BackNavigationController { } void finishPresentAnimations() { - if (!mComposed) { - return; - } - if (mCloseAdaptor != null) { mCloseAdaptor.mTarget.cancelAnimation(); mCloseAdaptor = null; @@ -1131,8 +1127,10 @@ class BackNavigationController { } void clearBackAnimateTarget() { - finishPresentAnimations(); - mComposed = false; + if (mComposed) { + mComposed = false; + finishPresentAnimations(); + } mWaitTransition = false; mStartingSurfaceTargetMatch = false; mSwitchType = UNKNOWN; @@ -1270,6 +1268,8 @@ class BackNavigationController { .setContainerLayer() .setHidden(false) .setParent(task.getSurfaceControl()) + .setCallsite( + "BackWindowAnimationAdaptorWrapper.getOrCreateAnimationTarget") .build(); mCloseTransaction = new SurfaceControl.Transaction(); mCloseTransaction.reparent(leashSurface, null); diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index 22ca82a29d59..1e7de2be87fa 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -286,6 +286,7 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal .setName(name) .setCallsite("createSurfaceForGestureMonitor") .setParent(inputOverlay) + .setCallsite("InputManagerCallback.createSurfaceForGestureMonitor") .build(); } } diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java index 05eeeb381d8a..cff40c768381 100644 --- a/services/core/java/com/android/server/wm/StartingSurfaceController.java +++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java @@ -140,23 +140,15 @@ public class StartingSurfaceController { } StartingSurface createTaskSnapshotSurface(ActivityRecord activity, TaskSnapshot taskSnapshot) { - final WindowState topFullscreenOpaqueWindow; final Task task = activity.getTask(); if (task == null) { Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find task for activity=" + activity); return null; } - final ActivityRecord topFullscreenActivity = task.getTopFullscreenActivity(); - if (topFullscreenActivity == null) { - Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find top fullscreen for task=" - + task); - return null; - } - topFullscreenOpaqueWindow = topFullscreenActivity.getTopFullscreenOpaqueWindow(); - if (topFullscreenOpaqueWindow == null) { - Slog.w(TAG, "TaskSnapshotSurface.create: no opaque window in " - + topFullscreenActivity); + final WindowState mainWindow = activity.findMainWindow(false); + if (mainWindow == null) { + Slog.w(TAG, "TaskSnapshotSurface.create: no main window in " + activity); return null; } if (activity.mDisplayContent.getRotation() != taskSnapshot.getRotation()) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index a555388ab233..cd22591d3629 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3624,14 +3624,15 @@ class Task extends TaskFragment { // If the developer has persist a different configuration, we need to override it to the // starting window because persisted configuration does not effect to Task. info.taskInfo.configuration.setTo(activity.getConfiguration()); - final ActivityRecord topFullscreenActivity = getTopFullscreenActivity(); - if (topFullscreenActivity != null) { - final WindowState topFullscreenOpaqueWindow = - topFullscreenActivity.getTopFullscreenOpaqueWindow(); - if (topFullscreenOpaqueWindow != null) { - info.topOpaqueWindowInsetsState = - topFullscreenOpaqueWindow.getInsetsStateWithVisibilityOverride(); - info.topOpaqueWindowLayoutParams = topFullscreenOpaqueWindow.getAttrs(); + if (!Flags.drawSnapshotAspectRatioMatch()) { + final ActivityRecord topFullscreenActivity = getTopFullscreenActivity(); + if (topFullscreenActivity != null) { + final WindowState mainWindow = topFullscreenActivity.findMainWindow(false); + if (mainWindow != null) { + info.topOpaqueWindowInsetsState = + mainWindow.getInsetsStateWithVisibilityOverride(); + info.topOpaqueWindowLayoutParams = mainWindow.getAttrs(); + } } } return info; diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 7ec31d5a8ecb..28369fa74527 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -2639,7 +2639,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } } final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName( - "Transition Root: " + leashReference.getName()).build(); + "Transition Root: " + leashReference.getName()) + .setCallsite("Transition.calculateTransitionRoots").build(); rootLeash.setUnreleasedWarningCallSite("Transition.calculateTransitionRoots"); // Update layers to start transaction because we prevent assignment during collect, so // the layer of transition root can be correct. diff --git a/services/core/java/com/android/server/wm/TrustedOverlayHost.java b/services/core/java/com/android/server/wm/TrustedOverlayHost.java index debe7946dc95..9b868bebd868 100644 --- a/services/core/java/com/android/server/wm/TrustedOverlayHost.java +++ b/services/core/java/com/android/server/wm/TrustedOverlayHost.java @@ -54,6 +54,7 @@ class TrustedOverlayHost { final SurfaceControl.Builder b = mWmService.makeSurfaceBuilder(null) .setContainerLayer() .setHidden(true) + .setCallsite("TrustedOverlayHost.requireOverlaySurfaceControl") .setName("Overlay Host Leash"); mSurfaceControl = b.build(); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 90c287c056e8..6953c60d0d74 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -188,6 +188,7 @@ import android.annotation.Nullable; import android.app.ActivityTaskManager; import android.app.AppOpsManager; import android.app.admin.DevicePolicyCache; +import android.app.servertransaction.WindowStateInsetsControlChangeItem; import android.app.servertransaction.WindowStateResizeItem; import android.content.Context; import android.content.res.Configuration; @@ -3818,11 +3819,17 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } final InsetsStateController stateController = getDisplayContent().getInsetsStateController(); + final InsetsState insetsState = getCompatInsetsState(); mLastReportedActiveControls.set(stateController.getControlsForDispatch(this)); - try { - mClient.insetsControlChanged(getCompatInsetsState(), mLastReportedActiveControls); - } catch (RemoteException e) { - Slog.w(TAG, "Failed to deliver inset control state change to w=" + this, e); + if (Flags.insetsControlChangedItem()) { + getProcess().scheduleClientTransactionItem(WindowStateInsetsControlChangeItem.obtain( + mClient, insetsState, mLastReportedActiveControls)); + } else { + try { + mClient.insetsControlChanged(insetsState, mLastReportedActiveControls); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to deliver inset control state change to w=" + this, e); + } } } diff --git a/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java index ad68de84eace..9d8d520fcb53 100644 --- a/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/power/batterysaver/BatterySaverStateMachineTest.java @@ -15,6 +15,8 @@ */ package com.android.server.power.batterysaver; +import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.NULL_DEFAULT; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; @@ -35,12 +37,14 @@ import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.os.PowerManager; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings.Global; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InOrder; @@ -65,6 +69,9 @@ public class BatterySaverStateMachineTest { private DevicePersistedState mPersistedState; + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(NULL_DEFAULT); + private class DevicePersistedState { // Current battery level. public int batteryLevel = 100; @@ -171,6 +178,11 @@ public class BatterySaverStateMachineTest { void triggerDynamicModeNotification() { // Do nothing } + + @Override + void triggerDynamicModeNotificationV2() { + // Do nothing + } } @Before diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java index 98e119cf0dad..473d1dc22d7a 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecMessageValidatorTest.java @@ -139,12 +139,13 @@ public class HdmiCecMessageValidatorTest { @Test public void isValid_setSystemAudioMode() { - assertMessageValidity("40:72:00").isEqualTo(OK); - assertMessageValidity("4F:72:01:03").isEqualTo(OK); + assertMessageValidity("50:72:00").isEqualTo(OK); + assertMessageValidity("50:72:01").isEqualTo(OK); + assertMessageValidity("5F:72:01:03").isEqualTo(ERROR_PARAMETER_LONG); - assertMessageValidity("F0:72").isEqualTo(ERROR_SOURCE); - assertMessageValidity("40:72").isEqualTo(ERROR_PARAMETER_SHORT); - assertMessageValidity("40:72:02").isEqualTo(ERROR_PARAMETER); + assertMessageValidity("40:72:00").isEqualTo(ERROR_SOURCE); + assertMessageValidity("50:72").isEqualTo(ERROR_PARAMETER_SHORT); + assertMessageValidity("50:72:02").isEqualTo(ERROR_PARAMETER); } @Test diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiUtilsTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiUtilsTest.java index c89c32a03553..74583dd619c7 100644 --- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiUtilsTest.java @@ -709,4 +709,18 @@ public class HdmiUtilsTest { assertThat(HdmiUtils.buildMessage("40:32:65:6E:67").getParams()).isEqualTo( new byte[]{0x65, 0x6E, 0x67}); } + + @Test + public void testVerifyAddressType() { + assertTrue(HdmiUtils.verifyAddressType(Constants.ADDR_TV, + HdmiDeviceInfo.DEVICE_TV)); + assertTrue(HdmiUtils.verifyAddressType(Constants.ADDR_AUDIO_SYSTEM, + HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)); + assertTrue(HdmiUtils.verifyAddressType(Constants.ADDR_PLAYBACK_1, + HdmiDeviceInfo.DEVICE_PLAYBACK)); + assertFalse(HdmiUtils.verifyAddressType(Constants.ADDR_SPECIFIC_USE, + HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)); + assertFalse(HdmiUtils.verifyAddressType(Constants.ADDR_PLAYBACK_2, + HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR)); + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 70319754253b..4a9760bc3317 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -2057,7 +2057,7 @@ public class ActivityRecordTests extends WindowTestsBase { final ActivityRecord activity = createActivityWithTask(); // TaskSnapshotSurface requires a fullscreen opaque window. final WindowManager.LayoutParams params = new WindowManager.LayoutParams( - WindowManager.LayoutParams.TYPE_APPLICATION_STARTING); + TYPE_BASE_APPLICATION); params.width = params.height = WindowManager.LayoutParams.MATCH_PARENT; final TestWindowState w = new TestWindowState( mAtm.mWindowManager, getTestSession(), new TestIWindow(), params, activity); @@ -2504,25 +2504,6 @@ public class ActivityRecordTests extends WindowTestsBase { activity.removeImmediately(); } - @Test - @Presubmit - public void testGetTopFullscreenOpaqueWindow() { - final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build(); - assertNull(activity.getTopFullscreenOpaqueWindow()); - - final WindowState window1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "window1"); - final WindowState window11 = createWindow(null, TYPE_APPLICATION, activity, "window11"); - final WindowState window12 = createWindow(null, TYPE_APPLICATION, activity, "window12"); - assertEquals(window12, activity.getTopFullscreenOpaqueWindow()); - window12.mAttrs.width = 500; - assertEquals(window11, activity.getTopFullscreenOpaqueWindow()); - window11.mAttrs.width = 500; - assertEquals(window1, activity.getTopFullscreenOpaqueWindow()); - window1.mAttrs.alpha = 0f; - assertNull(activity.getTopFullscreenOpaqueWindow()); - activity.removeImmediately(); - } - @SetupWindows(addWindows = W_ACTIVITY) @Test public void testLandscapeSeascapeRotationByApp() { |