diff options
121 files changed, 2630 insertions, 1175 deletions
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index 02b2c5d5db84..d609fb8eb234 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -124,7 +124,12 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { public void setControl(@Nullable InsetsSourceControl control, int[] showTypes, int[] hideTypes) { super.setControl(control, showTypes, hideTypes); - if (control == null && !isRequestedVisibleAwaitingControl()) { + // TODO(b/204524304): clean-up how to deal with the timing issues of hiding IME: + // 1) Already requested show IME, in the meantime of WM callback the control but got null + // control when relayout comes first + // 2) Make sure no regression on some implicit request IME visibility calls (e.g. + // toggleSoftInput) + if (control == null && !mIsRequestedVisibleAwaitingControl) { hide(); removeSurface(); } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index adda721e00a4..9bf71ec80998 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -1266,6 +1266,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation public void notifyFinished(InsetsAnimationControlRunner runner, boolean shown) { cancelAnimation(runner, false /* invokeCallback */); if (DEBUG) Log.d(TAG, "notifyFinished. shown: " + shown); + if (runner.getAnimationType() == ANIMATION_TYPE_RESIZE) { + // The resize animation doesn't show or hide the insets. We shouldn't change the + // requested visibility. + return; + } if (shown) { showDirectly(runner.getTypes(), true /* fromIme */); } else { diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java index e1352dd8dd4f..edcfc95fe4e4 100644 --- a/core/java/android/view/InsetsResizeAnimationRunner.java +++ b/core/java/android/view/InsetsResizeAnimationRunner.java @@ -131,6 +131,9 @@ public class InsetsResizeAnimationRunner implements InsetsAnimationControlRunner @Override public boolean applyChangeInsets(InsetsState outState) { + if (mCancelled) { + return false; + } final float fraction = mAnimation.getInterpolatedFraction(); for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) { final InsetsSource fromSource = mFromState.peekSource(type); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index cdfd7bea1a82..d053def6abe2 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -768,6 +768,12 @@ public final class ViewRootImpl implements ViewParent, private boolean mWaitForBlastSyncComplete = false; /** + * Keeps track of the last frame number that was attempted to draw. Should only be accessed on + * the RenderThread. + */ + private long mRtLastAttemptedDrawFrameNum = 0; + + /** * Keeps track of whether a traverse was triggered while the UI thread was paused. This can * occur when the client is waiting on another process to submit the transaction that * contains the buffer. The UI thread needs to wait on the callback before it can submit @@ -4051,73 +4057,112 @@ public final class ViewRootImpl implements ViewParent, } /** - * The callback will run on the render thread. + * Only call this on the UI Thread. */ - private HardwareRenderer.FrameCompleteCallback createFrameCompleteCallback(Handler handler, - boolean reportNextDraw, ArrayList<Runnable> commitCallbacks) { - final Consumer<SurfaceControl.Transaction> blastSyncConsumer = mBLASTDrawConsumer; - mBLASTDrawConsumer = null; - return frameNr -> { + void clearBlastSync() { + mNextDrawUseBlastSync = false; + mWaitForBlastSyncComplete = false; + if (DEBUG_BLAST) { + Log.d(mTag, "Scheduling a traversal=" + mRequestedTraverseWhilePaused + + " due to a previous skipped traversal."); + } + if (mRequestedTraverseWhilePaused) { + mRequestedTraverseWhilePaused = false; + scheduleTraversals(); + } + } + + /** + * @hide + */ + public boolean isHardwareEnabled() { + return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled(); + } + + private boolean addFrameCompleteCallbackIfNeeded(boolean reportNextDraw) { + if (!isHardwareEnabled()) { + return false; + } + + if (!mNextDrawUseBlastSync && !reportNextDraw) { + return false; + } + + if (DEBUG_BLAST) { + Log.d(mTag, "Creating frameCompleteCallback"); + } + + mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(() -> { + long frameNr = mBlastBufferQueue.getLastAcquiredFrameNum(); if (DEBUG_BLAST) { - Log.d(mTag, "Received frameCompleteCallback frameNum=" + frameNr); + Log.d(mTag, "Received frameCompleteCallback " + + " lastAcquiredFrameNum=" + frameNr + + " lastAttemptedDrawFrameNum=" + mRtLastAttemptedDrawFrameNum); } - handler.postAtFrontOfQueue(() -> { + boolean frameWasNotDrawn = frameNr != mRtLastAttemptedDrawFrameNum; + // If frame wasn't drawn, clear out the next transaction so it doesn't affect the next + // draw attempt. The next transaction and transaction complete callback were only set + // for the current draw attempt. + if (frameWasNotDrawn) { + mBlastBufferQueue.setNextTransaction(null); + mBlastBufferQueue.setTransactionCompleteCallback(mRtLastAttemptedDrawFrameNum, + null); + } + + mHandler.postAtFrontOfQueue(() -> { if (mNextDrawUseBlastSync) { // We don't need to synchronize mRtBLASTSyncTransaction here since we're // guaranteed that this is called after onFrameDraw and mNextDrawUseBlastSync // is only true when the UI thread is paused. Therefore, no one should be // modifying this object until the next vsync. mSurfaceChangedTransaction.merge(mRtBLASTSyncTransaction); - if (blastSyncConsumer != null) { - blastSyncConsumer.accept(mSurfaceChangedTransaction); + if (mBLASTDrawConsumer != null) { + mBLASTDrawConsumer.accept(mSurfaceChangedTransaction); } + mBLASTDrawConsumer = null; } if (reportNextDraw) { - // TODO: Use the frame number pendingDrawFinished(); } - if (commitCallbacks != null) { - for (int i = 0; i < commitCallbacks.size(); i++) { - commitCallbacks.get(i).run(); - } + + if (frameWasNotDrawn) { + clearBlastSync(); } }); - }; - } - - /** - * @hide - */ - public boolean isHardwareEnabled() { - return mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled(); + }); + return true; } - private boolean addFrameCompleteCallbackIfNeeded() { + private void addFrameCommitCallbackIfNeeded() { if (!isHardwareEnabled()) { - return false; + return; } ArrayList<Runnable> commitCallbacks = mAttachInfo.mTreeObserver .captureFrameCommitCallbacks(); - final boolean needFrameCompleteCallback = - mNextDrawUseBlastSync || mReportNextDraw - || (commitCallbacks != null && commitCallbacks.size() > 0); - if (needFrameCompleteCallback) { - if (DEBUG_BLAST) { - Log.d(mTag, "Creating frameCompleteCallback" - + " mNextDrawUseBlastSync=" + mNextDrawUseBlastSync - + " mReportNextDraw=" + mReportNextDraw - + " commitCallbacks size=" - + (commitCallbacks == null ? 0 : commitCallbacks.size())); - } - mAttachInfo.mThreadedRenderer.setFrameCompleteCallback( - createFrameCompleteCallback(mAttachInfo.mHandler, mReportNextDraw, - commitCallbacks)); - return true; + final boolean needFrameCommitCallback = + (commitCallbacks != null && commitCallbacks.size() > 0); + if (!needFrameCommitCallback) { + return; } - return false; + + if (DEBUG_DRAW) { + Log.d(mTag, "Creating frameCommitCallback" + + " commitCallbacks size=" + commitCallbacks.size()); + } + mAttachInfo.mThreadedRenderer.setFrameCommitCallback(didProduceBuffer -> { + if (DEBUG_DRAW) { + Log.d(mTag, "Received frameCommitCallback didProduceBuffer=" + didProduceBuffer); + } + + mHandler.postAtFrontOfQueue(() -> { + for (int i = 0; i < commitCallbacks.size(); i++) { + commitCallbacks.get(i).run(); + } + }); + }); } private void addFrameCallbackIfNeeded() { @@ -4147,6 +4192,8 @@ public final class ViewRootImpl implements ViewParent, + " Creating transactionCompleteCallback=" + nextDrawUseBlastSync); } + mRtLastAttemptedDrawFrameNum = frame; + if (needsCallbackForBlur) { mBlurRegionAggregator .dispatchBlurTransactionIfNeeded(frame, blurRegionsForFrame, hasBlurUpdates); @@ -4169,18 +4216,7 @@ public final class ViewRootImpl implements ViewParent, if (DEBUG_BLAST) { Log.d(mTag, "Received transactionCompleteCallback frameNum=" + frame); } - mHandler.postAtFrontOfQueue(() -> { - mNextDrawUseBlastSync = false; - mWaitForBlastSyncComplete = false; - if (DEBUG_BLAST) { - Log.d(mTag, "Scheduling a traversal=" + mRequestedTraverseWhilePaused - + " due to a previous skipped traversal."); - } - if (mRequestedTraverseWhilePaused) { - mRequestedTraverseWhilePaused = false; - scheduleTraversals(); - } - }); + mHandler.postAtFrontOfQueue(this::clearBlastSync); }); } }; @@ -4201,8 +4237,9 @@ public final class ViewRootImpl implements ViewParent, mIsDrawing = true; Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw"); - boolean usingAsyncReport = addFrameCompleteCallbackIfNeeded(); addFrameCallbackIfNeeded(); + addFrameCommitCallbackIfNeeded(); + boolean usingAsyncReport = addFrameCompleteCallbackIfNeeded(mReportNextDraw); try { boolean canUseAsync = draw(fullRedrawNeeded); diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 1c915cb016d4..7631269d9c1c 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -352,30 +352,33 @@ public final class WindowManagerImpl implements WindowManager { throw e.rethrowFromSystemServer(); } - Set<WindowMetrics> maxMetrics = new HashSet<>(); - WindowInsets windowInsets; + int size = possibleDisplayInfos.size(); DisplayInfo currentDisplayInfo; - final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); - for (int i = 0; i < possibleDisplayInfos.size(); i++) { - currentDisplayInfo = possibleDisplayInfos.get(i); + WindowInsets windowInsets = null; + if (size > 0) { + currentDisplayInfo = possibleDisplayInfos.get(0); - // Calculate max bounds for this rotation and state. - Rect maxBounds = new Rect(0, 0, currentDisplayInfo.logicalWidth, - currentDisplayInfo.logicalHeight); - - // Calculate insets for the rotated max bounds. + final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); final boolean isScreenRound = (currentDisplayInfo.flags & Display.FLAG_ROUND) != 0; - // Initialize insets based upon display rotation. Note any window-provided insets - // will not be set. + // TODO(181127261) not computing insets correctly - need to have underlying + // frame reflect the faked orientation. windowInsets = getWindowInsetsFromServerForDisplay( currentDisplayInfo.displayId, params, new Rect(0, 0, currentDisplayInfo.getNaturalWidth(), currentDisplayInfo.getNaturalHeight()), isScreenRound, WINDOWING_MODE_FULLSCREEN); - // Set the hardware-provided insets. - windowInsets = new WindowInsets.Builder(windowInsets).setRoundedCorners( - currentDisplayInfo.roundedCorners) - .setDisplayCutout(currentDisplayInfo.displayCutout).build(); + } + + Set<WindowMetrics> maxMetrics = new HashSet<>(); + for (int i = 0; i < size; i++) { + currentDisplayInfo = possibleDisplayInfos.get(i); + + // Calculate max bounds for this rotation and state. + Rect maxBounds = new Rect(0, 0, currentDisplayInfo.logicalWidth, + currentDisplayInfo.logicalHeight); + + // Calculate insets for the rotated max bounds. + // TODO(181127261) calculate insets for each display rotation and state. maxMetrics.add(new WindowMetrics(maxBounds, windowInsets)); } diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp index c1587eb2c4f4..3b7328e72913 100644 --- a/core/jni/android_graphics_BLASTBufferQueue.cpp +++ b/core/jni/android_graphics_BLASTBufferQueue.cpp @@ -134,6 +134,11 @@ static void nativeSetTransactionCompleteCallback(JNIEnv* env, jclass clazz, jlon } } +static jlong nativeGetLastAcquiredFrameNum(JNIEnv* env, jclass clazz, jlong ptr) { + sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr); + return queue->getLastAcquiredFrameNum(); +} + static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ // clang-format off @@ -145,7 +150,8 @@ static const JNINativeMethod gMethods[] = { {"nativeMergeWithNextTransaction", "(JJJ)V", (void*)nativeMergeWithNextTransaction}, {"nativeSetTransactionCompleteCallback", "(JJLandroid/graphics/BLASTBufferQueue$TransactionCompleteCallback;)V", - (void*)nativeSetTransactionCompleteCallback} + (void*)nativeSetTransactionCompleteCallback}, + {"nativeGetLastAcquiredFrameNum", "(J)J", (void*)nativeGetLastAcquiredFrameNum}, // clang-format on }; diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index 4c2b114c724a..5e0d9b32380c 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -34,6 +34,7 @@ #include <vector> #include <android-base/logging.h> +#include <android-base/properties.h> #include <bionic/malloc.h> #include <debuggerd/client.h> #include <log/log.h> @@ -859,7 +860,22 @@ static jlong android_os_Debug_getDmabufHeapPoolsSizeKb(JNIEnv* env, jobject claz return poolsSizeKb; } +static bool halSupportsGpuPrivateMemory() { + int productApiLevel = + android::base::GetIntProperty("ro.product.first_api_level", + android::base::GetIntProperty("ro.build.version.sdk", + __ANDROID_API_FUTURE__)); + int boardApiLevel = + android::base::GetIntProperty("ro.board.api_level", + android::base::GetIntProperty("ro.board.first_api_level", + __ANDROID_API_FUTURE__)); + + return std::min(productApiLevel, boardApiLevel) >= __ANDROID_API_S__; +} + static jlong android_os_Debug_getGpuPrivateMemoryKb(JNIEnv* env, jobject clazz) { + static bool gpuPrivateMemorySupported = halSupportsGpuPrivateMemory(); + struct memtrack_proc* p = memtrack_proc_new(); if (p == nullptr) { LOG(ERROR) << "getGpuPrivateMemoryKb: Failed to create memtrack_proc"; @@ -876,6 +892,12 @@ static jlong android_os_Debug_getGpuPrivateMemoryKb(JNIEnv* env, jobject clazz) ssize_t gpuPrivateMem = memtrack_proc_gl_pss(p); memtrack_proc_destroy(p); + + // Old HAL implementations may return 0 for GPU private memory if not supported + if (gpuPrivateMem == 0 && !gpuPrivateMemorySupported) { + return -1; + } + return gpuPrivateMem / 1024; } diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 8ccf02caf8c0..6bfbd8d55ab0 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -889,6 +889,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/NonAppWindowAnimationAdapter.java" }, + "-1145384901": { + "message": "shouldWaitAnimatingExit: isTransition: %s", + "level": "DEBUG", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "-1142279614": { "message": "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s", "level": "VERBOSE", @@ -1273,6 +1279,12 @@ "group": "WM_DEBUG_SCREEN_ON", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-743856570": { + "message": "shouldWaitAnimatingExit: isAnimating: %s", + "level": "DEBUG", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "-743431900": { "message": "Configuration no differences in %s", "level": "VERBOSE", @@ -1777,6 +1789,12 @@ "group": "WM_DEBUG_SYNC_ENGINE", "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" }, + "-208825711": { + "message": "shouldWaitAnimatingExit: isWallpaperTarget: %s", + "level": "DEBUG", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "-198463978": { "message": "updateRotationUnchecked: alwaysSendConfiguration=%b forceRelayout=%b", "level": "VERBOSE", @@ -2989,6 +3007,12 @@ "group": "WM_DEBUG_REMOTE_ANIMATIONS", "at": "com\/android\/server\/wm\/WallpaperAnimationAdapter.java" }, + "1087494661": { + "message": "Clear window stuck on animatingExit status: %s", + "level": "WARN", + "group": "WM_DEBUG_APP_TRANSITIONS", + "at": "com\/android\/server\/wm\/WindowState.java" + }, "1088929964": { "message": "onLockTaskPackagesUpdated: starting new locktask task=%s", "level": "DEBUG", diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java index fca4de91a352..2393eaf32e9a 100644 --- a/graphics/java/android/graphics/BLASTBufferQueue.java +++ b/graphics/java/android/graphics/BLASTBufferQueue.java @@ -38,6 +38,7 @@ public final class BLASTBufferQueue { long frameNumber); private static native void nativeSetTransactionCompleteCallback(long ptr, long frameNumber, TransactionCompleteCallback callback); + private static native long nativeGetLastAcquiredFrameNum(long ptr); /** * Callback sent to {@link #setTransactionCompleteCallback(long, TransactionCompleteCallback)} @@ -140,4 +141,7 @@ public final class BLASTBufferQueue { nativeMergeWithNextTransaction(mNativeObject, nativeTransaction, frameNumber); } + public long getLastAcquiredFrameNum() { + return nativeGetLastAcquiredFrameNum(mNativeObject); + } } diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index c3b1cd74d29b..14ad74e12618 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -388,7 +388,8 @@ public class HardwareRenderer { */ public @NonNull FrameRenderRequest setFrameCommitCallback(@NonNull Executor executor, @NonNull Runnable frameCommitCallback) { - setFrameCompleteCallback(frameNr -> executor.execute(frameCommitCallback)); + nSetFrameCommitCallback(mNativeProxy, + didProduceBuffer -> executor.execute(frameCommitCallback)); return this; } @@ -609,6 +610,11 @@ public class HardwareRenderer { } /** @hide */ + public void setFrameCommitCallback(FrameCommitCallback callback) { + nSetFrameCommitCallback(mNativeProxy, callback); + } + + /** @hide */ public void setFrameCompleteCallback(FrameCompleteCallback callback) { nSetFrameCompleteCallback(mNativeProxy, callback); } @@ -904,13 +910,27 @@ public class HardwareRenderer { * * @hide */ - public interface FrameCompleteCallback { + public interface FrameCommitCallback { /** - * Invoked after a frame draw + * Invoked after a new frame was drawn * - * @param frameNr The id of the frame that was drawn. + * @param didProduceBuffer The draw successfully produced a new buffer. + */ + void onFrameCommit(boolean didProduceBuffer); + } + + /** + * Interface used to be notified when RenderThread has finished an attempt to draw. This doesn't + * mean a new frame has drawn, specifically if there's nothing new to draw, but only that + * RenderThread had a chance to draw a frame. + * + * @hide + */ + public interface FrameCompleteCallback { + /** + * Invoked after a frame draw was attempted. */ - void onFrameComplete(long frameNr); + void onFrameComplete(); } /** @@ -1362,6 +1382,9 @@ public class HardwareRenderer { private static native void nSetFrameCallback(long nativeProxy, FrameDrawingCallback callback); + private static native void nSetFrameCommitCallback(long nativeProxy, + FrameCommitCallback callback); + private static native void nSetFrameCompleteCallback(long nativeProxy, FrameCompleteCallback callback); 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 75bc46125324..7f370367187c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -29,6 +29,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager.RunningTaskInfo; import android.app.TaskInfo; +import android.app.WindowConfiguration; import android.content.Context; import android.content.LocusId; import android.content.pm.ActivityInfo; @@ -122,6 +123,16 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } /** + * Callbacks for events in which the focus has changed. + */ + public interface FocusListener { + /** + * Notifies when the task which is focused has changed. + */ + void onFocusTaskChanged(RunningTaskInfo taskInfo); + } + + /** * Keys map from either a task id or {@link TaskListenerType}. * @see #addListenerForTaskId * @see #addListenerForType @@ -142,6 +153,8 @@ public class ShellTaskOrganizer extends TaskOrganizer implements /** @see #addLocusIdListener */ private final ArraySet<LocusIdListener> mLocusIdListeners = new ArraySet<>(); + private final ArraySet<FocusListener> mFocusListeners = new ArraySet<>(); + private final Object mLock = new Object(); private StartingWindowController mStartingWindow; @@ -155,6 +168,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements @Nullable private final Optional<RecentTasksController> mRecentTasks; + @Nullable + private RunningTaskInfo mLastFocusedTaskInfo; + public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) { this(null /* taskOrganizerController */, mainExecutor, context, null /* sizeCompatUI */, Optional.empty() /* recentTasksController */); @@ -331,6 +347,27 @@ public class ShellTaskOrganizer extends TaskOrganizer implements } } + /** + * Adds a listener to be notified for task focus changes. + */ + public void addFocusListener(FocusListener listener) { + synchronized (mLock) { + mFocusListeners.add(listener); + if (mLastFocusedTaskInfo != null) { + listener.onFocusTaskChanged(mLastFocusedTaskInfo); + } + } + } + + /** + * Removes listener. + */ + public void removeLocusIdListener(FocusListener listener) { + synchronized (mLock) { + mFocusListeners.remove(listener); + } + } + @Override public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { if (mStartingWindow != null) { @@ -422,6 +459,18 @@ public class ShellTaskOrganizer extends TaskOrganizer implements mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskWindowingModeChanged(taskInfo)); } + // TODO (b/207687679): Remove check for HOME once bug is fixed + final boolean isFocusedOrHome = taskInfo.isFocused + || (taskInfo.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME + && taskInfo.isVisible); + final boolean focusTaskChanged = (mLastFocusedTaskInfo == null + || mLastFocusedTaskInfo.taskId != taskInfo.taskId) && isFocusedOrHome; + if (focusTaskChanged) { + for (int i = 0; i < mFocusListeners.size(); i++) { + mFocusListeners.valueAt(i).onFocusTaskChanged(taskInfo); + } + mLastFocusedTaskInfo = taskInfo; + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 300319a2f78f..b40021ec82a7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -32,6 +32,7 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; +import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -529,9 +530,10 @@ public class BubbleStackView extends FrameLayout // Otherwise, we either tapped the stack (which means we're collapsed // and should expand) or the currently selected bubble (we're expanded // and should collapse). - if (!maybeShowStackEdu()) { + if (!maybeShowStackEdu() && !mShowedUserEducationInTouchListenerActive) { mBubbleData.setExpanded(!mBubbleData.isExpanded()); } + mShowedUserEducationInTouchListenerActive = false; } } }; @@ -549,6 +551,14 @@ public class BubbleStackView extends FrameLayout return true; } + mShowedUserEducationInTouchListenerActive = false; + if (maybeShowStackEdu()) { + mShowedUserEducationInTouchListenerActive = true; + return true; + } else if (isStackEduShowing()) { + mStackEduView.hide(false /* fromExpansion */); + } + // If the manage menu is visible, just hide it. if (mShowingManage) { showManageMenu(false /* show */); @@ -607,7 +617,8 @@ public class BubbleStackView extends FrameLayout // If we're expanding or collapsing, ignore all touch events. if (mIsExpansionAnimating // Also ignore events if we shouldn't be draggable. - || (mPositioner.showingInTaskbar() && !mIsExpanded)) { + || (mPositioner.showingInTaskbar() && !mIsExpanded) + || mShowedUserEducationInTouchListenerActive) { return; } @@ -628,7 +639,7 @@ public class BubbleStackView extends FrameLayout mExpandedAnimationController.dragBubbleOut( v, viewInitialX + dx, viewInitialY + dy); } else { - if (mStackEduView != null) { + if (isStackEduShowing()) { mStackEduView.hide(false /* fromExpansion */); } mStackAnimationController.moveStackFromTouch( @@ -646,6 +657,10 @@ public class BubbleStackView extends FrameLayout || (mPositioner.showingInTaskbar() && !mIsExpanded)) { return; } + if (mShowedUserEducationInTouchListenerActive) { + mShowedUserEducationInTouchListenerActive = false; + return; + } // First, see if the magnetized object consumes the event - if so, the bubble was // released in the target or flung out of it, and we should ignore the event. @@ -738,6 +753,7 @@ public class BubbleStackView extends FrameLayout private ImageView mManageSettingsIcon; private TextView mManageSettingsText; private boolean mShowingManage = false; + private boolean mShowedUserEducationInTouchListenerActive = false; private PhysicsAnimator.SpringConfig mManageSpringConfig = new PhysicsAnimator.SpringConfig( SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY); private BubblePositioner mPositioner; @@ -929,10 +945,12 @@ public class BubbleStackView extends FrameLayout showManageMenu(false /* show */); } else if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) { mManageEduView.hide(); - } else if (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE) { + } else if (isStackEduShowing()) { mStackEduView.hide(false /* isExpanding */); } else if (mBubbleData.isExpanded()) { mBubbleData.setExpanded(false); + } else { + maybeShowStackEdu(); } }); @@ -1116,6 +1134,9 @@ public class BubbleStackView extends FrameLayout * Whether the educational view should show for the expanded view "manage" menu. */ private boolean shouldShowManageEdu() { + if (ActivityManager.isRunningInTestHarness()) { + return false; + } final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION); final boolean shouldShow = (!seen || BubbleDebugConfig.forceShowUserEducation(mContext)) && mExpandedBubble != null; @@ -1140,6 +1161,9 @@ public class BubbleStackView extends FrameLayout * Whether education view should show for the collapsed stack. */ private boolean shouldShowStackEdu() { + if (ActivityManager.isRunningInTestHarness()) { + return false; + } final boolean seen = getPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION); final boolean shouldShow = !seen || BubbleDebugConfig.forceShowUserEducation(mContext); if (BubbleDebugConfig.DEBUG_USER_EDUCATION) { @@ -1157,7 +1181,7 @@ public class BubbleStackView extends FrameLayout * @return true if education view for collapsed stack should show and was not showing before. */ private boolean maybeShowStackEdu() { - if (!shouldShowStackEdu()) { + if (!shouldShowStackEdu() || isExpanded()) { return false; } if (mStackEduView == null) { @@ -1168,9 +1192,13 @@ public class BubbleStackView extends FrameLayout return mStackEduView.show(mPositioner.getDefaultStartPosition()); } + private boolean isStackEduShowing() { + return mStackEduView != null && mStackEduView.getVisibility() == VISIBLE; + } + // Recreates & shows the education views. Call when a theme/config change happens. private void updateUserEdu() { - if (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE) { + if (isStackEduShowing()) { removeView(mStackEduView); mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController); addView(mStackEduView); @@ -1852,7 +1880,7 @@ public class BubbleStackView extends FrameLayout cancelDelayedExpandCollapseSwitchAnimations(); final boolean showVertically = mPositioner.showBubblesVertically(); mIsExpanded = true; - if (mStackEduView != null) { + if (isStackEduShowing()) { mStackEduView.hide(true /* fromExpansion */); } beforeExpandedViewAnimation(); @@ -2390,7 +2418,7 @@ public class BubbleStackView extends FrameLayout if (flyoutMessage == null || flyoutMessage.message == null || !bubble.showFlyout() - || (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE) + || isStackEduShowing() || isExpanded() || mIsExpansionAnimating || mIsGestureInProgress @@ -2512,7 +2540,7 @@ public class BubbleStackView extends FrameLayout * them. */ public void getTouchableRegion(Rect outRect) { - if (mStackEduView != null && mStackEduView.getVisibility() == VISIBLE) { + if (isStackEduShowing()) { // When user education shows then capture all touches outRect.set(0, 0, getWidth(), getHeight()); return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt index f6a90b7a76cd..3846de73842d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt @@ -125,6 +125,7 @@ class StackEducationView constructor( * @return true if user education was shown, false otherwise. */ fun show(stackPosition: PointF): Boolean { + isHiding = false if (visibility == VISIBLE) return false controller.updateWindowFlagsForBackpress(true /* interceptBack */) @@ -164,6 +165,7 @@ class StackEducationView constructor( */ fun hide(isExpanding: Boolean) { if (visibility != VISIBLE || isHiding) return + isHiding = true controller.updateWindowFlagsForBackpress(false /* interceptBack */) animate() diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index d07fff3cce79..3579bf441390 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -321,6 +321,16 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mSplitLayoutHandler.onLayoutSizeChanged(this); } + /** Sets divide position base on the ratio within root bounds. */ + public void setDivideRatio(float ratio) { + final int position = isLandscape() + ? mRootBounds.left + (int) (mRootBounds.width() * ratio) + : mRootBounds.top + (int) (mRootBounds.height() * ratio); + DividerSnapAlgorithm.SnapTarget snapTarget = + mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(position); + setDividePosition(snapTarget.position); + } + /** Resets divider position. */ public void resetDividerPosition() { mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position; 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 46c7b508d6e8..f562fd9cf1af 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 @@ -241,10 +241,11 @@ public class WMShellModule { static PhonePipMenuController providesPipPhoneMenuController(Context context, PipBoundsState pipBoundsState, PipMediaController pipMediaController, SystemWindows systemWindows, + Optional<SplitScreenController> splitScreenOptional, @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) { return new PhonePipMenuController(context, pipBoundsState, pipMediaController, - systemWindows, mainExecutor, mainHandler); + systemWindows, splitScreenOptional, mainExecutor, mainHandler); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java index 8d9ad4d1b96c..caa1f017082b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java @@ -90,6 +90,11 @@ public interface PipMenuController { default void updateMenuBounds(Rect destinationBounds) {} /** + * Update when the current focused task changes. + */ + default void onFocusTaskChanged(RunningTaskInfo taskInfo) {} + + /** * Returns a default LayoutParams for the PIP Menu. * @param width the PIP stack width. * @param height the PIP stack height. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 90135f2a3c69..c2ebc3031b93 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -98,7 +98,7 @@ import java.util.function.IntConsumer; * see also {@link PipMotionHelper}. */ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, - DisplayController.OnDisplaysChangedListener { + DisplayController.OnDisplaysChangedListener, ShellTaskOrganizer.FocusListener { private static final String TAG = PipTaskOrganizer.class.getSimpleName(); private static final boolean DEBUG = false; /** @@ -286,6 +286,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mMainExecutor.execute(() -> { mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP); }); + mTaskOrganizer.addFocusListener(this); mPipTransitionController.setPipOrganizer(this); displayController.addDisplayWindowListener(this); } @@ -772,6 +773,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } @Override + public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) { + mPipMenuController.onFocusTaskChanged(taskInfo); + } + + @Override public boolean supportSizeCompatUI() { // PIP doesn't support size compat. return false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index 5687f4d62444..eb512afa644d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -19,6 +19,7 @@ package com.android.wm.shell.pip.phone; import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP; import android.annotation.Nullable; +import android.app.ActivityManager; import android.app.RemoteAction; import android.content.Context; import android.content.pm.ParceledListSlice; @@ -43,10 +44,12 @@ import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipMediaController.ActionListener; import com.android.wm.shell.pip.PipMenuController; +import com.android.wm.shell.splitscreen.SplitScreenController; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.Optional; /** * Manages the PiP menu view which can show menu options or a scrim. @@ -114,6 +117,7 @@ public class PhonePipMenuController implements PipMenuController { private final ArrayList<Listener> mListeners = new ArrayList<>(); private final SystemWindows mSystemWindows; + private final Optional<SplitScreenController> mSplitScreenController; private ParceledListSlice<RemoteAction> mAppActions; private ParceledListSlice<RemoteAction> mMediaActions; private SyncRtSurfaceTransactionApplier mApplier; @@ -145,6 +149,7 @@ public class PhonePipMenuController implements PipMenuController { public PhonePipMenuController(Context context, PipBoundsState pipBoundsState, PipMediaController mediaController, SystemWindows systemWindows, + Optional<SplitScreenController> splitScreenOptional, ShellExecutor mainExecutor, Handler mainHandler) { mContext = context; mPipBoundsState = pipBoundsState; @@ -152,6 +157,7 @@ public class PhonePipMenuController implements PipMenuController { mSystemWindows = systemWindows; mMainExecutor = mainExecutor; mMainHandler = mainHandler; + mSplitScreenController = splitScreenOptional; } public boolean isMenuVisible() { @@ -180,7 +186,8 @@ public class PhonePipMenuController implements PipMenuController { if (mPipMenuView != null) { detachPipMenuView(); } - mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler); + mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler, + mSplitScreenController); mSystemWindows.addView(mPipMenuView, getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */), 0, SHELL_ROOT_LAYER_PIP); @@ -209,6 +216,13 @@ public class PhonePipMenuController implements PipMenuController { updateMenuLayout(destinationBounds); } + @Override + public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) { + if (mPipMenuView != null) { + mPipMenuView.onFocusTaskChanged(taskInfo); + } + } + /** * Tries to grab a surface control from {@link PipMenuView}. If this isn't available for some * reason (ie. the window isn't ready yet, thus {@link android.view.ViewRootImpl} is diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index b209699c1a19..82e827398bb7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -16,6 +16,7 @@ package com.android.wm.shell.pip.phone; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.provider.Settings.ACTION_PICTURE_IN_PICTURE_SETTINGS; @@ -32,8 +33,10 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.IntDef; +import android.app.ActivityManager; import android.app.PendingIntent.CanceledException; import android.app.RemoteAction; +import android.app.WindowConfiguration; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -61,11 +64,13 @@ import com.android.wm.shell.R; import com.android.wm.shell.animation.Interpolators; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.pip.PipUtils; +import com.android.wm.shell.splitscreen.SplitScreenController; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; +import java.util.Optional; /** * Translucent window that gets started on top of a task in PIP to allow the user to control it. @@ -105,6 +110,7 @@ public class PipMenuView extends FrameLayout { private boolean mAllowMenuTimeout = true; private boolean mAllowTouches = true; private int mDismissFadeOutDurationMs; + private boolean mFocusedTaskAllowSplitScreen; private final List<RemoteAction> mActions = new ArrayList<>(); @@ -116,6 +122,7 @@ public class PipMenuView extends FrameLayout { private AnimatorSet mMenuContainerAnimator; private PhonePipMenuController mController; + private Optional<SplitScreenController> mSplitScreenControllerOptional; private ValueAnimator.AnimatorUpdateListener mMenuBgUpdateListener = new ValueAnimator.AnimatorUpdateListener() { @@ -144,12 +151,14 @@ public class PipMenuView extends FrameLayout { protected PipMenuIconsAlgorithm mPipMenuIconsAlgorithm; public PipMenuView(Context context, PhonePipMenuController controller, - ShellExecutor mainExecutor, Handler mainHandler) { + ShellExecutor mainExecutor, Handler mainHandler, + Optional<SplitScreenController> splitScreenController) { super(context, null, 0); mContext = context; mController = controller; mMainExecutor = mainExecutor; mMainHandler = mainHandler; + mSplitScreenControllerOptional = splitScreenController; mAccessibilityManager = context.getSystemService(AccessibilityManager.class); inflate(context, R.layout.pip_menu, this); @@ -255,6 +264,15 @@ public class PipMenuView extends FrameLayout { return super.dispatchGenericMotionEvent(event); } + public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) { + final boolean isSplitScreen = mSplitScreenControllerOptional.isPresent() + && mSplitScreenControllerOptional.get().isTaskInSplitScreen(taskInfo.taskId); + mFocusedTaskAllowSplitScreen = isSplitScreen + || (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN + && taskInfo.supportsSplitScreenMultiWindow + && taskInfo.topActivityType != WindowConfiguration.ACTIVITY_TYPE_HOME); + } + void showMenu(int menuState, Rect stackBounds, boolean allowMenuTimeout, boolean resizeMenuOnShow, boolean withDelay, boolean showResizeHandle) { mAllowMenuTimeout = allowMenuTimeout; @@ -278,7 +296,8 @@ public class PipMenuView extends FrameLayout { ObjectAnimator dismissAnim = ObjectAnimator.ofFloat(mDismissButton, View.ALPHA, mDismissButton.getAlpha(), 1f); ObjectAnimator enterSplitAnim = ObjectAnimator.ofFloat(mEnterSplitButton, View.ALPHA, - mEnterSplitButton.getAlpha(), ENABLE_ENTER_SPLIT ? 1f : 0f); + mEnterSplitButton.getAlpha(), + ENABLE_ENTER_SPLIT && mFocusedTaskAllowSplitScreen ? 1f : 0f); if (menuState == MENU_STATE_FULL) { mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, enterSplitAnim); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl index 3d3a63057dde..4a990975be0d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl @@ -83,14 +83,15 @@ interface ISplitScreen { * Starts tasks simultaneously in one transition. */ oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId, - in Bundle sideOptions, int sidePosition, in RemoteTransition remoteTransition) = 10; + in Bundle sideOptions, int sidePosition, float splitRatio, + in RemoteTransition remoteTransition) = 10; /** * Version of startTasks using legacy transition system. */ oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions, int sideTaskId, in Bundle sideOptions, int sidePosition, - in RemoteAnimationAdapter adapter) = 11; + float splitRatio, in RemoteAnimationAdapter adapter) = 11; /** * Blocking call that notifies and gets additional split-screen targets when entering diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 6b42ed775fb7..262a9a1ba2be 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -183,6 +183,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return mStageCoordinator.isSplitScreenVisible(); } + public boolean isTaskInSplitScreen(int taskId) { + return isSplitScreenVisible() + && mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED; + } + public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) { return moveToStage(taskId, STAGE_TYPE_SIDE, sideStagePosition, new WindowContainerTransaction()); @@ -605,21 +610,21 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, - RemoteAnimationAdapter adapter) { + float splitRatio, RemoteAnimationAdapter adapter) { executeRemoteCallWithTaskPermission(mController, "startTasks", (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition( mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition, - adapter)); + splitRatio, adapter)); } @Override public void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, @Nullable Bundle sideOptions, - @SplitPosition int sidePosition, + @SplitPosition int sidePosition, float splitRatio, @Nullable RemoteTransition remoteTransition) { executeRemoteCallWithTaskPermission(mController, "startTasks", (controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions, - sideTaskId, sideOptions, sidePosition, remoteTransition)); + sideTaskId, sideOptions, sidePosition, splitRatio, remoteTransition)); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index d095ba9b0de8..a5579ae7dc25 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -274,6 +274,17 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return mSideStageListener.mVisible && mMainStageListener.mVisible; } + @SplitScreen.StageType + int getStageOfTask(int taskId) { + if (mMainStage.containsTask(taskId)) { + return STAGE_TYPE_MAIN; + } else if (mSideStage.containsTask(taskId)) { + return STAGE_TYPE_SIDE; + } + + return STAGE_TYPE_UNDEFINED; + } + boolean moveToStage(ActivityManager.RunningTaskInfo task, @SplitScreen.StageType int stageType, @SplitPosition int stagePosition, WindowContainerTransaction wct) { StageTaskListener targetStage; @@ -322,7 +333,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Starts 2 tasks in one transition. */ void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, - @Nullable Bundle sideOptions, @SplitPosition int sidePosition, + @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio, @Nullable RemoteTransition remoteTransition) { final WindowContainerTransaction wct = new WindowContainerTransaction(); mainOptions = mainOptions != null ? mainOptions : new Bundle(); @@ -334,6 +345,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainStage.activate(getMainStageBounds(), wct, false /* reparent */); mSideStage.setBounds(getSideStageBounds(), wct); + mSplitLayout.setDivideRatio(splitRatio); // Make sure the launch options will put tasks in the corresponding split roots addActivityOptions(mainOptions, mMainStage); addActivityOptions(sideOptions, mSideStage); @@ -349,7 +361,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Starts 2 tasks in one legacy transition. */ void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, - RemoteAnimationAdapter adapter) { + float splitRatio, RemoteAnimationAdapter adapter) { final WindowContainerTransaction wct = new WindowContainerTransaction(); // Need to add another wrapper here in shell so that we can inject the divider bar // and also manage the process elevation via setRunningRemote @@ -404,6 +416,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, sideOptions = sideOptions != null ? sideOptions : new Bundle(); setSideStagePosition(sidePosition, wct); + mSplitLayout.setDivideRatio(splitRatio); // Build a request WCT that will launch both apps such that task 0 is on the main stage // while task 1 is on the side stage. mMainStage.activate(getMainStageBounds(), wct, false /* reparent */); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index 73eebad040d8..453050fcfab4 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -106,6 +106,12 @@ public class SplitLayoutTests extends ShellTestCase { } @Test + public void testSetDivideRatio() { + mSplitLayout.setDivideRatio(0.5f); + verify(mSplitLayoutHandler).onLayoutSizeChanged(any(SplitLayout.class)); + } + + @Test public void testOnDoubleTappedDivider() { mSplitLayout.onDoubleTappedDivider(); verify(mSplitLayoutHandler).onDoubleTappedDivider(); diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index 54367b8334cb..b5536ad4830d 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -73,6 +73,10 @@ struct { } gFrameDrawingCallback; struct { + jmethodID onFrameCommit; +} gFrameCommitCallback; + +struct { jmethodID onFrameComplete; } gFrameCompleteCallback; @@ -101,22 +105,21 @@ private: JavaVM* mVm; }; -class FrameCompleteWrapper : public LightRefBase<FrameCompleteWrapper> { +class FrameCommitWrapper : public LightRefBase<FrameCommitWrapper> { public: - explicit FrameCompleteWrapper(JNIEnv* env, jobject jobject) { + explicit FrameCommitWrapper(JNIEnv* env, jobject jobject) { env->GetJavaVM(&mVm); mObject = env->NewGlobalRef(jobject); LOG_ALWAYS_FATAL_IF(!mObject, "Failed to make global ref"); } - ~FrameCompleteWrapper() { - releaseObject(); - } + ~FrameCommitWrapper() { releaseObject(); } - void onFrameComplete(int64_t frameNr) { + void onFrameCommit(bool didProduceBuffer) { if (mObject) { - ATRACE_FORMAT("frameComplete %" PRId64, frameNr); - getenv(mVm)->CallVoidMethod(mObject, gFrameCompleteCallback.onFrameComplete, frameNr); + ATRACE_FORMAT("frameCommit success=%d", didProduceBuffer); + getenv(mVm)->CallVoidMethod(mObject, gFrameCommitCallback.onFrameCommit, + didProduceBuffer); releaseObject(); } } @@ -637,15 +640,33 @@ static void android_view_ThreadedRenderer_setFrameCallback(JNIEnv* env, } } +static void android_view_ThreadedRenderer_setFrameCommitCallback(JNIEnv* env, jobject clazz, + jlong proxyPtr, jobject callback) { + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + if (!callback) { + proxy->setFrameCommitCallback(nullptr); + } else { + sp<FrameCommitWrapper> wrapper = new FrameCommitWrapper{env, callback}; + proxy->setFrameCommitCallback( + [wrapper](bool didProduceBuffer) { wrapper->onFrameCommit(didProduceBuffer); }); + } +} + static void android_view_ThreadedRenderer_setFrameCompleteCallback(JNIEnv* env, jobject clazz, jlong proxyPtr, jobject callback) { RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); if (!callback) { proxy->setFrameCompleteCallback(nullptr); } else { - sp<FrameCompleteWrapper> wrapper = new FrameCompleteWrapper{env, callback}; - proxy->setFrameCompleteCallback([wrapper](int64_t frameNr) { - wrapper->onFrameComplete(frameNr); + RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr); + JavaVM* vm = nullptr; + LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM"); + auto globalCallbackRef = + std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(callback)); + proxy->setFrameCompleteCallback([globalCallbackRef]() { + JNIEnv* env = getenv(globalCallbackRef->vm()); + env->CallVoidMethod(globalCallbackRef->object(), + gFrameCompleteCallback.onFrameComplete); }); } } @@ -929,6 +950,8 @@ static const JNINativeMethod gMethods[] = { (void*)android_view_ThreadedRenderer_setPrepareSurfaceControlForWebviewCallback}, {"nSetFrameCallback", "(JLandroid/graphics/HardwareRenderer$FrameDrawingCallback;)V", (void*)android_view_ThreadedRenderer_setFrameCallback}, + {"nSetFrameCommitCallback", "(JLandroid/graphics/HardwareRenderer$FrameCommitCallback;)V", + (void*)android_view_ThreadedRenderer_setFrameCommitCallback}, {"nSetFrameCompleteCallback", "(JLandroid/graphics/HardwareRenderer$FrameCompleteCallback;)V", (void*)android_view_ThreadedRenderer_setFrameCompleteCallback}, @@ -994,10 +1017,15 @@ int register_android_view_ThreadedRenderer(JNIEnv* env) { gFrameDrawingCallback.onFrameDraw = GetMethodIDOrDie(env, frameCallbackClass, "onFrameDraw", "(J)V"); + jclass frameCommitClass = + FindClassOrDie(env, "android/graphics/HardwareRenderer$FrameCommitCallback"); + gFrameCommitCallback.onFrameCommit = + GetMethodIDOrDie(env, frameCommitClass, "onFrameCommit", "(Z)V"); + jclass frameCompleteClass = FindClassOrDie(env, "android/graphics/HardwareRenderer$FrameCompleteCallback"); - gFrameCompleteCallback.onFrameComplete = GetMethodIDOrDie(env, frameCompleteClass, - "onFrameComplete", "(J)V"); + gFrameCompleteCallback.onFrameComplete = + GetMethodIDOrDie(env, frameCompleteClass, "onFrameComplete", "()V"); void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE); fromSurface = (ANW_fromSurface)dlsym(handle_, "ANativeWindow_fromSurface"); diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 2f3a509831d1..a066e6f7c693 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -491,10 +491,10 @@ nsecs_t CanvasContext::draw() { // Notify the callbacks, even if there's nothing to draw so they aren't waiting // indefinitely waitOnFences(); - for (auto& func : mFrameCompleteCallbacks) { - std::invoke(func, mFrameNumber); + for (auto& func : mFrameCommitCallbacks) { + std::invoke(func, false /* didProduceBuffer */); } - mFrameCompleteCallbacks.clear(); + mFrameCommitCallbacks.clear(); return 0; } @@ -603,10 +603,10 @@ nsecs_t CanvasContext::draw() { #endif if (didSwap) { - for (auto& func : mFrameCompleteCallbacks) { - std::invoke(func, frameCompleteNr); + for (auto& func : mFrameCommitCallbacks) { + std::invoke(func, true /* didProduceBuffer */); } - mFrameCompleteCallbacks.clear(); + mFrameCommitCallbacks.clear(); } if (requireSwap) { diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index 6dbfcc349d50..9df429badd5e 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -187,8 +187,8 @@ public: IRenderPipeline* getRenderPipeline() { return mRenderPipeline.get(); } - void addFrameCompleteListener(std::function<void(int64_t)>&& func) { - mFrameCompleteCallbacks.push_back(std::move(func)); + void addFrameCommitListener(std::function<void(bool)>&& func) { + mFrameCommitCallbacks.push_back(std::move(func)); } void setPictureCapturedCallback(const std::function<void(sk_sp<SkPicture>&&)>& callback) { @@ -320,7 +320,7 @@ private: std::vector<std::future<void>> mFrameFences; std::unique_ptr<IRenderPipeline> mRenderPipeline; - std::vector<std::function<void(int64_t)>> mFrameCompleteCallbacks; + std::vector<std::function<void(bool)>> mFrameCommitCallbacks; // If set to true, we expect that callbacks into onSurfaceStatsAvailable bool mExpectSurfaceStats = false; diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp index e7081df2b558..94aedd0f43be 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -150,16 +150,18 @@ void DrawFrameTask::run() { canUnblockUiThread = syncFrameState(info); canDrawThisFrame = info.out.canDrawThisFrame; - if (mFrameCompleteCallback) { - mContext->addFrameCompleteListener(std::move(mFrameCompleteCallback)); - mFrameCompleteCallback = nullptr; + if (mFrameCommitCallback) { + mContext->addFrameCommitListener(std::move(mFrameCommitCallback)); + mFrameCommitCallback = nullptr; } } // Grab a copy of everything we need CanvasContext* context = mContext; - std::function<void(int64_t)> callback = std::move(mFrameCallback); + std::function<void(int64_t)> frameCallback = std::move(mFrameCallback); + std::function<void()> frameCompleteCallback = std::move(mFrameCompleteCallback); mFrameCallback = nullptr; + mFrameCompleteCallback = nullptr; int64_t intendedVsync = mFrameInfo[static_cast<int>(FrameInfoIndex::IntendedVsync)]; int64_t frameDeadline = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameDeadline)]; int64_t frameStartTime = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameStartTime)]; @@ -170,9 +172,9 @@ void DrawFrameTask::run() { } // Even if we aren't drawing this vsync pulse the next frame number will still be accurate - if (CC_UNLIKELY(callback)) { + if (CC_UNLIKELY(frameCallback)) { context->enqueueFrameWork( - [callback, frameNr = context->getFrameNumber()]() { callback(frameNr); }); + [frameCallback, frameNr = context->getFrameNumber()]() { frameCallback(frameNr); }); } nsecs_t dequeueBufferDuration = 0; @@ -189,6 +191,10 @@ void DrawFrameTask::run() { context->waitOnFences(); } + if (CC_UNLIKELY(frameCompleteCallback)) { + std::invoke(frameCompleteCallback); + } + if (!canUnblockUiThread) { unblockUiThread(); } diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h index 6a61a2bb645f..e3ea802b07b9 100644 --- a/libs/hwui/renderthread/DrawFrameTask.h +++ b/libs/hwui/renderthread/DrawFrameTask.h @@ -81,7 +81,11 @@ public: mFrameCallback = std::move(callback); } - void setFrameCompleteCallback(std::function<void(int64_t)>&& callback) { + void setFrameCommitCallback(std::function<void(bool)>&& callback) { + mFrameCommitCallback = std::move(callback); + } + + void setFrameCompleteCallback(std::function<void()>&& callback) { mFrameCompleteCallback = std::move(callback); } @@ -123,7 +127,8 @@ private: int64_t mFrameInfo[UI_THREAD_FRAME_INFO_SIZE]; std::function<void(int64_t)> mFrameCallback; - std::function<void(int64_t)> mFrameCompleteCallback; + std::function<void(bool)> mFrameCommitCallback; + std::function<void()> mFrameCompleteCallback; nsecs_t mLastDequeueBufferDuration = 0; nsecs_t mLastTargetWorkDuration = 0; diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index c485ce2781e5..72d4ac5081e6 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -326,7 +326,11 @@ void RenderProxy::setFrameCallback(std::function<void(int64_t)>&& callback) { mDrawFrameTask.setFrameCallback(std::move(callback)); } -void RenderProxy::setFrameCompleteCallback(std::function<void(int64_t)>&& callback) { +void RenderProxy::setFrameCommitCallback(std::function<void(bool)>&& callback) { + mDrawFrameTask.setFrameCommitCallback(std::move(callback)); +} + +void RenderProxy::setFrameCompleteCallback(std::function<void()>&& callback) { mDrawFrameTask.setFrameCompleteCallback(std::move(callback)); } diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 2b5405c82563..6417b38df064 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -124,7 +124,8 @@ public: const std::function<bool(int64_t, int64_t, int64_t)>& callback); void setPrepareSurfaceControlForWebviewCallback(const std::function<void()>& callback); void setFrameCallback(std::function<void(int64_t)>&& callback); - void setFrameCompleteCallback(std::function<void(int64_t)>&& callback); + void setFrameCommitCallback(std::function<void(bool)>&& callback); + void setFrameCompleteCallback(std::function<void()>&& callback); void addFrameMetricsObserver(FrameMetricsObserver* observer); void removeFrameMetricsObserver(FrameMetricsObserver* observer); diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index 9aad2783ba2d..e5726b08aff4 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -27,7 +27,6 @@ import android.os.Looper import android.util.Log import android.util.MathUtils import android.view.GhostView -import android.view.Gravity import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver.OnPreDrawListener @@ -87,10 +86,11 @@ class DialogLaunchAnimator( // If the parent of the view we are launching from is the background of some other animated // dialog, then this means the caller intent is to launch a dialog from another dialog. In // this case, we also animate the parent (which is the dialog background). - val animatedParent = openedDialogs - .firstOrNull { it.dialogContentParent == view.parent } - val parentHostDialog = animatedParent?.hostDialog - val animateFrom = animatedParent?.dialogContentParent ?: view + val animatedParent = openedDialogs.firstOrNull { + it.dialogContentWithBackground == view || it.dialogContentWithBackground == view.parent + } + val dialogContentWithBackground = animatedParent?.dialogContentWithBackground + val animateFrom = dialogContentWithBackground ?: view // Make sure we don't run the launch animation from the same view twice at the same time. if (animateFrom.getTag(TAG_LAUNCH_ANIMATION_RUNNING) != null) { @@ -109,7 +109,7 @@ class DialogLaunchAnimator( onDialogDismissed = { openedDialogs.remove(it) }, originalDialog = dialog, animateBackgroundBoundsChange, - openedDialogs.firstOrNull { it.hostDialog == parentHostDialog } + animatedParent ) val hostDialog = animatedDialog.hostDialog openedDialogs.add(animatedDialog) @@ -288,13 +288,12 @@ private class AnimatedDialog( private val hostDialogRoot = FrameLayout(context) /** - * The parent of the original dialog content view, that serves as a fake window that will have - * the same size as the original dialog window and to which we will set the original dialog - * window background. + * The dialog content with its background. When animating a fullscreen dialog, this is just the + * first ViewGroup of the dialog that has a background. When animating a normal (not fullscreen) + * dialog, this is an additional view that serves as a fake window that will have the same size + * as the original dialog window and to which we will set the original dialog window background. */ - val dialogContentParent = FrameLayout(context).apply { - id = DIALOG_CONTENT_PARENT_ID - } + var dialogContentWithBackground: ViewGroup? = null /** * The background color of [originalDialogView], taking into consideration the [originalDialog] @@ -451,59 +450,87 @@ private class AnimatedDialog( hostDialogRoot.setOnClickListener { hostDialog.dismiss() } dialogView.isClickable = true - // Set the background of the window dialog to the dialog itself. - // TODO(b/193634619): Support dialog windows without background. - // TODO(b/193634619): Support dialog whose background comes from the content view instead of - // the window. - val typedArray = - originalDialog.context.obtainStyledAttributes(com.android.internal.R.styleable.Window) - val backgroundRes = - typedArray.getResourceId(com.android.internal.R.styleable.Window_windowBackground, 0) - typedArray.recycle() - if (backgroundRes == 0) { - throw IllegalStateException("Dialogs with no backgrounds on window are not supported") - } + // Remove the original dialog view from its parent. + (dialogView.parent as? ViewGroup)?.removeView(dialogView) - // Add a parent view to the original dialog view to which we will set the original dialog - // window background. This View serves as a fake window with background, so that we are sure - // that we don't override the dialog view paddings with the window background that usually - // has insets. - dialogContentParent.setBackgroundResource(backgroundRes) - hostDialogRoot.addView( - dialogContentParent, - - // We give it the size of its original dialog window. - FrameLayout.LayoutParams( - originalDialog.window.attributes.width, - originalDialog.window.attributes.height, - Gravity.CENTER + val originalDialogWindow = originalDialog.window!! + val isOriginalWindowFullScreen = + originalDialogWindow.attributes.width == ViewGroup.LayoutParams.MATCH_PARENT && + originalDialogWindow.attributes.height == ViewGroup.LayoutParams.MATCH_PARENT + if (isOriginalWindowFullScreen) { + // If the original dialog window is fullscreen, then we look for the first ViewGroup + // that has a background and animate towards that ViewGroup given that this is probably + // what represents the actual dialog view. + dialogContentWithBackground = findFirstViewGroupWithBackground(dialogView) + ?: throw IllegalStateException("Unable to find ViewGroup with background") + + hostDialogRoot.addView( + dialogView, + + FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) ) - ) + } else { + // Add a parent view to the original dialog view to which we will set the original + // dialog window background. This View serves as a fake window with background, so that + // we are sure that we don't override the original dialog content view paddings with the + // window background that usually has insets. + dialogContentWithBackground = FrameLayout(context).apply { + id = DIALOG_CONTENT_PARENT_ID + + // TODO(b/193634619): Support dialog windows without background. + background = originalDialogWindow.decorView?.background + ?: throw IllegalStateException( + "Dialogs with no backgrounds on window are not supported") + + addView( + dialogView, + + // It should match its parent size, which is sized the same as the original + // dialog window. + FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + ) + } + + // Add the parent (that has the background) to the host window. + hostDialogRoot.addView( + dialogContentWithBackground, - // Make the dialog view parent invisible for now, to make sure it's not drawn yet. - dialogContentParent.visibility = View.INVISIBLE + // We give it the size and gravity of its original dialog window. + FrameLayout.LayoutParams( + originalDialogWindow.attributes.width, + originalDialogWindow.attributes.height, + originalDialogWindow.attributes.gravity + ) + ) + } + + val dialogContentWithBackground = this.dialogContentWithBackground!! + + // Make the dialog and its background invisible for now, to make sure it's not drawn yet. + dialogContentWithBackground.visibility = View.INVISIBLE - val background = dialogContentParent.background!! + val background = dialogContentWithBackground.background!! originalDialogBackgroundColor = GhostedViewLaunchAnimatorController.findGradientDrawable(background) ?.color ?.defaultColor ?: Color.BLACK - // Add the dialog view to its parent (that has the original window background). - (dialogView.parent as? ViewGroup)?.removeView(dialogView) - dialogContentParent.addView( - dialogView, - - // It should match its parent size, which is sized the same as the original dialog - // window. - FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ) - ) + if (isOriginalWindowFullScreen) { + // If the original window is full screen, the ViewGroup with background might already be + // correctly laid out. Make sure we relayout and that the layout listener below is still + // called. + dialogContentWithBackground.layout(0, 0, 0, 0) + dialogContentWithBackground.requestLayout() + } // Start the animation when the dialog is laid out in the center of the host dialog. - dialogContentParent.addOnLayoutChangeListener(object : View.OnLayoutChangeListener { + dialogContentWithBackground.addOnLayoutChangeListener(object : View.OnLayoutChangeListener { override fun onLayoutChange( view: View, left: Int, @@ -515,7 +542,7 @@ private class AnimatedDialog( oldRight: Int, oldBottom: Int ) { - dialogContentParent.removeOnLayoutChangeListener(this) + dialogContentWithBackground.removeOnLayoutChangeListener(this) isOriginalDialogViewLaidOut = true maybeStartLaunchAnimation() @@ -523,6 +550,25 @@ private class AnimatedDialog( }) } + private fun findFirstViewGroupWithBackground(view: View): ViewGroup? { + if (view !is ViewGroup) { + return null + } + + if (view.background != null) { + return view + } + + for (i in 0 until view.childCount) { + val match = findFirstViewGroupWithBackground(view.getChildAt(i)) + if (match != null) { + return match + } + } + + return null + } + fun onOriginalDialogSizeChanged() { // The dialog is the single child of the root. if (hostDialogRoot.childCount != 1) { @@ -571,7 +617,8 @@ private class AnimatedDialog( // at the end of the launch animation, because the lauch animation already correctly // handles bounds changes. if (backgroundLayoutListener != null) { - dialogContentParent.addOnLayoutChangeListener(backgroundLayoutListener) + dialogContentWithBackground!! + .addOnLayoutChangeListener(backgroundLayoutListener) } } ) @@ -638,10 +685,12 @@ private class AnimatedDialog( (touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(false) touchSurface.visibility = View.VISIBLE - dialogContentParent.visibility = View.INVISIBLE + val dialogContentWithBackground = this.dialogContentWithBackground!! + dialogContentWithBackground.visibility = View.INVISIBLE if (backgroundLayoutListener != null) { - dialogContentParent.removeOnLayoutChangeListener(backgroundLayoutListener) + dialogContentWithBackground + .removeOnLayoutChangeListener(backgroundLayoutListener) } // The animated ghost was just removed. We create a temporary ghost that will be @@ -674,8 +723,8 @@ private class AnimatedDialog( ) { // Create 2 ghost controllers to animate both the dialog and the touch surface in the host // dialog. - val startView = if (isLaunching) touchSurface else dialogContentParent - val endView = if (isLaunching) dialogContentParent else touchSurface + val startView = if (isLaunching) touchSurface else dialogContentWithBackground!! + val endView = if (isLaunching) dialogContentWithBackground!! else touchSurface val startViewController = GhostedViewLaunchAnimatorController(startView) val endViewController = GhostedViewLaunchAnimatorController(endView) startViewController.launchContainer = hostDialogRoot @@ -736,7 +785,9 @@ private class AnimatedDialog( } private fun shouldAnimateDialogIntoView(): Boolean { - if (exitAnimationDisabled) { + // Don't animate if the dialog was previously hidden using hide() (either on the host dialog + // or on the original dialog) or if we disabled the exit animation. + if (exitAnimationDisabled || !hostDialog.isShowing) { return false } diff --git a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml index 98518c2bbf97..4a5b637a05d9 100644 --- a/packages/SystemUI/res/layout/internet_connectivity_dialog.xml +++ b/packages/SystemUI/res/layout/internet_connectivity_dialog.xml @@ -185,7 +185,7 @@ <LinearLayout android:id="@+id/turn_on_wifi_layout" style="@style/InternetDialog.Network" - android:layout_height="72dp" + android:layout_height="@dimen/internet_dialog_wifi_network_height" android:gravity="center" android:clickable="false" android:focusable="false"> @@ -227,7 +227,7 @@ <LinearLayout android:id="@+id/wifi_connected_layout" style="@style/InternetDialog.Network" - android:layout_height="72dp" + android:layout_height="@dimen/internet_dialog_wifi_network_height" android:paddingStart="20dp" android:paddingEnd="24dp" android:background="@drawable/settingslib_switch_bar_bg_on" @@ -249,7 +249,7 @@ android:orientation="vertical" android:clickable="false" android:layout_width="wrap_content" - android:layout_height="72dp" + android:layout_height="@dimen/internet_dialog_wifi_network_height" android:layout_marginEnd="30dp" android:layout_weight="1" android:gravity="start|center_vertical"> diff --git a/packages/SystemUI/res/layout/internet_list_item.xml b/packages/SystemUI/res/layout/internet_list_item.xml index 868331ec830f..f6a213662a18 100644 --- a/packages/SystemUI/res/layout/internet_list_item.xml +++ b/packages/SystemUI/res/layout/internet_list_item.xml @@ -25,7 +25,7 @@ <LinearLayout android:id="@+id/wifi_list" style="@style/InternetDialog.Network" - android:layout_height="72dp" + android:layout_height="@dimen/internet_dialog_wifi_network_height" android:paddingStart="20dp" android:paddingEnd="24dp"> <FrameLayout @@ -45,7 +45,7 @@ android:orientation="vertical" android:clickable="false" android:layout_width="wrap_content" - android:layout_height="72dp" + android:layout_height="@dimen/internet_dialog_wifi_network_height" android:layout_marginEnd="30dp" android:layout_weight="1" android:gravity="start|center_vertical"> diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index 8dbd59d42ab7..759670e01e71 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -17,7 +17,6 @@ <com.android.systemui.statusbar.phone.KeyguardBottomAreaView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui" android:id="@+id/keyguard_bottom_area" android:layout_height="match_parent" android:layout_width="match_parent" @@ -114,7 +113,8 @@ android:layout_height="match_parent"> <include layout="@layout/keyguard_bottom_area_overlay" /> - </FrameLayout> + <include layout="@layout/ambient_indication" + android:id="@+id/ambient_indication_container" /> </com.android.systemui.statusbar.phone.KeyguardBottomAreaView> diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml index e43a149a6cd9..6c5ad5060495 100644 --- a/packages/SystemUI/res/layout/screen_record_dialog.xml +++ b/packages/SystemUI/res/layout/screen_record_dialog.xml @@ -93,41 +93,6 @@ android:id="@+id/screenrecord_audio_switch" style="@style/ScreenRecord.Switch"/> </LinearLayout> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:layout_marginTop="@dimen/screenrecord_option_padding"> - <ImageView - android:layout_width="@dimen/screenrecord_option_icon_size" - android:layout_height="@dimen/screenrecord_option_icon_size" - android:layout_weight="0" - android:src="@drawable/ic_touch" - android:tint="?android:attr/textColorSecondary" - android:layout_gravity="center" - android:layout_marginRight="@dimen/screenrecord_option_padding"/> - <TextView - android:layout_width="0dp" - android:layout_height="wrap_content" - android:minHeight="48dp" - android:layout_weight="1" - android:layout_gravity="fill_vertical" - android:gravity="center_vertical" - android:text="@string/screenrecord_taps_label" - android:textAppearance="?android:attr/textAppearanceMedium" - android:fontFamily="@*android:string/config_headlineFontFamily" - android:textColor="?android:attr/textColorPrimary" - android:importantForAccessibility="no"/> - <Switch - android:layout_width="wrap_content" - android:minWidth="48dp" - android:layout_height="48dp" - android:layout_weight="0" - android:id="@+id/screenrecord_taps_switch" - android:contentDescription="@string/screenrecord_taps_label" - style="@style/ScreenRecord.Switch"/> - </LinearLayout> </LinearLayout> <!-- Buttons --> diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index 82186c13394e..591d8f52c4be 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -119,9 +119,6 @@ systemui:layout_constraintEnd_toEndOf="parent" /> - <include layout="@layout/ambient_indication" - android:id="@+id/ambient_indication_container" /> - <include layout="@layout/photo_preview_overlay" /> <include diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index a2fd669c69fd..1938e48e859a 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1243,6 +1243,8 @@ <!-- Internet panel related dimensions --> <dimen name="internet_dialog_list_max_height">662dp</dimen> + <!-- The height of the WiFi network in Internet panel. --> + <dimen name="internet_dialog_wifi_network_height">72dp</dimen> <!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) --> <dimen name="large_dialog_width">@dimen/match_parent</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 3c7b53a8e6db..61591bd0a450 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -373,11 +373,11 @@ <item name="android:windowIsFloating">true</item> </style> - <style name="Theme.SystemUI.Dialog.GlobalActionsLite" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen"> - <item name="android:windowIsFloating">true</item> + <style name="Theme.SystemUI.Dialog.GlobalActionsLite" parent="Theme.SystemUI.Dialog"> + <!-- Settings windowFullscreen: true is necessary to be able to intercept touch events --> + <!-- that would otherwise be intercepted by the Shade. --> + <item name="android:windowFullscreen">true</item> <item name="android:windowBackground">@android:color/transparent</item> - <item name="android:backgroundDimEnabled">true</item> - <item name="android:windowCloseOnTouchOutside">true</item> </style> <style name="QSBorderlessButton"> diff --git a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java index 07ad0c8a5120..8aa3abac831f 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java +++ b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java @@ -51,6 +51,9 @@ public class KeyButtonRipple extends Drawable { private static final Interpolator ALPHA_OUT_INTERPOLATOR = new PathInterpolator(0f, 0f, 0.8f, 1f); + @DimenRes + private final int mMaxWidthResource; + private Paint mRipplePaint; private CanvasProperty<Float> mLeftProp; private CanvasProperty<Float> mTopProp; @@ -90,10 +93,17 @@ public class KeyButtonRipple extends Drawable { private Type mType = Type.ROUNDED_RECT; public KeyButtonRipple(Context ctx, View targetView, @DimenRes int maxWidthResource) { + mMaxWidthResource = maxWidthResource; mMaxWidth = ctx.getResources().getDimensionPixelSize(maxWidthResource); mTargetView = targetView; } + public void updateResources() { + mMaxWidth = mTargetView.getContext().getResources() + .getDimensionPixelSize(mMaxWidthResource); + invalidateSelf(); + } + public void setDarkIntensity(float darkIntensity) { mDark = darkIntensity >= 0.5f; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java index cbf739732361..857cc4620ebd 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java @@ -21,6 +21,8 @@ import android.annotation.IdRes; import android.annotation.LayoutRes; import android.annotation.StringRes; import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ActivityInfo.Config; import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.drawable.AnimatedVectorDrawable; @@ -29,12 +31,12 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; +import android.view.WindowManager.LayoutParams; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.FrameLayout; import androidx.core.view.OneShotPreDrawListener; -import com.android.systemui.shared.R; import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position; /** @@ -48,7 +50,21 @@ public class FloatingRotationButton implements RotationButton { private final ViewGroup mKeyButtonContainer; private final FloatingRotationButtonView mKeyButtonView; - private final int mContainerSize; + private int mContainerSize; + private final Context mContext; + + @StringRes + private final int mContentDescriptionResource; + @DimenRes + private final int mMinMarginResource; + @DimenRes + private final int mRoundedContentPaddingResource; + @DimenRes + private final int mTaskbarLeftMarginResource; + @DimenRes + private final int mTaskbarBottomMarginResource; + @DimenRes + private final int mButtonDiameterResource; private AnimatedVectorDrawable mAnimatedDrawable; private boolean mIsShowing; @@ -58,13 +74,13 @@ public class FloatingRotationButton implements RotationButton { private boolean mIsTaskbarVisible = false; private boolean mIsTaskbarStashed = false; - private final FloatingRotationButtonPositionCalculator mPositionCalculator; + private FloatingRotationButtonPositionCalculator mPositionCalculator; private RotationButtonController mRotationButtonController; private RotationButtonUpdatesCallback mUpdatesCallback; private Position mPosition; - public FloatingRotationButton(Context context, @StringRes int contentDescription, + public FloatingRotationButton(Context context, @StringRes int contentDescriptionResource, @LayoutRes int layout, @IdRes int keyButtonId, @DimenRes int minMargin, @DimenRes int roundedContentPadding, @DimenRes int taskbarLeftMargin, @DimenRes int taskbarBottomMargin, @DimenRes int buttonDiameter, @@ -73,24 +89,37 @@ public class FloatingRotationButton implements RotationButton { mKeyButtonContainer = (ViewGroup) LayoutInflater.from(context).inflate(layout, null); mKeyButtonView = mKeyButtonContainer.findViewById(keyButtonId); mKeyButtonView.setVisibility(View.VISIBLE); - mKeyButtonView.setContentDescription(context.getString(contentDescription)); + mKeyButtonView.setContentDescription(context.getString(contentDescriptionResource)); mKeyButtonView.setRipple(rippleMaxWidth); - Resources res = context.getResources(); + mContext = context; + + mContentDescriptionResource = contentDescriptionResource; + mMinMarginResource = minMargin; + mRoundedContentPaddingResource = roundedContentPadding; + mTaskbarLeftMarginResource = taskbarLeftMargin; + mTaskbarBottomMarginResource = taskbarBottomMargin; + mButtonDiameterResource = buttonDiameter; + + updateDimensionResources(); + } + + private void updateDimensionResources() { + Resources res = mContext.getResources(); int defaultMargin = Math.max( - res.getDimensionPixelSize(minMargin), - res.getDimensionPixelSize(roundedContentPadding)); + res.getDimensionPixelSize(mMinMarginResource), + res.getDimensionPixelSize(mRoundedContentPaddingResource)); int taskbarMarginLeft = - res.getDimensionPixelSize(taskbarLeftMargin); + res.getDimensionPixelSize(mTaskbarLeftMarginResource); int taskbarMarginBottom = - res.getDimensionPixelSize(taskbarBottomMargin); + res.getDimensionPixelSize(mTaskbarBottomMarginResource); mPositionCalculator = new FloatingRotationButtonPositionCalculator(defaultMargin, taskbarMarginLeft, taskbarMarginBottom); - final int diameter = res.getDimensionPixelSize(buttonDiameter); + final int diameter = res.getDimensionPixelSize(mButtonDiameterResource); mContainerSize = diameter + Math.max(defaultMargin, Math.max(taskbarMarginLeft, taskbarMarginBottom)); } @@ -119,32 +148,10 @@ public class FloatingRotationButton implements RotationButton { } mIsShowing = true; - int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - - // TODO(b/200103245): add new window type that has z-index above - // TYPE_NAVIGATION_BAR_PANEL as currently it could be below the taskbar which has - // the same window type - final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - mContainerSize, - mContainerSize, - 0, 0, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, flags, - PixelFormat.TRANSLUCENT); - - lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; - lp.setTitle("FloatingRotationButton"); - lp.setFitInsetsTypes(0 /*types */); - - mDisplayRotation = mWindowManager.getDefaultDisplay().getRotation(); - mPosition = mPositionCalculator - .calculatePosition(mDisplayRotation, mIsTaskbarVisible, mIsTaskbarStashed); - lp.gravity = mPosition.getGravity(); - ((FrameLayout.LayoutParams) mKeyButtonView.getLayoutParams()).gravity = - mPosition.getGravity(); + final LayoutParams layoutParams = adjustViewPositionAndCreateLayoutParams(); + mWindowManager.addView(mKeyButtonContainer, layoutParams); - updateTranslation(mPosition, /* animate */ false); - - mWindowManager.addView(mKeyButtonContainer, lp); if (mAnimatedDrawable != null) { mAnimatedDrawable.reset(); mAnimatedDrawable.start(); @@ -232,6 +239,53 @@ public class FloatingRotationButton implements RotationButton { } } + /** + * Updates resources that could be changed in runtime, should be called on configuration + * change with changes diff integer mask + * @param configurationChanges - configuration changes with flags from ActivityInfo e.g. + * {@link android.content.pm.ActivityInfo#CONFIG_DENSITY} + */ + public void onConfigurationChanged(@Config int configurationChanges) { + if ((configurationChanges & ActivityInfo.CONFIG_DENSITY) != 0 + || (configurationChanges & ActivityInfo.CONFIG_SCREEN_SIZE) != 0) { + updateDimensionResources(); + + if (mIsShowing) { + final LayoutParams layoutParams = adjustViewPositionAndCreateLayoutParams(); + mWindowManager.updateViewLayout(mKeyButtonContainer, layoutParams); + } + } + + if ((configurationChanges & ActivityInfo.CONFIG_LOCALE) != 0) { + mKeyButtonView.setContentDescription(mContext.getString(mContentDescriptionResource)); + } + } + + private LayoutParams adjustViewPositionAndCreateLayoutParams() { + final LayoutParams lp = new LayoutParams( + mContainerSize, + mContainerSize, + /* xpos */ 0, /* ypos */ 0, LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + + lp.privateFlags |= LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; + lp.setTitle("FloatingRotationButton"); + lp.setFitInsetsTypes(/* types */ 0); + + mDisplayRotation = mWindowManager.getDefaultDisplay().getRotation(); + mPosition = mPositionCalculator + .calculatePosition(mDisplayRotation, mIsTaskbarVisible, mIsTaskbarStashed); + + lp.gravity = mPosition.getGravity(); + ((FrameLayout.LayoutParams) mKeyButtonView.getLayoutParams()).gravity = + mPosition.getGravity(); + + updateTranslation(mPosition, /* animate */ false); + + return lp; + } + private void updateTranslation(Position position, boolean animate) { final int translationX = position.getTranslationX(); final int translationY = position.getTranslationY(); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java index c5f8fc15b3b7..a4b6451caaea 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonView.java @@ -17,6 +17,8 @@ package com.android.systemui.shared.rotation; import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; @@ -37,12 +39,15 @@ public class FloatingRotationButtonView extends ImageView { private KeyButtonRipple mRipple; private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + private final Configuration mLastConfiguration; + public FloatingRotationButtonView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public FloatingRotationButtonView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + mLastConfiguration = getResources().getConfiguration(); setClickable(true); @@ -63,6 +68,17 @@ public class FloatingRotationButtonView extends ImageView { } } + @Override + protected void onConfigurationChanged(Configuration newConfig) { + final int changes = mLastConfiguration.updateFrom(newConfig); + if ((changes & ActivityInfo.CONFIG_SCREEN_SIZE) != 0 + || ((changes & ActivityInfo.CONFIG_DENSITY) != 0)) { + if (mRipple != null) { + mRipple.updateResources(); + } + } + } + public void setColors(int lightColor, int darkColor) { getDrawable().setColorFilter(new PorterDuffColorFilter(lightColor, PorterDuff.Mode.SRC_IN)); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java index 2dbd5dee76aa..78867f7220af 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java @@ -481,7 +481,9 @@ public class RotationButtonController { * orientation overview. */ public void setSkipOverrideUserLockPrefsOnce() { - mSkipOverrideUserLockPrefsOnce = true; + // If live-tile is enabled (recents animation keeps running in overview), there is no + // activity switch so the display rotation is not changed, then it is no need to skip. + mSkipOverrideUserLockPrefsOnce = !mIsRecentsAnimationRunning; } private boolean shouldOverrideUserLockPrefs(final int rotation) { diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index 68132f4c598b..b2ecc6140460 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -18,6 +18,7 @@ package com.android.keyguard; import android.content.Context; import android.content.res.ColorStateList; +import android.graphics.Color; import android.graphics.PointF; import android.graphics.RectF; import android.graphics.drawable.Drawable; @@ -30,6 +31,7 @@ import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; +import com.android.internal.graphics.ColorUtils; import com.android.settingslib.Utils; import com.android.systemui.Dumpable; import com.android.systemui.R; @@ -82,14 +84,18 @@ public class LockIconView extends FrameLayout implements Dumpable { void updateColorAndBackgroundVisibility() { if (mUseBackground && mLockIcon.getDrawable() != null) { - mLockIconColor = Utils.getColorAttrDefaultColor(getContext(), - android.R.attr.textColorPrimary); + mLockIconColor = ColorUtils.blendARGB( + Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorPrimary), + Color.WHITE, + mDozeAmount); mBgView.setBackground(getContext().getDrawable(R.drawable.fingerprint_bg)); mBgView.setAlpha(1f - mDozeAmount); mBgView.setVisibility(View.VISIBLE); } else { - mLockIconColor = Utils.getColorAttrDefaultColor(getContext(), - R.attr.wallpaperTextColorAccent); + mLockIconColor = ColorUtils.blendARGB( + Utils.getColorAttrDefaultColor(getContext(), R.attr.wallpaperTextColorAccent), + Color.WHITE, + mDozeAmount); mBgView.setVisibility(View.GONE); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index fd3fcdd1d58a..12786f278a16 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -66,7 +66,6 @@ import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfC import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.dagger.StatusBarComponent; -import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ZenModeController; @@ -125,7 +124,6 @@ import dagger.Provides; }, subcomponents = { StatusBarComponent.class, - StatusBarFragmentComponent.class, NotificationRowComponent.class, DozeComponent.class, ExpandableNotificationRowComponent.class, diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index ad472326555e..ff14064834d7 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -31,8 +31,6 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_G import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.Dialog; @@ -56,6 +54,7 @@ import android.graphics.Color; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.os.Binder; +import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; @@ -80,8 +79,6 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; @@ -112,7 +109,7 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.MultiListLayout; import com.android.systemui.MultiListLayout.MultiListAdapter; -import com.android.systemui.animation.Interpolators; +import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dagger.qualifiers.Background; @@ -123,6 +120,7 @@ import com.android.systemui.plugins.GlobalActionsPanelPlugin; import com.android.systemui.scrim.ScrimDrawable; import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.telephony.TelephonyListenerManager; @@ -239,6 +237,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene private int mSmallestScreenWidthDp; private final Optional<StatusBar> mStatusBarOptional; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final DialogLaunchAnimator mDialogLaunchAnimator; @VisibleForTesting public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum { @@ -343,10 +342,11 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene UiEventLogger uiEventLogger, RingerModeTracker ringerModeTracker, SysUiState sysUiState, - @Main Handler handler, + @Main Handler handler, PackageManager packageManager, Optional<StatusBar> statusBarOptional, - KeyguardUpdateMonitor keyguardUpdateMonitor) { + KeyguardUpdateMonitor keyguardUpdateMonitor, + DialogLaunchAnimator dialogLaunchAnimator) { mContext = context; mWindowManagerFuncs = windowManagerFuncs; mAudioManager = audioManager; @@ -377,6 +377,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp; mStatusBarOptional = statusBarOptional; mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mDialogLaunchAnimator = dialogLaunchAnimator; // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -435,11 +436,14 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } /** - * Show the global actions dialog (creating if necessary) + * Show the global actions dialog (creating if necessary) or hide it if it's already showing. * - * @param keyguardShowing True if keyguard is showing + * @param keyguardShowing True if keyguard is showing + * @param isDeviceProvisioned True if device is provisioned + * @param view The view from which we should animate the dialog when showing it */ - public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned) { + public void showOrHideDialog(boolean keyguardShowing, boolean isDeviceProvisioned, + @Nullable View view) { mKeyguardShowing = keyguardShowing; mDeviceProvisioned = isDeviceProvisioned; if (mDialog != null && mDialog.isShowing()) { @@ -451,7 +455,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mDialog.dismiss(); mDialog = null; } else { - handleShow(); + handleShow(view); } } @@ -483,7 +487,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } } - protected void handleShow() { + protected void handleShow(@Nullable View view) { awakenIfNecessary(); mDialog = createDialog(); prepareDialog(); @@ -493,8 +497,13 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; mDialog.getWindow().setAttributes(attrs); // Don't acquire soft keyboard focus, to avoid destroying state when capturing bugreports - mDialog.getWindow().setFlags(FLAG_ALT_FOCUSABLE_IM, FLAG_ALT_FOCUSABLE_IM); - mDialog.show(); + mDialog.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM); + + if (view != null) { + mDialogLaunchAnimator.showFromView(mDialog, view); + } else { + mDialog.show(); + } mWindowManagerFuncs.onGlobalActionsShown(); } @@ -643,7 +652,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } } - protected void onRotate() { + protected void onRefresh() { // re-allocate actions between main and overflow lists this.createActionItems(); } @@ -667,7 +676,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActionsLite, mAdapter, mOverflowAdapter, mSysuiColorExtractor, mStatusBarService, mNotificationShadeWindowController, - mSysUiState, this::onRotate, mKeyguardShowing, mPowerAdapter, mUiEventLogger, + mSysUiState, this::onRefresh, mKeyguardShowing, mPowerAdapter, mUiEventLogger, mStatusBarOptional, mKeyguardUpdateMonitor, mLockPatternUtils); dialog.setOnDismissListener(this); @@ -702,14 +711,6 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } @Override - public void onUiModeChanged() { - mContext.getTheme().applyStyle(mContext.getThemeResId(), true); - if (mDialog != null && mDialog.isShowing()) { - mDialog.refreshDialog(); - } - } - - @Override public void onConfigChanged(Configuration newConfig) { if (mDialog != null && mDialog.isShowing() && (newConfig.smallestScreenWidthDp != mSmallestScreenWidthDp)) { @@ -717,6 +718,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mDialog.refreshDialog(); } } + /** * Implements {@link GlobalActionsPanelPlugin.Callbacks#dismissGlobalActionsMenu()}, which is * called when the quick access wallet requests dismissal. @@ -1363,6 +1365,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene final Action action = mAdapter.getItem(position); if (action instanceof LongPressAction) { if (mDialog != null) { + // Usually clicking an item shuts down the phone, locks, or starts an activity. + // We don't want to animate back into the power button when that happens, so we + // disable the dialog animation before dismissing. + mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); mDialog.dismiss(); } else { Log.w(TAG, "Action long-clicked while mDialog is null."); @@ -1379,6 +1385,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene if (mDialog != null) { // don't dismiss the dialog if we're opening the power options menu if (!(item instanceof PowerOptionsAction)) { + // Usually clicking an item shuts down the phone, locks, or starts an + // activity. We don't want to animate back into the power button when that + // happens, so we disable the dialog animation before dismissing. + mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); mDialog.dismiss(); } } else { @@ -1446,6 +1456,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene final Action action = getItem(position); if (action instanceof LongPressAction) { if (mDialog != null) { + // Usually clicking an item shuts down the phone, locks, or starts an activity. + // We don't want to animate back into the power button when that happens, so we + // disable the dialog animation before dismissing. + mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); mDialog.dismiss(); } else { Log.w(TAG, "Action long-clicked while mDialog is null."); @@ -1459,6 +1473,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene Action item = getItem(position); if (!(item instanceof SilentModeTriStateAction)) { if (mDialog != null) { + // Usually clicking an item shuts down the phone, locks, or starts an activity. + // We don't want to animate back into the power button when that happens, so we + // disable the dialog animation before dismissing. + mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); mDialog.dismiss(); } else { Log.w(TAG, "Action clicked while mDialog is null."); @@ -1510,6 +1528,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene final Action action = getItem(position); if (action instanceof LongPressAction) { if (mDialog != null) { + // Usually clicking an item shuts down the phone, locks, or starts an activity. + // We don't want to animate back into the power button when that happens, so we + // disable the dialog animation before dismissing. + mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); mDialog.dismiss(); } else { Log.w(TAG, "Action long-clicked while mDialog is null."); @@ -1523,6 +1545,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene Action item = getItem(position); if (!(item instanceof SilentModeTriStateAction)) { if (mDialog != null) { + // Usually clicking an item shuts down the phone, locks, or starts an activity. + // We don't want to animate back into the power button when that happens, so we + // disable the dialog animation before dismissing. + mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); mDialog.dismiss(); } else { Log.w(TAG, "Action clicked while mDialog is null."); @@ -2066,7 +2092,9 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene case MESSAGE_DISMISS: if (mDialog != null) { if (SYSTEM_DIALOG_REASON_DREAM.equals(msg.obj)) { - mDialog.completeDismiss(); + // Hide instantly. + mDialog.hide(); + mDialog.dismiss(); } else { mDialog.dismiss(); } @@ -2113,7 +2141,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } @VisibleForTesting - static class ActionsDialogLite extends Dialog implements DialogInterface, + static class ActionsDialogLite extends SystemUIDialog implements DialogInterface, ColorExtractor.OnColorsChangedListener { protected final Context mContext; @@ -2126,13 +2154,12 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene protected Drawable mBackgroundDrawable; protected final SysuiColorExtractor mColorExtractor; private boolean mKeyguardShowing; - protected boolean mShowing; protected float mScrimAlpha; protected final NotificationShadeWindowController mNotificationShadeWindowController; protected final SysUiState mSysUiState; private ListPopupWindow mOverflowPopup; private Dialog mPowerOptionsDialog; - protected final Runnable mOnRotateCallback; + protected final Runnable mOnRefreshCallback; private UiEventLogger mUiEventLogger; private GestureDetector mGestureDetector; private Optional<StatusBar> mStatusBarOptional; @@ -2151,7 +2178,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } @Override - public boolean onSingleTapConfirmed(MotionEvent e) { + public boolean onSingleTapUp(MotionEvent e) { // Close without opening shade mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); cancel(); @@ -2189,11 +2216,13 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene MyOverflowAdapter overflowAdapter, SysuiColorExtractor sysuiColorExtractor, IStatusBarService statusBarService, NotificationShadeWindowController notificationShadeWindowController, - SysUiState sysuiState, Runnable onRotateCallback, boolean keyguardShowing, + SysUiState sysuiState, Runnable onRefreshCallback, boolean keyguardShowing, MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger, Optional<StatusBar> statusBarOptional, KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils) { - super(context, themeRes); + // We set dismissOnDeviceLock to false because we have a custom broadcast receiver to + // dismiss this dialog when the device is locked. + super(context, themeRes, false /* dismissOnDeviceLock */); mContext = context; mAdapter = adapter; mOverflowAdapter = overflowAdapter; @@ -2202,36 +2231,32 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene mStatusBarService = statusBarService; mNotificationShadeWindowController = notificationShadeWindowController; mSysUiState = sysuiState; - mOnRotateCallback = onRotateCallback; + mOnRefreshCallback = onRefreshCallback; mKeyguardShowing = keyguardShowing; mUiEventLogger = uiEventLogger; mStatusBarOptional = statusBarOptional; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; - mGestureDetector = new GestureDetector(mContext, mGestureListener); + } - // Window initialization - Window window = getWindow(); - window.requestFeature(Window.FEATURE_NO_TITLE); - // Inflate the decor view, so the attributes below are not overwritten by the theme. - window.getDecorView(); - window.getAttributes().systemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; - window.setLayout(MATCH_PARENT, MATCH_PARENT); - window.addFlags( - WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR - | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED - | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); - window.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); - window.getAttributes().setFitInsetsTypes(0 /* types */); - setTitle(R.string.global_actions); - + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); initializeLayout(); } @Override + protected int getWidth() { + return MATCH_PARENT; + } + + @Override + protected int getHeight() { + return MATCH_PARENT; + } + + @Override public boolean onTouchEvent(MotionEvent event) { return mGestureDetector.onTouchEvent(event) || super.onTouchEvent(event); } @@ -2371,7 +2396,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene message.animate() .alpha(0f) .setDuration(TOAST_FADE_TIME) - .setStartDelay(visibleTime); + .setStartDelay(visibleTime) + .setListener(null); } }); } @@ -2423,122 +2449,32 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene @Override public void show() { super.show(); - // split this up so we can override but still call Dialog.show - showDialog(); - } - - protected void showDialog() { - mShowing = true; mNotificationShadeWindowController.setRequestTopUi(true, TAG); mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, true) .commitUpdate(mContext.getDisplayId()); - - ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView(); - root.setOnApplyWindowInsetsListener((v, windowInsets) -> { - root.setPadding(windowInsets.getStableInsetLeft(), - windowInsets.getStableInsetTop(), - windowInsets.getStableInsetRight(), - windowInsets.getStableInsetBottom()); - return WindowInsets.CONSUMED; - }); - - mBackgroundDrawable.setAlpha(0); - float xOffset = mGlobalActionsLayout.getAnimationOffsetX(); - ObjectAnimator alphaAnimator = - ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f); - alphaAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); - alphaAnimator.setDuration(183); - alphaAnimator.addUpdateListener((animation) -> { - float animatedValue = animation.getAnimatedFraction(); - int alpha = (int) (animatedValue * mScrimAlpha * 255); - mBackgroundDrawable.setAlpha(alpha); - }); - - ObjectAnimator xAnimator = - ObjectAnimator.ofFloat(mContainer, "translationX", xOffset, 0f); - xAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); - xAnimator.setDuration(350); - - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(alphaAnimator, xAnimator); - animatorSet.start(); } @Override public void dismiss() { - dismissWithAnimation(() -> { - dismissInternal(); - }); - } - - protected void dismissInternal() { - mContainer.setTranslationX(0); - ObjectAnimator alphaAnimator = - ObjectAnimator.ofFloat(mContainer, "alpha", 1f, 0f); - alphaAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); - alphaAnimator.setDuration(233); - alphaAnimator.addUpdateListener((animation) -> { - float animatedValue = 1f - animation.getAnimatedFraction(); - int alpha = (int) (animatedValue * mScrimAlpha * 255); - mBackgroundDrawable.setAlpha(alpha); - }); - - float xOffset = mGlobalActionsLayout.getAnimationOffsetX(); - ObjectAnimator xAnimator = - ObjectAnimator.ofFloat(mContainer, "translationX", 0f, xOffset); - xAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); - xAnimator.setDuration(350); - - AnimatorSet animatorSet = new AnimatorSet(); - animatorSet.playTogether(alphaAnimator, xAnimator); - animatorSet.addListener(new AnimatorListenerAdapter() { - public void onAnimationEnd(Animator animation) { - completeDismiss(); - } - }); - - animatorSet.start(); - - // close first, as popup windows will not fade during the animation - dismissOverflow(false); - dismissPowerOptions(false); - } + dismissOverflow(); + dismissPowerOptions(); - void dismissWithAnimation(Runnable animation) { - if (!mShowing) { - return; - } - mShowing = false; - animation.run(); - } - - protected void completeDismiss() { - mShowing = false; - dismissOverflow(true); - dismissPowerOptions(true); mNotificationShadeWindowController.setRequestTopUi(false, TAG); mSysUiState.setFlag(SYSUI_STATE_GLOBAL_ACTIONS_SHOWING, false) .commitUpdate(mContext.getDisplayId()); + super.dismiss(); } - protected final void dismissOverflow(boolean immediate) { + protected final void dismissOverflow() { if (mOverflowPopup != null) { - if (immediate) { - mOverflowPopup.dismissImmediate(); - } else { - mOverflowPopup.dismiss(); - } + mOverflowPopup.dismiss(); } } - protected final void dismissPowerOptions(boolean immediate) { + protected final void dismissPowerOptions() { if (mPowerOptionsDialog != null) { - if (immediate) { - mPowerOptionsDialog.dismiss(); - } else { - mPowerOptionsDialog.dismiss(); - } + mPowerOptionsDialog.dismiss(); } } @@ -2574,20 +2510,18 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } public void refreshDialog() { - // ensure dropdown menus are dismissed before re-initializing the dialog - dismissOverflow(true); - dismissPowerOptions(true); + mOnRefreshCallback.run(); - // re-create dialog - initializeLayout(); + // Dismiss the dropdown menus. + dismissOverflow(); + dismissPowerOptions(); + + // Update the list as the max number of items per row has probably changed. mGlobalActionsLayout.updateList(); } public void onRotate(int from, int to) { - if (mShowing) { - mOnRotateCallback.run(); - refreshDialog(); - } + refreshDialog(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java index c4508e043c7d..96ae646ac7f9 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java @@ -82,7 +82,7 @@ public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks if (mDisabled) return; mGlobalActionsDialog = mGlobalActionsDialogLazy.get(); mGlobalActionsDialog.showOrHideDialog(mKeyguardStateController.isShowing(), - mDeviceProvisionedController.isDeviceProvisioned()); + mDeviceProvisionedController.isDeviceProvisioned(), null /* view */); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java index 868193b44704..54e40f1429be 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java @@ -289,6 +289,9 @@ public abstract class MediaOutputBaseAdapter extends public void onAnimationEnd(Animator animation) { to.requireViewById(R.id.volume_indeterminate_progress).setVisibility( View.VISIBLE); + // Unset the listener, otherwise this may persist for another view + // property animation + toTitleText.animate().setListener(null); } }); // Animation for seek bar @@ -312,8 +315,14 @@ public abstract class MediaOutputBaseAdapter extends public void onAnimationEnd(Animator animation) { mIsAnimating = false; notifyDataSetChanged(); + // Unset the listener, otherwise this may persist for + // another view property animation + fromTitleText.animate().setListener(null); } }); + // Unset the listener, otherwise this may persist for another view + // property animation + fromSeekBar.animate().setListener(null); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java new file mode 100644 index 000000000000..25337b6d52a1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java @@ -0,0 +1,272 @@ +/* + * 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. + */ + +package com.android.systemui.navigationbar; + +import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; + +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.inputmethodservice.InputMethodService; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.provider.Settings; +import android.view.View; +import android.view.WindowInsets; +import android.view.accessibility.AccessibilityManager; + +import androidx.annotation.NonNull; + +import com.android.systemui.Dumpable; +import com.android.systemui.accessibility.AccessibilityButtonModeObserver; +import com.android.systemui.assist.AssistManager; +import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import javax.inject.Inject; + +import dagger.Lazy; + +/** + * Extracts shared elements between navbar and taskbar delegate to de-dupe logic and help them + * experience the joys of friendship. + * The events are then passed through + * + * Currently consolidates + * * A11y + * * Assistant + */ +@SysUISingleton +public final class NavBarHelper implements + AccessibilityButtonModeObserver.ModeChangedListener, + OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener, + Dumpable { + private final AccessibilityManager mAccessibilityManager; + private final Lazy<AssistManager> mAssistManagerLazy; + private final Lazy<Optional<StatusBar>> mStatusBarOptionalLazy; + private final UserTracker mUserTracker; + private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver; + private final List<NavbarTaskbarStateUpdater> mA11yEventListeners = new ArrayList<>(); + private final Context mContext; + private ContentResolver mContentResolver; + private boolean mAssistantAvailable; + private boolean mLongPressHomeEnabled; + private boolean mAssistantTouchGestureEnabled; + private int mNavBarMode; + + private final ContentObserver mAssistContentObserver = new ContentObserver( + new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange, Uri uri) { + updateAssitantAvailability(); + } + }; + + /** + * @param context This is not display specific, then again neither is any of the code in + * this class. Once there's display specific code, we may want to create an + * instance of this class per navbar vs having it be a singleton. + */ + @Inject + public NavBarHelper(Context context, AccessibilityManager accessibilityManager, + AccessibilityManagerWrapper accessibilityManagerWrapper, + AccessibilityButtonModeObserver accessibilityButtonModeObserver, + OverviewProxyService overviewProxyService, + Lazy<AssistManager> assistManagerLazy, + Lazy<Optional<StatusBar>> statusBarOptionalLazy, + NavigationModeController navigationModeController, + UserTracker userTracker, + DumpManager dumpManager) { + mContext = context; + mAccessibilityManager = accessibilityManager; + mAssistManagerLazy = assistManagerLazy; + mStatusBarOptionalLazy = statusBarOptionalLazy; + mUserTracker = userTracker; + accessibilityManagerWrapper.addCallback( + accessibilityManager1 -> NavBarHelper.this.dispatchA11yEventUpdate()); + mAccessibilityButtonModeObserver = accessibilityButtonModeObserver; + + mAccessibilityButtonModeObserver.addListener(this); + mNavBarMode = navigationModeController.addListener(this); + overviewProxyService.addCallback(this); + dumpManager.registerDumpable(this); + } + + public void init() { + mContentResolver = mContext.getContentResolver(); + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ASSISTANT), + false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL); + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED), + false, mAssistContentObserver, UserHandle.USER_ALL); + mContentResolver.registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED), + false, mAssistContentObserver, UserHandle.USER_ALL); + updateAssitantAvailability(); + } + + public void destroy() { + mContentResolver.unregisterContentObserver(mAssistContentObserver); + } + + /** + * @param listener Will immediately get callbacks based on current state + */ + public void registerNavTaskStateUpdater(NavbarTaskbarStateUpdater listener) { + mA11yEventListeners.add(listener); + listener.updateAccessibilityServicesState(); + listener.updateAssistantAvailable(mAssistantAvailable); + } + + public void removeNavTaskStateUpdater(NavbarTaskbarStateUpdater listener) { + mA11yEventListeners.remove(listener); + } + + private void dispatchA11yEventUpdate() { + for (NavbarTaskbarStateUpdater listener : mA11yEventListeners) { + listener.updateAccessibilityServicesState(); + } + } + + private void dispatchAssistantEventUpdate(boolean assistantAvailable) { + for (NavbarTaskbarStateUpdater listener : mA11yEventListeners) { + listener.updateAssistantAvailable(assistantAvailable); + } + } + + @Override + public void onAccessibilityButtonModeChanged(int mode) { + dispatchA11yEventUpdate(); + } + + /** + * See {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_CLICKABLE} and + * {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE} + * + * @return the a11y button clickable and long_clickable states, or 0 if there is no + * a11y button in the navbar + */ + public int getA11yButtonState() { + // AccessibilityManagerService resolves services for the current user since the local + // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission + final List<String> a11yButtonTargets = + mAccessibilityManager.getAccessibilityShortcutTargets( + AccessibilityManager.ACCESSIBILITY_BUTTON); + final int requestingServices = a11yButtonTargets.size(); + + // If accessibility button is floating menu mode, click and long click state should be + // disabled. + if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode() + == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) { + return 0; + } + + return (requestingServices >= 1 ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0) + | (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0); + } + + @Override + public void onConnectionChanged(boolean isConnected) { + if (isConnected) { + updateAssitantAvailability(); + } + } + + private void updateAssitantAvailability() { + boolean assistantAvailableForUser = mAssistManagerLazy.get() + .getAssistInfoForUser(UserHandle.USER_CURRENT) != null; + boolean longPressDefault = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_assistLongPressHomeEnabledDefault); + mLongPressHomeEnabled = Settings.Secure.getIntForUser(mContentResolver, + Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, longPressDefault ? 1 : 0, + mUserTracker.getUserId()) != 0; + boolean gestureDefault = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_assistTouchGestureEnabledDefault); + mAssistantTouchGestureEnabled = Settings.Secure.getIntForUser(mContentResolver, + Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED, gestureDefault ? 1 : 0, + mUserTracker.getUserId()) != 0; + + mAssistantAvailable = assistantAvailableForUser + && mAssistantTouchGestureEnabled + && QuickStepContract.isGesturalMode(mNavBarMode); + dispatchAssistantEventUpdate(mAssistantAvailable); + } + + public boolean getLongPressHomeEnabled() { + return mLongPressHomeEnabled; + } + + @Override + public void startAssistant(Bundle bundle) { + mAssistManagerLazy.get().startAssist(bundle); + } + + @Override + public void onNavigationModeChanged(int mode) { + mNavBarMode = mode; + updateAssitantAvailability(); + } + + /** + * @return Whether the IME is shown on top of the screen given the {@code vis} flag of + * {@link InputMethodService} and the keyguard states. + */ + public boolean isImeShown(int vis) { + View shadeWindowView = mStatusBarOptionalLazy.get().get().getNotificationShadeWindowView(); + boolean isKeyguardShowing = mStatusBarOptionalLazy.get().get().isKeyguardShowing(); + boolean imeVisibleOnShade = shadeWindowView != null && shadeWindowView.isAttachedToWindow() + && shadeWindowView.getRootWindowInsets().isVisible(WindowInsets.Type.ime()); + return imeVisibleOnShade + || (!isKeyguardShowing && (vis & InputMethodService.IME_VISIBLE) != 0); + } + + /** + * Callbacks will get fired once immediately after registering via + * {@link #registerNavTaskStateUpdater(NavbarTaskbarStateUpdater)} + */ + public interface NavbarTaskbarStateUpdater { + void updateAccessibilityServicesState(); + void updateAssistantAvailable(boolean available); + } + + @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("NavbarTaskbarFriendster"); + pw.println(" longPressHomeEnabled=" + mLongPressHomeEnabled); + pw.println(" mAssistantTouchGestureEnabled=" + mAssistantTouchGestureEnabled); + pw.println(" mAssistantAvailable=" + mAssistantAvailable); + pw.println(" mNavBarMode=" + mNavBarMode); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index bc023cc892a6..03bceacdfbdc 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -68,22 +68,17 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; -import android.database.ContentObserver; import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; -import android.inputmethodservice.InputMethodService; -import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; import android.provider.DeviceConfig; -import android.provider.Settings; import android.telecom.TelecomManager; import android.text.TextUtils; import android.util.Log; @@ -200,8 +195,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private final Handler mHandler; private final NavigationBarOverlayController mNavbarOverlayController; private final UiEventLogger mUiEventLogger; - private final NavigationBarA11yHelper mNavigationBarA11yHelper; - private final UserTracker mUserTracker; + private final NavBarHelper mNavBarHelper; private final NotificationShadeDepthController mNotificationShadeDepthController; private Bundle mSavedState; @@ -213,9 +207,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private int mNavigationIconHints = 0; private @TransitionMode int mNavigationBarMode; private ContentResolver mContentResolver; - private boolean mAssistantAvailable; private boolean mLongPressHomeEnabled; - private boolean mAssistantTouchGestureEnabled; private int mDisabledFlags1; private int mDisabledFlags2; @@ -309,16 +301,31 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } }; + private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater = + new NavBarHelper.NavbarTaskbarStateUpdater() { + @Override + public void updateAccessibilityServicesState() { + updateAcessibilityStateFlags(); + } + + @Override + public void updateAssistantAvailable(boolean available) { + // TODO(b/198002034): Content observers currently can still be called back after + // being unregistered, and in this case we can ignore the change if the nav bar + // has been destroyed already + if (mNavigationBarView == null) { + return; + } + mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled(); + updateAssistantEntrypoints(available); + } + }; + private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() { @Override public void onConnectionChanged(boolean isConnected) { mNavigationBarView.updateStates(); updateScreenPinningGestures(); - - // Send the assistant availability upon connection - if (isConnected) { - updateAssistantEntrypoints(); - } } @Override @@ -421,20 +428,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } }; - private final ContentObserver mAssistContentObserver = new ContentObserver( - new Handler(Looper.getMainLooper())) { - @Override - public void onChange(boolean selfChange, Uri uri) { - // TODO(b/198002034): Content observers currently can still be called back after being - // unregistered, and in this case we can ignore the change if the nav bar has been - // destroyed already - if (mNavigationBarView == null) { - return; - } - updateAssistantEntrypoints(); - } - }; - private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener = new DeviceConfig.OnPropertiesChangedListener() { @Override @@ -504,7 +497,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, @Main Handler mainHandler, NavigationBarOverlayController navbarOverlayController, UiEventLogger uiEventLogger, - NavigationBarA11yHelper navigationBarA11yHelper, + NavBarHelper navBarHelper, UserTracker userTracker, LightBarController mainLightBarController, LightBarController.Factory lightBarControllerFactory, @@ -535,8 +528,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mHandler = mainHandler; mNavbarOverlayController = navbarOverlayController; mUiEventLogger = uiEventLogger; - mNavigationBarA11yHelper = navigationBarA11yHelper; - mUserTracker = userTracker; + mNavBarHelper = navBarHelper; mNotificationShadeDepthController = notificationShadeDepthController; mMainLightBarController = mainLightBarController; mLightBarControllerFactory = lightBarControllerFactory; @@ -568,18 +560,9 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mIsOnDefaultDisplay = mDisplayId == DEFAULT_DISPLAY; mCommandQueue.addCallback(this); - mAssistantAvailable = mAssistManagerLazy.get() - .getAssistInfoForUser(UserHandle.USER_CURRENT) != null; + mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled(); mContentResolver = mContext.getContentResolver(); - mContentResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.ASSISTANT), - false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL); - mContentResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED), - false, mAssistContentObserver, UserHandle.USER_ALL); - mContentResolver.registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED), - false, mAssistContentObserver, UserHandle.USER_ALL); + mNavBarHelper.init(); mAllowForceNavBarHandleOpaque = mContext.getResources().getBoolean( R.bool.allow_force_nav_bar_handle_opaque); mForceNavBarHandleOpaque = DeviceConfig.getBoolean( @@ -593,7 +576,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, )).filter(duration -> duration != 0); DeviceConfig.addOnPropertiesChangedListener( DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener); - updateAssistantEntrypoints(); if (savedState != null) { mDisabledFlags1 = savedState.getInt(EXTRA_DISABLE_STATE, 0); @@ -620,8 +602,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mWindowManager.removeViewImmediate(mNavigationBarView.getRootView()); mNavigationModeController.removeListener(this); - mNavigationBarA11yHelper.removeA11yEventListener(mAccessibilityListener); - mContentResolver.unregisterContentObserver(mAssistContentObserver); + mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + mNavBarHelper.destroy(); mDeviceProvisionedController.removeCallback(mUserSetupListener); mNotificationShadeDepthController.removeListener(mDepthListener); @@ -643,7 +625,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mNavigationBarView.setWindowVisible(isNavBarWindowVisible()); mNavigationBarView.setBehavior(mBehavior); - mNavigationBarA11yHelper.registerA11yEventListener(mAccessibilityListener); + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); mSplitScreenOptional.ifPresent(mNavigationBarView::registerDockedListener); mPipOptional.ifPresent(mNavigationBarView::registerPipExclusionBoundsChangeListener); @@ -716,7 +698,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mHandler.removeCallbacks(mAutoDim); mHandler.removeCallbacks(mOnVariableDurationHomeLongClick); mHandler.removeCallbacks(mEnableLayoutTransitions); - mNavigationBarA11yHelper.removeA11yEventListener(mAccessibilityListener); + mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); mFrame = null; mNavigationBarView = null; mOrientationHandle = null; @@ -885,7 +867,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, pw.println(" mCurrentRotation=" + mCurrentRotation); pw.println(" mHomeButtonLongPressDurationMs=" + mHomeButtonLongPressDurationMs); pw.println(" mLongPressHomeEnabled=" + mLongPressHomeEnabled); - pw.println(" mAssistantTouchGestureEnabled=" + mAssistantTouchGestureEnabled); pw.println(" mNavigationBarWindowState=" + windowStateToString(mNavigationBarWindowState)); pw.println(" mNavigationBarMode=" @@ -902,7 +883,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, if (displayId != mDisplayId) { return; } - boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0; + boolean imeShown = mNavBarHelper.isImeShown(vis); + showImeSwitcher = imeShown && showImeSwitcher; int hints = Utilities.calculateBackDispositionHints(mNavigationIconHints, backDisposition, imeShown, showImeSwitcher); if (hints == mNavigationIconHints) return; @@ -1165,7 +1147,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton(); accessibilityButton.setOnClickListener(this::onAccessibilityClick); accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick); - updateAccessibilityServicesState(); + updateAcessibilityStateFlags(); ButtonDispatcher imeSwitcherButton = mNavigationBarView.getImeSwitchButton(); imeSwitcherButton.setOnClickListener(this::onImeSwitcherClick); @@ -1390,8 +1372,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, return true; } - void updateAccessibilityServicesState() { - int a11yFlags = mNavigationBarA11yHelper.getA11yButtonState(); + void updateAcessibilityStateFlags() { + int a11yFlags = mNavBarHelper.getA11yButtonState(); if (mNavigationBarView != null) { boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0; @@ -1403,7 +1385,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, public void updateSystemUiStateFlags(int a11yFlags) { if (a11yFlags < 0) { - a11yFlags = mNavigationBarA11yHelper.getA11yButtonState(); + a11yFlags = mNavBarHelper.getA11yButtonState(); } boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0; boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0; @@ -1430,24 +1412,10 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } } - private void updateAssistantEntrypoints() { - mAssistantAvailable = mAssistManagerLazy.get() - .getAssistInfoForUser(UserHandle.USER_CURRENT) != null; - boolean longPressDefault = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_assistLongPressHomeEnabledDefault); - mLongPressHomeEnabled = Settings.Secure.getIntForUser(mContentResolver, - Settings.Secure.ASSIST_LONG_PRESS_HOME_ENABLED, longPressDefault ? 1 : 0, - mUserTracker.getUserId()) != 0; - boolean gestureDefault = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_assistTouchGestureEnabledDefault); - mAssistantTouchGestureEnabled = Settings.Secure.getIntForUser(mContentResolver, - Settings.Secure.ASSIST_TOUCH_GESTURE_ENABLED, gestureDefault ? 1 : 0, - mUserTracker.getUserId()) != 0; + private void updateAssistantEntrypoints(boolean assistantAvailable) { if (mOverviewProxyService.getProxy() != null) { try { - mOverviewProxyService.getProxy().onAssistantAvailable(mAssistantAvailable - && mAssistantTouchGestureEnabled - && QuickStepContract.isGesturalMode(mNavBarMode)); + mOverviewProxyService.getProxy().onAssistantAvailable(assistantAvailable); } catch (RemoteException e) { Log.w(TAG, "Unable to send assistant availability data to launcher"); } @@ -1518,8 +1486,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, @Override public void onNavigationModeChanged(int mode) { mNavBarMode = mode; - // update assistant entry points on system navigation radio button click - updateAssistantEntrypoints(); if (!QuickStepContract.isGesturalMode(mode)) { // Reset the override alpha @@ -1558,9 +1524,6 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mNavigationBarView.getBarTransitions().finishAnimations(); } - private final NavigationBarA11yHelper.NavA11yEventListener mAccessibilityListener = - this::updateAccessibilityServicesState; - private WindowManager.LayoutParams getBarLayoutParams(int rotation) { WindowManager.LayoutParams lp = getBarLayoutParamsForRotation(rotation); lp.paramsForRotation = new WindowManager.LayoutParams[4]; @@ -1668,7 +1631,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } if (Intent.ACTION_USER_SWITCHED.equals(action)) { // The accessibility settings may be different for the new user - updateAccessibilityServicesState(); + updateAcessibilityStateFlags(); } } }; @@ -1704,7 +1667,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, private final Handler mMainHandler; private final NavigationBarOverlayController mNavbarOverlayController; private final UiEventLogger mUiEventLogger; - private final NavigationBarA11yHelper mNavigationBarA11yHelper; + private final NavBarHelper mNavBarHelper; private final UserTracker mUserTracker; private final LightBarController mMainLightBarController; private final LightBarController.Factory mLightBarControllerFactory; @@ -1737,7 +1700,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, @Main Handler mainHandler, NavigationBarOverlayController navbarOverlayController, UiEventLogger uiEventLogger, - NavigationBarA11yHelper navigationBarA11yHelper, + NavBarHelper navBarHelper, UserTracker userTracker, LightBarController mainLightBarController, LightBarController.Factory lightBarControllerFactory, @@ -1767,7 +1730,7 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mMainHandler = mainHandler; mNavbarOverlayController = navbarOverlayController; mUiEventLogger = uiEventLogger; - mNavigationBarA11yHelper = navigationBarA11yHelper; + mNavBarHelper = navBarHelper; mUserTracker = userTracker; mMainLightBarController = mainLightBarController; mLightBarControllerFactory = lightBarControllerFactory; @@ -1788,10 +1751,10 @@ public class NavigationBar implements View.OnAttachStateChangeListener, mSplitScreenOptional, mRecentsOptional, mStatusBarOptionalLazy, mShadeController, mNotificationRemoteInputManager, mNotificationShadeDepthController, mSystemActions, mMainHandler, - mNavbarOverlayController, mUiEventLogger, mNavigationBarA11yHelper, + mNavbarOverlayController, mUiEventLogger, mNavBarHelper, mUserTracker, mMainLightBarController, mLightBarControllerFactory, mMainAutoHideController, mAutoHideControllerFactory, mTelecomManagerOptional, mInputMethodManager); } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarA11yHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarA11yHelper.java deleted file mode 100644 index 13e6d8b410d6..000000000000 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarA11yHelper.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.android.systemui.navigationbar; - -import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; - -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE; -import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE; - -import android.view.accessibility.AccessibilityManager; - -import com.android.systemui.accessibility.AccessibilityButtonModeObserver; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.shared.system.QuickStepContract; -import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -/** - * Extracts shared elements of a11y necessary between navbar and taskbar delegate - */ -@SysUISingleton -public final class NavigationBarA11yHelper implements - AccessibilityButtonModeObserver.ModeChangedListener { - private final AccessibilityManager mAccessibilityManager; - private final AccessibilityButtonModeObserver mAccessibilityButtonModeObserver; - private final List<NavA11yEventListener> mA11yEventListeners = new ArrayList<>(); - - @Inject - public NavigationBarA11yHelper(AccessibilityManager accessibilityManager, - AccessibilityManagerWrapper accessibilityManagerWrapper, - AccessibilityButtonModeObserver accessibilityButtonModeObserver) { - mAccessibilityManager = accessibilityManager; - accessibilityManagerWrapper.addCallback( - accessibilityManager1 -> NavigationBarA11yHelper.this.dispatchEventUpdate()); - mAccessibilityButtonModeObserver = accessibilityButtonModeObserver; - - mAccessibilityButtonModeObserver.addListener(this); - } - - public void registerA11yEventListener(NavA11yEventListener listener) { - mA11yEventListeners.add(listener); - } - - public void removeA11yEventListener(NavA11yEventListener listener) { - mA11yEventListeners.remove(listener); - } - - private void dispatchEventUpdate() { - for (NavA11yEventListener listener : mA11yEventListeners) { - listener.updateAccessibilityServicesState(); - } - } - - @Override - public void onAccessibilityButtonModeChanged(int mode) { - dispatchEventUpdate(); - } - - /** - * See {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_CLICKABLE} and - * {@link QuickStepContract#SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE} - * - * @return the a11y button clickable and long_clickable states, or 0 if there is no - * a11y button in the navbar - */ - public int getA11yButtonState() { - // AccessibilityManagerService resolves services for the current user since the local - // AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission - final List<String> a11yButtonTargets = - mAccessibilityManager.getAccessibilityShortcutTargets( - AccessibilityManager.ACCESSIBILITY_BUTTON); - final int requestingServices = a11yButtonTargets.size(); - - // If accessibility button is floating menu mode, click and long click state should be - // disabled. - if (mAccessibilityButtonModeObserver.getCurrentAccessibilityButtonMode() - == ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU) { - return 0; - } - - return (requestingServices >= 1 ? SYSUI_STATE_A11Y_BUTTON_CLICKABLE : 0) - | (requestingServices >= 2 ? SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE : 0); - } - - public interface NavA11yEventListener { - void updateAccessibilityServicesState(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java index 472f1f1245e1..0429c022234d 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java @@ -101,7 +101,7 @@ public class NavigationBarController implements CommandQueue commandQueue, @Main Handler mainHandler, ConfigurationController configurationController, - NavigationBarA11yHelper navigationBarA11yHelper, + NavBarHelper navBarHelper, TaskbarDelegate taskbarDelegate, NavigationBar.Factory navigationBarFactory, DumpManager dumpManager, @@ -117,7 +117,7 @@ public class NavigationBarController implements mNavMode = navigationModeController.addListener(this); mTaskbarDelegate = taskbarDelegate; mTaskbarDelegate.setDependencies(commandQueue, overviewProxyService, - navigationBarA11yHelper, navigationModeController, sysUiFlagsContainer, + navBarHelper, navigationModeController, sysUiFlagsContainer, dumpManager, autoHideController, lightBarController); mIsTablet = isTablet(mContext); dumpManager.registerDumpable(this); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index 680cc5cb5cba..7c8c3e0dce44 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -110,6 +110,7 @@ public class NavigationBarView extends FrameLayout implements private final int mNavColorSampleMargin; private final SysUiState mSysUiFlagContainer; + // The current view is one of mHorizontal or mVertical depending on the current configuration View mCurrentView = null; private View mVertical; private View mHorizontal; @@ -397,12 +398,6 @@ public class NavigationBarView extends FrameLayout implements } } - @Override - protected boolean onSetAlpha(int alpha) { - Log.e(TAG, "onSetAlpha", new Throwable()); - return super.onSetAlpha(alpha); - } - public void setAutoHideController(AutoHideController autoHideController) { mAutoHideController = autoHideController; } @@ -505,6 +500,18 @@ public class NavigationBarView extends FrameLayout implements return mCurrentView; } + /** + * Applies {@param consumer} to each of the nav bar views. + */ + public void forEachView(Consumer<View> consumer) { + if (mVertical != null) { + consumer.accept(mVertical); + } + if (mHorizontal != null) { + consumer.accept(mHorizontal); + } + } + public RotationButtonController getRotationButtonController() { return mRotationButtonController; } @@ -1205,7 +1212,9 @@ public class NavigationBarView extends FrameLayout implements protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mTmpLastConfiguration.updateFrom(mConfiguration); - mConfiguration.updateFrom(newConfig); + final int changes = mConfiguration.updateFrom(newConfig); + mFloatingRotationButton.onConfigurationChanged(changes); + boolean uiCarModeChanged = updateCarMode(); updateIcons(mTmpLastConfiguration); updateRecentsIcon(); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java index 089423fd7286..8fb394c06ba1 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java @@ -41,8 +41,9 @@ import android.content.ComponentCallbacks; import android.content.Context; import android.content.res.Configuration; import android.hardware.display.DisplayManager; -import android.inputmethodservice.InputMethodService; import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; import android.view.Display; import android.view.InsetsVisibilities; import android.view.View; @@ -76,12 +77,13 @@ import javax.inject.Singleton; public class TaskbarDelegate implements CommandQueue.Callbacks, OverviewProxyService.OverviewProxyListener, NavigationModeController.ModeChangedListener, ComponentCallbacks, Dumpable { + private static final String TAG = TaskbarDelegate.class.getSimpleName(); private final EdgeBackGestureHandler mEdgeBackGestureHandler; - + private boolean mInitialized; private CommandQueue mCommandQueue; private OverviewProxyService mOverviewProxyService; - private NavigationBarA11yHelper mNavigationBarA11yHelper; + private NavBarHelper mNavBarHelper; private NavigationModeController mNavigationModeController; private SysUiState mSysUiState; private AutoHideController mAutoHideController; @@ -89,8 +91,18 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, private LightBarTransitionsController mLightBarTransitionsController; private int mDisplayId; private int mNavigationIconHints; - private final NavigationBarA11yHelper.NavA11yEventListener mNavA11yEventListener = - this::updateSysuiFlags; + private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater = + new NavBarHelper.NavbarTaskbarStateUpdater() { + @Override + public void updateAccessibilityServicesState() { + updateSysuiFlags(); + } + + @Override + public void updateAssistantAvailable(boolean available) { + updateAssistantAvailability(available); + } + }; private int mDisabledFlags; private @WindowVisibleState int mTaskBarWindowState = WINDOW_STATE_SHOWING; private @Behavior int mBehavior; @@ -130,7 +142,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, public void setDependencies(CommandQueue commandQueue, OverviewProxyService overviewProxyService, - NavigationBarA11yHelper navigationBarA11yHelper, + NavBarHelper navBarHelper, NavigationModeController navigationModeController, SysUiState sysUiState, DumpManager dumpManager, AutoHideController autoHideController, @@ -138,7 +150,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, // TODO: adding this in the ctor results in a dagger dependency cycle :( mCommandQueue = commandQueue; mOverviewProxyService = overviewProxyService; - mNavigationBarA11yHelper = navigationBarA11yHelper; + mNavBarHelper = navBarHelper; mNavigationModeController = navigationModeController; mSysUiState = sysUiState; dumpManager.registerDumpable(this); @@ -170,12 +182,16 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, } public void init(int displayId) { + if (mInitialized) { + return; + } mDisplayId = displayId; mCommandQueue.addCallback(this); mOverviewProxyService.addCallback(this); mEdgeBackGestureHandler.onNavigationModeChanged( mNavigationModeController.addListener(this)); - mNavigationBarA11yHelper.registerA11yEventListener(mNavA11yEventListener); + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + mNavBarHelper.init(); mEdgeBackGestureHandler.onNavBarAttached(); // Initialize component callback Display display = mDisplayManager.getDisplay(displayId); @@ -185,29 +201,31 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, updateSysuiFlags(); mAutoHideController.setNavigationBar(mAutoHideUiElement); mLightBarController.setNavigationBar(mLightBarTransitionsController); + mInitialized = true; } public void destroy() { + if (!mInitialized) { + return; + } mCommandQueue.removeCallback(this); mOverviewProxyService.removeCallback(this); mNavigationModeController.removeListener(this); - mNavigationBarA11yHelper.removeA11yEventListener(mNavA11yEventListener); + mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + mNavBarHelper.destroy(); mEdgeBackGestureHandler.onNavBarDetached(); if (mWindowContext != null) { mWindowContext.unregisterComponentCallbacks(this); mWindowContext = null; } mAutoHideController.setNavigationBar(null); -//<<<<<<< HEAD mLightBarTransitionsController.destroy(mContext); mLightBarController.setNavigationBar(null); -// mInitialized = false; -//======= -//>>>>>>> 2d145412fc44 (Revert "Extract assistant logic from NavigationBar to share with Taskbar") + mInitialized = false; } private void updateSysuiFlags() { - int a11yFlags = mNavigationBarA11yHelper.getA11yButtonState(); + int a11yFlags = mNavBarHelper.getA11yButtonState(); boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0; boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0; @@ -231,10 +249,23 @@ public class TaskbarDelegate implements CommandQueue.Callbacks, .commitUpdate(mDisplayId); } + private void updateAssistantAvailability(boolean assistantAvailable) { + if (mOverviewProxyService.getProxy() == null) { + return; + } + + try { + mOverviewProxyService.getProxy().onAssistantAvailable(assistantAvailable); + } catch (RemoteException e) { + Log.e(TAG, "onAssistantAvailable() failed, available: " + assistantAvailable, e); + } + } + @Override public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, boolean showImeSwitcher) { - boolean imeShown = (vis & InputMethodService.IME_VISIBLE) != 0; + boolean imeShown = mNavBarHelper.isImeShown(vis); + showImeSwitcher = imeShown && showImeSwitcher; int hints = Utilities.calculateBackDispositionHints(mNavigationIconHints, backDisposition, imeShown, showImeSwitcher); if (hints != mNavigationIconHints) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt index 98b914672112..bc3a4fdfd7ff 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt @@ -116,7 +116,7 @@ class FooterActionsController @Inject constructor( } } else if (v === powerMenuLite) { uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS) - globalActionsDialog.showOrHideDialog(false, true) + globalActionsDialog.showOrHideDialog(false, true, v) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index e42c47b1d3be..ee59ae6ab6d7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -770,6 +770,8 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca public void onAnimationEnd(Animator animation) { mHeaderAnimating = false; updateQsState(); + // Unset the listener, otherwise this may persist for another view property animation + getView().animate().setListener(null); } }; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 883552a1f7c3..033fe1e0e6a9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -130,6 +130,7 @@ public class InternetDialog extends SystemUIDialog implements private boolean mCanConfigMobileData; // Wi-Fi entries + private int mWifiNetworkHeight; @VisibleForTesting protected WifiEntry mConnectedWifiEntry; @VisibleForTesting @@ -187,6 +188,9 @@ public class InternetDialog extends SystemUIDialog implements window.setWindowAnimations(R.style.Animation_InternetDialog); + mWifiNetworkHeight = mContext.getResources() + .getDimensionPixelSize(R.dimen.internet_dialog_wifi_network_height); + mInternetDialogLayout = mDialogView.requireViewById(R.id.internet_connectivity_dialog); mInternetDialogTitle = mDialogView.requireViewById(R.id.internet_dialog_title); mInternetDialogSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle); @@ -335,9 +339,6 @@ public class InternetDialog extends SystemUIDialog implements mSeeAllLayout.setOnClickListener(v -> onClickSeeMoreButton()); mWiFiToggle.setOnCheckedChangeListener( (buttonView, isChecked) -> { - if (isChecked) { - mWifiScanNotifyLayout.setVisibility(View.GONE); - } buttonView.setChecked(isChecked); mWifiManager.setWifiEnabled(isChecked); }); @@ -390,6 +391,8 @@ public class InternetDialog extends SystemUIDialog implements array.recycle(); mMobileDataToggle.setVisibility(mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE); + mMobileToggleDivider.setVisibility( + mCanConfigMobileData ? View.VISIBLE : View.INVISIBLE); } } @@ -427,9 +430,26 @@ public class InternetDialog extends SystemUIDialog implements mSeeAllLayout.setVisibility(View.GONE); return; } - mWifiRecyclerView.setVisibility(mWifiEntriesCount > 0 ? View.VISIBLE : View.GONE); - mSeeAllLayout.setVisibility( - (mConnectedWifiEntry != null || mWifiEntriesCount > 0) ? View.VISIBLE : View.GONE); + mWifiRecyclerView.setMinimumHeight(mWifiNetworkHeight * getWifiListMaxCount()); + mWifiRecyclerView.setVisibility(View.VISIBLE); + final boolean showSeeAll = mConnectedWifiEntry != null || mWifiEntriesCount > 0; + mSeeAllLayout.setVisibility(showSeeAll ? View.VISIBLE : View.INVISIBLE); + } + + @VisibleForTesting + @MainThread + int getWifiListMaxCount() { + int count = InternetDialogController.MAX_WIFI_ENTRY_COUNT; + if (mEthernetLayout.getVisibility() == View.VISIBLE) { + count -= 1; + } + if (mMobileNetworkLayout.getVisibility() == View.VISIBLE) { + count -= 1; + } + if (mConnectedWifListLayout.getVisibility() == View.VISIBLE) { + count -= 1; + } + return count; } @MainThread diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index 5bb3413595ba..d64c05fadeb8 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -32,7 +32,6 @@ import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; -import android.provider.Settings; import android.util.Log; import android.widget.Toast; @@ -62,7 +61,6 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis private static final String EXTRA_RESULT_CODE = "extra_resultCode"; private static final String EXTRA_PATH = "extra_path"; private static final String EXTRA_AUDIO_SOURCE = "extra_useAudio"; - private static final String EXTRA_SHOW_TAPS = "extra_showTaps"; private static final String ACTION_START = "com.android.systemui.screenrecord.START"; private static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP"; @@ -74,8 +72,6 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis private final RecordingController mController; private final KeyguardDismissUtil mKeyguardDismissUtil; private ScreenRecordingAudioSource mAudioSource; - private boolean mShowTaps; - private boolean mOriginalShowTaps; private ScreenMediaRecorder mRecorder; private final Executor mLongExecutor; private final UiEventLogger mUiEventLogger; @@ -102,15 +98,12 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis * android.content.Intent)} * @param audioSource The ordinal value of the audio source * {@link com.android.systemui.screenrecord.ScreenRecordingAudioSource} - * @param showTaps True to make touches visible while recording */ - public static Intent getStartIntent(Context context, int resultCode, - int audioSource, boolean showTaps) { + public static Intent getStartIntent(Context context, int resultCode, int audioSource) { return new Intent(context, RecordingService.class) .setAction(ACTION_START) .putExtra(EXTRA_RESULT_CODE, resultCode) - .putExtra(EXTRA_AUDIO_SOURCE, audioSource) - .putExtra(EXTRA_SHOW_TAPS, showTaps); + .putExtra(EXTRA_AUDIO_SOURCE, audioSource); } @Override @@ -128,13 +121,6 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis mAudioSource = ScreenRecordingAudioSource .values()[intent.getIntExtra(EXTRA_AUDIO_SOURCE, 0)]; Log.d(TAG, "recording with audio source" + mAudioSource); - mShowTaps = intent.getBooleanExtra(EXTRA_SHOW_TAPS, false); - - mOriginalShowTaps = Settings.System.getInt( - getApplicationContext().getContentResolver(), - Settings.System.SHOW_TOUCHES, 0) != 0; - - setTapsVisible(mShowTaps); mRecorder = new ScreenMediaRecorder( mUserContextTracker.getUserContext(), @@ -379,7 +365,6 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis } private void stopRecording(int userId) { - setTapsVisible(mOriginalShowTaps); if (getRecorder() != null) { getRecorder().end(); saveRecording(userId); @@ -411,11 +396,6 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis }); } - private void setTapsVisible(boolean turnOn) { - int value = turnOn ? 1 : 0; - Settings.System.putInt(getContentResolver(), Settings.System.SHOW_TOUCHES, value); - } - /** * Get an intent to stop the recording service. * @param context Context from the requesting activity diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java index 1fb88dfe9b52..582cc762c1b7 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java @@ -55,7 +55,6 @@ public class ScreenRecordDialog extends SystemUIDialog { private final UserContextProvider mUserContextProvider; @Nullable private final Runnable mOnStartRecordingClicked; - private Switch mTapsSwitch; private Switch mAudioSwitch; private Spinner mOptions; @@ -96,7 +95,6 @@ public class ScreenRecordDialog extends SystemUIDialog { }); mAudioSwitch = findViewById(R.id.screenrecord_audio_switch); - mTapsSwitch = findViewById(R.id.screenrecord_taps_switch); mOptions = findViewById(R.id.screen_recording_options); ArrayAdapter a = new ScreenRecordingAdapter(getContext().getApplicationContext(), android.R.layout.simple_spinner_dropdown_item, @@ -110,7 +108,6 @@ public class ScreenRecordDialog extends SystemUIDialog { private void requestScreenCapture() { Context userContext = mUserContextProvider.getUserContext(); - boolean showTaps = mTapsSwitch.isChecked(); ScreenRecordingAudioSource audioMode = mAudioSwitch.isChecked() ? (ScreenRecordingAudioSource) mOptions.getSelectedItem() : NONE; @@ -118,7 +115,7 @@ public class ScreenRecordDialog extends SystemUIDialog { RecordingService.REQUEST_CODE, RecordingService.getStartIntent( userContext, Activity.RESULT_OK, - audioMode.ordinal(), showTaps), + audioMode.ordinal()), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); PendingIntent stopIntent = PendingIntent.getService(userContext, RecordingService.REQUEST_CODE, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 190c773ef422..f23a7cac7d2a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -717,6 +717,9 @@ public class KeyguardIndicationController { textView.setTranslationY(BOUNCE_ANIMATION_FINAL_Y); ViewClippingUtil.setClippingDeactivated(textView, false, mClippingParams); + // Unset the listener, otherwise this may persist for + // another view property animation + textView.animate().setListener(null); } }); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFadeAware.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFadeAware.java new file mode 100644 index 000000000000..8d2e3c92c92a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFadeAware.java @@ -0,0 +1,70 @@ +/* + * 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. + */ + +package com.android.systemui.statusbar.notification; + +import android.view.View; + +import androidx.annotation.Nullable; + +/** + * Used to let views that have an alpha not apply the HARDWARE layer type directly, and instead + * delegate that to specific children. This is useful if we want to fake not having overlapping + * rendering to avoid layer trashing, when fading out a view that is also changing. + */ +public interface NotificationFadeAware { + /** + * Calls {@link View#setLayerType} with {@link View#LAYER_TYPE_HARDWARE} if faded and + * {@link View#LAYER_TYPE_NONE} otherwise. + */ + static void setLayerTypeForFaded(@Nullable View view, boolean faded) { + if (view != null) { + int newLayerType = faded ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE; + view.setLayerType(newLayerType, null); + } + } + + /** + * Used like {@link View#setLayerType} with {@link View#LAYER_TYPE_HARDWARE} or + * {@link View#LAYER_TYPE_NONE} except that instead of necessarily affecting this view + * specifically, this may delegate the call to child views. + * + * When set to <code>true</code>, the view has two possible paths: + * 1. If a hardware layer is required to ensure correct appearance of this view, then + * set that layer type. + * 2. Otherwise, delegate this call to children, who might make that call for themselves. + * + * When set to <code>false</code>, the view should undo the above, typically by calling + * {@link View#setLayerType} with {@link View#LAYER_TYPE_NONE} on itself and children, and + * delegating to this method on children where implemented. + * + * When this delegates to {@link View#setLayerType} on this view or a subview, `null` will be + * passed for the `paint` argument of that call. + */ + void setNotificationFaded(boolean faded); + + /** + * Interface for the top level notification view that fades and optimizes that through deep + * awareness of individual components. + */ + interface FadeOptimizedNotification extends NotificationFadeAware { + /** Top-level feature switch */ + boolean FADE_LAYER_OPTIMIZATION_ENABLED = true; + + /** Determine if the notification is currently faded. */ + boolean isNotificationFaded(); + } +} 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 a2d1040b8a4e..23a0a750561c 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 @@ -87,6 +87,7 @@ import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarIconView; import com.android.systemui.statusbar.notification.AboveShelfChangedListener; import com.android.systemui.statusbar.notification.ExpandAnimationParameters; +import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -125,7 +126,8 @@ import java.util.function.Consumer; * the group summary (which contains 1 or more child notifications). */ public class ExpandableNotificationRow extends ActivatableNotificationView - implements PluginListener<NotificationMenuRowPlugin>, SwipeableView { + implements PluginListener<NotificationMenuRowPlugin>, SwipeableView, + NotificationFadeAware.FadeOptimizedNotification { private static final boolean DEBUG = false; private static final int DEFAULT_DIVIDER_ALPHA = 0x29; @@ -138,6 +140,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView private boolean mUpdateBackgroundOnUpdate; private boolean mNotificationTranslationFinished = false; private boolean mIsSnoozed; + private boolean mIsFaded; /** * Listener for when {@link ExpandableNotificationRow} is laid out. @@ -2753,17 +2756,112 @@ public class ExpandableNotificationRow extends ActivatableNotificationView // alphas are reset if (mChildrenContainer != null) { mChildrenContainer.setAlpha(1.0f); - mChildrenContainer.setLayerType(LAYER_TYPE_NONE, null); } for (NotificationContentView l : mLayouts) { l.setAlpha(1.0f); - l.setLayerType(LAYER_TYPE_NONE, null); + } + if (FADE_LAYER_OPTIMIZATION_ENABLED) { + setNotificationFaded(false); + } else { + setNotificationFadedOnChildren(false); } } else { setHeadsUpAnimatingAway(false); } } + /** Gets the last value set with {@link #setNotificationFaded(boolean)} */ + @Override + public boolean isNotificationFaded() { + return mIsFaded; + } + + /** + * This class needs to delegate the faded state set on it by + * {@link com.android.systemui.statusbar.notification.stack.ViewState} to its children. + * Having each notification use layerType of HARDWARE anytime it fades in/out can result in + * extremely large layers (in the case of groups, it can even exceed the device height). + * Because these large renders can cause serious jank when rendering, we instead have + * notifications return false from {@link #hasOverlappingRendering()} and delegate the + * layerType to child views which really need it in order to render correctly, such as icon + * views or the conversation face pile. + * + * Another compounding factor for notifications is that we change clipping on each frame of the + * animation, so the hardware layer isn't able to do any caching at the top level, but the + * individual elements we render with hardware layers (e.g. icons) cache wonderfully because we + * never invalidate them. + */ + @Override + public void setNotificationFaded(boolean faded) { + mIsFaded = faded; + if (childrenRequireOverlappingRendering()) { + // == Simple Scenario == + // If a child (like remote input) needs this to have overlapping rendering, then set + // the layerType of this view and reset the children to render as if the notification is + // not fading. + NotificationFadeAware.setLayerTypeForFaded(this, faded); + setNotificationFadedOnChildren(false); + } else { + // == Delegating Scenario == + // This is the new normal for alpha: Explicitly reset this view's layer type to NONE, + // and require that all children use their own hardware layer if they have bad + // overlapping rendering. + NotificationFadeAware.setLayerTypeForFaded(this, false); + setNotificationFadedOnChildren(faded); + } + } + + /** Private helper for iterating over the layouts and children containers to set faded state */ + private void setNotificationFadedOnChildren(boolean faded) { + delegateNotificationFaded(mChildrenContainer, faded); + for (NotificationContentView layout : mLayouts) { + delegateNotificationFaded(layout, faded); + } + } + + private static void delegateNotificationFaded(@Nullable View view, boolean faded) { + if (FADE_LAYER_OPTIMIZATION_ENABLED && view instanceof NotificationFadeAware) { + ((NotificationFadeAware) view).setNotificationFaded(faded); + } else { + NotificationFadeAware.setLayerTypeForFaded(view, faded); + } + } + + /** + * Only declare overlapping rendering if independent children of the view require it. + */ + @Override + public boolean hasOverlappingRendering() { + return super.hasOverlappingRendering() && childrenRequireOverlappingRendering(); + } + + /** + * Because RemoteInputView is designed to be an opaque view that overlaps the Actions row, the + * row should require overlapping rendering to ensure that the overlapped view doesn't bleed + * through when alpha fading. + * + * Note that this currently works for top-level notifications which squish their height down + * while collapsing the shade, but does not work for children inside groups, because the + * accordion affect does not apply to those views, so super.hasOverlappingRendering() will + * always return false to avoid the clipping caused when the view's measured height is less than + * the 'actual height'. + */ + private boolean childrenRequireOverlappingRendering() { + if (!FADE_LAYER_OPTIMIZATION_ENABLED) { + return true; + } + // The colorized background is another layer with which all other elements overlap + if (getEntry().getSbn().getNotification().isColorized()) { + return true; + } + // Check if the showing layout has a need for overlapping rendering. + // NOTE: We could check both public and private layouts here, but becuause these states + // don't animate well, there are bigger visual artifacts if we start changing the shown + // layout during shade expansion. + NotificationContentView showingLayout = getShowingLayout(); + return showingLayout != null && showingLayout.requireRowToHaveOverlappingRendering(); + } + @Override public int getExtraBottomPadding() { if (mIsSummaryWithChildren && isGroupExpanded()) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java index caba3ac7e17b..c66140822d92 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java @@ -28,6 +28,7 @@ import android.widget.TextView; import com.android.internal.widget.ConversationLayout; import com.android.systemui.R; +import com.android.systemui.statusbar.notification.NotificationFadeAware; /** * A hybrid view which may contain information about one ore more conversations. @@ -138,4 +139,14 @@ public class HybridConversationNotificationView extends HybridNotificationView { lp.height = size; view.setLayoutParams(lp); } + + /** + * Apply the faded state as a layer type change to the face pile view which needs to have + * overlapping contents render precisely. + */ + @Override + public void setNotificationFaded(boolean faded) { + super.setNotificationFaded(faded); + NotificationFadeAware.setLayerTypeForFaded(mConversationFacePile, faded); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java index bc2adac31d07..c0d85a6a16ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java @@ -28,13 +28,14 @@ import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.TransformableView; import com.android.systemui.statusbar.ViewTransformationHelper; +import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.TransformState; /** * A hybrid view which may contain information about one ore more notifications. */ public class HybridNotificationView extends AlphaOptimizedLinearLayout - implements TransformableView { + implements TransformableView, NotificationFadeAware { protected final ViewTransformationHelper mTransformationHelper = new ViewTransformationHelper(); protected TextView mTitleView; @@ -148,4 +149,7 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout setVisibility(visible ? View.VISIBLE : View.INVISIBLE); mTransformationHelper.setVisible(visible); } + + @Override + public void setNotificationFaded(boolean faded) {} } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 0ffca30ed409..9cc484c02802 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -46,6 +46,7 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.SmartReplyController; import com.android.systemui.statusbar.TransformableView; +import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager; @@ -71,7 +72,7 @@ import java.util.List; * expanded and heads up layout. This class is responsible for clipping the content and and * switching between the expanded, contracted and the heads up view depending on its clipped size. */ -public class NotificationContentView extends FrameLayout { +public class NotificationContentView extends FrameLayout implements NotificationFadeAware { private static final String TAG = "NotificationContentView"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -1994,4 +1995,39 @@ public class NotificationContentView extends FrameLayout { } return Notification.COLOR_INVALID; } + + /** + * Delegate the faded state to the notification content views which actually + * need to have overlapping contents render precisely. + */ + @Override + public void setNotificationFaded(boolean faded) { + if (mContractedWrapper != null) { + mContractedWrapper.setNotificationFaded(faded); + } + if (mHeadsUpWrapper != null) { + mHeadsUpWrapper.setNotificationFaded(faded); + } + if (mExpandedWrapper != null) { + mExpandedWrapper.setNotificationFaded(faded); + } + if (mSingleLineView != null) { + mSingleLineView.setNotificationFaded(faded); + } + } + + /** + * @return true if a visible view has a remote input active, as this requires that the entire + * row report that it has overlapping rendering. + */ + public boolean requireRowToHaveOverlappingRendering() { + // This inexpensive check is done on both states to avoid state invalidating the result. + if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isActive()) { + return true; + } + if (mExpandedRemoteInput != null && mExpandedRemoteInput.isActive()) { + return true; + } + return false; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt index 12e94cbc1ab9..bb43b95357db 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt @@ -21,6 +21,7 @@ import android.view.View import com.android.internal.widget.CachingIconView import com.android.internal.widget.CallLayout import com.android.systemui.R +import com.android.systemui.statusbar.notification.NotificationFadeAware import com.android.systemui.statusbar.notification.NotificationUtils import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow @@ -37,6 +38,7 @@ class NotificationCallTemplateViewWrapper constructor( NotificationUtils.getFontScaledHeight(ctx, R.dimen.notification_max_height) private val callLayout: CallLayout = view as CallLayout + private lateinit var conversationIconContainer: View private lateinit var conversationIconView: CachingIconView private lateinit var conversationBadgeBg: View private lateinit var expandBtn: View @@ -45,6 +47,8 @@ class NotificationCallTemplateViewWrapper constructor( private fun resolveViews() { with(callLayout) { + conversationIconContainer = + requireViewById(com.android.internal.R.id.conversation_icon_container) conversationIconView = requireViewById(com.android.internal.R.id.conversation_icon) conversationBadgeBg = requireViewById(com.android.internal.R.id.conversation_icon_badge_bg) @@ -82,4 +86,14 @@ class NotificationCallTemplateViewWrapper constructor( } override fun getMinLayoutHeight(): Int = minHeightWithActions + + /** + * Apply the faded state as a layer type change to the face pile view which needs to have + * overlapping contents render precisely. + */ + override fun setNotificationFaded(faded: Boolean) { + // Do not call super + NotificationFadeAware.setLayerTypeForFaded(expandBtn, faded) + NotificationFadeAware.setLayerTypeForFaded(conversationIconContainer, faded) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt index 3ef5b665d778..e136055b80b3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt @@ -23,6 +23,7 @@ import com.android.internal.widget.CachingIconView import com.android.internal.widget.ConversationLayout import com.android.internal.widget.MessagingLinearLayout import com.android.systemui.R +import com.android.systemui.statusbar.notification.NotificationFadeAware import com.android.systemui.statusbar.notification.NotificationUtils import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.wrapper.NotificationMessagingTemplateViewWrapper.setCustomImageMessageTransform @@ -42,6 +43,7 @@ class NotificationConversationTemplateViewWrapper constructor( ) private val conversationLayout: ConversationLayout = view as ConversationLayout + private lateinit var conversationIconContainer: View private lateinit var conversationIconView: CachingIconView private lateinit var conversationBadgeBg: View private lateinit var expandBtn: View @@ -59,6 +61,8 @@ class NotificationConversationTemplateViewWrapper constructor( messagingLinearLayout = conversationLayout.messagingLinearLayout imageMessageContainer = conversationLayout.imageMessageContainer with(conversationLayout) { + conversationIconContainer = + requireViewById(com.android.internal.R.id.conversation_icon_container) conversationIconView = requireViewById(com.android.internal.R.id.conversation_icon) conversationBadgeBg = requireViewById(com.android.internal.R.id.conversation_icon_badge_bg) @@ -136,4 +140,10 @@ class NotificationConversationTemplateViewWrapper constructor( minHeightWithActions else super.getMinLayoutHeight() + + override fun setNotificationFaded(faded: Boolean) { + // Do not call super + NotificationFadeAware.setLayerTypeForFaded(expandBtn, faded) + NotificationFadeAware.setLayerTypeForFaded(conversationIconContainer, faded) + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java index 4c9c2f95b35c..fdff12d22354 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapper.java @@ -22,6 +22,7 @@ import android.view.View; import com.android.internal.graphics.ColorUtils; import com.android.systemui.R; +import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; /** @@ -86,4 +87,14 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper { public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) { return true; } + + /** + * Apply the faded state as a layer type change to the custom view which needs to have + * overlapping contents render precisely. + */ + @Override + public void setNotificationFaded(boolean faded) { + super.setNotificationFaded(faded); + NotificationFadeAware.setLayerTypeForFaded(mView, faded); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java index 8c6fa023d92b..31595397b9b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationDecoratedCustomViewWrapper.java @@ -20,6 +20,7 @@ import android.content.Context; import android.view.View; import android.view.ViewGroup; +import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; /** @@ -67,4 +68,14 @@ public class NotificationDecoratedCustomViewWrapper extends NotificationTemplate } super.onContentUpdated(row); } + + /** + * Apply the faded state as a layer type change to the custom view which needs to have + * overlapping contents render precisely. + */ + @Override + public void setNotificationFaded(boolean faded) { + super.setNotificationFaded(faded); + NotificationFadeAware.setLayerTypeForFaded(mWrappedView, faded); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java index 76301917b458..6c3e0d2f798b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java @@ -42,6 +42,7 @@ import com.android.internal.widget.CachingIconView; import com.android.settingslib.Utils; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.TransformableView; +import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.TransformState; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -395,4 +396,13 @@ public abstract class NotificationViewWrapper implements TransformableView { */ public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) { } + + /** + * Apply the faded state as a layer type change to the views which need to have overlapping + * contents render precisely. + */ + public void setNotificationFaded(boolean faded) { + NotificationFadeAware.setLayerTypeForFaded(getIcon(), faded); + NotificationFadeAware.setLayerTypeForFaded(getExpandButton(), faded); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java index c2716b949b71..7ece3d89ce6e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java @@ -38,6 +38,7 @@ import com.android.internal.widget.NotificationExpandButton; import com.android.systemui.R; import com.android.systemui.statusbar.CrossFadeHelper; import com.android.systemui.statusbar.NotificationGroupingUtil; +import com.android.systemui.statusbar.notification.NotificationFadeAware; import com.android.systemui.statusbar.notification.NotificationUtils; import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; @@ -51,7 +52,8 @@ import java.util.List; /** * A container containing child notifications */ -public class NotificationChildrenContainer extends ViewGroup { +public class NotificationChildrenContainer extends ViewGroup + implements NotificationFadeAware { @VisibleForTesting static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2; @@ -111,6 +113,7 @@ public class NotificationChildrenContainer extends ViewGroup { private int mCurrentHeaderTranslation = 0; private float mHeaderVisibleAmount = 1.0f; private int mUntruncatedChildCount; + private boolean mContainingNotificationIsFaded = false; public NotificationChildrenContainer(Context context) { this(context, null); @@ -277,6 +280,7 @@ public class NotificationChildrenContainer extends ViewGroup { mDividers.add(newIndex, divider); row.setContentTransformationAmount(0, false /* isLastChild */); + row.setNotificationFaded(mContainingNotificationIsFaded); // It doesn't make sense to keep old animations around, lets cancel them! ExpandableViewState viewState = row.getViewState(); if (viewState != null) { @@ -301,6 +305,7 @@ public class NotificationChildrenContainer extends ViewGroup { }); row.setSystemChildExpanded(false); + row.setNotificationFaded(false); row.setUserLocked(false); if (!row.isRemoved()) { mGroupingUtil.restoreChildNotification(row); @@ -1308,4 +1313,18 @@ public class NotificationChildrenContainer extends ViewGroup { mNotificationHeaderWrapperLowPriority.setRecentlyAudiblyAlerted(audiblyAlertedRecently); } } + + @Override + public void setNotificationFaded(boolean faded) { + mContainingNotificationIsFaded = faded; + if (mNotificationHeaderWrapper != null) { + mNotificationHeaderWrapper.setNotificationFaded(faded); + } + if (mNotificationHeaderWrapperLowPriority != null) { + mNotificationHeaderWrapperLowPriority.setNotificationFaded(faded); + } + for (ExpandableNotificationRow child : mAttachedChildren) { + child.setNotificationFaded(faded); + } + } } 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 6a127102b726..ab08865c8bef 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 @@ -6092,6 +6092,14 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable return mExpandHelperCallback; } + float getAppearFraction() { + return mLastSentAppear; + } + + float getExpandedHeight() { + return mLastSentExpandedHeight; + } + /** Enum for selecting some or all notification rows (does not included non-notif views). */ @Retention(SOURCE) @IntDef({ROWS_ALL, ROWS_HIGH_PRIORITY, ROWS_GENTLE}) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 03fc76760133..f14cc93c2046 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -858,6 +858,14 @@ public class NotificationStackScrollLayoutController { mView.setHeadsUpAppearanceController(controller); } + public float getAppearFraction() { + return mView.getAppearFraction(); + } + + public float getExpandedHeight() { + return mView.getExpandedHeight(); + } + public void requestLayout() { mView.requestLayout(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java index 6d82a45313d1..83bea84c8d33 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ViewState.java @@ -29,6 +29,7 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.statusbar.notification.AnimatableProperty; +import com.android.systemui.statusbar.notification.NotificationFadeAware.FadeOptimizedNotification; import com.android.systemui.statusbar.notification.PropertyAnimator; import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.policy.HeadsUpUtil; @@ -206,14 +207,26 @@ public class ViewState implements Dumpable { } else if (view.getAlpha() != this.alpha) { // apply layer type boolean becomesFullyVisible = this.alpha == 1.0f; - boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible - && view.hasOverlappingRendering(); - int layerType = view.getLayerType(); - int newLayerType = newLayerTypeIsHardware - ? View.LAYER_TYPE_HARDWARE - : View.LAYER_TYPE_NONE; - if (layerType != newLayerType) { - view.setLayerType(newLayerType, null); + boolean becomesFaded = !becomesInvisible && !becomesFullyVisible; + if (FadeOptimizedNotification.FADE_LAYER_OPTIMIZATION_ENABLED + && view instanceof FadeOptimizedNotification) { + // NOTE: A view that's going to utilize this interface to avoid having a hardware + // layer will have to return false from hasOverlappingRendering(), so we + // intentionally do not check that value in this if, even though we do in the else. + FadeOptimizedNotification fadeOptimizedView = (FadeOptimizedNotification) view; + boolean isFaded = fadeOptimizedView.isNotificationFaded(); + if (isFaded != becomesFaded) { + fadeOptimizedView.setNotificationFaded(becomesFaded); + } + } else { + boolean newLayerTypeIsHardware = becomesFaded && view.hasOverlappingRendering(); + int layerType = view.getLayerType(); + int newLayerType = newLayerTypeIsHardware + ? View.LAYER_TYPE_HARDWARE + : View.LAYER_TYPE_NONE; + if (layerType != newLayerType) { + view.setLayerType(newLayerType, null); + } } // apply alpha diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java index 927b4c8cc919..8a7cf360a254 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java @@ -24,6 +24,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ViewClippingUtil; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.RootView; import com.android.systemui.plugins.DarkIconDispatcher; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.CommandQueue; @@ -35,23 +36,29 @@ import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; +import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; +import com.android.systemui.util.ViewController; import java.util.function.BiConsumer; import java.util.function.Consumer; +import javax.inject.Inject; + /** * Controls the appearance of heads up notifications in the icon area and the header itself. */ -public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, - DarkIconDispatcher.DarkReceiver, NotificationWakeUpCoordinator.WakeUpListener { +@StatusBarFragmentScope +public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBarView> + implements OnHeadsUpChangedListener, + DarkIconDispatcher.DarkReceiver, + NotificationWakeUpCoordinator.WakeUpListener { public static final int CONTENT_FADE_DURATION = 110; public static final int CONTENT_FADE_DELAY = 100; private final NotificationIconAreaController mNotificationIconAreaController; private final HeadsUpManagerPhone mHeadsUpManager; private final NotificationStackScrollLayoutController mStackScrollerController; - private final HeadsUpStatusBarView mHeadsUpStatusBarView; private final View mCenteredIconView; private final View mClockView; private final View mOperatorNameView; @@ -67,8 +74,6 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, @VisibleForTesting float mExpandedHeight; @VisibleForTesting - boolean mIsExpanded; - @VisibleForTesting float mAppearFraction; private ExpandableNotificationRow mTrackedChild; private boolean mShown; @@ -83,7 +88,7 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, Point mPoint; private KeyguardStateController mKeyguardStateController; - + @Inject public HeadsUpAppearanceController( NotificationIconAreaController notificationIconAreaController, HeadsUpManagerPhone headsUpManager, @@ -92,11 +97,15 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, KeyguardBypassController keyguardBypassController, KeyguardStateController keyguardStateController, NotificationWakeUpCoordinator wakeUpCoordinator, CommandQueue commandQueue, - NotificationPanelViewController notificationPanelViewController, View statusBarView) { + NotificationPanelViewController notificationPanelViewController, + @RootView PhoneStatusBarView statusBarView) { this(notificationIconAreaController, headsUpManager, statusBarStateController, keyguardBypassController, wakeUpCoordinator, keyguardStateController, commandQueue, notificationStackScrollLayoutController, notificationPanelViewController, + // TODO(b/205609837): We should have the StatusBarFragmentComponent provide these + // four views, and then we can delete this constructor and just use the one below + // (which also removes the undesirable @VisibleForTesting). statusBarView.findViewById(R.id.heads_up_status_bar_view), statusBarView.findViewById(R.id.clock), statusBarView.findViewById(R.id.operator_name_frame), @@ -118,25 +127,27 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, View clockView, View operatorNameView, View centeredIconView) { + super(headsUpStatusBarView); mNotificationIconAreaController = notificationIconAreaController; mHeadsUpManager = headsUpManager; - mHeadsUpManager.addListener(this); - mHeadsUpStatusBarView = headsUpStatusBarView; mCenteredIconView = centeredIconView; - headsUpStatusBarView.setOnDrawingRectChangedListener( - () -> updateIsolatedIconLocation(true /* requireUpdate */)); + + // We may be mid-HUN-expansion when this controller is re-created (for example, if the user + // has started pulling down the notification shade from the HUN and then the font size + // changes). We need to re-fetch these values since they're used to correctly display the + // HUN during this shade expansion. + mTrackedChild = notificationPanelViewController.getTrackedHeadsUpNotification(); + mAppearFraction = stackScrollerController.getAppearFraction(); + mExpandedHeight = stackScrollerController.getExpandedHeight(); + mStackScrollerController = stackScrollerController; mNotificationPanelViewController = notificationPanelViewController; - notificationPanelViewController.addTrackingHeadsUpListener(mSetTrackingHeadsUp); - notificationPanelViewController.setHeadsUpAppearanceController(this); - mStackScrollerController.addOnExpandedHeightChangedListener(mSetExpandedHeight); mStackScrollerController.setHeadsUpAppearanceController(this); mClockView = clockView; mOperatorNameView = operatorNameView; mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class); - mDarkIconDispatcher.addDarkReceiver(this); - mHeadsUpStatusBarView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { + mView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { @@ -146,21 +157,32 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, // trigger scroller to notify the latest panel translation mStackScrollerController.requestLayout(); } - mHeadsUpStatusBarView.removeOnLayoutChangeListener(this); + mView.removeOnLayoutChangeListener(this); } }); mBypassController = bypassController; mStatusBarStateController = stateController; mWakeUpCoordinator = wakeUpCoordinator; - wakeUpCoordinator.addListener(this); mCommandQueue = commandQueue; mKeyguardStateController = keyguardStateController; } + @Override + protected void onViewAttached() { + mHeadsUpManager.addListener(this); + mView.setOnDrawingRectChangedListener( + () -> updateIsolatedIconLocation(true /* requireUpdate */)); + mWakeUpCoordinator.addListener(this); + mNotificationPanelViewController.addTrackingHeadsUpListener(mSetTrackingHeadsUp); + mNotificationPanelViewController.setHeadsUpAppearanceController(this); + mStackScrollerController.addOnExpandedHeightChangedListener(mSetExpandedHeight); + mDarkIconDispatcher.addDarkReceiver(this); + } - public void destroy() { + @Override + protected void onViewDetached() { mHeadsUpManager.removeListener(this); - mHeadsUpStatusBarView.setOnDrawingRectChangedListener(null); + mView.setOnDrawingRectChangedListener(null); mWakeUpCoordinator.removeListener(this); mNotificationPanelViewController.removeTrackingHeadsUpListener(mSetTrackingHeadsUp); mNotificationPanelViewController.setHeadsUpAppearanceController(null); @@ -170,7 +192,7 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, private void updateIsolatedIconLocation(boolean requireStateUpdate) { mNotificationIconAreaController.setIsolatedIconLocation( - mHeadsUpStatusBarView.getIconDrawingRect(), requireStateUpdate); + mView.getIconDrawingRect(), requireStateUpdate); } @Override @@ -184,20 +206,20 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, if (shouldBeVisible()) { newEntry = mHeadsUpManager.getTopEntry(); } - NotificationEntry previousEntry = mHeadsUpStatusBarView.getShowingEntry(); - mHeadsUpStatusBarView.setEntry(newEntry); + NotificationEntry previousEntry = mView.getShowingEntry(); + mView.setEntry(newEntry); if (newEntry != previousEntry) { boolean animateIsolation = false; if (newEntry == null) { // no heads up anymore, lets start the disappear animation setShown(false); - animateIsolation = !mIsExpanded; + animateIsolation = !isExpanded(); } else if (previousEntry == null) { // We now have a headsUp and didn't have one before. Let's start the disappear // animation setShown(true); - animateIsolation = !mIsExpanded; + animateIsolation = !isExpanded(); } updateIsolatedIconLocation(false /* requireUpdate */); mNotificationIconAreaController.showIconIsolated(newEntry == null ? null @@ -210,8 +232,8 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, mShown = isShown; if (isShown) { updateParentClipping(false /* shouldClip */); - mHeadsUpStatusBarView.setVisibility(View.VISIBLE); - show(mHeadsUpStatusBarView); + mView.setVisibility(View.VISIBLE); + show(mView); hide(mClockView, View.INVISIBLE); if (mCenteredIconView.getVisibility() != View.GONE) { hide(mCenteredIconView, View.INVISIBLE); @@ -227,21 +249,21 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, if (mOperatorNameView != null) { show(mOperatorNameView); } - hide(mHeadsUpStatusBarView, View.GONE, () -> { + hide(mView, View.GONE, () -> { updateParentClipping(true /* shouldClip */); }); } // Show the status bar icons when the view gets shown / hidden if (mStatusBarStateController.getState() != StatusBarState.SHADE) { mCommandQueue.recomputeDisableFlags( - mHeadsUpStatusBarView.getContext().getDisplayId(), false); + mView.getContext().getDisplayId(), false); } } } private void updateParentClipping(boolean shouldClip) { ViewClippingUtil.setClippingDeactivated( - mHeadsUpStatusBarView, !shouldClip, mParentClippingParams); + mView, !shouldClip, mParentClippingParams); } /** @@ -310,7 +332,7 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, */ public boolean shouldBeVisible() { boolean notificationsShown = !mWakeUpCoordinator.getNotificationsFullyHidden(); - boolean canShow = !mIsExpanded && notificationsShown; + boolean canShow = !isExpanded() && notificationsShown; if (mBypassController.getBypassEnabled() && (mStatusBarStateController.getState() == StatusBarState.KEYGUARD || mKeyguardStateController.isKeyguardGoingAway()) @@ -328,17 +350,17 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, public void setAppearFraction(float expandedHeight, float appearFraction) { boolean changed = expandedHeight != mExpandedHeight; + boolean oldIsExpanded = isExpanded(); + mExpandedHeight = expandedHeight; mAppearFraction = appearFraction; - boolean isExpanded = expandedHeight > 0; // We only notify if the expandedHeight changed and not on the appearFraction, since // otherwise we may run into an infinite loop where the panel and this are constantly // updating themselves over just a small fraction if (changed) { updateHeadsUpHeaders(); } - if (isExpanded != mIsExpanded) { - mIsExpanded = isExpanded; + if (isExpanded() != oldIsExpanded) { updateTopEntry(); } } @@ -358,6 +380,10 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, } } + private boolean isExpanded() { + return mExpandedHeight > 0; + } + private void updateHeadsUpHeaders() { mHeadsUpManager.getAllEntries().forEach(entry -> { updateHeader(entry); @@ -376,22 +402,13 @@ public class HeadsUpAppearanceController implements OnHeadsUpChangedListener, @Override public void onDarkChanged(Rect area, float darkIntensity, int tint) { - mHeadsUpStatusBarView.onDarkChanged(area, darkIntensity, tint); + mView.onDarkChanged(area, darkIntensity, tint); } public void onStateChanged() { updateTopEntry(); } - void readFrom(HeadsUpAppearanceController oldController) { - if (oldController != null) { - mTrackedChild = oldController.mTrackedChild; - mExpandedHeight = oldController.mExpandedHeight; - mIsExpanded = oldController.mIsExpanded; - mAppearFraction = oldController.mAppearFraction; - } - } - @Override public void onFullyHiddenChanged(boolean isFullyHidden) { updateTopEntry(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index e26f75f7ce40..d27b5fcf598a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -71,6 +71,7 @@ import android.widget.ImageView; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; @@ -150,6 +151,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL private ControlsComponent mControlsComponent; private boolean mControlServicesAvailable = false; + @Nullable private View mAmbientIndicationArea; private ViewGroup mIndicationArea; private TextView mIndicationText; private TextView mIndicationTextBottom; @@ -270,6 +272,29 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL public void initFrom(KeyguardBottomAreaView oldBottomArea) { setStatusBar(oldBottomArea.mStatusBar); + + // if it exists, continue to use the original ambient indication container + // instead of the newly inflated one + if (mAmbientIndicationArea != null) { + // remove old ambient indication from its parent + View originalAmbientIndicationView = + oldBottomArea.findViewById(R.id.ambient_indication_container); + ((ViewGroup) originalAmbientIndicationView.getParent()) + .removeView(originalAmbientIndicationView); + + // remove current ambient indication from its parent (discard) + ViewGroup ambientIndicationParent = (ViewGroup) mAmbientIndicationArea.getParent(); + int ambientIndicationIndex = + ambientIndicationParent.indexOfChild(mAmbientIndicationArea); + ambientIndicationParent.removeView(mAmbientIndicationArea); + + // add the old ambient indication to this view + ambientIndicationParent.addView(originalAmbientIndicationView, ambientIndicationIndex); + mAmbientIndicationArea = originalAmbientIndicationView; + + // update burn-in offsets + dozeTimeTick(); + } } @Override @@ -283,6 +308,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL mWalletButton = findViewById(R.id.wallet_button); mControlsButton = findViewById(R.id.controls_button); mIndicationArea = findViewById(R.id.keyguard_indication_area); + mAmbientIndicationArea = findViewById(R.id.ambient_indication_container); mIndicationText = findViewById(R.id.keyguard_indication_text); mIndicationTextBottom = findViewById(R.id.keyguard_indication_text_bottom); mIndicationBottomMargin = getResources().getDimensionPixelSize( @@ -901,6 +927,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL int burnInYOffset = getBurnInOffset(mBurnInYOffset * 2, false /* xAxis */) - mBurnInYOffset; mIndicationArea.setTranslationY(burnInYOffset * mDarkAmount); + mAmbientIndicationArea.setTranslationY(burnInYOffset * mDarkAmount); } public void setAntiBurnInOffsetX(int burnInXOffset) { @@ -909,6 +936,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL } mBurnInXOffset = burnInXOffset; mIndicationArea.setTranslationX(burnInXOffset); + mAmbientIndicationArea.setTranslationX(burnInXOffset); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java index 3f3328172e12..68ab07798520 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java @@ -124,6 +124,9 @@ public class LightsOutNotifController { public void onAnimationEnd(Animator a) { mLightsOutNotifView.setAlpha(showDot ? 1 : 0); mLightsOutNotifView.setVisibility(showDot ? View.VISIBLE : View.GONE); + // Unset the listener, otherwise this may persist for + // another view property animation + mLightsOutNotifView.animate().setListener(null); } }) .start(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 9b5fa2a49ff1..0312c30c73ec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -88,6 +88,7 @@ import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; +import androidx.annotation.Nullable; import androidx.constraintlayout.widget.ConstraintSet; import com.android.internal.annotations.VisibleForTesting; @@ -451,9 +452,13 @@ public class NotificationPanelViewController extends PanelViewController { private boolean mUserSetupComplete; private boolean mHideIconsDuringLaunchAnimation = true; private int mStackScrollerMeasuringPass; - private ArrayList<Consumer<ExpandableNotificationRow>> - mTrackingHeadsUpListeners = - new ArrayList<>(); + /** + * Non-null if there's a heads-up notification that we're currently tracking the position of. + */ + @Nullable + private ExpandableNotificationRow mTrackedHeadsUpNotification; + private final ArrayList<Consumer<ExpandableNotificationRow>> + mTrackingHeadsUpListeners = new ArrayList<>(); private HeadsUpAppearanceController mHeadsUpAppearanceController; private int mPanelAlpha; @@ -3050,18 +3055,24 @@ public class NotificationPanelViewController extends PanelViewController { mQsExpandImmediate = false; mNotificationStackScrollLayoutController.setShouldShowShelfOnly(false); mTwoFingerQsExpandPossible = false; - notifyListenersTrackingHeadsUp(null); + updateTrackingHeadsUp(null); mExpandingFromHeadsUp = false; setPanelScrimMinFraction(0.0f); } - private void notifyListenersTrackingHeadsUp(ExpandableNotificationRow pickedChild) { + private void updateTrackingHeadsUp(@Nullable ExpandableNotificationRow pickedChild) { + mTrackedHeadsUpNotification = pickedChild; for (int i = 0; i < mTrackingHeadsUpListeners.size(); i++) { Consumer<ExpandableNotificationRow> listener = mTrackingHeadsUpListeners.get(i); listener.accept(pickedChild); } } + @Nullable + public ExpandableNotificationRow getTrackedHeadsUpNotification() { + return mTrackedHeadsUpNotification; + } + private void setListening(boolean listening) { mKeyguardStatusBarViewController.setBatteryListening(listening); if (mQs == null) return; @@ -3298,7 +3309,7 @@ public class NotificationPanelViewController extends PanelViewController { public void setTrackedHeadsUp(ExpandableNotificationRow pickedChild) { if (pickedChild != null) { - notifyListenersTrackingHeadsUp(pickedChild); + updateTrackingHeadsUp(pickedChild); mExpandingFromHeadsUp = true; } // otherwise we update the state when the expansion is finished diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java index 9cefded72365..bf5467716910 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java @@ -120,6 +120,9 @@ public class SettingsButton extends AlphaOptimizedImageButton { setAlpha(1f); setTranslationX(0); cancelLongClick(); + // Unset the listener, otherwise this may persist for + // another view property animation + animate().setListener(null); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 0f55f289ff27..d96fec5bf92e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -519,7 +519,6 @@ public class StatusBar extends SystemUI implements private QSPanelController mQSPanelController; private final OperatorNameViewController.Factory mOperatorNameViewControllerFactory; - private final PhoneStatusBarViewController.Factory mPhoneStatusBarViewControllerFactory; KeyguardIndicationController mKeyguardIndicationController; private View mReportRejectedTouch; @@ -664,7 +663,6 @@ public class StatusBar extends SystemUI implements private boolean mNoAnimationOnNextBarModeChange; private final SysuiStatusBarStateController mStatusBarStateController; - private HeadsUpAppearanceController mHeadsUpAppearanceController; private final ActivityLaunchAnimator mActivityLaunchAnimator; private NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider; protected StatusBarNotificationPresenter mPresenter; @@ -765,7 +763,6 @@ public class StatusBar extends SystemUI implements ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, OperatorNameViewController.Factory operatorNameViewControllerFactory, - PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory, PhoneStatusBarPolicy phoneStatusBarPolicy, KeyguardIndicationController keyguardIndicationController, DemoModeController demoModeController, @@ -804,7 +801,6 @@ public class StatusBar extends SystemUI implements mKeyguardStateController = keyguardStateController; mHeadsUpManager = headsUpManagerPhone; mOperatorNameViewControllerFactory = operatorNameViewControllerFactory; - mPhoneStatusBarViewControllerFactory = phoneStatusBarViewControllerFactory; mKeyguardIndicationController = keyguardIndicationController; mStatusBarTouchableRegionManager = statusBarTouchableRegionManager; mDynamicPrivacyController = dynamicPrivacyController; @@ -1149,12 +1145,8 @@ public class StatusBar extends SystemUI implements } mStatusBarView = statusBarFragmentComponent.getPhoneStatusBarView(); - - // TODO(b/205609837): Migrate this to StatusBarFragmentComponent. - mPhoneStatusBarViewController = mPhoneStatusBarViewControllerFactory - .create(mStatusBarView, mNotificationPanelViewController - .getStatusBarTouchEventHandler()); - mPhoneStatusBarViewController.init(); + mPhoneStatusBarViewController = + statusBarFragmentComponent.getPhoneStatusBarViewController(); // Ensure we re-propagate panel expansion values to the panel controller and // any listeners it may have, such as PanelBar. This will also ensure we @@ -1164,21 +1156,6 @@ public class StatusBar extends SystemUI implements mNotificationPanelViewController.updatePanelExpansionAndVisibility(); setBouncerShowingForStatusBarComponents(mBouncerShowing); - HeadsUpAppearanceController oldController = mHeadsUpAppearanceController; - if (mHeadsUpAppearanceController != null) { - // This view is being recreated, let's destroy the old one - mHeadsUpAppearanceController.destroy(); - } - // TODO (b/136993073) Separate notification shade and status bar - // TODO(b/205609837): Migrate this to StatusBarFragmentComponent. - mHeadsUpAppearanceController = new HeadsUpAppearanceController( - mNotificationIconAreaController, mHeadsUpManager, - mStackScrollerController, - mStatusBarStateController, mKeyguardBypassController, - mKeyguardStateController, mWakeUpCoordinator, mCommandQueue, - mNotificationPanelViewController, mStatusBarView); - mHeadsUpAppearanceController.readFrom(oldController); - mLightsOutNotifController.setLightsOutNotifView( mStatusBarView.findViewById(R.id.notification_lights_out)); mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView); @@ -1904,10 +1881,6 @@ public class StatusBar extends SystemUI implements mScrimController.setKeyguardOccluded(occluded); } - public boolean headsUpShouldBeVisible() { - return mHeadsUpAppearanceController.shouldBeVisible(); - } - /** A launch animation was cancelled. */ //TODO: These can / should probably be moved to NotificationPresenter or ShadeController public void onLaunchAnimationCancelled(boolean isLaunchForActivity) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 0d23d663c51c..e2bf0db6eb1d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -555,12 +555,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void onStartedWakingUp() { mStatusBar.getNotificationShadeWindowView().getWindowInsetsController() .setAnimationsDisabled(false); - View currentView = getCurrentNavBarView(); - if (currentView != null) { - currentView.animate() - .alpha(1f) - .setDuration(NAV_BAR_CONTENT_FADE_DURATION) - .start(); + NavigationBarView navBarView = mStatusBar.getNavigationBarView(); + if (navBarView != null) { + navBarView.forEachView(view -> + view.animate() + .alpha(1f) + .setDuration(NAV_BAR_CONTENT_FADE_DURATION) + .start()); } } @@ -568,12 +569,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb public void onStartedGoingToSleep() { mStatusBar.getNotificationShadeWindowView().getWindowInsetsController() .setAnimationsDisabled(true); - View currentView = getCurrentNavBarView(); - if (currentView != null) { - currentView.animate() - .alpha(0f) - .setDuration(NAV_BAR_CONTENT_FADE_DURATION) - .start(); + NavigationBarView navBarView = mStatusBar.getNavigationBarView(); + if (navBarView != null) { + navBarView.forEachView(view -> + view.animate() + .alpha(0f) + .setDuration(NAV_BAR_CONTENT_FADE_DURATION) + .start()); } } @@ -1015,17 +1017,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mStatusBar.onKeyguardViewManagerStatesUpdated(); } - /** - * Updates the visibility of the nav bar content views. - */ - private void updateNavigationBarContentVisibility(boolean navBarContentVisible) { - final NavigationBarView navBarView = mStatusBar.getNavigationBarView(); - if (navBarView != null && navBarView.getCurrentView() != null) { - final View currentView = navBarView.getCurrentView(); - currentView.setVisibility(navBarContentVisible ? View.VISIBLE : View.INVISIBLE); - } - } - private View getCurrentNavBarView() { final NavigationBarView navBarView = mStatusBar.getNavigationBarView(); return navBarView != null ? navBarView.getCurrentView() : null; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java index 50e6151bd3a3..633be3c32d03 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java @@ -88,7 +88,6 @@ import com.android.systemui.statusbar.phone.LockscreenGestureLogger; import com.android.systemui.statusbar.phone.LockscreenWallpaper; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy; -import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.ScrimController; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; @@ -216,7 +215,6 @@ public interface StatusBarPhoneModule { ExtensionController extensionController, UserInfoControllerImpl userInfoControllerImpl, OperatorNameViewController.Factory operatorNameViewControllerFactory, - PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory, PhoneStatusBarPolicy phoneStatusBarPolicy, KeyguardIndicationController keyguardIndicationController, DemoModeController demoModeController, @@ -317,7 +315,6 @@ public interface StatusBarPhoneModule { extensionController, userInfoControllerImpl, operatorNameViewControllerFactory, - phoneStatusBarViewControllerFactory, phoneStatusBarPolicy, keyguardIndicationController, demoModeController, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index 76176df136b5..32f3ba4a6f5b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -68,7 +68,7 @@ import dagger.Lazy; import dagger.Module; import dagger.Provides; -@Module +@Module(subcomponents = StatusBarFragmentComponent.class) public abstract class StatusBarViewModule { public static final String SPLIT_SHADE_HEADER = "split_shade_header"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index b32acce51e84..d4d890773927 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -329,8 +329,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } protected int adjustDisableFlags(int state) { - boolean headsUpVisible = mStatusBarOptionalLazy.get() - .map(StatusBar::headsUpShouldBeVisible).orElse(false); + boolean headsUpVisible = + mStatusBarFragmentComponent.getHeadsUpAppearanceController().shouldBeVisible(); if (headsUpVisible) { state |= DISABLE_CLOCK; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java index 47c187540740..3656ed116b32 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java @@ -18,7 +18,9 @@ package com.android.systemui.statusbar.phone.fragment.dagger; import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.dagger.qualifiers.RootView; +import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.PhoneStatusBarView; +import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import dagger.BindsInstance; @@ -56,6 +58,8 @@ public interface StatusBarFragmentComponent { // No one accesses this controller, so we need to make sure we reference it here so it does // get initialized. getBatteryMeterViewController().init(); + getHeadsUpAppearanceController().init(); + getPhoneStatusBarViewController().init(); } /** */ @@ -66,4 +70,12 @@ public interface StatusBarFragmentComponent { @StatusBarFragmentScope @RootView PhoneStatusBarView getPhoneStatusBarView(); + + /** */ + @StatusBarFragmentScope + PhoneStatusBarViewController getPhoneStatusBarViewController(); + + /** */ + @StatusBarFragmentScope + HeadsUpAppearanceController getHeadsUpAppearanceController(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java index 969361bb6100..d2445580d56b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java @@ -19,7 +19,9 @@ package com.android.systemui.statusbar.phone.fragment.dagger; import com.android.systemui.R; import com.android.systemui.battery.BatteryMeterView; import com.android.systemui.dagger.qualifiers.RootView; +import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.PhoneStatusBarView; +import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment; import dagger.Module; @@ -43,4 +45,16 @@ public interface StatusBarFragmentModule { static BatteryMeterView provideBatteryMeterView(@RootView PhoneStatusBarView view) { return view.findViewById(R.id.battery); } + + /** */ + @Provides + @StatusBarFragmentScope + static PhoneStatusBarViewController providePhoneStatusBarViewController( + PhoneStatusBarViewController.Factory phoneStatusBarViewControllerFactory, + @RootView PhoneStatusBarView phoneStatusBarView, + NotificationPanelViewController notificationPanelViewController) { + return phoneStatusBarViewControllerFactory.create( + phoneStatusBarView, + notificationPanelViewController.getStatusBarTouchEventHandler()); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index 3b1c5f32a772..bf5522c50a78 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -54,6 +54,7 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.SysuiTestCase; +import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.model.SysUiState; @@ -115,6 +116,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { @Mock private UserContextProvider mUserContextProvider; @Mock private StatusBar mStatusBar; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock private DialogLaunchAnimator mDialogLaunchAnimator; private TestableLooper mTestableLooper; @@ -159,8 +161,8 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { mHandler, mPackageManager, Optional.of(mStatusBar), - mKeyguardUpdateMonitor - ); + mKeyguardUpdateMonitor, + mDialogLaunchAnimator); mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting(); ColorExtractor.GradientColors backdropColors = new ColorExtractor.GradientColors(); @@ -218,7 +220,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { GlobalActionsDialogLite.ActionsDialogLite dialog = mGlobalActionsDialogLite.createDialog(); GestureDetector.SimpleOnGestureListener gestureListener = spy(dialog.mGestureListener); - gestureListener.onSingleTapConfirmed(null); + gestureListener.onSingleTapUp(null); verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE); } @@ -444,12 +446,12 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase { // When entering power menu from lockscreen, with smart lock enabled when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(true); - mGlobalActionsDialogLite.showOrHideDialog(true, true); + mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */); // Then smart lock will be disabled verify(mLockPatternUtils).requireCredentialEntry(eq(user)); // hide dialog again - mGlobalActionsDialogLite.showOrHideDialog(true, true); + mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java new file mode 100644 index 000000000000..a445d6f4e5c5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavBarHelperTest.java @@ -0,0 +1,182 @@ +/* + * 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. + */ + +package com.android.systemui.navigationbar; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.view.accessibility.AccessibilityManager; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.accessibility.AccessibilityButtonModeObserver; +import com.android.systemui.assist.AssistManager; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.recents.OverviewProxyService; +import com.android.systemui.settings.UserTracker; +import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Optional; + +import dagger.Lazy; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NavBarHelperTest extends SysuiTestCase { + + @Mock + AccessibilityManager mAccessibilityManager; + @Mock + AccessibilityManagerWrapper mAccessibilityManagerWrapper; + @Mock + AccessibilityButtonModeObserver mAccessibilityButtonModeObserver; + @Mock + OverviewProxyService mOverviewProxyService; + @Mock + Lazy<AssistManager> mAssistManagerLazy; + @Mock + AssistManager mAssistManager; + @Mock + NavigationModeController mNavigationModeController; + @Mock + UserTracker mUserTracker; + @Mock + ComponentName mAssistantComponent; + @Mock + DumpManager mDumpManager; + @Mock + NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater; + + private NavBarHelper mNavBarHelper; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mAssistManagerLazy.get()).thenReturn(mAssistManager); + when(mAssistManager.getAssistInfoForUser(anyInt())).thenReturn(mAssistantComponent); + when(mUserTracker.getUserId()).thenReturn(1); + + mNavBarHelper = new NavBarHelper(mContext, mAccessibilityManager, + mAccessibilityManagerWrapper, mAccessibilityButtonModeObserver, + mOverviewProxyService, mAssistManagerLazy, () -> Optional.of(mock(StatusBar.class)), + mNavigationModeController, mUserTracker, mDumpManager); + + } + + @Test + public void registerListenersInCtor() { + verify(mAccessibilityButtonModeObserver, times(1)).addListener(mNavBarHelper); + verify(mNavigationModeController, times(1)).addListener(mNavBarHelper); + verify(mOverviewProxyService, times(1)).addCallback(mNavBarHelper); + } + + @Test + public void registerAssistantContentObserver() { + mNavBarHelper.init(); + verify(mAssistManager, times(1)).getAssistInfoForUser(anyInt()); + } + + @Test + public void callbacksFiredWhenRegistering() { + mNavBarHelper.init(); + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAccessibilityServicesState(); + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAssistantAvailable(anyBoolean()); + } + + @Test + public void assistantCallbacksFiredAfterConnecting() { + mNavBarHelper.init(); + // 1st set of callbacks get called when registering + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + + mNavBarHelper.onConnectionChanged(false); + // assert no more callbacks fired + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAccessibilityServicesState(); + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAssistantAvailable(anyBoolean()); + + mNavBarHelper.onConnectionChanged(true); + // assert no more callbacks fired + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAccessibilityServicesState(); + verify(mNavbarTaskbarStateUpdater, times(2)) + .updateAssistantAvailable(anyBoolean()); + } + + @Test + public void a11yCallbacksFiredAfterModeChange() { + mNavBarHelper.init(); + // 1st set of callbacks get called when registering + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + + mNavBarHelper.onAccessibilityButtonModeChanged(0); + verify(mNavbarTaskbarStateUpdater, times(2)) + .updateAccessibilityServicesState(); + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAssistantAvailable(anyBoolean()); + } + + @Test + public void assistantCallbacksFiredAfterNavModeChange() { + mNavBarHelper.init(); + // 1st set of callbacks get called when registering + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + + mNavBarHelper.onNavigationModeChanged(0); + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAccessibilityServicesState(); + verify(mNavbarTaskbarStateUpdater, times(2)) + .updateAssistantAvailable(anyBoolean()); + } + + @Test + public void removeListenerNoCallbacksFired() { + mNavBarHelper.init(); + // 1st set of callbacks get called when registering + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + + // Remove listener + mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater); + + // Would have fired 2nd callback if not removed + mNavBarHelper.onAccessibilityButtonModeChanged(0); + + // assert no more callbacks fired + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAccessibilityServicesState(); + verify(mNavbarTaskbarStateUpdater, times(1)) + .updateAssistantAvailable(anyBoolean()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java index 62832205a097..9d2541c0150f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerTest.java @@ -83,7 +83,7 @@ public class NavigationBarControllerTest extends SysuiTestCase { mCommandQueue, Dependency.get(Dependency.MAIN_HANDLER), mock(ConfigurationController.class), - mock(NavigationBarA11yHelper.class), + mock(NavBarHelper.class), mock(TaskbarDelegate.class), mNavigationBarFactory, mock(DumpManager.class), diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 223ffbd7bba5..5003013358be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -23,17 +23,20 @@ import static android.inputmethodservice.InputMethodService.IME_INVISIBLE; import static android.inputmethodservice.InputMethodService.IME_VISIBLE; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS; +import static android.view.WindowInsets.Type.ime; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS; import static com.android.systemui.navigationbar.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +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.Matchers.eq; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -57,6 +60,7 @@ import android.view.Display; import android.view.DisplayInfo; import android.view.MotionEvent; import android.view.View; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowMetrics; import android.view.accessibility.AccessibilityManager; @@ -72,6 +76,7 @@ import com.android.systemui.accessibility.AccessibilityButtonModeObserver; import com.android.systemui.accessibility.SystemActions; import com.android.systemui.assist.AssistManager; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -83,8 +88,10 @@ import com.android.systemui.statusbar.NotificationRemoteInputManager; import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.phone.AutoHideController; import com.android.systemui.statusbar.phone.LightBarController; +import com.android.systemui.statusbar.phone.NotificationShadeWindowView; import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; +import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.utils.leaks.LeakCheckedTest; @@ -98,6 +105,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.util.Optional; @@ -130,8 +138,7 @@ public class NavigationBarTest extends SysuiTestCase { EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory; @Mock EdgeBackGestureHandler mEdgeBackGestureHandler; - @Mock - NavigationBarA11yHelper mNavigationBarA11yHelper; + NavBarHelper mNavBarHelper; @Mock private LightBarController mLightBarController; @Mock @@ -148,6 +155,8 @@ public class NavigationBarTest extends SysuiTestCase { private InputMethodManager mInputMethodManager; @Mock private AssistManager mAssistManager; + @Mock + private StatusBar mStatusBar; @Rule public final LeakCheckedTest.SysuiLeakCheck mLeakCheck = new LeakCheckedTest.SysuiLeakCheck(); @@ -172,6 +181,12 @@ public class NavigationBarTest extends SysuiTestCase { mDependency.injectTestDependency(OverviewProxyService.class, mOverviewProxyService); mDependency.injectTestDependency(NavigationModeController.class, mNavigationModeController); TestableLooper.get(this).runWithLooper(() -> { + mNavBarHelper = spy(new NavBarHelper(mContext, mock(AccessibilityManager.class), + mock(AccessibilityManagerWrapper.class), + mock(AccessibilityButtonModeObserver.class), mOverviewProxyService, + () -> mock(AssistManager.class), () -> Optional.of(mStatusBar), + mock(NavigationModeController.class), mock(UserTracker.class), + mock(DumpManager.class))); mNavigationBar = createNavBar(mContext); mExternalDisplayNavigationBar = createNavBar(mSysuiTestableContextExternal); }); @@ -220,6 +235,7 @@ public class NavigationBarTest extends SysuiTestCase { new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_SYSTEMUI) .setLong(HOME_BUTTON_LONG_PRESS_DURATION_MS, 100) .build()); + when(mNavBarHelper.getLongPressHomeEnabled()).thenReturn(true); mNavigationBar.onViewAttachedToWindow(mNavigationBar.createView(null)); mNavigationBar.onHomeTouch(mNavigationBar.getView(), MotionEvent.obtain( @@ -255,6 +271,11 @@ public class NavigationBarTest extends SysuiTestCase { // Create default & external NavBar fragment. NavigationBar defaultNavBar = mNavigationBar; NavigationBar externalNavBar = mExternalDisplayNavigationBar; + NotificationShadeWindowView mockShadeWindowView = mock(NotificationShadeWindowView.class); + WindowInsets windowInsets = new WindowInsets.Builder().setVisible(ime(), false).build(); + doReturn(windowInsets).when(mockShadeWindowView).getRootWindowInsets(); + doReturn(mockShadeWindowView).when(mStatusBar).getNotificationShadeWindowView(); + doReturn(true).when(mockShadeWindowView).isAttachedToWindow(); doNothing().when(defaultNavBar).checkNavBarModes(); doNothing().when(externalNavBar).checkNavBarModes(); defaultNavBar.createView(null); @@ -281,17 +302,51 @@ public class NavigationBarTest extends SysuiTestCase { } @Test + public void testSetImeWindowStatusWhenKeyguardLockingAndImeInsetsChange() { + NotificationShadeWindowView mockShadeWindowView = mock(NotificationShadeWindowView.class); + doReturn(mockShadeWindowView).when(mStatusBar).getNotificationShadeWindowView(); + doReturn(true).when(mockShadeWindowView).isAttachedToWindow(); + doNothing().when(mNavigationBar).checkNavBarModes(); + mNavigationBar.createView(null); + WindowInsets windowInsets = new WindowInsets.Builder().setVisible(ime(), false).build(); + doReturn(windowInsets).when(mockShadeWindowView).getRootWindowInsets(); + + // Verify navbar altered back icon when an app is showing IME + mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE, + BACK_DISPOSITION_DEFAULT, true); + assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); + assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); + + // Verify navbar didn't alter and showing back icon when the keyguard is showing without + // requesting IME insets visible. + doReturn(true).when(mStatusBar).isKeyguardShowing(); + mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE, + BACK_DISPOSITION_DEFAULT, true); + assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); + assertFalse((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); + + // Verify navbar altered and showing back icon when the keyguard is showing and + // requesting IME insets visible. + windowInsets = new WindowInsets.Builder().setVisible(ime(), true).build(); + doReturn(windowInsets).when(mockShadeWindowView).getRootWindowInsets(); + mNavigationBar.setImeWindowStatus(DEFAULT_DISPLAY, null, IME_VISIBLE, + BACK_DISPOSITION_DEFAULT, true); + assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_BACK_ALT) != 0); + assertTrue((mNavigationBar.getNavigationIconHints() & NAVIGATION_HINT_IME_SHOWN) != 0); + } + + @Test public void testA11yEventAfterDetach() { View v = mNavigationBar.createView(null); mNavigationBar.onViewAttachedToWindow(v); - verify(mNavigationBarA11yHelper).registerA11yEventListener(any( - NavigationBarA11yHelper.NavA11yEventListener.class)); + verify(mNavBarHelper).registerNavTaskStateUpdater(any( + NavBarHelper.NavbarTaskbarStateUpdater.class)); mNavigationBar.onViewDetachedFromWindow(v); - verify(mNavigationBarA11yHelper).removeA11yEventListener(any( - NavigationBarA11yHelper.NavA11yEventListener.class)); + verify(mNavBarHelper).removeNavTaskStateUpdater(any( + NavBarHelper.NavbarTaskbarStateUpdater.class)); // Should be safe even though the internal view is now null. - mNavigationBar.updateAccessibilityServicesState(); + mNavigationBar.updateAcessibilityStateFlags(); } private NavigationBar createNavBar(Context context) { @@ -313,7 +368,7 @@ public class NavigationBarTest extends SysuiTestCase { Optional.of(mock(Pip.class)), Optional.of(mock(LegacySplitScreen.class)), Optional.of(mock(Recents.class)), - () -> Optional.of(mock(StatusBar.class)), + () -> Optional.of(mStatusBar), mock(ShadeController.class), mock(NotificationRemoteInputManager.class), mock(NotificationShadeDepthController.class), @@ -321,7 +376,7 @@ public class NavigationBarTest extends SysuiTestCase { mHandler, mock(NavigationBarOverlayController.class), mUiEventLogger, - mNavigationBarA11yHelper, + mNavBarHelper, mock(UserTracker.class), mLightBarController, mLightBarcontrollerFactory, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java index b32b4d4f3810..339d5bb04d74 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java @@ -1,5 +1,7 @@ package com.android.systemui.qs.tiles.dialog; +import static com.android.systemui.qs.tiles.dialog.InternetDialogController.MAX_WIFI_ENTRY_COUNT; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -219,7 +221,7 @@ public class InternetDialogTest extends SysuiTestCase { } @Test - public void updateDialog_wifiOnAndNoWifiEntry_hideWifiEntryAndSeeAll() { + public void updateDialog_wifiOnAndNoWifiEntry_showWifiListAndSeeAllArea() { // The precondition WiFi ON is already in setUp() mInternetDialog.mConnectedWifiEntry = null; mInternetDialog.mWifiEntriesCount = 0; @@ -227,19 +229,21 @@ public class InternetDialogTest extends SysuiTestCase { mInternetDialog.updateDialog(false); assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE); - assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE); - assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE); + // Show a blank block to fix the dialog height even if there is no WiFi list + assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE); + assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE); } @Test - public void updateDialog_wifiOnAndHasConnectedWifi_showConnectedWifiAndSeeAll() { + public void updateDialog_wifiOnAndHasConnectedWifi_showAllWifiAndSeeAllArea() { // The preconditions WiFi ON and WiFi entries are already in setUp() mInternetDialog.mWifiEntriesCount = 0; mInternetDialog.updateDialog(false); assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE); - assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE); + // Show a blank block to fix the dialog height even if there is no WiFi list + assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE); assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE); } @@ -412,4 +416,54 @@ public class InternetDialogTest extends SysuiTestCase { assertThat(mInternetDialog.mIsProgressBarVisible).isTrue(); assertThat(mInternetDialog.mIsSearchingHidden).isTrue(); } + + @Test + public void getWifiListMaxCount_returnCountCorrectly() { + // Ethernet, MobileData, ConnectedWiFi are all hidden. + // Then the maximum count is equal to MAX_WIFI_ENTRY_COUNT. + setNetworkVisible(false, false, false); + + assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT); + + // Only one of Ethernet, MobileData, ConnectedWiFi is displayed. + // Then the maximum count is equal to MAX_WIFI_ENTRY_COUNT - 1. + setNetworkVisible(true, false, false); + + assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 1); + + setNetworkVisible(false, true, false); + + assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 1); + + setNetworkVisible(false, false, true); + + assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 1); + + // Only one of Ethernet, MobileData, ConnectedWiFi is hidden. + // Then the maximum count is equal to MAX_WIFI_ENTRY_COUNT - 2. + setNetworkVisible(true, true, false); + + assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 2); + + setNetworkVisible(true, false, true); + + assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 2); + + setNetworkVisible(false, true, true); + + assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 2); + + // Ethernet, MobileData, ConnectedWiFi are all displayed. + // Then the maximum count is equal to MAX_WIFI_ENTRY_COUNT - 3. + setNetworkVisible(true, true, true); + + assertThat(mInternetDialog.getWifiListMaxCount()).isEqualTo(MAX_WIFI_ENTRY_COUNT - 3); + } + + private void setNetworkVisible(boolean ethernetVisible, boolean mobileDataVisible, + boolean connectedWifiVisible) { + mEthernet.setVisibility(ethernetVisible ? View.VISIBLE : View.GONE); + mMobileDataToggle.setVisibility(mobileDataVisible ? View.VISIBLE : View.GONE); + mConnectedWifi.setVisibility(connectedWifiVisible ? View.VISIBLE : View.GONE); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java index 91cafead596c..4073bb336283 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java @@ -104,7 +104,7 @@ public class RecordingServiceTest extends SysuiTestCase { @Test public void testLogStartRecording() { - Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false); + Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0); mRecordingService.onStartCommand(startIntent, 0, 0); verify(mUiEventLogger, times(1)).log(Events.ScreenRecordEvent.SCREEN_RECORD_START); @@ -137,7 +137,7 @@ public class RecordingServiceTest extends SysuiTestCase { // When the screen recording does not start properly doThrow(new RuntimeException("fail")).when(mScreenMediaRecorder).start(); - Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0, false); + Intent startIntent = RecordingService.getStartIntent(mContext, 0, 0); mRecordingService.onStartCommand(startIntent, 0, 0); // Then the state is set to not recording diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java index bafbccdb87d2..db5fd262a1d1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java @@ -162,8 +162,11 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { } @Test - public void testHeaderReadFromOldController() { - mHeadsUpAppearanceController.setAppearFraction(1.0f, 1.0f); + public void constructor_animationValuesUpdated() { + float appearFraction = .75f; + float expandedHeight = 400f; + when(mStackScrollerController.getAppearFraction()).thenReturn(appearFraction); + when(mStackScrollerController.getExpandedHeight()).thenReturn(expandedHeight); HeadsUpAppearanceController newController = new HeadsUpAppearanceController( mock(NotificationIconAreaController.class), @@ -179,14 +182,9 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { new View(mContext), new View(mContext), new View(mContext)); - newController.readFrom(mHeadsUpAppearanceController); - - Assert.assertEquals(mHeadsUpAppearanceController.mExpandedHeight, - newController.mExpandedHeight, 0.0f); - Assert.assertEquals(mHeadsUpAppearanceController.mAppearFraction, - newController.mAppearFraction, 0.0f); - Assert.assertEquals(mHeadsUpAppearanceController.mIsExpanded, - newController.mIsExpanded); + + Assert.assertEquals(expandedHeight, newController.mExpandedHeight, 0.0f); + Assert.assertEquals(appearFraction, newController.mAppearFraction, 0.0f); } @Test @@ -195,7 +193,9 @@ public class HeadsUpAppearanceControllerTest extends SysuiTestCase { reset(mDarkIconDispatcher); reset(mPanelView); reset(mStackScrollerController); - mHeadsUpAppearanceController.destroy(); + + mHeadsUpAppearanceController.onViewDetached(); + verify(mHeadsUpManager).removeListener(any()); verify(mDarkIconDispatcher).removeDarkReceiver((DarkIconDispatcher.DarkReceiver) any()); verify(mPanelView).removeTrackingHeadsUpListener(any()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt index 210744eb30cf..3257a84f14d6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt @@ -47,8 +47,8 @@ class KeyguardBottomAreaTest : SysuiTestCase() { @Test fun initFrom_doesntCrash() { - val other = LayoutInflater.from(mContext).inflate( - R.layout.keyguard_bottom_area, null, false) as KeyguardBottomAreaView + val other = LayoutInflater.from(mContext).inflate(R.layout.keyguard_bottom_area, + null, false) as KeyguardBottomAreaView other.initFrom(mKeyguardBottomArea) other.launchVoiceAssist() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java index d58e13cd8a64..b0933b51d1d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java @@ -275,7 +275,6 @@ public class StatusBarTest extends SysuiTestCase { @Mock private StartingSurface mStartingSurface; @Mock private OperatorNameViewController mOperatorNameViewController; @Mock private OperatorNameViewController.Factory mOperatorNameViewControllerFactory; - @Mock private PhoneStatusBarViewController.Factory mPhoneStatusBarViewControllerFactory; @Mock private ActivityLaunchAnimator mActivityLaunchAnimator; private ShadeController mShadeController; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); @@ -433,7 +432,6 @@ public class StatusBarTest extends SysuiTestCase { mExtensionController, mUserInfoControllerImpl, mOperatorNameViewControllerFactory, - mPhoneStatusBarViewControllerFactory, mPhoneStatusBarPolicy, mKeyguardIndicationController, mDemoModeController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 8b5989ff61a3..6818947bd8d9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -21,7 +21,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.when; import android.app.Fragment; @@ -48,6 +47,7 @@ import com.android.systemui.statusbar.DisableFlagsLogger; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.connectivity.NetworkController; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; +import com.android.systemui.statusbar.phone.HeadsUpAppearanceController; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.StatusBar; @@ -62,10 +62,11 @@ import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; import java.util.Optional; @@ -82,7 +83,6 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { // Set in instantiate() private StatusBarIconController mStatusBarIconController; private NetworkController mNetworkController; - private StatusBarStateController mStatusBarStateController; private KeyguardStateController mKeyguardStateController; private final StatusBar mStatusBar = mock(StatusBar.class); @@ -90,8 +90,16 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { private OperatorNameViewController.Factory mOperatorNameViewControllerFactory; private OperatorNameViewController mOperatorNameViewController; + @Mock private StatusBarFragmentComponent.Factory mStatusBarFragmentComponentFactory; + @Mock private StatusBarFragmentComponent mStatusBarFragmentComponent; + @Mock + private StatusBarStateController mStatusBarStateController; + @Mock + private HeadsUpAppearanceController mHeadsUpAppearanceController; + @Mock + private NotificationPanelViewController mNotificationPanelViewController; public CollapsedStatusBarFragmentTest() { super(CollapsedStatusBarFragment.class); @@ -99,49 +107,35 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Before public void setup() { - mStatusBarStateController = mDependency - .injectMockDependency(StatusBarStateController.class); injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES); - when(mStatusBar.getPanelController()).thenReturn( - mock(NotificationPanelViewController.class)); } @Test - public void testDisableNone() throws Exception { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + public void testDisableNone() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - assertEquals(View.VISIBLE, mFragment.getView().findViewById(R.id.system_icon_area) - .getVisibility()); - assertEquals(View.VISIBLE, mFragment.getView().findViewById(R.id.clock) - .getVisibility()); + assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.VISIBLE, getClockView().getVisibility()); } @Test - public void testDisableSystemInfo() throws Exception { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + public void testDisableSystemInfo() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false); - assertEquals(View.INVISIBLE, mFragment.getView().findViewById(R.id.system_icon_area) - .getVisibility()); + assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility()); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - assertEquals(View.VISIBLE, mFragment.getView().findViewById(R.id.system_icon_area) - .getVisibility()); + assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); } @Test - public void testDisableNotifications() throws Exception { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + public void testDisableNotifications() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false); @@ -153,25 +147,21 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - public void testDisableClock() throws Exception { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + public void testDisableClock() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_CLOCK, 0, false); - assertEquals(View.GONE, mFragment.getView().findViewById(R.id.clock).getVisibility()); + assertEquals(View.GONE, getClockView().getVisibility()); fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - assertEquals(View.VISIBLE, mFragment.getView().findViewById(R.id.clock).getVisibility()); + assertEquals(View.VISIBLE, getClockView().getVisibility()); } @Test public void disable_noOngoingCall_chipHidden() { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mOngoingCallController.hasOngoingCall()).thenReturn(false); @@ -183,9 +173,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test public void disable_hasOngoingCall_chipDisplayedAndNotificationIconsHidden() { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mOngoingCallController.hasOngoingCall()).thenReturn(true); @@ -199,9 +187,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test public void disable_hasOngoingCallButNotificationIconsDisabled_chipHidden() { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mOngoingCallController.hasOngoingCall()).thenReturn(true); @@ -214,9 +200,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test public void disable_ongoingCallEnded_chipHidden() { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mOngoingCallController.hasOngoingCall()).thenReturn(true); @@ -234,41 +218,95 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility()); } - @Ignore("b/192618546") @Test - public void testOnDozingChanged() throws Exception { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + public void disable_isDozingButNoCustomClock_clockAndSystemInfoVisible() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + when(mStatusBarStateController.isDozing()).thenReturn(true); + when(mNotificationPanelViewController.hasCustomClock()).thenReturn(false); - fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false); + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); - Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.INVISIBLE)); + assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.VISIBLE, getClockView().getVisibility()); + } + + @Test + public void disable_customClockButNotDozing_clockAndSystemInfoVisible() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + when(mStatusBarStateController.isDozing()).thenReturn(false); + when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true); - reset(mStatusBarStateController); + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.VISIBLE, getClockView().getVisibility()); + } + + @Test + public void disable_dozingAndCustomClock_clockAndSystemInfoHidden() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); when(mStatusBarStateController.isDozing()).thenReturn(true); + when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true); + + // Make sure they start out as visible + assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.VISIBLE, getClockView().getVisibility()); + + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.GONE, getClockView().getVisibility()); + } + + @Test + public void onDozingChanged_clockAndSystemInfoVisibilitiesUpdated() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + when(mStatusBarStateController.isDozing()).thenReturn(true); + when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true); + + // Make sure they start out as visible + assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.VISIBLE, getClockView().getVisibility()); + fragment.onDozingChanged(true); - Mockito.verify(mStatusBarStateController).isDozing(); - Mockito.verify(mNotificationAreaInner, atLeast(1)).setVisibility(eq(View.VISIBLE)); + // When this callback is triggered, we want to make sure the clock and system info + // visibilities are recalculated. Since dozing=true, they shouldn't be visible. + assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility()); + assertEquals(View.GONE, getClockView().getVisibility()); + } + + @Test + public void disable_headsUpShouldBeVisibleTrue_clockDisabled() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(true); + + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + assertEquals(View.GONE, getClockView().getVisibility()); + } + + @Test + public void disable_headsUpShouldBeVisibleFalse_clockNotDisabled() { + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); + when(mHeadsUpAppearanceController.shouldBeVisible()).thenReturn(false); + + fragment.disable(DEFAULT_DISPLAY, 0, 0, false); + + assertEquals(View.VISIBLE, getClockView().getVisibility()); } @Test public void setUp_fragmentCreatesDaggerComponent() { - mFragments.dispatchResume(); - processAllMessages(); - CollapsedStatusBarFragment fragment = (CollapsedStatusBarFragment) mFragment; + CollapsedStatusBarFragment fragment = resumeAndGetFragment(); assertEquals(mStatusBarFragmentComponent, fragment.getStatusBarFragmentComponent()); } @Override protected Fragment instantiate(Context context, String className, Bundle arguments) { - mStatusBarFragmentComponentFactory = - mock(StatusBarFragmentComponent.Factory.class); - mStatusBarFragmentComponent = mock(StatusBarFragmentComponent.class); - when(mStatusBarFragmentComponentFactory.create(any())) - .thenReturn(mStatusBarFragmentComponent); + MockitoAnnotations.initMocks(this); + setUpDaggerComponent(); mOngoingCallController = mock(OngoingCallController.class); mAnimationScheduler = mock(SystemStatusAnimationScheduler.class); mLocationPublisher = mock(StatusBarLocationPublisher.class); @@ -295,7 +333,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { new StatusBarHideIconsForBouncerManager( mCommandQueue, new FakeExecutor(new FakeSystemClock()), new DumpManager()), mKeyguardStateController, - mock(NotificationPanelViewController.class), + mNotificationPanelViewController, mNetworkController, mStatusBarStateController, mCommandQueue, @@ -306,6 +344,13 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mOperatorNameViewControllerFactory); } + private void setUpDaggerComponent() { + when(mStatusBarFragmentComponentFactory.create(any())) + .thenReturn(mStatusBarFragmentComponent); + when(mStatusBarFragmentComponent.getHeadsUpAppearanceController()) + .thenReturn(mHeadsUpAppearanceController); + } + private void setUpNotificationIconAreaController() { mMockNotificationAreaController = mock(NotificationIconAreaController.class); @@ -324,4 +369,18 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { when(mMockNotificationAreaController.getNotificationInnerAreaView()).thenReturn( mNotificationAreaInner); } + + private CollapsedStatusBarFragment resumeAndGetFragment() { + mFragments.dispatchResume(); + processAllMessages(); + return (CollapsedStatusBarFragment) mFragment; + } + + private View getClockView() { + return mFragment.getView().findViewById(R.id.clock); + } + + private View getSystemIconAreaView() { + return mFragment.getView().findViewById(R.id.system_icon_area); + } } diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index 61b8ded60db7..7341e744dea3 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -251,7 +251,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> "Successful background authentication!"); } - mAlreadyDone = true; + markAlreadyDone(); if (mTaskStackListener != null) { mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener); @@ -327,7 +327,7 @@ public abstract class AuthenticationClient<T> extends AcquisitionClient<T> final @LockoutTracker.LockoutMode int lockoutMode = handleFailedAttempt(getTargetUserId()); if (lockoutMode != LockoutTracker.LOCKOUT_NONE) { - mAlreadyDone = true; + markAlreadyDone(); } final CoexCoordinator coordinator = CoexCoordinator.getInstance(); diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java index 9764a167fbbf..b73e91173a43 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java +++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java @@ -114,7 +114,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor // Currently only used for authentication client. The cookie generated by BiometricService // is never 0. private final int mCookie; - boolean mAlreadyDone; + private boolean mAlreadyDone = false; // Use an empty callback by default since delayed operations can receive events // before they are started and cause NPE in subclasses that access this field directly. @@ -202,11 +202,9 @@ public abstract class BaseClientMonitor extends LoggableMonitor return callback; } - public boolean isAlreadyDone() { - return mAlreadyDone; - } - - public void destroy() { + /** Signals this operation has completed its lifecycle and should no longer be used. */ + void destroy() { + mAlreadyDone = true; if (mToken != null) { try { mToken.unlinkToDeath(this, 0); @@ -218,6 +216,20 @@ public abstract class BaseClientMonitor extends LoggableMonitor } } + /** + * Call while the operation is still active, but nearly done, to prevent any action + * upon client death (only needed for authentication clients). + */ + void markAlreadyDone() { + Slog.d(TAG, "marking operation as done: " + this); + mAlreadyDone = true; + } + + /** If this operation has been marked as completely done (or cancelled). */ + public boolean isAlreadyDone() { + return mAlreadyDone; + } + @Override public void binderDied() { binderDiedInternal(true /* clearListener */); @@ -225,10 +237,9 @@ public abstract class BaseClientMonitor extends LoggableMonitor // TODO(b/157790417): Move this to the scheduler void binderDiedInternal(boolean clearListener) { - Slog.e(TAG, "Binder died, owner: " + getOwnerString() - + ", operation: " + this.getClass().getName()); + Slog.e(TAG, "Binder died, operation: " + this); - if (isAlreadyDone()) { + if (mAlreadyDone) { Slog.w(TAG, "Binder died but client is finished, ignoring"); return; } @@ -299,7 +310,7 @@ public abstract class BaseClientMonitor extends LoggableMonitor @Override public String toString() { return "{[" + mSequentialId + "] " - + this.getClass().getSimpleName() + + this.getClass().getName() + ", proto=" + getProtoEnum() + ", owner=" + getOwnerString() + ", cookie=" + getCookie() diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index 361ec40f2877..a358bc2bad55 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -605,6 +605,9 @@ public class BiometricScheduler { if (operation.mState == Operation.STATE_WAITING_FOR_COOKIE) { Slog.w(getTag(), "Skipping cancellation for non-started operation: " + operation); // We can set it to null immediately, since the HAL was never notified to start. + if (mCurrentOperation != null) { + mCurrentOperation.mClientMonitor.destroy(); + } mCurrentOperation = null; startNextOperationIfIdle(); return; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 9d6678053533..51da107af283 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -542,9 +542,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ private InputMethodSubtype mCurrentSubtype; - // Was the keyguard locked when this client became current? - private boolean mCurClientInKeyguard; - /** * {@code true} if the IME has not been mostly hidden via {@link android.view.InsetsController} */ @@ -2363,14 +2360,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mImeHiddenByDisplayPolicy = false; if (mCurClient != cs) { - // Was the keyguard locked when switching over to the new client? - mCurClientInKeyguard = isKeyguardLocked(); // If the client is changing, we need to switch over to the new // one. unbindCurrentClientLocked(UnbindReason.SWITCH_CLIENT); - if (DEBUG) Slog.v(TAG, "switching to client: client=" - + cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard); - // If the screen is on, inform the new client it is active if (mIsInteractive) { scheduleSetActiveToClient(cs, true /* active */, false /* fullscreen */, @@ -2875,10 +2867,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // all updateSystemUi happens on system previlege. final long ident = Binder.clearCallingIdentity(); try { - // apply policy for binder calls - if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) { - vis = 0; - } if (!mCurPerceptible) { vis &= ~InputMethodService.IME_VISIBLE; } diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java index 8460d6797543..1781588b0ba2 100644 --- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java @@ -52,8 +52,6 @@ public final class GnssMeasurementsProvider extends private class GnssMeasurementListenerRegistration extends GnssListenerRegistration { - private static final String GNSS_MEASUREMENTS_BUCKET = "gnss_measurement"; - protected GnssMeasurementListenerRegistration( @Nullable GnssMeasurementRequest request, CallerIdentity callerIdentity, @@ -70,15 +68,13 @@ public final class GnssMeasurementsProvider extends @Nullable @Override protected void onActive() { - mLocationAttributionHelper.reportHighPowerLocationStart( - getIdentity(), GNSS_MEASUREMENTS_BUCKET, getKey()); + mLocationAttributionHelper.reportHighPowerLocationStart(getIdentity()); } @Nullable @Override protected void onInactive() { - mLocationAttributionHelper.reportHighPowerLocationStop( - getIdentity(), GNSS_MEASUREMENTS_BUCKET, getKey()); + mLocationAttributionHelper.reportHighPowerLocationStop(getIdentity()); } } diff --git a/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java b/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java index 5cb360be819a..483875230ac4 100644 --- a/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java +++ b/services/core/java/com/android/server/location/injector/LocationAttributionHelper.java @@ -24,55 +24,23 @@ import static com.android.server.location.LocationManagerService.TAG; import android.location.util.identity.CallerIdentity; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.GuardedBy; import java.util.Map; -import java.util.Objects; -import java.util.Set; /** * Helps manage appop monitoring for multiple location clients. */ public class LocationAttributionHelper { - private static class BucketKey { - private final String mBucket; - private final Object mKey; - - private BucketKey(String bucket, Object key) { - mBucket = Objects.requireNonNull(bucket); - mKey = Objects.requireNonNull(key); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - BucketKey that = (BucketKey) o; - return mBucket.equals(that.mBucket) - && mKey.equals(that.mKey); - } - - @Override - public int hashCode() { - return Objects.hash(mBucket, mKey); - } - } - private final AppOpsHelper mAppOpsHelper; @GuardedBy("this") - private final Map<CallerIdentity, Set<BucketKey>> mAttributions; + private final Map<CallerIdentity, Integer> mAttributions; @GuardedBy("this") - private final Map<CallerIdentity, Set<BucketKey>> mHighPowerAttributions; + private final Map<CallerIdentity, Integer> mHighPowerAttributions; public LocationAttributionHelper(AppOpsHelper appOpsHelper) { mAppOpsHelper = appOpsHelper; @@ -84,15 +52,16 @@ public class LocationAttributionHelper { /** * Report normal location usage for the given caller in the given bucket, with a unique key. */ - public synchronized void reportLocationStart(CallerIdentity identity, String bucket, - Object key) { - Set<BucketKey> keySet = mAttributions.computeIfAbsent(identity, - i -> new ArraySet<>()); - boolean empty = keySet.isEmpty(); - if (keySet.add(new BucketKey(bucket, key)) && empty) { - if (!mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)) { - mAttributions.remove(identity); + public synchronized void reportLocationStart(CallerIdentity identity) { + identity = CallerIdentity.forAggregation(identity); + + int count = mAttributions.getOrDefault(identity, 0); + if (count == 0) { + if (mAppOpsHelper.startOpNoThrow(OP_MONITOR_LOCATION, identity)) { + mAttributions.put(identity, 1); } + } else { + mAttributions.put(identity, count + 1); } } @@ -100,13 +69,15 @@ public class LocationAttributionHelper { * Report normal location usage has stopped for the given caller in the given bucket, with a * unique key. */ - public synchronized void reportLocationStop(CallerIdentity identity, String bucket, - Object key) { - Set<BucketKey> keySet = mAttributions.get(identity); - if (keySet != null && keySet.remove(new BucketKey(bucket, key)) - && keySet.isEmpty()) { + public synchronized void reportLocationStop(CallerIdentity identity) { + identity = CallerIdentity.forAggregation(identity); + + int count = mAttributions.getOrDefault(identity, 0); + if (count == 1) { mAttributions.remove(identity); mAppOpsHelper.finishOp(OP_MONITOR_LOCATION, identity); + } else if (count > 1) { + mAttributions.put(identity, count - 1); } } @@ -114,19 +85,19 @@ public class LocationAttributionHelper { * Report high power location usage for the given caller in the given bucket, with a unique * key. */ - public synchronized void reportHighPowerLocationStart(CallerIdentity identity, String bucket, - Object key) { - Set<BucketKey> keySet = mHighPowerAttributions.computeIfAbsent(identity, - i -> new ArraySet<>()); - boolean empty = keySet.isEmpty(); - if (keySet.add(new BucketKey(bucket, key)) && empty) { + public synchronized void reportHighPowerLocationStart(CallerIdentity identity) { + identity = CallerIdentity.forAggregation(identity); + + int count = mHighPowerAttributions.getOrDefault(identity, 0); + if (count == 0) { + if (D) { + Log.v(TAG, "starting high power location attribution for " + identity); + } if (mAppOpsHelper.startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, identity)) { - if (D) { - Log.v(TAG, "starting high power location attribution for " + identity); - } - } else { - mHighPowerAttributions.remove(identity); + mHighPowerAttributions.put(identity, 1); } + } else { + mHighPowerAttributions.put(identity, count + 1); } } @@ -134,16 +105,18 @@ public class LocationAttributionHelper { * Report high power location usage has stopped for the given caller in the given bucket, * with a unique key. */ - public synchronized void reportHighPowerLocationStop(CallerIdentity identity, String bucket, - Object key) { - Set<BucketKey> keySet = mHighPowerAttributions.get(identity); - if (keySet != null && keySet.remove(new BucketKey(bucket, key)) - && keySet.isEmpty()) { + public synchronized void reportHighPowerLocationStop(CallerIdentity identity) { + identity = CallerIdentity.forAggregation(identity); + + int count = mHighPowerAttributions.getOrDefault(identity, 0); + if (count == 1) { if (D) { Log.v(TAG, "stopping high power location attribution for " + identity); } mHighPowerAttributions.remove(identity); mAppOpsHelper.finishOp(OP_MONITOR_HIGH_POWER_LOCATION, identity); + } else if (count > 1) { + mHighPowerAttributions.put(identity, count - 1); } } } diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index f7c4d03bfdf3..155b61891d12 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -398,7 +398,7 @@ public class LocationProviderManager extends EVENT_LOG.logProviderClientActive(mName, getIdentity()); if (!getRequest().isHiddenFromAppOps()) { - mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey()); + mLocationAttributionHelper.reportLocationStart(getIdentity()); } onHighPowerUsageChanged(); @@ -413,7 +413,7 @@ public class LocationProviderManager extends onHighPowerUsageChanged(); if (!getRequest().isHiddenFromAppOps()) { - mLocationAttributionHelper.reportLocationStop(getIdentity(), getName(), getKey()); + mLocationAttributionHelper.reportLocationStop(getIdentity()); } onProviderListenerInactive(); @@ -488,10 +488,10 @@ public class LocationProviderManager extends if (!getRequest().isHiddenFromAppOps()) { if (mIsUsingHighPower) { mLocationAttributionHelper.reportHighPowerLocationStart( - getIdentity(), getName(), getKey()); + getIdentity()); } else { mLocationAttributionHelper.reportHighPowerLocationStop( - getIdentity(), getName(), getKey()); + getIdentity()); } } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 95a286cf9047..d97c845ef7c4 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1387,9 +1387,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** Whether we should prepare a transition for this {@link ActivityRecord} parent change. */ private boolean shouldStartChangeTransition( @Nullable TaskFragment newParent, @Nullable TaskFragment oldParent) { - if (mWmService.mDisableTransitionAnimation - || mDisplayContent == null || newParent == null || oldParent == null - || getSurfaceControl() == null || !isVisible() || !isVisibleRequested()) { + if (newParent == null || oldParent == null || !canStartChangeTransition()) { return false; } diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 878822522d08..721907c21904 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -176,6 +176,9 @@ public class AppTransitionController { Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady"); ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "**** GOOD TO GO"); + // TODO(b/205335975): Remove window which stuck in animatingExit status. Find actual cause. + mDisplayContent.forAllWindows(WindowState::cleanupAnimatingExitWindow, + true /* traverseTopToBottom */); // TODO(new-app-transition): Remove code using appTransition.getAppTransition() final AppTransition appTransition = mDisplayContent.mAppTransition; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 36579d96d6c5..b7c992e073f3 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -44,6 +44,8 @@ import static android.view.Display.FLAG_PRIVATE; import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; import static android.view.Display.INVALID_DISPLAY; import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT; +import static android.view.Display.STATE_UNKNOWN; +import static android.view.Display.isSuspendedState; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_LEFT_GESTURES; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; @@ -322,11 +324,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp */ private Rect mLastMirroredDisplayAreaBounds = null; - /** - * The last state of the display. - */ - private int mLastDisplayState; - // Contains all IME window containers. Note that the z-ordering of the IME windows will depend // on the IME target. We mainly have this container grouping so we can keep track of all the IME // window containers together and move them in-sync if/when needed. We use a subclass of @@ -594,7 +591,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** Caches the value whether told display manager that we have content. */ private boolean mLastHasContent; - private static DisplayRotationUtil sRotationUtil = new DisplayRotationUtil(); + private DisplayRotationUtil mRotationUtil = new DisplayRotationUtil(); /** * The input method window for this display. @@ -1658,11 +1655,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // to cover the activity configuration change. return false; } - if (r.attachedToProcess() && mayImeShowOnLaunchingActivity(r)) { - // Currently it is unknown that when will IME window be ready. Reject the case to - // avoid flickering by showing IME in inconsistent orientation. - return false; - } if (checkOpening) { if (!mAppTransition.isTransitionSet() || !mOpeningApps.contains(r)) { // Apply normal rotation animation in case of the activity set different requested @@ -2098,35 +2090,29 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mDisplayCutoutCache.getOrCompute(mInitialDisplayCutout, rotation); } - static WmDisplayCutout calculateDisplayCutoutForRotationAndDisplaySizeUncached( - DisplayCutout cutout, int rotation, int displayWidth, int displayHeight) { + private WmDisplayCutout calculateDisplayCutoutForRotationUncached( + DisplayCutout cutout, int rotation) { if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) { return WmDisplayCutout.NO_CUTOUT; } if (rotation == ROTATION_0) { return WmDisplayCutout.computeSafeInsets( - cutout, displayWidth, displayHeight); + cutout, mInitialDisplayWidth, mInitialDisplayHeight); } final Insets waterfallInsets = RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation); final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); - final Rect[] newBounds = sRotationUtil.getRotatedBounds( + final Rect[] newBounds = mRotationUtil.getRotatedBounds( cutout.getBoundingRectsAll(), - rotation, displayWidth, displayHeight); + rotation, mInitialDisplayWidth, mInitialDisplayHeight); final CutoutPathParserInfo info = cutout.getCutoutPathParserInfo(); final CutoutPathParserInfo newInfo = new CutoutPathParserInfo( info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(), info.getCutoutSpec(), rotation, info.getScale()); return WmDisplayCutout.computeSafeInsets( DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo), - rotated ? displayHeight : displayWidth, - rotated ? displayWidth : displayHeight); - } - - private WmDisplayCutout calculateDisplayCutoutForRotationUncached( - DisplayCutout cutout, int rotation) { - return calculateDisplayCutoutForRotationAndDisplaySizeUncached(cutout, rotation, - mInitialDisplayWidth, mInitialDisplayHeight); + rotated ? mInitialDisplayHeight : mInitialDisplayWidth, + rotated ? mInitialDisplayWidth : mInitialDisplayHeight); } RoundedCorners calculateRoundedCornersForRotation(int rotation) { @@ -4723,12 +4709,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWmService.requestTraversal(); } + @Override boolean okToDisplay() { - return okToDisplay(false); - } - - boolean okToDisplay(boolean ignoreFrozen) { - return okToDisplay(ignoreFrozen, false /* ignoreScreenOn */); + return okToDisplay(false /* ignoreFrozen */, false /* ignoreScreenOn */); } boolean okToDisplay(boolean ignoreFrozen, boolean ignoreScreenOn) { @@ -4740,18 +4723,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return mDisplayInfo.state == Display.STATE_ON; } - boolean okToAnimate() { - return okToAnimate(false); - } - - boolean okToAnimate(boolean ignoreFrozen) { - return okToAnimate(ignoreFrozen, false /* ignoreScreenOn */); - } - + @Override boolean okToAnimate(boolean ignoreFrozen, boolean ignoreScreenOn) { return okToDisplay(ignoreFrozen, ignoreScreenOn) && (mDisplayId != DEFAULT_DISPLAY - || mWmService.mPolicy.okToAnimate(ignoreScreenOn)); + || mWmService.mPolicy.okToAnimate(ignoreScreenOn)) + && getDisplayPolicy().isScreenOnFully(); } static final class TaskForResizePointSearchResult { @@ -4884,7 +4861,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // WindowState#applyImeWindowsIfNeeded} in case of any state mismatch. return dc.mImeLayeringTarget != null && (!dc.getDefaultTaskDisplayArea().isSplitScreenModeActivated() - || dc.mImeLayeringTarget.getTask() == null); + || dc.mImeLayeringTarget.getTask() == null) + // Make sure that the IME window won't be skipped to report that it has + // completed the orientation change. + && !dc.mWmService.mDisplayFrozen; } /** Like {@link #forAllWindows}, but ignores {@link #skipImeWindowsDuringTraversal} */ @@ -5623,12 +5603,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp void onDisplayChanged() { mDisplay.getRealSize(mTmpDisplaySize); setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y); + final int lastDisplayState = mDisplayInfo.state; updateDisplayInfo(); // The window policy is responsible for stopping activities on the default display. final int displayId = mDisplay.getDisplayId(); + final int displayState = mDisplayInfo.state; if (displayId != DEFAULT_DISPLAY) { - final int displayState = mDisplay.getState(); if (displayState == Display.STATE_OFF) { mOffTokenAcquirer.acquire(mDisplayId); } else if (displayState == Display.STATE_ON) { @@ -5637,12 +5618,18 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp ProtoLog.v(WM_DEBUG_LAYER_MIRRORING, "Display %d state is now (%d), so update layer mirroring?", mDisplayId, displayState); - if (mLastDisplayState != displayState) { + if (lastDisplayState != displayState) { // If state is on due to surface being added, then start layer mirroring. // If state is off due to surface being removed, then stop layer mirroring. updateMirroring(); } - mLastDisplayState = displayState; + } + // Dispatch pending Configuration to WindowContext if the associated display changes to + // un-suspended state from suspended. + if (isSuspendedState(lastDisplayState) + && !isSuspendedState(displayState) && displayState != STATE_UNKNOWN) { + mWmService.mWindowContextListenerController + .dispatchPendingConfigurationIfNeeded(mDisplayId); } mWmService.requestTraversal(); } diff --git a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java index 11a27c593d9e..ef8dee401b05 100644 --- a/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java +++ b/services/core/java/com/android/server/wm/PossibleDisplayInfoMapper.java @@ -120,15 +120,8 @@ public class PossibleDisplayInfoMapper { @Surface.Rotation int rotation) { DisplayInfo updatedDisplayInfo = new DisplayInfo(); updatedDisplayInfo.copyFrom(displayInfo); - // Apply rotations before updating width and height - updatedDisplayInfo.roundedCorners = updatedDisplayInfo.roundedCorners.rotate(rotation, - updatedDisplayInfo.logicalWidth, updatedDisplayInfo.logicalHeight); - updatedDisplayInfo.displayCutout = - DisplayContent.calculateDisplayCutoutForRotationAndDisplaySizeUncached( - updatedDisplayInfo.displayCutout, rotation, updatedDisplayInfo.logicalWidth, - updatedDisplayInfo.logicalHeight).getDisplayCutout(); - updatedDisplayInfo.rotation = rotation; + final int naturalWidth = updatedDisplayInfo.getNaturalWidth(); final int naturalHeight = updatedDisplayInfo.getNaturalHeight(); updatedDisplayInfo.logicalWidth = naturalWidth; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 1b7a012094f6..d2eea76a29fc 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2163,10 +2163,7 @@ class Task extends TaskFragment { } private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) { - if (mWmService.mDisableTransitionAnimation - || !isVisible() - || getSurfaceControl() == null - || !isLeafTask()) { + if (!isLeafTask() || !canStartChangeTransition()) { return false; } // Only do an animation into and out-of freeform mode for now. Other mode diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 51cede0c051d..f32ab1e53318 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -2125,13 +2125,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { /** Whether we should prepare a transition for this {@link TaskFragment} bounds change. */ private boolean shouldStartChangeTransition(Rect startBounds) { - if (mWmService.mDisableTransitionAnimation - || mDisplayContent == null - || mTaskFragmentOrganizer == null - || getSurfaceControl() == null - // The change transition will be covered by display. - || mDisplayContent.inTransition() - || !isVisible()) { + if (mTaskFragmentOrganizer == null || !canStartChangeTransition()) { return false; } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 58bc244e9250..5af9147cd56f 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2600,6 +2600,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< mSurfaceFreezer.unfreeze(getPendingTransaction()); } + /** Whether we can start change transition with this window and current display status. */ + boolean canStartChangeTransition() { + return !mWmService.mDisableTransitionAnimation && mDisplayContent != null + && getSurfaceControl() != null && !mDisplayContent.inTransition() + && isVisible() && isVisibleRequested() && okToAnimate(); + } + /** * Initializes a change transition. See {@link SurfaceFreezer} for more information. * @@ -2948,12 +2955,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } boolean okToAnimate() { - return okToAnimate(false /* ignoreFrozen */); - } - - boolean okToAnimate(boolean ignoreFrozen) { - final DisplayContent dc = getDisplayContent(); - return dc != null && dc.okToAnimate(ignoreFrozen); + return okToAnimate(false /* ignoreFrozen */, false /* ignoreScreenOn */); } boolean okToAnimate(boolean ignoreFrozen, boolean ignoreScreenOn) { diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java index 86e356a876b5..cc527136eb51 100644 --- a/services/core/java/com/android/server/wm/WindowContextListenerController.java +++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java @@ -17,7 +17,9 @@ package com.android.server.wm; import static android.view.Display.INVALID_DISPLAY; +import static android.view.Display.isSuspendedState; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; +import static android.window.WindowProviderService.isWindowProviderService; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR; @@ -80,7 +82,7 @@ class WindowContextListenerController { * @param options a bundle used to pass window-related options. */ void registerWindowContainerListener(@NonNull IBinder clientToken, - @NonNull WindowContainer container, int ownerUid, @WindowType int type, + @NonNull WindowContainer<?> container, int ownerUid, @WindowType int type, @Nullable Bundle options) { WindowContextListenerImpl listener = mListeners.get(clientToken); if (listener == null) { @@ -103,6 +105,16 @@ class WindowContextListenerController { listener.unregister(); } + void dispatchPendingConfigurationIfNeeded(int displayId) { + for (int i = mListeners.size() - 1; i >= 0; --i) { + final WindowContextListenerImpl listener = mListeners.valueAt(i); + if (listener.getWindowContainer().getDisplayContent().getDisplayId() == displayId + && listener.mHasPendingConfiguration) { + listener.reportConfigToWindowTokenClient(); + } + } + } + /** * Verifies if the caller is allowed to do the operation to the listener specified by * {@code clientToken}. @@ -138,7 +150,7 @@ class WindowContextListenerController { return listener != null ? listener.mOptions : null; } - @Nullable WindowContainer getContainer(IBinder clientToken) { + @Nullable WindowContainer<?> getContainer(IBinder clientToken) { final WindowContextListenerImpl listener = mListeners.get(clientToken); return listener != null ? listener.mContainer : null; } @@ -163,7 +175,7 @@ class WindowContextListenerController { class WindowContextListenerImpl implements WindowContainerListener { @NonNull private final IBinder mClientToken; private final int mOwnerUid; - @NonNull private WindowContainer mContainer; + @NonNull private WindowContainer<?> mContainer; /** * The options from {@link Context#createWindowContext(int, Bundle)}. * <p>It can be used for choosing the {@link DisplayArea} where the window context @@ -177,7 +189,9 @@ class WindowContextListenerController { private int mLastReportedDisplay = INVALID_DISPLAY; private Configuration mLastReportedConfig; - private WindowContextListenerImpl(IBinder clientToken, WindowContainer container, + private boolean mHasPendingConfiguration; + + private WindowContextListenerImpl(IBinder clientToken, WindowContainer<?> container, int ownerUid, @WindowType int type, @Nullable Bundle options) { mClientToken = clientToken; mContainer = Objects.requireNonNull(container); @@ -197,11 +211,11 @@ class WindowContextListenerController { /** TEST ONLY: returns the {@link WindowContainer} of the listener */ @VisibleForTesting - WindowContainer getWindowContainer() { + WindowContainer<?> getWindowContainer() { return mContainer; } - private void updateContainer(@NonNull WindowContainer newContainer) { + private void updateContainer(@NonNull WindowContainer<?> newContainer) { Objects.requireNonNull(newContainer); if (mContainer.equals(newContainer)) { @@ -246,12 +260,20 @@ class WindowContextListenerController { if (mDeathRecipient == null) { throw new IllegalStateException("Invalid client token: " + mClientToken); } - - if (mLastReportedConfig == null) { - mLastReportedConfig = new Configuration(); + // If the display of window context associated window container is suspended, don't + // report the configuration update. Note that we still dispatch the configuration update + // to WindowProviderService to make it compatible with Service#onConfigurationChanged. + // Service always receives #onConfigurationChanged callback regardless of display state. + if (!isWindowProviderService(mOptions) + && isSuspendedState(mContainer.getDisplayContent().getDisplayInfo().state)) { + mHasPendingConfiguration = true; + return; } final Configuration config = mContainer.getConfiguration(); final int displayId = mContainer.getDisplayContent().getDisplayId(); + if (mLastReportedConfig == null) { + mLastReportedConfig = new Configuration(); + } if (config.equals(mLastReportedConfig) && displayId == mLastReportedDisplay) { // No changes since last reported time. return; @@ -266,6 +288,7 @@ class WindowContextListenerController { } catch (RemoteException e) { ProtoLog.w(WM_ERROR, "Could not report config changes to the window token client."); } + mHasPendingConfiguration = false; } @Override @@ -283,7 +306,7 @@ class WindowContextListenerController { // If we cannot obtain the DisplayContent, the DisplayContent may also be removed. // We should proceed the removal process. if (dc != null) { - final DisplayArea da = dc.findAreaForToken(windowToken); + final DisplayArea<?> da = dc.findAreaForToken(windowToken); updateContainer(da); return; } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 9128732db366..44d2a7f593d0 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -5064,6 +5064,48 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP return false; } + private boolean shouldFinishAnimatingExit() { + // Exit animation might be applied soon. + if (inTransition()) { + ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "shouldWaitAnimatingExit: isTransition: %s", + this); + return false; + } + if (!mDisplayContent.okToAnimate()) { + return true; + } + // Exit animation is running. + if (isAnimating(TRANSITION | PARENTS, EXIT_ANIMATING_TYPES)) { + ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, "shouldWaitAnimatingExit: isAnimating: %s", + this); + return false; + } + // If the wallpaper is currently behind this app window, we need to change both of + // them inside of a transaction to avoid artifacts. + if (mDisplayContent.mWallpaperController.isWallpaperTarget(this)) { + ProtoLog.d(WM_DEBUG_APP_TRANSITIONS, + "shouldWaitAnimatingExit: isWallpaperTarget: %s", this); + return false; + } + return true; + } + + /** + * If this is window is stuck in the animatingExit status, resume clean up procedure blocked + * by the exit animation. + */ + void cleanupAnimatingExitWindow() { + // TODO(b/205335975): WindowManagerService#tryStartExitingAnimation starts an exit animation + // and set #mAnimationExit. After the exit animation finishes, #onExitAnimationDone shall + // be called, but there seems to be a case that #onExitAnimationDone is not triggered, so + // a windows stuck in the animatingExit status. + if (mAnimatingExit && shouldFinishAnimatingExit()) { + ProtoLog.w(WM_DEBUG_APP_TRANSITIONS, "Clear window stuck on animatingExit status: %s", + this); + onExitAnimationDone(); + } + } + void onExitAnimationDone() { if (DEBUG_ANIM) Slog.v(TAG, "onExitAnimationDone in " + this + ": exiting=" + mAnimatingExit + " remove=" + mRemoveOnExit diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java index e2e7f5dd7ba2..94dcdf92d9d4 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/LocationAttributionHelperTest.java @@ -58,72 +58,86 @@ public class LocationAttributionHelperTest { @Test public void testLocationMonitoring() { CallerIdentity caller1 = CallerIdentity.forTest(1, 1, "test1", null); - Object key1 = new Object(); - Object key2 = new Object(); CallerIdentity caller2 = CallerIdentity.forTest(2, 2, "test2", null); - Object key3 = new Object(); - Object key4 = new Object(); - - mHelper.reportLocationStart(caller1, "gps", key1); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller1); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1); - - mHelper.reportLocationStart(caller1, "gps", key2); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller1); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1); - - mHelper.reportLocationStart(caller2, "gps", key3); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller2); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2); - - mHelper.reportLocationStart(caller2, "gps", key4); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, caller2); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2); - - mHelper.reportLocationStop(caller1, "gps", key2); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller1); - mHelper.reportLocationStop(caller1, "gps", key1); - verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, caller1); - - mHelper.reportLocationStop(caller2, "gps", key3); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, caller2); - mHelper.reportLocationStop(caller2, "gps", key4); - verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, caller2); + + mHelper.reportLocationStart(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller1)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller1)); + + mHelper.reportLocationStart(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller1)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller1)); + + mHelper.reportLocationStart(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller2)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller2)); + + mHelper.reportLocationStart(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller2)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller2)); + + mHelper.reportLocationStop(caller1); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller1)); + mHelper.reportLocationStop(caller1); + verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, CallerIdentity.forAggregation(caller1)); + + mHelper.reportLocationStop(caller2); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_LOCATION, + CallerIdentity.forAggregation(caller2)); + mHelper.reportLocationStop(caller2); + verify(mAppOpsHelper).finishOp(OP_MONITOR_LOCATION, CallerIdentity.forAggregation(caller2)); } @Test public void testHighPowerLocationMonitoring() { CallerIdentity caller1 = CallerIdentity.forTest(1, 1, "test1", null); - Object key1 = new Object(); - Object key2 = new Object(); CallerIdentity caller2 = CallerIdentity.forTest(2, 2, "test2", null); - Object key3 = new Object(); - Object key4 = new Object(); - - mHelper.reportHighPowerLocationStart(caller1, "gps", key1); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller1); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); - - mHelper.reportHighPowerLocationStart(caller1, "gps", key2); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller1); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); - - mHelper.reportHighPowerLocationStart(caller2, "gps", key3); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller2); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); - - mHelper.reportHighPowerLocationStart(caller2, "gps", key4); - verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, caller2); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); - - mHelper.reportHighPowerLocationStop(caller1, "gps", key2); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); - mHelper.reportHighPowerLocationStop(caller1, "gps", key1); - verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller1); - - mHelper.reportHighPowerLocationStop(caller2, "gps", key3); - verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); - mHelper.reportHighPowerLocationStop(caller2, "gps", key4); - verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, caller2); + + mHelper.reportHighPowerLocationStart(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller1)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller1)); + + mHelper.reportHighPowerLocationStart(caller1); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller1)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller1)); + + mHelper.reportHighPowerLocationStart(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller2)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller2)); + + mHelper.reportHighPowerLocationStart(caller2); + verify(mAppOpsHelper).startOpNoThrow(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller2)); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller2)); + + mHelper.reportHighPowerLocationStop(caller1); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller1)); + mHelper.reportHighPowerLocationStop(caller1); + verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller1)); + + mHelper.reportHighPowerLocationStop(caller2); + verify(mAppOpsHelper, never()).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller2)); + mHelper.reportHighPowerLocationStop(caller2); + verify(mAppOpsHelper).finishOp(OP_MONITOR_HIGH_POWER_LOCATION, + CallerIdentity.forAggregation(caller2)); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java index d0b2edadc714..890a5495ef16 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java @@ -845,6 +845,48 @@ public class LocationProviderManagerTest { } @Test + public void testLocationMonitoring_multipleIdentities() { + CallerIdentity identity1 = CallerIdentity.forTest(CURRENT_USER, 1, + "mypackage", "attribution", "listener1"); + CallerIdentity identity2 = CallerIdentity.forTest(CURRENT_USER, 1, + "mypackage", "attribution", "listener2"); + + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION, + IDENTITY.getPackageName())).isFalse(); + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION, + IDENTITY.getPackageName())).isFalse(); + + ILocationListener listener1 = createMockLocationListener(); + LocationRequest request1 = new LocationRequest.Builder(0).setWorkSource( + WORK_SOURCE).build(); + mManager.registerLocationRequest(request1, identity1, PERMISSION_FINE, listener1); + + ILocationListener listener2 = createMockLocationListener(); + LocationRequest request2 = new LocationRequest.Builder(0).setWorkSource( + WORK_SOURCE).build(); + mManager.registerLocationRequest(request2, identity2, PERMISSION_FINE, listener2); + + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION, + "mypackage")).isTrue(); + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION, + "mypackage")).isTrue(); + + mManager.unregisterLocationRequest(listener2); + + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION, + "mypackage")).isTrue(); + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION, + "mypackage")).isTrue(); + + mManager.unregisterLocationRequest(listener1); + + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_LOCATION, + "mypackage")).isFalse(); + assertThat(mInjector.getAppOpsHelper().isAppOpStarted(OP_MONITOR_HIGH_POWER_LOCATION, + "mypackage")).isFalse(); + } + + @Test public void testProviderRequest() { assertThat(mProvider.getRequest().isActive()).isFalse(); diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java index e3e3900c47e0..d192697827f6 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java @@ -143,8 +143,8 @@ public class BiometricSchedulerTest { final ClientMonitorCallbackConverter listener1 = mock(ClientMonitorCallbackConverter.class); - final BiometricPromptClientMonitor client1 = - new BiometricPromptClientMonitor(mContext, mToken, lazyDaemon1, listener1); + final TestAuthenticationClient client1 = + new TestAuthenticationClient(mContext, lazyDaemon1, mToken, listener1); final TestClientMonitor client2 = new TestClientMonitor(mContext, mToken, lazyDaemon2); final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class); @@ -180,8 +180,8 @@ public class BiometricSchedulerTest { @Test public void testCancelNotInvoked_whenOperationWaitingForCookie() { final HalClientMonitor.LazyDaemon<Object> lazyDaemon1 = () -> mock(Object.class); - final BiometricPromptClientMonitor client1 = new BiometricPromptClientMonitor(mContext, - mToken, lazyDaemon1, mock(ClientMonitorCallbackConverter.class)); + final TestAuthenticationClient client1 = new TestAuthenticationClient(mContext, + lazyDaemon1, mToken, mock(ClientMonitorCallbackConverter.class)); final BaseClientMonitor.Callback callback1 = mock(BaseClientMonitor.Callback.class); // Schedule a BiometricPrompt authentication request @@ -195,6 +195,8 @@ public class BiometricSchedulerTest { // should go back to idle, since in this case the framework has not even requested the HAL // to authenticate yet. mScheduler.cancelAuthenticationOrDetection(mToken, 1 /* requestId */); + assertTrue(client1.isAlreadyDone()); + assertTrue(client1.mDestroyed); assertNull(mScheduler.mCurrentOperation); } @@ -316,6 +318,10 @@ public class BiometricSchedulerTest { eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED), eq(0) /* vendorCode */); assertNull(mScheduler.getCurrentClient()); + assertTrue(client1.isAlreadyDone()); + assertTrue(client1.mDestroyed); + assertTrue(client2.isAlreadyDone()); + assertTrue(client2.mDestroyed); } @Test @@ -465,39 +471,9 @@ public class BiometricSchedulerTest { return BiometricSchedulerProto.parseFrom(mScheduler.dumpProtoState(clearSchedulerBuffer)); } - private static class BiometricPromptClientMonitor extends AuthenticationClient<Object> { - - public BiometricPromptClientMonitor(@NonNull Context context, @NonNull IBinder token, - @NonNull LazyDaemon<Object> lazyDaemon, ClientMonitorCallbackConverter listener) { - super(context, lazyDaemon, token, listener, 0 /* targetUserId */, 0 /* operationId */, - false /* restricted */, TAG, 1 /* cookie */, false /* requireConfirmation */, - TEST_SENSOR_ID, true /* isStrongBiometric */, 0 /* statsModality */, - 0 /* statsClient */, null /* taskStackListener */, mock(LockoutTracker.class), - false /* isKeyguard */, true /* shouldVibrate */, - false /* isKeyguardBypassEnabled */); - } - - @Override - protected void stopHalOperation() { - } - - @Override - protected void startHalOperation() { - } - - @Override - protected void handleLifecycleAfterAuth(boolean authenticated) { - - } - - @Override - public boolean wasUserDetected() { - return false; - } - } - private static class TestAuthenticationClient extends AuthenticationClient<Object> { int mNumCancels = 0; + boolean mDestroyed = false; public TestAuthenticationClient(@NonNull Context context, @NonNull LazyDaemon<Object> lazyDaemon, @NonNull IBinder token, @@ -530,6 +506,13 @@ public class BiometricSchedulerTest { return false; } + @Override + public void destroy() { + mDestroyed = true; + super.destroy(); + } + + @Override public void cancel() { mNumCancels++; super.cancel(); @@ -595,6 +578,7 @@ public class BiometricSchedulerTest { @Override public void destroy() { + super.destroy(); mDestroyed = true; } 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 fc2c162d058b..707e463424ae 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -1779,11 +1779,6 @@ public class ActivityRecordTests extends WindowTestsBase { anyInt() /* orientation */, anyInt() /* lastRotation */); // Set to visible so the activity can freeze the screen. activity.setVisibility(true); - // Update the display policy to make the screen fully turned on so the freeze is allowed - display.getDisplayPolicy().screenTurnedOn(null); - display.getDisplayPolicy().finishKeyguardDrawn(); - display.getDisplayPolicy().finishWindowsDrawn(); - display.getDisplayPolicy().finishScreenTurningOn(); display.rotateInDifferentOrientationIfNeeded(activity); display.setFixedRotationLaunchingAppUnchecked(activity); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java index 6fa306b004a2..506270657e42 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java @@ -334,6 +334,18 @@ public class AppTransitionControllerTest extends WindowTestsBase { } @Test + public void testExitAnimationDone_beforeAppTransition() { + final Task task = createTask(mDisplayContent); + final WindowState win = createAppWindow(task, ACTIVITY_TYPE_STANDARD, "Win"); + spyOn(win); + win.mAnimatingExit = true; + mDisplayContent.mAppTransition.setTimeout(); + mDisplayContent.mAppTransitionController.handleAppTransitionReady(); + + verify(win).onExitAnimationDone(); + } + + @Test public void testGetAnimationTargets_windowsAreBeingReplaced() { // [DisplayContent] -+- [Task1] - [ActivityRecord1] (opening, visible) // +- [AppWindow1] (being-replaced) diff --git a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java index d7daa57cc9da..407f9cfdbe3e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InputMethodMenuControllerTest.java @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.STATE_ON; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; @@ -70,7 +71,7 @@ public class InputMethodMenuControllerTest extends WindowTestsBase { @Before public void setUp() throws Exception { - // Let the Display to be created with the DualDisplay policy. + // Let the Display be created with the DualDisplay policy. final DisplayAreaPolicy.Provider policyProvider = new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider(); Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider(); @@ -78,6 +79,7 @@ public class InputMethodMenuControllerTest extends WindowTestsBase { mController = new InputMethodMenuController(mock(InputMethodManagerService.class)); mSecondaryDisplay = new DualDisplayAreaGroupPolicyTest.DualDisplayContent .Builder(mAtm, 1000, 1000).build(); + mSecondaryDisplay.getDisplayInfo().state = STATE_ON; // Mock addWindowTokenWithOptions to create a test window token. mIWindowManager = WindowManagerGlobal.getWindowManagerService(); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java index 6737b1ade785..730275cde40b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java @@ -16,11 +16,14 @@ package com.android.server.wm; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.mockito.Mockito.clearInvocations; import android.graphics.Rect; @@ -91,6 +94,7 @@ public class TaskFragmentTest extends WindowTestsBase { final Rect endBounds = new Rect(500, 500, 1000, 1000); mTaskFragment.setBounds(startBounds); doReturn(true).when(mTaskFragment).isVisible(); + doReturn(true).when(mTaskFragment).isVisibleRequested(); clearInvocations(mTransaction); mTaskFragment.setBounds(endBounds); @@ -108,6 +112,25 @@ public class TaskFragmentTest extends WindowTestsBase { verify(mTransaction).setWindowCrop(mLeash, 500, 500); } + @Test + public void testNotOkToAnimate_doNotStartChangeTransition() { + mockSurfaceFreezerSnapshot(mTaskFragment.mSurfaceFreezer); + final Rect startBounds = new Rect(0, 0, 1000, 1000); + final Rect endBounds = new Rect(500, 500, 1000, 1000); + mTaskFragment.setBounds(startBounds); + doReturn(true).when(mTaskFragment).isVisible(); + doReturn(true).when(mTaskFragment).isVisibleRequested(); + + final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy(); + displayPolicy.screenTurnedOff(); + + assertFalse(mTaskFragment.okToAnimate()); + + mTaskFragment.setBounds(endBounds); + + verify(mTaskFragment, never()).initializeChangeTransition(any()); + } + /** * Tests that when a {@link TaskFragmentInfo} is generated from a {@link TaskFragment}, an * activity that has not yet been attached to a process because it is being initialized but diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java index 0e504d326280..e0f69d4f2eaf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java @@ -165,6 +165,11 @@ class TestDisplayContent extends DisplayContent { doReturn(false).when(displayPolicy).hasStatusBar(); doReturn(false).when(newDisplay).supportsSystemDecorations(); } + // Update the display policy to make the screen fully turned on so animation is allowed + displayPolicy.screenTurnedOn(null /* screenOnListener */); + displayPolicy.finishKeyguardDrawn(); + displayPolicy.finishWindowsDrawn(); + displayPolicy.finishScreenTurningOn(); if (mStatusBarHeight > 0) { doReturn(true).when(displayPolicy).hasStatusBar(); doAnswer(invocation -> { diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java index e5eba57f223d..646647fcc4ca 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java @@ -17,22 +17,35 @@ package com.android.server.wm; import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.STATE_OFF; +import static android.view.Display.STATE_ON; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; +import static android.window.WindowProvider.KEY_IS_WINDOW_PROVIDER_SERVICE; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import android.app.IWindowToken; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; +import android.os.Bundle; import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.view.Display; +import android.view.DisplayInfo; import androidx.test.filters.SmallTest; @@ -55,12 +68,15 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { private static final int ANOTHER_UID = 1000; private final IBinder mClientToken = new Binder(); - private WindowContainer mContainer; + private WindowContainer<?> mContainer; @Before public void setUp() { mController = new WindowContextListenerController(); mContainer = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDisplayContent); + // Make display on to verify configuration propagation. + mDefaultDisplay.getDisplayInfo().state = STATE_ON; + mDisplayContent.getDisplayInfo().state = STATE_ON; } @Test @@ -76,7 +92,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { assertEquals(2, mController.mListeners.size()); - final WindowContainer container = createTestWindowToken(TYPE_APPLICATION_OVERLAY, + final WindowContainer<?> container = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDefaultDisplay); mController.registerWindowContainerListener(mClientToken, container, -1, TYPE_APPLICATION_OVERLAY, null /* options */); @@ -89,6 +105,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { assertEquals(container, listener.getWindowContainer()); } + @UseTestDisplay @Test public void testRegisterWindowContextListenerClientConfigPropagation() { final TestWindowTokenClient clientToken = new TestWindowTokenClient(); @@ -107,7 +124,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { assertEquals(mDisplayContent.mDisplayId, clientToken.mDisplayId); // Update the WindowContainer. - final WindowContainer container = createTestWindowToken(TYPE_APPLICATION_OVERLAY, + final WindowContainer<?> container = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDefaultDisplay); final Configuration config2 = container.getConfiguration(); final Rect bounds2 = new Rect(0, 0, 20, 20); @@ -174,7 +191,7 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { .setDisplayContent(mDefaultDisplay) .setFromClientToken(true) .build(); - final DisplayArea da = windowContextCreatedToken.getDisplayArea(); + final DisplayArea<?> da = windowContextCreatedToken.getDisplayArea(); mController.registerWindowContainerListener(mClientToken, windowContextCreatedToken, TEST_UID, TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, null /* options */); @@ -192,11 +209,12 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { // Let the Display to be created with the DualDisplay policy. final DisplayAreaPolicy.Provider policyProvider = new DualDisplayAreaGroupPolicyTest.DualDisplayTestPolicyProvider(); - Mockito.doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider(); + doReturn(policyProvider).when(mWm).getDisplayAreaPolicyProvider(); // Create a DisplayContent with dual RootDisplayArea DualDisplayAreaGroupPolicyTest.DualDisplayContent dualDisplayContent = new DualDisplayAreaGroupPolicyTest.DualDisplayContent .Builder(mAtm, 1000, 1000).build(); + dualDisplayContent.getDisplayInfo().state = STATE_ON; final DisplayArea.Tokens imeContainer = dualDisplayContent.getImeContainer(); // Put the ImeContainer to the first sub-RootDisplayArea dualDisplayContent.mFirstRoot.placeImeContainer(imeContainer); @@ -222,7 +240,62 @@ public class WindowContextListenerControllerTests extends WindowTestsBase { assertThat(mController.getContainer(mClientToken)).isEqualTo(imeContainer); } - private class TestWindowTokenClient extends IWindowToken.Stub { + @Test + public void testConfigUpdateForSuspendedWindowContext() { + final TestWindowTokenClient mockToken = new TestWindowTokenClient(); + spyOn(mockToken); + + mContainer.getDisplayContent().getDisplayInfo().state = STATE_OFF; + + final Configuration config1 = mContainer.getConfiguration(); + final Rect bounds1 = new Rect(0, 0, 10, 10); + config1.windowConfiguration.setBounds(bounds1); + config1.densityDpi = 100; + mContainer.onRequestedOverrideConfigurationChanged(config1); + + mController.registerWindowContainerListener(mockToken, mContainer, -1, + TYPE_APPLICATION_OVERLAY, null /* options */); + + verify(mockToken, never()).onConfigurationChanged(any(), anyInt()); + + // Turn on the display and verify if the client receive the callback + Display display = mContainer.getDisplayContent().getDisplay(); + spyOn(display); + Mockito.doAnswer(invocation -> { + final DisplayInfo info = mContainer.getDisplayContent().getDisplayInfo(); + info.state = STATE_ON; + ((DisplayInfo) invocation.getArgument(0)).copyFrom(info); + return null; + }).when(display).getDisplayInfo(any(DisplayInfo.class)); + + mContainer.getDisplayContent().onDisplayChanged(); + + assertThat(mockToken.mConfiguration).isEqualTo(config1); + assertThat(mockToken.mDisplayId).isEqualTo(mContainer.getDisplayContent().getDisplayId()); + } + + @Test + public void testReportConfigUpdateForSuspendedWindowProviderService() { + final TestWindowTokenClient clientToken = new TestWindowTokenClient(); + final Bundle options = new Bundle(); + options.putBoolean(KEY_IS_WINDOW_PROVIDER_SERVICE, true); + + mContainer.getDisplayContent().getDisplayInfo().state = STATE_OFF; + + final Configuration config1 = mContainer.getConfiguration(); + final Rect bounds1 = new Rect(0, 0, 10, 10); + config1.windowConfiguration.setBounds(bounds1); + config1.densityDpi = 100; + mContainer.onRequestedOverrideConfigurationChanged(config1); + + mController.registerWindowContainerListener(clientToken, mContainer, -1, + TYPE_APPLICATION_OVERLAY, options); + + assertThat(clientToken.mConfiguration).isEqualTo(config1); + assertThat(clientToken.mDisplayId).isEqualTo(mDisplayContent.mDisplayId); + } + + private static class TestWindowTokenClient extends IWindowToken.Stub { private Configuration mConfiguration; private int mDisplayId; private boolean mRemoved; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index 88d8ba308b45..b997acfaadcf 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -200,6 +200,13 @@ class WindowTestsBase extends SystemServiceTestsBase { SystemServicesTestRule.checkHoldsLock(mWm.mGlobalLock); mDefaultDisplay = mWm.mRoot.getDefaultDisplay(); + // Update the display policy to make the screen fully turned on so animation is allowed + final DisplayPolicy displayPolicy = mDefaultDisplay.getDisplayPolicy(); + displayPolicy.screenTurnedOn(null /* screenOnListener */); + displayPolicy.finishKeyguardDrawn(); + displayPolicy.finishWindowsDrawn(); + displayPolicy.finishScreenTurningOn(); + mTransaction = mSystemServicesTestRule.mTransaction; mMockSession = mock(Session.class); |