diff options
124 files changed, 3342 insertions, 964 deletions
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index f6d27ad08b00..37a90de8d600 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -317,7 +317,10 @@ public class NotificationManager { /** * Intent that is broadcast when the state of {@link #getEffectsSuppressor()} changes. - * This broadcast is only sent to registered receivers. + * + * <p>This broadcast is only sent to registered receivers and (starting from + * {@link Build.VERSION_CODES#Q}) receivers in packages that have been granted Do Not + * Disturb access (see {@link #isNotificationPolicyAccessGranted()}). * * @hide */ @@ -337,7 +340,10 @@ public class NotificationManager { /** * Intent that is broadcast when the state of getNotificationPolicy() changes. - * This broadcast is only sent to registered receivers. + * + * <p>This broadcast is only sent to registered receivers and (starting from + * {@link Build.VERSION_CODES#Q}) receivers in packages that have been granted Do Not + * Disturb access (see {@link #isNotificationPolicyAccessGranted()}). */ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_NOTIFICATION_POLICY_CHANGED @@ -345,7 +351,10 @@ public class NotificationManager { /** * Intent that is broadcast when the state of getCurrentInterruptionFilter() changes. - * This broadcast is only sent to registered receivers. + * + * <p>This broadcast is only sent to registered receivers and (starting from + * {@link Build.VERSION_CODES#Q}) receivers in packages that have been granted Do Not + * Disturb access (see {@link #isNotificationPolicyAccessGranted()}). */ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_INTERRUPTION_FILTER_CHANGED diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index e27af17ebc3d..d53ad17f07ef 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -61,6 +61,7 @@ import android.hardware.display.DisplayManager.DisplayListener; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; @@ -180,6 +181,9 @@ public abstract class WallpaperService extends Service { private final ArrayList<Engine> mActiveEngines = new ArrayList<Engine>(); + private Handler mBackgroundHandler; + private HandlerThread mBackgroundThread; + static final class WallpaperCommand { String action; int x; @@ -198,14 +202,6 @@ public abstract class WallpaperService extends Service { */ public class Engine { IWallpaperEngineWrapper mIWallpaperEngine; - final ArraySet<RectF> mLocalColorAreas = new ArraySet<>(4); - final ArraySet<RectF> mLocalColorsToAdd = new ArraySet<>(4); - - // 2D matrix [x][y] to represent a page of a portion of a window - EngineWindowPage[] mWindowPages = new EngineWindowPage[0]; - Bitmap mLastScreenshot; - int mLastWindowPage = -1; - private boolean mResetWindowPages; // Copies from mIWallpaperEngine. HandlerCaller mCaller; @@ -267,11 +263,27 @@ public abstract class WallpaperService extends Service { final Object mLock = new Object(); boolean mOffsetMessageEnqueued; + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) float mPendingXOffset; float mPendingYOffset; float mPendingXOffsetStep; float mPendingYOffsetStep; + + /** + * local color extraction related fields + * to be used by the background thread only (except the atomic boolean) + */ + final ArraySet<RectF> mLocalColorAreas = new ArraySet<>(4); + final ArraySet<RectF> mLocalColorsToAdd = new ArraySet<>(4); + private long mLastProcessLocalColorsTimestamp; + private AtomicBoolean mProcessLocalColorsPending = new AtomicBoolean(false); + private int mPixelCopyCount = 0; + // 2D matrix [x][y] to represent a page of a portion of a window + EngineWindowPage[] mWindowPages = new EngineWindowPage[0]; + Bitmap mLastScreenshot; + private boolean mResetWindowPages; + boolean mPendingSync; MotionEvent mPendingMove; boolean mIsInAmbientMode; @@ -280,12 +292,8 @@ public abstract class WallpaperService extends Service { private long mLastColorInvalidation; private final Runnable mNotifyColorsChanged = this::notifyColorsChanged; - // used to throttle processLocalColors - private long mLastProcessLocalColorsTimestamp; - private AtomicBoolean mProcessLocalColorsPending = new AtomicBoolean(false); private final Supplier<Long> mClockFunction; private final Handler mHandler; - private Display mDisplay; private Context mDisplayContext; private int mDisplayState; @@ -825,7 +833,7 @@ public abstract class WallpaperService extends Service { + "was not established."); } mResetWindowPages = true; - processLocalColors(mPendingXOffset, mPendingXOffsetStep); + processLocalColors(); } catch (RemoteException e) { Log.w(TAG, "Can't notify system because wallpaper connection was lost.", e); } @@ -1364,7 +1372,7 @@ public abstract class WallpaperService extends Service { resetWindowPages(); mSession.finishDrawing(mWindow, null /* postDrawTransaction */, Integer.MAX_VALUE); - processLocalColors(mPendingXOffset, mPendingXOffsetStep); + processLocalColors(); } reposition(); reportEngineShown(shouldWaitForEngineShown()); @@ -1509,7 +1517,7 @@ public abstract class WallpaperService extends Service { if (!mDestroyed) { mVisible = visible; reportVisibility(); - if (mReportedVisible) processLocalColors(mPendingXOffset, mPendingXOffsetStep); + if (mReportedVisible) processLocalColors(); } else { AnimationHandler.requestAnimatorsEnabled(visible, this); } @@ -1593,31 +1601,41 @@ public abstract class WallpaperService extends Service { } // setup local color extraction data - processLocalColors(xOffset, xOffsetStep); + processLocalColors(); } /** * Thread-safe util to call {@link #processLocalColorsInternal} with a minimum interval of * {@link #PROCESS_LOCAL_COLORS_INTERVAL_MS} between two calls. */ - private void processLocalColors(float xOffset, float xOffsetStep) { + private void processLocalColors() { if (mProcessLocalColorsPending.compareAndSet(false, true)) { final long now = mClockFunction.get(); final long timeSinceLastColorProcess = now - mLastProcessLocalColorsTimestamp; final long timeToWait = Math.max(0, PROCESS_LOCAL_COLORS_INTERVAL_MS - timeSinceLastColorProcess); - mHandler.postDelayed(() -> { + mBackgroundHandler.postDelayed(() -> { mLastProcessLocalColorsTimestamp = now + timeToWait; mProcessLocalColorsPending.set(false); - processLocalColorsInternal(xOffset, xOffsetStep); + processLocalColorsInternal(); }, timeToWait); } } - private void processLocalColorsInternal(float xOffset, float xOffsetStep) { + private void processLocalColorsInternal() { // implemented by the wallpaper if (supportsLocalColorExtraction()) return; + assertBackgroundThread(); + float xOffset; + float xOffsetStep; + float wallpaperDimAmount; + synchronized (mLock) { + xOffset = mPendingXOffset; + xOffsetStep = mPendingXOffsetStep; + wallpaperDimAmount = mWallpaperDimAmount; + } + if (DEBUG) { Log.d(TAG, "processLocalColors " + xOffset + " of step " + xOffsetStep); @@ -1680,7 +1698,7 @@ public abstract class WallpaperService extends Service { xPage = mWindowPages.length - 1; } current = mWindowPages[xPage]; - updatePage(current, xPage, xPages, finalXOffsetStep); + updatePage(current, xPage, xPages, wallpaperDimAmount); Trace.endSection(); } @@ -1700,16 +1718,23 @@ public abstract class WallpaperService extends Service { } } + /** + * Must be called with the surface lock held. + * Must not be called if the surface is not valid. + * Will unlock the surface when done using it. + */ void updatePage(EngineWindowPage currentPage, int pageIndx, int numPages, - float xOffsetStep) { + float wallpaperDimAmount) { + + assertBackgroundThread(); + // in case the clock is zero, we start with negative time long current = SystemClock.elapsedRealtime() - DEFAULT_UPDATE_SCREENSHOT_DURATION; long lapsed = current - currentPage.getLastUpdateTime(); // Always update the page when the last update time is <= 0 // This is important especially when the device first boots - if (lapsed < DEFAULT_UPDATE_SCREENSHOT_DURATION) { - return; - } + if (lapsed < DEFAULT_UPDATE_SCREENSHOT_DURATION) return; + Surface surface = mSurfaceHolder.getSurface(); if (!surface.isValid()) return; boolean widthIsLarger = mSurfaceSize.x > mSurfaceSize.y; @@ -1725,33 +1750,42 @@ public abstract class WallpaperService extends Service { Bitmap screenShot = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); final Bitmap finalScreenShot = screenShot; - Trace.beginSection("WallpaperService#pixelCopy"); - PixelCopy.request(surface, screenShot, (res) -> { - Trace.endSection(); - if (DEBUG) Log.d(TAG, "result of pixel copy is " + res); - if (res != PixelCopy.SUCCESS) { - Bitmap lastBitmap = currentPage.getBitmap(); - // assign the last bitmap taken for now - currentPage.setBitmap(mLastScreenshot); - Bitmap lastScreenshot = mLastScreenshot; - if (lastScreenshot != null && !lastScreenshot.isRecycled() - && !Objects.equals(lastBitmap, lastScreenshot)) { - updatePageColors(currentPage, pageIndx, numPages, xOffsetStep); + final String pixelCopySectionName = "WallpaperService#pixelCopy"; + final int pixelCopyCount = mPixelCopyCount++; + Trace.beginAsyncSection(pixelCopySectionName, pixelCopyCount); + try { + PixelCopy.request(surface, screenShot, (res) -> { + Trace.endAsyncSection(pixelCopySectionName, pixelCopyCount); + if (DEBUG) Log.d(TAG, "result of pixel copy is " + res); + if (res != PixelCopy.SUCCESS) { + Bitmap lastBitmap = currentPage.getBitmap(); + // assign the last bitmap taken for now + currentPage.setBitmap(mLastScreenshot); + Bitmap lastScreenshot = mLastScreenshot; + if (lastScreenshot != null && !lastScreenshot.isRecycled() + && !Objects.equals(lastBitmap, lastScreenshot)) { + updatePageColors(currentPage, pageIndx, numPages, wallpaperDimAmount); + } + } else { + mLastScreenshot = finalScreenShot; + // going to hold this lock for a while + currentPage.setBitmap(finalScreenShot); + currentPage.setLastUpdateTime(current); + updatePageColors(currentPage, pageIndx, numPages, wallpaperDimAmount); } - } else { - mLastScreenshot = finalScreenShot; - // going to hold this lock for a while - currentPage.setBitmap(finalScreenShot); - currentPage.setLastUpdateTime(current); - updatePageColors(currentPage, pageIndx, numPages, xOffsetStep); - } - }, mHandler); - + }, mBackgroundHandler); + } catch (IllegalArgumentException e) { + // this can potentially happen if the surface is invalidated right between the + // surface.isValid() check and the PixelCopy operation. + // in this case, stop: we'll compute colors on the next processLocalColors call. + Log.i(TAG, "Cancelling processLocalColors: exception caught during PixelCopy"); + } } // locked by the passed page - private void updatePageColors(EngineWindowPage page, int pageIndx, int numPages, - float xOffsetStep) { + private void updatePageColors( + EngineWindowPage page, int pageIndx, int numPages, float wallpaperDimAmount) { if (page.getBitmap() == null) return; + assertBackgroundThread(); Trace.beginSection("WallpaperService#updatePageColors"); if (DEBUG) { Log.d(TAG, "updatePageColorsLocked for page " + pageIndx + " with areas " @@ -1773,7 +1807,7 @@ public abstract class WallpaperService extends Service { Log.e(TAG, "Error creating page local color bitmap", e); continue; } - WallpaperColors color = WallpaperColors.fromBitmap(target, mWallpaperDimAmount); + WallpaperColors color = WallpaperColors.fromBitmap(target, wallpaperDimAmount); target.recycle(); WallpaperColors currentColor = page.getColors(area); @@ -1790,17 +1824,26 @@ public abstract class WallpaperService extends Service { + " local color callback for area" + area + " for page " + pageIndx + " of " + numPages); } - try { - mConnection.onLocalWallpaperColorsChanged(area, color, - mDisplayContext.getDisplayId()); - } catch (RemoteException e) { - Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e); - } + mHandler.post(() -> { + try { + mConnection.onLocalWallpaperColorsChanged(area, color, + mDisplayContext.getDisplayId()); + } catch (RemoteException e) { + Log.e(TAG, "Error calling Connection.onLocalWallpaperColorsChanged", e); + } + }); } } Trace.endSection(); } + private void assertBackgroundThread() { + if (!mBackgroundHandler.getLooper().isCurrentThread()) { + throw new IllegalStateException( + "ProcessLocalColors should be called from the background thread"); + } + } + private RectF generateSubRect(RectF in, int pageInx, int numPages) { float minLeft = (float) (pageInx) / (float) (numPages); float maxRight = (float) (pageInx + 1) / (float) (numPages); @@ -1825,7 +1868,6 @@ public abstract class WallpaperService extends Service { if (supportsLocalColorExtraction()) return; if (!mResetWindowPages) return; mResetWindowPages = false; - mLastWindowPage = -1; for (int i = 0; i < mWindowPages.length; i++) { mWindowPages[i].setLastUpdateTime(0L); } @@ -1851,12 +1893,10 @@ public abstract class WallpaperService extends Service { if (DEBUG) { Log.d(TAG, "addLocalColorsAreas adding local color areas " + regions); } - mHandler.post(() -> { + mBackgroundHandler.post(() -> { mLocalColorsToAdd.addAll(regions); - processLocalColors(mPendingXOffset, mPendingYOffset); + processLocalColors(); }); - - } /** @@ -1866,7 +1906,7 @@ public abstract class WallpaperService extends Service { */ public void removeLocalColorsAreas(@NonNull List<RectF> regions) { if (supportsLocalColorExtraction()) return; - mHandler.post(() -> { + mBackgroundHandler.post(() -> { float step = mPendingXOffsetStep; mLocalColorsToAdd.removeAll(regions); mLocalColorAreas.removeAll(regions); @@ -2497,6 +2537,9 @@ public abstract class WallpaperService extends Service { @Override public void onCreate() { Trace.beginSection("WPMS.onCreate"); + mBackgroundThread = new HandlerThread("DefaultWallpaperLocalColorExtractor"); + mBackgroundThread.start(); + mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); super.onCreate(); Trace.endSection(); } @@ -2509,6 +2552,7 @@ public abstract class WallpaperService extends Service { mActiveEngines.get(i).detach(); } mActiveEngines.clear(); + mBackgroundThread.quitSafely(); Trace.endSection(); } diff --git a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java index d2b612a9e6f3..f1ed3bed5d89 100644 --- a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java +++ b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java @@ -56,6 +56,9 @@ public class GestureNavigationSettingsObserver extends ContentObserver { } }; + /** + * Registers the observer for all users. + */ public void register() { ContentResolver r = mContext.getContentResolver(); r.registerContentObserver( @@ -73,7 +76,10 @@ public class GestureNavigationSettingsObserver extends ContentObserver { mOnPropertiesChangedListener); } - public void registerForCurrentUser() { + /** + * Registers the observer for the calling user. + */ + public void registerForCallingUser() { ContentResolver r = mContext.getContentResolver(); r.registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT), @@ -103,12 +109,46 @@ public class GestureNavigationSettingsObserver extends ContentObserver { } } + /** + * Returns the left sensitivity for the current user. To be used in code that runs primarily + * in one user's process. + */ public int getLeftSensitivity(Resources userRes) { - return getSensitivity(userRes, Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT); + final float scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(), + Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT, 1.0f, UserHandle.USER_CURRENT); + return (int) (getUnscaledInset(userRes) * scale); } + /** + * Returns the left sensitivity for the calling user. To be used in code that runs in a + * per-user process. + */ + @SuppressWarnings("NonUserGetterCalled") + public int getLeftSensitivityForCallingUser(Resources userRes) { + final float scale = Settings.Secure.getFloat(mContext.getContentResolver(), + Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT, 1.0f); + return (int) (getUnscaledInset(userRes) * scale); + } + + /** + * Returns the right sensitivity for the current user. To be used in code that runs primarily + * in one user's process. + */ public int getRightSensitivity(Resources userRes) { - return getSensitivity(userRes, Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT); + final float scale = Settings.Secure.getFloatForUser(mContext.getContentResolver(), + Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT, 1.0f, UserHandle.USER_CURRENT); + return (int) (getUnscaledInset(userRes) * scale); + } + + /** + * Returns the right sensitivity for the calling user. To be used in code that runs in a + * per-user process. + */ + @SuppressWarnings("NonUserGetterCalled") + public int getRightSensitivityForCallingUser(Resources userRes) { + final float scale = Settings.Secure.getFloat(mContext.getContentResolver(), + Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT, 1.0f); + return (int) (getUnscaledInset(userRes) * scale); } public boolean areNavigationButtonForcedVisible() { @@ -116,7 +156,7 @@ public class GestureNavigationSettingsObserver extends ContentObserver { Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) == 0; } - private int getSensitivity(Resources userRes, String side) { + private float getUnscaledInset(Resources userRes) { final DisplayMetrics dm = userRes.getDisplayMetrics(); final float defaultInset = userRes.getDimension( com.android.internal.R.dimen.config_backGestureInset) / dm.density; @@ -127,8 +167,6 @@ public class GestureNavigationSettingsObserver extends ContentObserver { : defaultInset; final float inset = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, backGestureInset, dm); - final float scale = Settings.Secure.getFloatForUser( - mContext.getContentResolver(), side, 1.0f, UserHandle.USER_CURRENT); - return (int) (inset * scale); + return inset; } } diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 0f4521951e3d..c7c94246b96a 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -49,6 +49,7 @@ filegroup { "src/com/android/wm/shell/animation/Interpolators.java", "src/com/android/wm/shell/pip/PipContentOverlay.java", "src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java", + "src/com/android/wm/shell/draganddrop/DragAndDropConstants.java", ], path: "src", } diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background.xml deleted file mode 100644 index 0d8811357c05..000000000000 --- a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2022 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - <solid android:color="@color/letterbox_education_accent_primary"/> - <corners android:radius="12dp"/> -</shape>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml index 42572d64b96f..a2699681e656 100644 --- a/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml +++ b/libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml @@ -14,7 +14,30 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="@color/letterbox_education_dismiss_button_background_ripple"> - <item android:drawable="@drawable/letterbox_education_dismiss_button_background"/> -</ripple>
\ No newline at end of file +<inset xmlns:android="http://schemas.android.com/apk/res/android" + android:insetTop="@dimen/letterbox_education_dialog_vertical_inset" + android:insetBottom="@dimen/letterbox_education_dialog_vertical_inset"> + <ripple android:color="@color/letterbox_education_dismiss_button_background_ripple"> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <corners android:radius="@dimen/letterbox_education_dialog_button_radius"/> + <solid android:color="@android:color/white"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <solid android:color="@android:color/transparent"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <solid android:color="@color/letterbox_education_accent_primary"/> + <corners android:radius="@dimen/letterbox_education_dialog_button_radius"/> + <padding android:left="@dimen/letterbox_education_dialog_horizontal_padding" + android:top="@dimen/letterbox_education_dialog_vertical_padding" + android:right="@dimen/letterbox_education_dialog_horizontal_padding" + android:bottom="@dimen/letterbox_education_dialog_vertical_padding"/> + </shape> + </item> + </ripple> +</inset> diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background.xml deleted file mode 100644 index 60f3cfe6dde6..000000000000 --- a/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:shape="rectangle"> - <solid android:color="?androidprv:attr/colorAccentPrimaryVariant"/> - <corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/> -</shape>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml index ef97ea19e993..1f125148775d 100644 --- a/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml +++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml @@ -14,7 +14,31 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="@color/letterbox_restart_button_background_ripple"> - <item android:drawable="@drawable/letterbox_restart_button_background"/> -</ripple>
\ No newline at end of file +<inset xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:insetTop="@dimen/letterbox_restart_dialog_vertical_inset" + android:insetBottom="@dimen/letterbox_restart_dialog_vertical_inset"> + <ripple android:color="@color/letterbox_restart_dismiss_button_background_ripple"> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/> + <solid android:color="@android:color/white"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <solid android:color="@android:color/transparent"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <solid android:color="?androidprv:attr/colorAccentPrimaryVariant"/> + <corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/> + <padding android:left="@dimen/letterbox_restart_dialog_horizontal_padding" + android:top="@dimen/letterbox_restart_dialog_vertical_padding" + android:right="@dimen/letterbox_restart_dialog_horizontal_padding" + android:bottom="@dimen/letterbox_restart_dialog_vertical_padding"/> + </shape> + </item> + </ripple> +</inset>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background.xml deleted file mode 100644 index af89d41ee6b5..000000000000 --- a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2023 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:shape="rectangle"> - <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant" android:width="1dp"/> - <solid android:color="?androidprv:attr/colorSurface" /> - <corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/> -</shape>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml index e32aefca78ac..3aa0981e45aa 100644 --- a/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml +++ b/libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml @@ -14,7 +14,33 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<ripple xmlns:android="http://schemas.android.com/apk/res/android" - android:color="@color/letterbox_restart_dismiss_button_background_ripple"> - <item android:drawable="@drawable/letterbox_restart_dismiss_button_background"/> -</ripple>
\ No newline at end of file +<inset xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" + android:insetTop="@dimen/letterbox_restart_dialog_vertical_inset" + android:insetBottom="@dimen/letterbox_restart_dialog_vertical_inset"> + <ripple android:color="@color/letterbox_restart_dismiss_button_background_ripple"> + <item android:id="@android:id/mask"> + <shape android:shape="rectangle"> + <corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/> + <solid android:color="@android:color/white"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <solid android:color="@android:color/transparent"/> + </shape> + </item> + <item> + <shape android:shape="rectangle"> + <stroke android:color="?androidprv:attr/colorAccentPrimaryVariant" + android:width="1dp"/> + <solid android:color="?androidprv:attr/colorSurface"/> + <corners android:radius="@dimen/letterbox_restart_dialog_button_radius"/> + <padding android:left="@dimen/letterbox_restart_dialog_horizontal_padding" + android:top="@dimen/letterbox_restart_dialog_vertical_padding" + android:right="@dimen/letterbox_restart_dialog_horizontal_padding" + android:bottom="@dimen/letterbox_restart_dialog_vertical_padding"/> + </shape> + </item> + </ripple> +</inset>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 6f31d0674246..336c156e831a 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -273,6 +273,18 @@ <!-- The space between two actions in the letterbox education dialog --> <dimen name="letterbox_education_dialog_space_between_actions">24dp</dimen> + <!-- The corner radius of the buttons in the letterbox education dialog --> + <dimen name="letterbox_education_dialog_button_radius">12dp</dimen> + + <!-- The horizontal padding for the buttons in the letterbox education dialog --> + <dimen name="letterbox_education_dialog_horizontal_padding">16dp</dimen> + + <!-- The vertical padding for the buttons in the letterbox education dialog --> + <dimen name="letterbox_education_dialog_vertical_padding">8dp</dimen> + + <!-- The insets for the buttons in the letterbox education dialog --> + <dimen name="letterbox_education_dialog_vertical_inset">6dp</dimen> + <!-- The margin between the dialog container and its parent. --> <dimen name="letterbox_restart_dialog_margin">24dp</dimen> @@ -306,6 +318,15 @@ <!-- The corner radius of the buttons in the restart dialog --> <dimen name="letterbox_restart_dialog_button_radius">18dp</dimen> + <!-- The insets for the buttons in the letterbox restart dialog --> + <dimen name="letterbox_restart_dialog_vertical_inset">6dp</dimen> + + <!-- The horizontal padding for the buttons in the letterbox restart dialog --> + <dimen name="letterbox_restart_dialog_horizontal_padding">16dp</dimen> + + <!-- The vertical padding for the buttons in the letterbox restart dialog --> + <dimen name="letterbox_restart_dialog_vertical_padding">8dp</dimen> + <!-- The width of the brand image on staring surface. --> <dimen name="starting_surface_brand_image_width">200dp</dimen> @@ -331,30 +352,6 @@ --> <dimen name="overridable_minimal_size_pip_resizable_task">48dp</dimen> - <!-- The size of the drag handle / menu shown along with a floating task. --> - <dimen name="floating_task_menu_size">32dp</dimen> - - <!-- The size of menu items in the floating task menu. --> - <dimen name="floating_task_menu_item_size">24dp</dimen> - - <!-- The horizontal margin of menu items in the floating task menu. --> - <dimen name="floating_task_menu_item_padding">5dp</dimen> - - <!-- The width of visible floating view region when stashed. --> - <dimen name="floating_task_stash_offset">32dp</dimen> - - <!-- The amount of elevation for a floating task. --> - <dimen name="floating_task_elevation">8dp</dimen> - - <!-- The amount of padding around the bottom and top of the task. --> - <dimen name="floating_task_vertical_padding">8dp</dimen> - - <!-- The normal size of the dismiss target. --> - <dimen name="floating_task_dismiss_circle_size">150dp</dimen> - - <!-- The smaller size of the dismiss target (shrinks when something is in the target). --> - <dimen name="floating_dismiss_circle_small">120dp</dimen> - <!-- The thickness of shadows of a window that has focus in DIP. --> <dimen name="freeform_decor_shadow_focused_thickness">20dp</dimen> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java index 88525aabe53b..e2012b4e36dc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ProtoLogController.java @@ -16,6 +16,8 @@ package com.android.wm.shell; +import android.os.Build; + import com.android.wm.shell.protolog.ShellProtoLogImpl; import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellInit; @@ -41,6 +43,9 @@ public class ProtoLogController implements ShellCommandHandler.ShellCommandActio void onInit() { mShellCommandHandler.addCommandCallback("protolog", this, this); + if (Build.IS_DEBUGGABLE) { + mShellProtoLog.startProtoLog(null /* PrintWriter */); + } } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java new file mode 100644 index 000000000000..22587f4c6456 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.content.Context; +import android.hardware.devicestate.DeviceStateManager; +import android.util.SparseIntArray; + +import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.wm.shell.sysui.ShellInit; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +/** + * Wrapper class to track the device posture change on Fold-ables. + * See also <a + * href="https://developer.android.com/guide/topics/large-screens/learn-about-foldables + * #foldable_postures">Foldable states and postures</a> for reference. + * + * Note that most of the implementation here inherits from + * {@link com.android.systemui.statusbar.policy.DevicePostureController}. + */ +public class DevicePostureController { + @IntDef(prefix = {"DEVICE_POSTURE_"}, value = { + DEVICE_POSTURE_UNKNOWN, + DEVICE_POSTURE_CLOSED, + DEVICE_POSTURE_HALF_OPENED, + DEVICE_POSTURE_OPENED, + DEVICE_POSTURE_FLIPPED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DevicePostureInt {} + + // NOTE: These constants **must** match those defined for Jetpack Sidecar. This is because we + // use the Device State -> Jetpack Posture map to translate between the two. + public static final int DEVICE_POSTURE_UNKNOWN = 0; + public static final int DEVICE_POSTURE_CLOSED = 1; + public static final int DEVICE_POSTURE_HALF_OPENED = 2; + public static final int DEVICE_POSTURE_OPENED = 3; + public static final int DEVICE_POSTURE_FLIPPED = 4; + + private final Context mContext; + private final ShellExecutor mMainExecutor; + private final List<OnDevicePostureChangedListener> mListeners = new ArrayList<>(); + private final SparseIntArray mDeviceStateToPostureMap = new SparseIntArray(); + + private int mDevicePosture = DEVICE_POSTURE_UNKNOWN; + + public DevicePostureController( + Context context, ShellInit shellInit, ShellExecutor mainExecutor) { + mContext = context; + mMainExecutor = mainExecutor; + shellInit.addInitCallback(this::onInit, this); + } + + private void onInit() { + // Most of this is borrowed from WindowManager/Jetpack/DeviceStateManagerPostureProducer. + // Using the sidecar/extension libraries directly brings in a new dependency that it'd be + // good to avoid (along with the fact that sidecar is deprecated, and extensions isn't fully + // ready yet), and we'd have to make our own layer over the sidecar library anyway to easily + // allow the implementation to change, so it was easier to just interface with + // DeviceStateManager directly. + String[] deviceStatePosturePairs = mContext.getResources() + .getStringArray(R.array.config_device_state_postures); + for (String deviceStatePosturePair : deviceStatePosturePairs) { + String[] deviceStatePostureMapping = deviceStatePosturePair.split(":"); + if (deviceStatePostureMapping.length != 2) { + continue; + } + + int deviceState; + int posture; + try { + deviceState = Integer.parseInt(deviceStatePostureMapping[0]); + posture = Integer.parseInt(deviceStatePostureMapping[1]); + } catch (NumberFormatException e) { + continue; + } + + mDeviceStateToPostureMap.put(deviceState, posture); + } + + final DeviceStateManager deviceStateManager = mContext.getSystemService( + DeviceStateManager.class); + if (deviceStateManager != null) { + deviceStateManager.registerCallback(mMainExecutor, state -> onDevicePostureChanged( + mDeviceStateToPostureMap.get(state, DEVICE_POSTURE_UNKNOWN))); + } + } + + @VisibleForTesting + void onDevicePostureChanged(int devicePosture) { + if (devicePosture == mDevicePosture) return; + mDevicePosture = devicePosture; + mListeners.forEach(l -> l.onDevicePostureChanged(mDevicePosture)); + } + + /** + * Register {@link OnDevicePostureChangedListener} for device posture changes. + * The listener will receive callback with current device posture upon registration. + */ + public void registerOnDevicePostureChangedListener( + @NonNull OnDevicePostureChangedListener listener) { + if (mListeners.contains(listener)) return; + mListeners.add(listener); + listener.onDevicePostureChanged(mDevicePosture); + } + + /** + * Unregister {@link OnDevicePostureChangedListener} for device posture changes. + */ + public void unregisterOnDevicePostureChangedListener( + @NonNull OnDevicePostureChangedListener listener) { + mListeners.remove(listener); + } + + /** + * Listener interface for device posture change. + */ + public interface OnDevicePostureChangedListener { + /** + * Callback when device posture changes. + * See {@link DevicePostureInt} for callback values. + */ + void onDevicePostureChanged(@DevicePostureInt int posture); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 25c430c27457..72dc771ee08c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -41,6 +41,7 @@ import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.back.BackAnimationController; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.Bubbles; +import com.android.wm.shell.common.DevicePostureController; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.common.DisplayInsetsController; @@ -160,6 +161,16 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides + static DevicePostureController provideDevicePostureController( + Context context, + ShellInit shellInit, + @ShellMainThread ShellExecutor mainExecutor + ) { + return new DevicePostureController(context, shellInit, mainExecutor); + } + + @WMSingleton + @Provides static DragAndDropController provideDragAndDropController(Context context, ShellInit shellInit, ShellController shellController, 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 efc7d1ff0bb9..1239cdc5b606 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 @@ -558,16 +558,18 @@ public abstract class WMShellModule { static FullscreenUnfoldTaskAnimator provideFullscreenUnfoldTaskAnimator( Context context, UnfoldBackgroundController unfoldBackgroundController, + ShellController shellController, DisplayInsetsController displayInsetsController ) { return new FullscreenUnfoldTaskAnimator(context, unfoldBackgroundController, - displayInsetsController); + shellController, displayInsetsController); } @Provides static SplitTaskUnfoldAnimator provideSplitTaskUnfoldAnimatorBase( Context context, UnfoldBackgroundController backgroundController, + ShellController shellController, @ShellMainThread ShellExecutor executor, Lazy<Optional<SplitScreenController>> splitScreenOptional, DisplayInsetsController displayInsetsController @@ -577,7 +579,7 @@ public abstract class WMShellModule { // controller directly once we refactor ShellTaskOrganizer to not depend on the unfold // animation controller directly. return new SplitTaskUnfoldAnimator(context, executor, splitScreenOptional, - backgroundController, displayInsetsController); + shellController, backgroundController, displayInsetsController); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropConstants.java new file mode 100644 index 000000000000..20da54efd286 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropConstants.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.draganddrop; + +/** Constants that can be used by both Shell and other users of the library, e.g. Launcher */ +public class DragAndDropConstants { + + /** + * An Intent extra that Launcher can use to specify a region of the screen where Shell should + * ignore drag events. + */ + public static final String EXTRA_DISALLOW_HIT_REGION = "DISALLOW_HIT_REGION"; +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java index d93a9012c8f1..df94b414c092 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java @@ -34,6 +34,7 @@ import static android.content.Intent.EXTRA_USER; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.draganddrop.DragAndDropConstants.EXTRA_DISALLOW_HIT_REGION; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM; import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT; @@ -53,6 +54,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.LauncherApps; import android.graphics.Insets; import android.graphics.Rect; +import android.graphics.RectF; import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; @@ -86,6 +88,7 @@ public class DragAndDropPolicy { private final Starter mStarter; private final SplitScreenController mSplitScreen; private final ArrayList<DragAndDropPolicy.Target> mTargets = new ArrayList<>(); + private final RectF mDisallowHitRegion = new RectF(); private InstanceId mLoggerSessionId; private DragSession mSession; @@ -111,6 +114,12 @@ public class DragAndDropPolicy { mSession = new DragSession(mActivityTaskManager, displayLayout, data); // TODO(b/169894807): Also update the session data with task stack changes mSession.update(); + RectF disallowHitRegion = (RectF) mSession.dragData.getExtra(EXTRA_DISALLOW_HIT_REGION); + if (disallowHitRegion == null) { + mDisallowHitRegion.setEmpty(); + } else { + mDisallowHitRegion.set(disallowHitRegion); + } } /** @@ -218,6 +227,9 @@ public class DragAndDropPolicy { */ @Nullable Target getTargetAtLocation(int x, int y) { + if (mDisallowHitRegion.contains(x, y)) { + return null; + } for (int i = mTargets.size() - 1; i >= 0; i--) { DragAndDropPolicy.Target t = mTargets.get(i); if (t.hitRegion.contains(x, y)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java index 3ade1ed9392f..44fd8eec4d06 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java @@ -118,7 +118,7 @@ public class DragLayout extends LinearLayout { @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { - mInsets = insets.getInsets(Type.systemBars() | Type.displayCutout()); + mInsets = insets.getInsets(Type.tappableElement() | Type.displayCutout()); recomputeDropTargets(); final int orientation = getResources().getConfiguration().orientation; @@ -369,7 +369,9 @@ public class DragLayout extends LinearLayout { // Start animating the drop UI out with the drag surface hide(event, dropCompleteCallback); - hideDragSurface(dragSurface); + if (handledDrop) { + hideDragSurface(dragSurface); + } return handledDrop; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index d9ac76e833d8..23f73f614294 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -209,7 +209,7 @@ public class PipAnimationController { /** * Quietly cancel the animator by removing the listeners first. */ - static void quietCancel(@NonNull ValueAnimator animator) { + public static void quietCancel(@NonNull ValueAnimator animator) { animator.removeAllUpdateListeners(); animator.removeAllListeners(); animator.cancel(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index ec34f73126fb..fa3efeb51bd0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -715,6 +715,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb private void onDisplayChanged(DisplayLayout layout, boolean saveRestoreSnapFraction) { if (!mPipBoundsState.getDisplayLayout().isSameGeometry(layout)) { + PipAnimationController.PipTransitionAnimator animator = + mPipAnimationController.getCurrentAnimator(); + if (animator != null && animator.isRunning()) { + // cancel any running animator, as it is using stale display layout information + PipAnimationController.quietCancel(animator); + } onDisplayChangedUncheck(layout, saveRestoreSnapFraction); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java index 93ffb3dc8115..c59c42dadb9d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogImpl.java @@ -31,8 +31,7 @@ import java.io.PrintWriter; */ public class ShellProtoLogImpl extends BaseProtoLogImpl { private static final String TAG = "ProtoLogImpl"; - private static final int BUFFER_CAPACITY = 1024 * 1024; - // TODO: find a proper location to save the protolog message file + private static final int BUFFER_CAPACITY = 128 * 1024; private static final String LOG_FILENAME = "/data/misc/wmtrace/shell_log.winscope"; private static final String VIEWER_CONFIG_FILENAME = "/system_ext/etc/wmshell.protolog.json.gz"; 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 0c3eaf0b904f..746bfad56ea8 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 @@ -499,6 +499,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, break; } } + } else if (mSideStage.getChildCount() != 0) { + // There are chances the entering app transition got canceled by performing + // rotation transition. Checks if there is any child task existed in split + // screen before fallback to cancel entering flow. + openingToSide = true; } if (isEnteringSplit && !openingToSide) { @@ -515,7 +520,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - if (!isEnteringSplit && openingToSide) { + if (!isEnteringSplit && apps != null) { final WindowContainerTransaction evictWct = new WindowContainerTransaction(); prepareEvictNonOpeningChildTasks(position, apps, evictWct); mSyncQueue.queue(evictWct); @@ -598,6 +603,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, break; } } + } else if (mSideStage.getChildCount() != 0) { + // There are chances the entering app transition got canceled by performing + // rotation transition. Checks if there is any child task existed in split + // screen before fallback to cancel entering flow. + openingToSide = true; } if (isEnteringSplit && !openingToSide && apps != null) { @@ -624,7 +634,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } - if (!isEnteringSplit && openingToSide) { + if (!isEnteringSplit && apps != null) { final WindowContainerTransaction evictWct = new WindowContainerTransaction(); prepareEvictNonOpeningChildTasks(position, apps, evictWct); mSyncQueue.queue(evictWct); @@ -1685,9 +1695,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } mSyncQueue.queue(wct); - mSyncQueue.runInSync(t -> { - setDividerVisibility(mainStageVisible, t); - }); + setDividerVisibility(mainStageVisible, null); } private void setDividerVisibility(boolean visible, @Nullable SurfaceControl.Transaction t) { @@ -1769,6 +1777,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void onAnimationEnd(Animator animation) { + if (dividerLeash != null && dividerLeash.isValid()) { + transaction.setAlpha(dividerLeash, 1); + transaction.apply(); + } mTransactionPool.release(transaction); mDividerFadeInAnimator = null; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java index eab82f00e962..e0f3fcd932c2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java @@ -26,8 +26,10 @@ import android.animation.TypeEvaluator; import android.annotation.NonNull; import android.app.TaskInfo; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Matrix; import android.graphics.Rect; +import android.os.Trace; import android.util.SparseArray; import android.view.InsetsSource; import android.view.InsetsState; @@ -36,6 +38,8 @@ import android.view.SurfaceControl.Transaction; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.sysui.ConfigurationChangeListener; +import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldBackgroundController; @@ -51,7 +55,7 @@ import com.android.wm.shell.unfold.UnfoldBackgroundController; * instances of FullscreenUnfoldTaskAnimator. */ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator, - DisplayInsetsController.OnInsetsChangedListener { + DisplayInsetsController.OnInsetsChangedListener, ConfigurationChangeListener { private static final float[] FLOAT_9 = new float[9]; private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect()); @@ -63,17 +67,21 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator, private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>(); private final int mExpandedTaskBarHeight; - private final float mWindowCornerRadiusPx; private final DisplayInsetsController mDisplayInsetsController; private final UnfoldBackgroundController mBackgroundController; + private final Context mContext; + private final ShellController mShellController; private InsetsSource mTaskbarInsetsSource; + private float mWindowCornerRadiusPx; public FullscreenUnfoldTaskAnimator(Context context, @NonNull UnfoldBackgroundController backgroundController, - DisplayInsetsController displayInsetsController) { + ShellController shellController, DisplayInsetsController displayInsetsController) { + mContext = context; mDisplayInsetsController = displayInsetsController; mBackgroundController = backgroundController; + mShellController = shellController; mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.taskbar_frame_height); mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context); @@ -81,6 +89,14 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator, public void init() { mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this); + mShellController.addConfigurationChangeListener(this); + } + + @Override + public void onConfigurationChanged(Configuration newConfiguration) { + Trace.beginSection("FullscreenUnfoldTaskAnimator#onConfigurationChanged"); + mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(mContext); + Trace.endSection(); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java index 6e10ebe94c5d..addd0a6012c4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java @@ -28,8 +28,10 @@ import android.animation.RectEvaluator; import android.animation.TypeEvaluator; import android.app.TaskInfo; import android.content.Context; +import android.content.res.Configuration; import android.graphics.Insets; import android.graphics.Rect; +import android.os.Trace; import android.util.SparseArray; import android.view.InsetsSource; import android.view.InsetsState; @@ -42,6 +44,8 @@ import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener; import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.sysui.ConfigurationChangeListener; +import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldBackgroundController; @@ -62,16 +66,18 @@ import dagger.Lazy; * They use independent instances of SplitTaskUnfoldAnimator. */ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator, - DisplayInsetsController.OnInsetsChangedListener, SplitScreenListener { + DisplayInsetsController.OnInsetsChangedListener, SplitScreenListener, + ConfigurationChangeListener { private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect()); private static final float CROPPING_START_MARGIN_FRACTION = 0.05f; + private final Context mContext; private final Executor mExecutor; private final DisplayInsetsController mDisplayInsetsController; private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>(); private final int mExpandedTaskBarHeight; - private final float mWindowCornerRadiusPx; + private final ShellController mShellController; private final Lazy<Optional<SplitScreenController>> mSplitScreenController; private final UnfoldBackgroundController mUnfoldBackgroundController; @@ -79,6 +85,7 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator, private final Rect mSideStageBounds = new Rect(); private final Rect mRootStageBounds = new Rect(); + private float mWindowCornerRadiusPx; private InsetsSource mTaskbarInsetsSource; @SplitPosition @@ -88,10 +95,12 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator, public SplitTaskUnfoldAnimator(Context context, Executor executor, Lazy<Optional<SplitScreenController>> splitScreenController, - UnfoldBackgroundController unfoldBackgroundController, + ShellController shellController, UnfoldBackgroundController unfoldBackgroundController, DisplayInsetsController displayInsetsController) { mDisplayInsetsController = displayInsetsController; mExecutor = executor; + mContext = context; + mShellController = shellController; mUnfoldBackgroundController = unfoldBackgroundController; mSplitScreenController = splitScreenController; mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize( @@ -103,6 +112,14 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator, @Override public void init() { mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this); + mShellController.addConfigurationChangeListener(this); + } + + @Override + public void onConfigurationChanged(Configuration newConfiguration) { + Trace.beginSection("SplitTaskUnfoldAnimator#onConfigurationChanged"); + mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(mContext); + Trace.endSection(); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 245cc8d7ee87..0779f1d72551 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -213,6 +213,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final View handle = caption.findViewById(R.id.caption_handle); handle.setOnTouchListener(mOnCaptionTouchListener); handle.setOnClickListener(mOnCaptionButtonClickListener); + if (DesktopModeStatus.isProto1Enabled()) { + final View back = caption.findViewById(R.id.back_button); + back.setOnClickListener(mOnCaptionButtonClickListener); + final View close = caption.findViewById(R.id.close_window); + close.setOnClickListener(mOnCaptionButtonClickListener); + } updateButtonVisibility(); } @@ -319,6 +325,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final View handle = caption.findViewById(R.id.caption_handle); final Drawable handleBackground = handle.getBackground(); handleBackground.setTintList(buttonTintColor); + if (DesktopModeStatus.isProto1Enabled()) { + final View back = caption.findViewById(R.id.back_button); + final Drawable backBackground = back.getBackground(); + backBackground.setTintList(buttonTintColor); + final View close = caption.findViewById(R.id.close_window); + final Drawable closeBackground = close.getBackground(); + closeBackground.setTintList(buttonTintColor); + } } private void closeDragResizeListener() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index 7d954ad92285..81c4176b0f39 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -215,6 +215,7 @@ class DragResizeInputListener implements AutoCloseable { @Override public void close() { + mInputEventReceiver.dispose(); mInputChannel.dispose(); try { mWindowSession.remove(mFakeWindow); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java new file mode 100644 index 000000000000..f8ee300e411c --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DevicePostureControllerTest.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.common; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +import android.content.Context; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.sysui.ShellInit; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DevicePostureControllerTest { + @Mock + private Context mContext; + + @Mock + private ShellInit mShellInit; + + @Mock + private ShellExecutor mMainExecutor; + + @Captor + private ArgumentCaptor<Integer> mDevicePostureCaptor; + + @Mock + private DevicePostureController.OnDevicePostureChangedListener mOnDevicePostureChangedListener; + + private DevicePostureController mDevicePostureController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mDevicePostureController = new DevicePostureController(mContext, mShellInit, mMainExecutor); + } + + @Test + public void instantiateController_addInitCallback() { + verify(mShellInit, times(1)).addInitCallback(any(), eq(mDevicePostureController)); + } + + @Test + public void registerOnDevicePostureChangedListener_callbackCurrentPosture() { + mDevicePostureController.registerOnDevicePostureChangedListener( + mOnDevicePostureChangedListener); + verify(mOnDevicePostureChangedListener, times(1)) + .onDevicePostureChanged(anyInt()); + } + + @Test + public void onDevicePostureChanged_differentPosture_callbackListener() { + mDevicePostureController.registerOnDevicePostureChangedListener( + mOnDevicePostureChangedListener); + verify(mOnDevicePostureChangedListener).onDevicePostureChanged( + mDevicePostureCaptor.capture()); + clearInvocations(mOnDevicePostureChangedListener); + + int differentDevicePosture = mDevicePostureCaptor.getValue() + 1; + mDevicePostureController.onDevicePostureChanged(differentDevicePosture); + + verify(mOnDevicePostureChangedListener, times(1)) + .onDevicePostureChanged(differentDevicePosture); + } + + @Test + public void onDevicePostureChanged_samePosture_doesNotCallbackListener() { + mDevicePostureController.registerOnDevicePostureChangedListener( + mOnDevicePostureChangedListener); + verify(mOnDevicePostureChangedListener).onDevicePostureChanged( + mDevicePostureCaptor.capture()); + clearInvocations(mOnDevicePostureChangedListener); + + int sameDevicePosture = mDevicePostureCaptor.getValue(); + mDevicePostureController.onDevicePostureChanged(sameDevicePosture); + + verifyZeroInteractions(mOnDevicePostureChangedListener); + } +} diff --git a/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml index 33872ed63b89..0da5de75548c 100644 --- a/media/tests/MediaFrameworkTest/AndroidManifest.xml +++ b/media/tests/MediaFrameworkTest/AndroidManifest.xml @@ -37,7 +37,6 @@ </activity> <activity android:label="Camera2CtsActivity" android:name="Camera2SurfaceViewActivity" - android:screenOrientation="landscape" android:configChanges="keyboardHidden|orientation|screenSize"> </activity> </application> diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java index 3b902757c43e..37ae2d45e614 100644 --- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java +++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java @@ -55,6 +55,7 @@ public class IllustrationPreference extends Preference { private int mMaxHeight = SIZE_UNSPECIFIED; private int mImageResId; + private boolean mCacheComposition = true; private boolean mIsAutoScale; private Uri mImageUri; private Drawable mImageDrawable; @@ -133,6 +134,7 @@ public class IllustrationPreference extends Preference { lp.width = screenWidth < screenHeight ? screenWidth : screenHeight; illustrationFrame.setLayoutParams(lp); + illustrationView.setCacheComposition(mCacheComposition); handleImageWithAnimation(illustrationView); handleImageFrameMaxHeight(backgroundView, illustrationView); @@ -427,6 +429,8 @@ public class IllustrationPreference extends Preference { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LottieAnimationView, 0 /*defStyleAttr*/, 0 /*defStyleRes*/); mImageResId = a.getResourceId(R.styleable.LottieAnimationView_lottie_rawRes, 0); + mCacheComposition = a.getBoolean( + R.styleable.LottieAnimationView_lottie_cacheComposition, true); a = context.obtainStyledAttributes(attrs, R.styleable.IllustrationPreference, 0 /*defStyleAttr*/, 0 /*defStyleRes*/); diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt index 774255be4007..b98b92219c33 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt @@ -35,8 +35,7 @@ interface LaunchableView { * * Note that calls to [View.setTransitionVisibility] shouldn't be blocked. * - * @param block whether we should block/postpone all calls to `setVisibility` and - * `setTransitionVisibility`. + * @param block whether we should block/postpone all calls to `setVisibility`. */ fun setShouldBlockVisibilityChanges(block: Boolean) } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt index a1d9d90523eb..e42b589f05cf 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt @@ -24,7 +24,7 @@ import com.android.systemui.animation.LaunchableView import com.android.systemui.animation.LaunchableViewDelegate /** An [ImageView] that also implements [LaunchableView]. */ -class LaunchableImageView : ImageView, LaunchableView { +open class LaunchableImageView : ImageView, LaunchableView { private val delegate = LaunchableViewDelegate( this, diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt index 286996dcaeaa..147669528c5e 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt @@ -24,7 +24,7 @@ import com.android.systemui.animation.LaunchableView import com.android.systemui.animation.LaunchableViewDelegate /** A [TextView] that also implements [LaunchableView]. */ -class LaunchableTextView : TextView, LaunchableView { +open class LaunchableTextView : TextView, LaunchableView { private val delegate = LaunchableViewDelegate( this, diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt index 442c6fadb7c1..bd91c65ecc6e 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt @@ -68,7 +68,7 @@ class RippleAnimation(private val config: RippleAnimationConfig) { private fun applyConfigToShader() { with(rippleShader) { setCenter(config.centerX, config.centerY) - setMaxSize(config.maxWidth, config.maxHeight) + rippleSize.setMaxSize(config.maxWidth, config.maxHeight) pixelDensity = config.pixelDensity color = ColorUtils.setAlphaComponent(config.color, config.opacity) sparkleStrength = config.sparkleStrength diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt index 61ca90a297fe..0b842ad5331c 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt @@ -15,9 +15,10 @@ */ package com.android.systemui.surfaceeffects.ripple -import android.graphics.PointF import android.graphics.RuntimeShader +import android.util.Log import android.util.MathUtils +import androidx.annotation.VisibleForTesting import com.android.systemui.animation.Interpolators import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary @@ -44,6 +45,8 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : } // language=AGSL companion object { + private val TAG = RippleShader::class.simpleName + // Default fade in/ out values. The value range is [0,1]. const val DEFAULT_FADE_IN_START = 0f const val DEFAULT_FADE_OUT_END = 1f @@ -99,7 +102,7 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : vec4 main(vec2 p) { float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius, in_thickness), in_blur); - float inside = soften(sdRoundedBox(p-in_center, in_size * 1.2, in_cornerRadius), + float inside = soften(sdRoundedBox(p-in_center, in_size * 1.25, in_cornerRadius), in_blur); float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175) * (1.-sparkleRing) * in_fadeSparkle; @@ -184,12 +187,17 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : setFloatUniform("in_center", x, y) } - /** Max width of the ripple. */ - private var maxSize: PointF = PointF() - fun setMaxSize(width: Float, height: Float) { - maxSize.x = width - maxSize.y = height - } + /** + * Blur multipliers for the ripple. + * + * <p>It interpolates from [blurStart] to [blurEnd] based on the [progress]. Increase number to + * add more blur. + */ + var blurStart: Float = 1.25f + var blurEnd: Float = 0.5f + + /** Size of the ripple. */ + val rippleSize = RippleSize() /** * Linear progress of the ripple. Float value between [0, 1]. @@ -209,15 +217,19 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : /** Progress with Standard easing curve applied. */ private var progress: Float = 0.0f set(value) { - currentWidth = maxSize.x * value - currentHeight = maxSize.y * value - setFloatUniform("in_size", currentWidth, currentHeight) + field = value + + rippleSize.update(value) - setFloatUniform("in_thickness", maxSize.y * value * 0.5f) - // radius should not exceed width and height values. - setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * value) + setFloatUniform("in_size", rippleSize.currentWidth, rippleSize.currentHeight) + setFloatUniform("in_thickness", rippleSize.currentHeight * 0.5f) + // Corner radius is always max of the min between the current width and height. + setFloatUniform( + "in_cornerRadius", + Math.min(rippleSize.currentWidth, rippleSize.currentHeight) + ) - setFloatUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value)) + setFloatUniform("in_blur", MathUtils.lerp(blurStart, blurEnd, value)) } /** Play time since the start of the effect. */ @@ -264,12 +276,6 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : setFloatUniform("in_pixelDensity", value) } - var currentWidth: Float = 0f - private set - - var currentHeight: Float = 0f - private set - /** Parameters that are used to fade in/ out of the sparkle ring. */ val sparkleRingFadeParams = FadeParams( @@ -342,4 +348,102 @@ class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) : /** The endpoint of the fade out, given that the animation goes from 0 to 1. */ var fadeOutEnd: Float = DEFAULT_FADE_OUT_END, ) + + /** + * Desired size of the ripple at a point t in [progress]. + * + * <p>Note that [progress] is curved and normalized. Below is an example usage: + * SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 0.2f, width= 500f, height= + * 700f), SizeAtProgress(t= 1f, width= 100f, height= 300f) + * + * <p>For simple ripple effects, you will want to use [setMaxSize] as it is translated into: + * SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 1f, width= maxWidth, height= + * maxHeight) + */ + data class SizeAtProgress( + /** Time t in [0,1] progress range. */ + var t: Float, + /** Target width size of the ripple at time [t]. */ + var width: Float, + /** Target height size of the ripple at time [t]. */ + var height: Float + ) + + /** Updates and stores the ripple size. */ + inner class RippleSize { + @VisibleForTesting var sizes = mutableListOf<SizeAtProgress>() + @VisibleForTesting var currentSizeIndex = 0 + @VisibleForTesting val initialSize = SizeAtProgress(0f, 0f, 0f) + + var currentWidth: Float = 0f + private set + var currentHeight: Float = 0f + private set + + /** + * Sets the max size of the ripple. + * + * <p>Use this if the ripple shape simply changes linearly. + */ + fun setMaxSize(width: Float, height: Float) { + setSizeAtProgresses(initialSize, SizeAtProgress(1f, width, height)) + } + + /** + * Sets the list of [sizes]. + * + * <p>Note that setting this clears the existing sizes. + */ + fun setSizeAtProgresses(vararg sizes: SizeAtProgress) { + // Reset everything. + this.sizes.clear() + currentSizeIndex = 0 + + this.sizes.addAll(sizes) + this.sizes.sortBy { it.t } + } + + /** + * Updates the current ripple size based on the progress. + * + * <p>Should be called when progress updates. + */ + fun update(progress: Float) { + val targetIndex = updateTargetIndex(progress) + val prevIndex = Math.max(targetIndex - 1, 0) + + val targetSize = sizes[targetIndex] + val prevSize = sizes[prevIndex] + + val subProgress = subProgress(prevSize.t, targetSize.t, progress) + + currentWidth = targetSize.width * subProgress + prevSize.width + currentHeight = targetSize.height * subProgress + prevSize.height + } + + private fun updateTargetIndex(progress: Float): Int { + if (sizes.isEmpty()) { + // It could be empty on init. + if (progress > 0f) { + Log.e( + TAG, + "Did you forget to set the ripple size? Use [setMaxSize] or " + + "[setSizeAtProgresses] before playing the animation." + ) + } + // If there's no size is set, we set everything to 0 and return early. + setSizeAtProgresses(initialSize) + return currentSizeIndex + } + + var candidate = sizes[currentSizeIndex] + + while (progress > candidate.t) { + currentSizeIndex = Math.min(currentSizeIndex + 1, sizes.size - 1) + candidate = sizes[currentSizeIndex] + } + + return currentSizeIndex + } + } } diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt index 3c9328c82b83..4c7c43548016 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt @@ -45,12 +45,8 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a var duration: Long = 1750 - private var maxWidth: Float = 0.0f - private var maxHeight: Float = 0.0f fun setMaxSize(maxWidth: Float, maxHeight: Float) { - this.maxWidth = maxWidth - this.maxHeight = maxHeight - rippleShader.setMaxSize(maxWidth, maxHeight) + rippleShader.rippleSize.setMaxSize(maxWidth, maxHeight) } private var centerX: Float = 0.0f @@ -84,6 +80,106 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a ripplePaint.shader = rippleShader } + /** + * Sets the fade parameters for the base ring. + * + * <p>Base ring indicates a blurred ring below the sparkle ring. See + * [RippleShader.baseRingFadeParams]. + */ + @JvmOverloads + fun setBaseRingFadeParams( + fadeInStart: Float = rippleShader.baseRingFadeParams.fadeInStart, + fadeInEnd: Float = rippleShader.baseRingFadeParams.fadeInEnd, + fadeOutStart: Float = rippleShader.baseRingFadeParams.fadeOutStart, + fadeOutEnd: Float = rippleShader.baseRingFadeParams.fadeOutEnd + ) { + setFadeParams( + rippleShader.baseRingFadeParams, + fadeInStart, + fadeInEnd, + fadeOutStart, + fadeOutEnd + ) + } + + /** + * Sets the fade parameters for the sparkle ring. + * + * <p>Sparkle ring refers to the ring that's drawn on top of the base ring. See + * [RippleShader.sparkleRingFadeParams]. + */ + @JvmOverloads + fun setSparkleRingFadeParams( + fadeInStart: Float = rippleShader.sparkleRingFadeParams.fadeInStart, + fadeInEnd: Float = rippleShader.sparkleRingFadeParams.fadeInEnd, + fadeOutStart: Float = rippleShader.sparkleRingFadeParams.fadeOutStart, + fadeOutEnd: Float = rippleShader.sparkleRingFadeParams.fadeOutEnd + ) { + setFadeParams( + rippleShader.sparkleRingFadeParams, + fadeInStart, + fadeInEnd, + fadeOutStart, + fadeOutEnd + ) + } + + /** + * Sets the fade parameters for the center fill. + * + * <p>One common use case is set all the params to 1, which completely removes the center fill. + * See [RippleShader.centerFillFadeParams]. + */ + @JvmOverloads + fun setCenterFillFadeParams( + fadeInStart: Float = rippleShader.centerFillFadeParams.fadeInStart, + fadeInEnd: Float = rippleShader.centerFillFadeParams.fadeInEnd, + fadeOutStart: Float = rippleShader.centerFillFadeParams.fadeOutStart, + fadeOutEnd: Float = rippleShader.centerFillFadeParams.fadeOutEnd + ) { + setFadeParams( + rippleShader.centerFillFadeParams, + fadeInStart, + fadeInEnd, + fadeOutStart, + fadeOutEnd + ) + } + + private fun setFadeParams( + fadeParams: RippleShader.FadeParams, + fadeInStart: Float, + fadeInEnd: Float, + fadeOutStart: Float, + fadeOutEnd: Float + ) { + with(fadeParams) { + this.fadeInStart = fadeInStart + this.fadeInEnd = fadeInEnd + this.fadeOutStart = fadeOutStart + this.fadeOutEnd = fadeOutEnd + } + } + + /** + * Sets blur multiplier at start and end of the progress. + * + * <p>It interpolates between [start] and [end]. No need to set it if using default blur. + */ + fun setBlur(start: Float, end: Float) { + rippleShader.blurStart = start + rippleShader.blurEnd = end + } + + /** + * Sets the list of [RippleShader.SizeAtProgress]. + * + * <p>Note that this clears the list before it sets with the new data. + */ + fun setSizeAtProgresses(vararg targetSizes: RippleShader.SizeAtProgress) { + rippleShader.rippleSize.setSizeAtProgresses(*targetSizes) + } + @JvmOverloads fun startRipple(onAnimationEnd: Runnable? = null) { if (animator.isRunning) { @@ -133,13 +229,13 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a } // To reduce overdraw, we mask the effect to a circle or a rectangle that's bigger than the // active effect area. Values here should be kept in sync with the animation implementation - // in the ripple shader. (Twice bigger) + // in the ripple shader. if (rippleShape == RippleShape.CIRCLE) { - val maskRadius = rippleShader.currentWidth + val maskRadius = rippleShader.rippleSize.currentWidth canvas.drawCircle(centerX, centerY, maskRadius, ripplePaint) - } else { - val maskWidth = rippleShader.currentWidth * 2 - val maskHeight = rippleShader.currentHeight * 2 + } else if (rippleShape == RippleShape.ELLIPSE) { + val maskWidth = rippleShader.rippleSize.currentWidth * 2 + val maskHeight = rippleShader.rippleSize.currentHeight * 2 canvas.drawRect( /* left= */ centerX - maskWidth, /* top= */ centerY - maskHeight, @@ -147,6 +243,10 @@ open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, a /* bottom= */ centerY + maskHeight, ripplePaint ) + } else { // RippleShape.RoundedBox + // No masking for the rounded box, as it has more blur which requires larger bounds. + // Masking creates sharp bounds even when the masking is 4 times bigger. + canvas.drawPaint(ripplePaint) } } } diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt index 8b2f46648fef..78898932249b 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt @@ -50,9 +50,9 @@ class SdfShaderLibrary { float roundedBoxRing(vec2 p, vec2 size, float cornerRadius, float borderThickness) { - float outerRoundBox = sdRoundedBox(p, size, cornerRadius); - float innerRoundBox = sdRoundedBox(p, size - vec2(borderThickness), - cornerRadius - borderThickness); + float outerRoundBox = sdRoundedBox(p, size + vec2(borderThickness), + cornerRadius + borderThickness); + float innerRoundBox = sdRoundedBox(p, size, cornerRadius); return subtract(outerRoundBox, innerRoundBox); } """ @@ -69,10 +69,13 @@ class SdfShaderLibrary { vec2 u = wh*p, v = wh*wh; - float U1 = u.y/2.0; float U5 = 4.0*U1; - float U2 = v.y-v.x; float U6 = 6.0*U1; - float U3 = u.x-U2; float U7 = 3.0*U3; + float U1 = u.y/2.0; + float U2 = v.y-v.x; + float U3 = u.x-U2; float U4 = u.x+U2; + float U5 = 4.0*U1; + float U6 = 6.0*U1; + float U7 = 3.0*U3; float t = 0.5; for (int i = 0; i < 3; i ++) { diff --git a/packages/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md index 79d5718efe0d..d662649ac419 100644 --- a/packages/SystemUI/docs/device-entry/quickaffordance.md +++ b/packages/SystemUI/docs/device-entry/quickaffordance.md @@ -52,6 +52,11 @@ A picker experience may: * Unselect an already-selected quick affordance from a slot * Unselect all already-selected quick affordances from a slot +## Device Policy +Returning `DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL` or +`DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL` from +`DevicePolicyManager#getKeyguardDisabledFeatures` will disable the keyguard quick affordance feature on the device. + ## Testing * Add a unit test for your implementation of `KeyguardQuickAffordanceConfig` * Manually verify that your implementation works in multi-user environments from both the main user and a secondary user diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt index 1c2f38beb867..ab4aca569bd4 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt @@ -113,7 +113,7 @@ interface ClockEvents { fun onColorPaletteChanged(resources: Resources) {} /** Call whenever the weather data should update */ - fun onWeatherDataChanged(data: Weather) {} + fun onWeatherDataChanged(data: WeatherData) {} } /** Methods which trigger various clock animations */ diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt deleted file mode 100644 index 302f17556bc7..000000000000 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.android.systemui.plugins - -import android.os.Bundle - -class Weather(val conditions: WeatherStateIcon, val temperature: Int, val isCelsius: Boolean) { - companion object { - private const val TAG = "Weather" - private const val WEATHER_STATE_ICON_KEY = "weather_state_icon_extra_key" - private const val TEMPERATURE_VALUE_KEY = "temperature_value_extra_key" - private const val TEMPERATURE_UNIT_KEY = "temperature_unit_extra_key" - private const val INVALID_TEMPERATURE = Int.MIN_VALUE - - fun fromBundle(extras: Bundle): Weather? { - val icon = - WeatherStateIcon.fromInt( - extras.getInt(WEATHER_STATE_ICON_KEY, WeatherStateIcon.UNKNOWN_ICON.id) - ) - if (icon == null || icon == WeatherStateIcon.UNKNOWN_ICON) { - return null - } - val temperature = extras.getInt(TEMPERATURE_VALUE_KEY, INVALID_TEMPERATURE) - if (temperature == INVALID_TEMPERATURE) { - return null - } - return Weather(icon, temperature, extras.getBoolean(TEMPERATURE_UNIT_KEY)) - } - } - - enum class WeatherStateIcon(val id: Int) { - UNKNOWN_ICON(0), - - // Clear, day & night. - SUNNY(1), - CLEAR_NIGHT(2), - - // Mostly clear, day & night. - MOSTLY_SUNNY(3), - MOSTLY_CLEAR_NIGHT(4), - - // Partly cloudy, day & night. - PARTLY_CLOUDY(5), - PARTLY_CLOUDY_NIGHT(6), - - // Mostly cloudy, day & night. - MOSTLY_CLOUDY_DAY(7), - MOSTLY_CLOUDY_NIGHT(8), - CLOUDY(9), - HAZE_FOG_DUST_SMOKE(10), - DRIZZLE(11), - HEAVY_RAIN(12), - SHOWERS_RAIN(13), - - // Scattered showers, day & night. - SCATTERED_SHOWERS_DAY(14), - SCATTERED_SHOWERS_NIGHT(15), - - // Isolated scattered thunderstorms, day & night. - ISOLATED_SCATTERED_TSTORMS_DAY(16), - ISOLATED_SCATTERED_TSTORMS_NIGHT(17), - STRONG_TSTORMS(18), - BLIZZARD(19), - BLOWING_SNOW(20), - FLURRIES(21), - HEAVY_SNOW(22), - - // Scattered snow showers, day & night. - SCATTERED_SNOW_SHOWERS_DAY(23), - SCATTERED_SNOW_SHOWERS_NIGHT(24), - SNOW_SHOWERS_SNOW(25), - MIXED_RAIN_HAIL_RAIN_SLEET(26), - SLEET_HAIL(27), - TORNADO(28), - TROPICAL_STORM_HURRICANE(29), - WINDY_BREEZY(30), - WINTRY_MIX_RAIN_SNOW(31); - - companion object { - fun fromInt(value: Int) = values().firstOrNull { it.id == value } - } - } - - override fun toString(): String { - return "$conditions $temperature${if (isCelsius) "C" else "F"}" - } -} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt new file mode 100644 index 000000000000..52dfc55c105d --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt @@ -0,0 +1,107 @@ +package com.android.systemui.plugins + +import android.os.Bundle +import androidx.annotation.VisibleForTesting + +class WeatherData +private constructor( + val description: String, + val state: WeatherStateIcon, + val useCelsius: Boolean, + val temperature: Int, +) { + companion object { + private const val TAG = "WeatherData" + @VisibleForTesting const val DESCRIPTION_KEY = "description" + @VisibleForTesting const val STATE_KEY = "state" + @VisibleForTesting const val USE_CELSIUS_KEY = "use_celsius" + @VisibleForTesting const val TEMPERATURE_KEY = "temperature" + private const val INVALID_WEATHER_ICON_STATE = -1 + + fun fromBundle(extras: Bundle): WeatherData? { + val description = extras.getString(DESCRIPTION_KEY) + val state = + WeatherStateIcon.fromInt(extras.getInt(STATE_KEY, INVALID_WEATHER_ICON_STATE)) + val temperature = readIntFromBundle(extras, TEMPERATURE_KEY) + return if ( + description == null || + state == null || + !extras.containsKey(USE_CELSIUS_KEY) || + temperature == null + ) + null + else + WeatherData( + description = description, + state = state, + useCelsius = extras.getBoolean(USE_CELSIUS_KEY), + temperature = temperature + ) + } + + private fun readIntFromBundle(extras: Bundle, key: String): Int? = + try { + extras.getString(key).toInt() + } catch (e: Exception) { + null + } + } + + enum class WeatherStateIcon(val id: Int) { + UNKNOWN_ICON(0), + + // Clear, day & night. + SUNNY(1), + CLEAR_NIGHT(2), + + // Mostly clear, day & night. + MOSTLY_SUNNY(3), + MOSTLY_CLEAR_NIGHT(4), + + // Partly cloudy, day & night. + PARTLY_CLOUDY(5), + PARTLY_CLOUDY_NIGHT(6), + + // Mostly cloudy, day & night. + MOSTLY_CLOUDY_DAY(7), + MOSTLY_CLOUDY_NIGHT(8), + CLOUDY(9), + HAZE_FOG_DUST_SMOKE(10), + DRIZZLE(11), + HEAVY_RAIN(12), + SHOWERS_RAIN(13), + + // Scattered showers, day & night. + SCATTERED_SHOWERS_DAY(14), + SCATTERED_SHOWERS_NIGHT(15), + + // Isolated scattered thunderstorms, day & night. + ISOLATED_SCATTERED_TSTORMS_DAY(16), + ISOLATED_SCATTERED_TSTORMS_NIGHT(17), + STRONG_TSTORMS(18), + BLIZZARD(19), + BLOWING_SNOW(20), + FLURRIES(21), + HEAVY_SNOW(22), + + // Scattered snow showers, day & night. + SCATTERED_SNOW_SHOWERS_DAY(23), + SCATTERED_SNOW_SHOWERS_NIGHT(24), + SNOW_SHOWERS_SNOW(25), + MIXED_RAIN_HAIL_RAIN_SLEET(26), + SLEET_HAIL(27), + TORNADO(28), + TROPICAL_STORM_HURRICANE(29), + WINDY_BREEZY(30), + WINTRY_MIX_RAIN_SNOW(31); + + companion object { + fun fromInt(value: Int) = values().firstOrNull { it.id == value } + } + } + + override fun toString(): String { + val unit = if (useCelsius) "C" else "F" + return "$state (\"$description\") $temperature°$unit" + } +} diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index e6ac59e6b106..464ce0333fd1 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -479,6 +479,8 @@ <string name="accessibility_desc_notification_shade">Notification shade.</string> <!-- Content description for the quick settings panel (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_desc_quick_settings">Quick settings.</string> + <!-- Content description for the split notification shade that also includes QS (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_desc_qs_notification_shade">Quick settings and Notification shade.</string> <!-- Content description for the lock screen (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_desc_lock_screen">Lock screen.</string> <!-- Content description for the work profile lock screen. This prevents work profile apps from being used, but personal apps can be used as normal (not shown on the screen). [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt index c9a25b067b94..b6aae69ebad6 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt @@ -145,7 +145,7 @@ data class SysPropBooleanFlag constructor( override val namespace: String, override val default: Boolean = false, ) : SysPropFlag<Boolean> { - // TODO(b/223379190): Teamfood not supported for sysprop flags yet. + // TODO(b/268520433): Teamfood not supported for sysprop flags yet. override val teamfood: Boolean = false } diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 1254e1ee3311..92ee37310130 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -48,7 +48,7 @@ import com.android.systemui.plugins.ClockTickRate import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.plugins.log.LogLevel.DEBUG import com.android.systemui.shared.regionsampling.RegionSampler -import com.android.systemui.plugins.Weather +import com.android.systemui.plugins.WeatherData import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback import com.android.systemui.statusbar.policy.ConfigurationController @@ -291,10 +291,8 @@ constructor( clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context)) } - override fun onWeatherDataChanged(data: Weather?) { - if (data != null) { - clock?.events?.onWeatherDataChanged(data) - } + override fun onWeatherDataChanged(data: WeatherData) { + clock?.events?.onWeatherDataChanged(data) } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index eec788b7add8..f164e7d33642 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -492,12 +492,14 @@ public class KeyguardSecurityContainer extends ConstraintLayout { case MotionEvent.ACTION_MOVE: mVelocityTracker.addMovement(event); int pointerIndex = event.findPointerIndex(mActivePointerId); - float y = event.getY(pointerIndex); - if (mLastTouchY != -1) { - float dy = y - mLastTouchY; - setTranslationY(getTranslationY() + dy * TOUCH_Y_MULTIPLIER); + if (pointerIndex != -1) { + float y = event.getY(pointerIndex); + if (mLastTouchY != -1) { + float dy = y - mLastTouchY; + setTranslationY(getTranslationY() + dy * TOUCH_Y_MULTIPLIER); + } + mLastTouchY = y; } - mLastTouchY = y; break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 92cbb296f270..b8bb2603fa03 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -17,7 +17,6 @@ package com.android.keyguard; import static android.app.StatusBarManager.SESSION_KEYGUARD; -import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC; import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS; @@ -36,7 +35,6 @@ import android.content.res.ColorStateList; import android.content.res.Configuration; import android.content.res.Resources; import android.hardware.biometrics.BiometricOverlayConstants; -import android.hardware.biometrics.BiometricSourceType; import android.media.AudioManager; import android.metrics.LogMaker; import android.os.SystemClock; @@ -320,7 +318,6 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard KeyguardSecurityContainerController.this.onDensityOrFontScaleChanged(); } }; - private boolean mBouncerVisible = false; private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { @Override @@ -355,19 +352,6 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard public void onDevicePolicyManagerStateChanged() { showPrimarySecurityScreen(false); } - - @Override - public void onBiometricRunningStateChanged(boolean running, - BiometricSourceType biometricSourceType) { - if (biometricSourceType == FINGERPRINT) { - updateSideFpsVisibility(); - } - } - - @Override - public void onStrongAuthStateChanged(int userId) { - updateSideFpsVisibility(); - } }; @Inject @@ -459,35 +443,24 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard getCurrentSecurityController().onPause(); } mView.onPause(); - // It might happen that onStartingToHide is not called when the device is locked while on - // bouncer. - setBouncerVisible(false); mView.clearFocus(); } - private void updateSideFpsVisibility() { + /** + * Shows and hides the side finger print sensor animation. + * + * @param isVisible sets whether we show or hide the side fps animation + */ + public void updateSideFpsVisibility(boolean isVisible) { if (!mSideFpsController.isPresent()) { return; } - final boolean sfpsEnabled = getResources().getBoolean( - R.bool.config_show_sidefps_hint_on_bouncer); - final boolean fpsDetectionRunning = mUpdateMonitor.isFingerprintDetectionRunning(); - final boolean isUnlockingWithFpAllowed = - mUpdateMonitor.isUnlockingWithFingerprintAllowed(); - boolean toShow = mBouncerVisible && sfpsEnabled && fpsDetectionRunning - && isUnlockingWithFpAllowed; - - if (DEBUG) { - Log.d(TAG, "sideFpsToShow=" + toShow + ", " - + "mBouncerVisible=" + mBouncerVisible + ", " - + "configEnabled=" + sfpsEnabled + ", " - + "fpsDetectionRunning=" + fpsDetectionRunning + ", " - + "isUnlockingWithFpAllowed=" + isUnlockingWithFpAllowed); - } - if (toShow) { - mSideFpsController.get().show(SideFpsUiRequestSource.PRIMARY_BOUNCER, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD); + if (isVisible) { + mSideFpsController.get().show( + SideFpsUiRequestSource.PRIMARY_BOUNCER, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD + ); } else { mSideFpsController.get().hide(SideFpsUiRequestSource.PRIMARY_BOUNCER); } @@ -636,7 +609,6 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, state); getCurrentSecurityController().onResume(reason); - updateSideFpsVisibility(); } mView.onResume( mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()), @@ -690,22 +662,15 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard if (mCurrentSecurityMode != SecurityMode.None) { getCurrentSecurityController().onStartingToHide(); } - setBouncerVisible(false); } /** Called when the bouncer changes visibility. */ - public void onBouncerVisibilityChanged(@View.Visibility int visibility) { - setBouncerVisible(visibility == View.VISIBLE); - if (visibility == View.INVISIBLE) { + public void onBouncerVisibilityChanged(boolean isVisible) { + if (!isVisible) { mView.resetScale(); } } - private void setBouncerVisible(boolean visible) { - mBouncerVisible = visible; - updateSideFpsVisibility(); - } - /** * Shows the next security screen if there is one. * @param authenticated true if the user entered the correct authentication diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index be9264dbfcf3..b2d4215b388a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -148,7 +148,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpsysTableLogger; import com.android.systemui.log.SessionTracker; -import com.android.systemui.plugins.Weather; +import com.android.systemui.plugins.WeatherData; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.TaskStackChangeListener; @@ -2765,7 +2765,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab boolean shouldListen = shouldListenKeyguardState && shouldListenUserState && shouldListenBouncerState && shouldListenUdfpsState - && shouldListenSideFpsState; + && shouldListenSideFpsState + && !isFingerprintLockedOut(); logListenerModelData( new KeyguardFingerprintListenModel( System.currentTimeMillis(), @@ -3285,12 +3286,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab /** * @param data the weather data (temp, conditions, unit) for weather clock to use */ - public void sendWeatherData(Weather data) { + public void sendWeatherData(WeatherData data) { mHandler.post(()-> { handleWeatherDataUpdate(data); }); } - private void handleWeatherDataUpdate(Weather data) { + private void handleWeatherDataUpdate(WeatherData data) { Assert.isMainThread(); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 0da799e0964d..38f3e5065eec 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -23,7 +23,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.settingslib.fuelgauge.BatteryStatus; -import com.android.systemui.plugins.Weather; +import com.android.systemui.plugins.WeatherData; import com.android.systemui.statusbar.KeyguardIndicationController; import java.util.TimeZone; @@ -61,7 +61,7 @@ public class KeyguardUpdateMonitorCallback { /** * Called when receive new weather data. */ - public void onWeatherDataChanged(Weather data) { } + public void onWeatherDataChanged(WeatherData data) { } /** * Called when the carrier PLMN or SPN changes. diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index febf75e90a14..68e1f72d042a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -847,7 +847,7 @@ public class AuthContainerView extends LinearLayout final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, + WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL, windowFlags, PixelFormat.TRANSLUCENT); lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt index 58b230f52e93..4b32759588e0 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt @@ -75,7 +75,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at } private var radius: Float = 0f set(value) { - rippleShader.setMaxSize(value * 2f, value * 2f) + rippleShader.rippleSize.setMaxSize(value * 2f, value * 2f) field = value } private var origin: Point = Point() @@ -364,7 +364,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at if (drawRipple) { canvas?.drawCircle(origin.x.toFloat(), origin.y.toFloat(), - rippleShader.currentWidth, ripplePaint) + rippleShader.rippleSize.currentWidth, ripplePaint) } } } diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java index 36103f8db8d8..cef415c8a490 100644 --- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java +++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java @@ -33,7 +33,9 @@ import android.widget.TextView; import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; +import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.surfaceeffects.ripple.RippleAnimationConfig; +import com.android.systemui.surfaceeffects.ripple.RippleShader; import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape; import com.android.systemui.surfaceeffects.ripple.RippleView; @@ -44,10 +46,12 @@ import java.text.NumberFormat; */ final class WirelessChargingLayout extends FrameLayout { private static final long CIRCLE_RIPPLE_ANIMATION_DURATION = 1500; - private static final long ROUNDED_BOX_RIPPLE_ANIMATION_DURATION = 1750; + private static final long ROUNDED_BOX_RIPPLE_ANIMATION_DURATION = 3000; private static final int SCRIM_COLOR = 0x4C000000; private static final int SCRIM_FADE_DURATION = 300; private RippleView mRippleView; + // This is only relevant to the rounded box ripple. + private RippleShader.SizeAtProgress[] mSizeAtProgressArray; WirelessChargingLayout(Context context, int transmittingBatteryLevel, int batteryLevel, boolean isDozing, RippleShape rippleShape) { @@ -125,20 +129,23 @@ final class WirelessChargingLayout extends FrameLayout { AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator); - ValueAnimator scrimFadeInAnimator = ObjectAnimator.ofArgb(this, - "backgroundColor", Color.TRANSPARENT, SCRIM_COLOR); - scrimFadeInAnimator.setDuration(SCRIM_FADE_DURATION); - scrimFadeInAnimator.setInterpolator(Interpolators.LINEAR); - ValueAnimator scrimFadeOutAnimator = ObjectAnimator.ofArgb(this, - "backgroundColor", SCRIM_COLOR, Color.TRANSPARENT); - scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION); - scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR); - scrimFadeOutAnimator.setStartDelay((rippleShape == RippleShape.CIRCLE - ? CIRCLE_RIPPLE_ANIMATION_DURATION : ROUNDED_BOX_RIPPLE_ANIMATION_DURATION) - - SCRIM_FADE_DURATION); - AnimatorSet animatorSetScrim = new AnimatorSet(); - animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator); - animatorSetScrim.start(); + // For tablet docking animation, we don't play the background scrim. + if (!Utilities.isTablet(context)) { + ValueAnimator scrimFadeInAnimator = ObjectAnimator.ofArgb(this, + "backgroundColor", Color.TRANSPARENT, SCRIM_COLOR); + scrimFadeInAnimator.setDuration(SCRIM_FADE_DURATION); + scrimFadeInAnimator.setInterpolator(Interpolators.LINEAR); + ValueAnimator scrimFadeOutAnimator = ObjectAnimator.ofArgb(this, + "backgroundColor", SCRIM_COLOR, Color.TRANSPARENT); + scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION); + scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR); + scrimFadeOutAnimator.setStartDelay((rippleShape == RippleShape.CIRCLE + ? CIRCLE_RIPPLE_ANIMATION_DURATION : ROUNDED_BOX_RIPPLE_ANIMATION_DURATION) + - SCRIM_FADE_DURATION); + AnimatorSet animatorSetScrim = new AnimatorSet(); + animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator); + animatorSetScrim.start(); + } mRippleView = findViewById(R.id.wireless_charging_ripple); mRippleView.setupShader(rippleShape); @@ -147,7 +154,26 @@ final class WirelessChargingLayout extends FrameLayout { if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) { mRippleView.setDuration(ROUNDED_BOX_RIPPLE_ANIMATION_DURATION); mRippleView.setSparkleStrength(0.22f); - mRippleView.setColor(color, 28); + mRippleView.setColor(color, 102); // 40% of opacity. + mRippleView.setBaseRingFadeParams( + /* fadeInStart = */ 0f, + /* fadeInEnd = */ 0f, + /* fadeOutStart = */ 0.2f, + /* fadeOutEnd= */ 0.47f + ); + mRippleView.setSparkleRingFadeParams( + /* fadeInStart = */ 0f, + /* fadeInEnd = */ 0f, + /* fadeOutStart = */ 0.2f, + /* fadeOutEnd= */ 1f + ); + mRippleView.setCenterFillFadeParams( + /* fadeInStart = */ 0f, + /* fadeInEnd = */ 0f, + /* fadeOutStart = */ 0f, + /* fadeOutEnd= */ 0.2f + ); + mRippleView.setBlur(6.5f, 2.5f); } else { mRippleView.setDuration(CIRCLE_RIPPLE_ANIMATION_DURATION); mRippleView.setColor(color, RippleAnimationConfig.RIPPLE_DEFAULT_ALPHA); @@ -246,9 +272,7 @@ final class WirelessChargingLayout extends FrameLayout { int height = getMeasuredHeight(); mRippleView.setCenter(width * 0.5f, height * 0.5f); if (mRippleView.getRippleShape() == RippleShape.ROUNDED_BOX) { - // Those magic numbers are introduced for visual polish. This aspect ratio maps with - // the tablet's docking station. - mRippleView.setMaxSize(width * 1.36f, height * 1.46f); + updateRippleSizeAtProgressList(width, height); } else { float maxSize = Math.max(width, height); mRippleView.setMaxSize(maxSize, maxSize); @@ -257,4 +281,36 @@ final class WirelessChargingLayout extends FrameLayout { super.onLayout(changed, left, top, right, bottom); } + + private void updateRippleSizeAtProgressList(float width, float height) { + if (mSizeAtProgressArray == null) { + float maxSize = Math.max(width, height); + mSizeAtProgressArray = new RippleShader.SizeAtProgress[] { + // Those magic numbers are introduced for visual polish. It starts from a pill + // shape and expand to a full circle. + new RippleShader.SizeAtProgress(0f, 0f, 0f), + new RippleShader.SizeAtProgress(0.3f, width * 0.4f, height * 0.4f), + new RippleShader.SizeAtProgress(1f, maxSize, maxSize) + }; + } else { + // Same multipliers, just need to recompute with the new width and height. + RippleShader.SizeAtProgress first = mSizeAtProgressArray[0]; + first.setT(0f); + first.setWidth(0f); + first.setHeight(0f); + + RippleShader.SizeAtProgress second = mSizeAtProgressArray[1]; + second.setT(0.3f); + second.setWidth(width * 0.4f); + second.setHeight(height * 0.4f); + + float maxSize = Math.max(width, height); + RippleShader.SizeAtProgress last = mSizeAtProgressArray[2]; + last.setT(1f); + last.setWidth(maxSize); + last.setHeight(maxSize); + } + + mRippleView.setSizeAtProgresses(mSizeAtProgressArray); + } } diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java index e9ac840cf4f4..f6b71336675f 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java @@ -402,7 +402,7 @@ public class BrightLineFalsingManager implements FalsingManager { || mAccessibilityManager.isTouchExplorationEnabled() || mDataProvider.isA11yAction() || (mFeatureFlags.isEnabled(Flags.FALSING_OFF_FOR_UNFOLDED) - && !mDataProvider.isFolded()); + && mDataProvider.isUnfolded()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java index 5f347c158818..bc0f9950f865 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java @@ -380,8 +380,8 @@ public class FalsingDataProvider { return mBatteryController.isWirelessCharging() || mDockManager.isDocked(); } - public boolean isFolded() { - return Boolean.TRUE.equals(mFoldStateListener.getFolded()); + public boolean isUnfolded() { + return Boolean.FALSE.equals(mFoldStateListener.getFolded()); } /** Implement to be alerted abotu the beginning and ending of falsing tracking. */ diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java index 88c02b8aa790..13563dfd03f0 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java @@ -29,7 +29,7 @@ import com.android.systemui.dreams.DreamOverlayNotificationCountProvider; import com.android.systemui.dreams.DreamOverlayService; import com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule; import com.android.systemui.dreams.touch.scrim.dagger.ScrimModule; -import com.android.systemui.process.condition.UserProcessCondition; +import com.android.systemui.process.condition.SystemProcessCondition; import com.android.systemui.shared.condition.Condition; import com.android.systemui.shared.condition.Monitor; @@ -126,7 +126,7 @@ public interface DreamModule { @Binds @IntoSet @Named(DREAM_PRETEXT_CONDITIONS) - Condition bindsUserProcessCondition(UserProcessCondition condition); + Condition bindSystemProcessCondition(SystemProcessCondition condition); /** */ @Provides diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 537401f3ffd2..6bc1eddf26f7 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -59,7 +59,7 @@ object Flags { ) // TODO(b/254512517): Tracking Bug - val FSI_REQUIRES_KEYGUARD = unreleasedFlag(110, "fsi_requires_keyguard", teamfood = true) + val FSI_REQUIRES_KEYGUARD = releasedFlag(110, "fsi_requires_keyguard") // TODO(b/259130119): Tracking Bug val FSI_ON_DND_UPDATE = unreleasedFlag(259130119, "fsi_on_dnd_update", teamfood = true) @@ -600,7 +600,8 @@ object Flags { @JvmField val UDFPS_ELLIPSE_DETECTION = unreleasedFlag(2202, "udfps_ellipse_detection") // 2300 - stylus - @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag(2300, "track_stylus_ever_used") + @JvmField + val TRACK_STYLUS_EVER_USED = unreleasedFlag(2300, "track_stylus_ever_used", teamfood = true) @JvmField val ENABLE_STYLUS_CHARGING_UI = unreleasedFlag(2301, "enable_stylus_charging_ui") @JvmField val ENABLE_USI_BATTERY_NOTIFICATIONS = unreleasedFlag(2302, "enable_usi_battery_notifications") diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt index 7acd3f3447dd..9b748d0a0eb2 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt @@ -57,6 +57,7 @@ class ServerFlagReaderImpl @Inject constructor( override fun onPropertiesChanged(properties: DeviceConfig.Properties) { if (isTestHarness) { Log.w(TAG, "Ignore server flag changes in Test Harness mode.") + return } if (properties.namespace != namespace) { return diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt index e9b8908214fc..496c64e1120e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt @@ -17,6 +17,14 @@ package com.android.systemui.keyboard +import com.android.systemui.keyboard.data.repository.KeyboardRepository +import com.android.systemui.keyboard.data.repository.KeyboardRepositoryImpl +import dagger.Binds import dagger.Module -@Module abstract class KeyboardModule +@Module +abstract class KeyboardModule { + + @Binds + abstract fun bindKeyboardRepository(repository: KeyboardRepositoryImpl): KeyboardRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/data/model/BacklightModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/data/model/BacklightModel.kt new file mode 100644 index 000000000000..ea15a9f18584 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/data/model/BacklightModel.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyboard.data.model + +/** + * Model for current state of keyboard backlight brightness. [level] indicates current level of + * backlight brightness and [maxLevel] its max possible value. + */ +data class BacklightModel(val level: Int, val maxLevel: Int) diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt new file mode 100644 index 000000000000..70faf406d621 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyboard.data.repository + +import android.hardware.input.InputManager +import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyboard.data.model.BacklightModel +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.shareIn + +interface KeyboardRepository { + val keyboardConnected: Flow<Boolean> + val backlight: Flow<BacklightModel> +} + +@SysUISingleton +class KeyboardRepositoryImpl +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + @Background private val backgroundDispatcher: CoroutineDispatcher, + private val inputManager: InputManager, +) : KeyboardRepository { + + private val connectedDeviceIds: Flow<Set<Int>> = + conflatedCallbackFlow { + fun send(element: Set<Int>) = trySendWithFailureLogging(element, TAG) + + var connectedKeyboards = inputManager.inputDeviceIds.toSet() + val listener = + object : InputManager.InputDeviceListener { + override fun onInputDeviceAdded(deviceId: Int) { + connectedKeyboards = connectedKeyboards + deviceId + send(connectedKeyboards) + } + + override fun onInputDeviceChanged(deviceId: Int) = Unit + + override fun onInputDeviceRemoved(deviceId: Int) { + connectedKeyboards = connectedKeyboards - deviceId + send(connectedKeyboards) + } + } + send(connectedKeyboards) + inputManager.registerInputDeviceListener(listener, /* handler= */ null) + awaitClose { inputManager.unregisterInputDeviceListener(listener) } + } + .shareIn( + scope = applicationScope, + started = SharingStarted.Lazily, + replay = 1, + ) + + override val keyboardConnected: Flow<Boolean> = + connectedDeviceIds + .map { it.any { deviceId -> isPhysicalFullKeyboard(deviceId) } } + .distinctUntilChanged() + .flowOn(backgroundDispatcher) + + override val backlight: Flow<BacklightModel> = + conflatedCallbackFlow { + // TODO(b/268645734) register BacklightListener + } + + private fun isPhysicalFullKeyboard(deviceId: Int): Boolean { + val device = inputManager.getInputDevice(deviceId) + return !device.isVirtual && device.isFullKeyboard + } + + companion object { + const val TAG = "KeyboardRepositoryImpl" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt index 680c504d50fc..27a5974e6299 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt @@ -131,7 +131,7 @@ class CustomizationProvider : throw UnsupportedOperationException() } - return insertSelection(values) + return runBlocking(mainDispatcher) { insertSelection(values) } } override fun query( @@ -171,7 +171,7 @@ class CustomizationProvider : throw UnsupportedOperationException() } - return deleteSelection(uri, selectionArgs) + return runBlocking(mainDispatcher) { deleteSelection(uri, selectionArgs) } } override fun call(method: String, arg: String?, extras: Bundle?): Bundle? { @@ -189,7 +189,7 @@ class CustomizationProvider : } } - private fun insertSelection(values: ContentValues?): Uri? { + private suspend fun insertSelection(values: ContentValues?): Uri? { if (values == null) { throw IllegalArgumentException("Cannot insert selection, no values passed in!") } @@ -311,7 +311,7 @@ class CustomizationProvider : } } - private fun querySlots(): Cursor { + private suspend fun querySlots(): Cursor { return MatrixCursor( arrayOf( Contract.LockScreenQuickAffordances.SlotTable.Columns.ID, @@ -330,7 +330,7 @@ class CustomizationProvider : } } - private fun queryFlags(): Cursor { + private suspend fun queryFlags(): Cursor { return MatrixCursor( arrayOf( Contract.FlagsTable.Columns.NAME, @@ -353,7 +353,7 @@ class CustomizationProvider : } } - private fun deleteSelection( + private suspend fun deleteSelection( uri: Uri, selectionArgs: Array<out String>?, ): Int { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 6db1f8959e8a..c857f8945b71 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -58,6 +58,7 @@ import android.hardware.biometrics.BiometricSourceType; import android.media.AudioAttributes; import android.media.AudioManager; import android.media.SoundPool; +import android.os.Binder; import android.os.Bundle; import android.os.DeadObjectException; import android.os.Handler; @@ -66,6 +67,7 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; @@ -101,6 +103,7 @@ import com.android.internal.policy.IKeyguardDismissCallback; import com.android.internal.policy.IKeyguardExitCallback; import com.android.internal.policy.IKeyguardStateCallback; import com.android.internal.policy.ScreenDecorationsUtils; +import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardConstants; @@ -269,6 +272,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private AlarmManager mAlarmManager; private AudioManager mAudioManager; private StatusBarManager mStatusBarManager; + private final IStatusBarService mStatusBarService; + private final IBinder mStatusBarDisableToken = new Binder(); private final UserTracker mUserTracker; private final SysuiStatusBarStateController mStatusBarStateController; private final Executor mUiBgExecutor; @@ -1211,6 +1216,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mPM = powerManager; mTrustManager = trustManager; mUserSwitcherController = userSwitcherController; + mStatusBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); mKeyguardDisplayManager = keyguardDisplayManager; mShadeController = shadeControllerLazy; dumpManager.registerDumpable(getClass().getName(), this); @@ -2931,7 +2938,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, // TODO (b/155663717) After restart, status bar will not properly hide home button // unless disable is called to show un-hide it once first if (forceClearFlags) { - mStatusBarManager.disable(flags); + try { + mStatusBarService.disableForUser(flags, mStatusBarDisableToken, + mContext.getPackageName(), mUserTracker.getUserId()); + } catch (RemoteException e) { + Log.d(TAG, "Failed to force clear flags", e); + } } if (forceHideHomeRecentsButtons || isShowingAndNotOccluded()) { @@ -2947,7 +2959,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, + " --> flags=0x" + Integer.toHexString(flags)); } - mStatusBarManager.disable(flags); + try { + mStatusBarService.disableForUser(flags, mStatusBarDisableToken, + mContext.getPackageName(), mUserTracker.getUserId()); + } catch (RemoteException e) { + Log.d(TAG, "Failed to set disable flags: " + flags, e); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt index 4331fe66a0dc..0e85347c24b0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt @@ -60,7 +60,6 @@ interface KeyguardBouncerRepository { */ val panelExpansionAmount: StateFlow<Float> val keyguardPosition: StateFlow<Float> - val onScreenTurnedOff: StateFlow<Boolean> val isBackButtonEnabled: StateFlow<Boolean?> /** Determines if user is already unlocked */ val keyguardAuthenticated: StateFlow<Boolean?> @@ -70,6 +69,8 @@ interface KeyguardBouncerRepository { val bouncerErrorMessage: CharSequence? val alternateBouncerVisible: StateFlow<Boolean> val alternateBouncerUIAvailable: StateFlow<Boolean> + val sideFpsShowing: StateFlow<Boolean> + var lastAlternateBouncerVisibleTime: Long fun setPrimaryScrimmed(isScrimmed: Boolean) @@ -98,11 +99,11 @@ interface KeyguardBouncerRepository { fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) - fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) - fun setAlternateVisible(isVisible: Boolean) fun setAlternateBouncerUIAvailable(isAvailable: Boolean) + + fun setSideFpsShowing(isShowing: Boolean) } @SysUISingleton @@ -142,8 +143,6 @@ constructor( override val panelExpansionAmount = _panelExpansionAmount.asStateFlow() private val _keyguardPosition = MutableStateFlow(0f) override val keyguardPosition = _keyguardPosition.asStateFlow() - private val _onScreenTurnedOff = MutableStateFlow(false) - override val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow() private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null) override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow() private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null) @@ -165,6 +164,8 @@ constructor( private val _alternateBouncerUIAvailable = MutableStateFlow(false) override val alternateBouncerUIAvailable: StateFlow<Boolean> = _alternateBouncerUIAvailable.asStateFlow() + private val _sideFpsShowing = MutableStateFlow(false) + override val sideFpsShowing: StateFlow<Boolean> = _sideFpsShowing.asStateFlow() init { setUpLogging() @@ -235,8 +236,8 @@ constructor( _isBackButtonEnabled.value = isBackButtonEnabled } - override fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) { - _onScreenTurnedOff.value = onScreenTurnedOff + override fun setSideFpsShowing(isShowing: Boolean) { + _sideFpsShowing.value = isShowing } /** Sets up logs for state flows. */ @@ -276,9 +277,6 @@ constructor( .map { it.toInt() } .logDiffsForTable(buffer, "", "KeyguardPosition", -1) .launchIn(applicationScope) - onScreenTurnedOff - .logDiffsForTable(buffer, "", "OnScreenTurnedOff", false) - .launchIn(applicationScope) isBackButtonEnabled .filterNotNull() .logDiffsForTable(buffer, "", "IsBackButtonEnabled", false) @@ -293,6 +291,9 @@ constructor( alternateBouncerUIAvailable .logDiffsForTable(buffer, "", "IsAlternateBouncerUIAvailable", false) .launchIn(applicationScope) + sideFpsShowing + .logDiffsForTable(buffer, "", "isSideFpsShowing", false) + .launchIn(applicationScope) } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index b2da793bb8e0..dfbe1c216847 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -18,12 +18,14 @@ package com.android.systemui.keyguard.domain.interactor import android.app.AlertDialog +import android.app.admin.DevicePolicyManager import android.content.Intent import android.util.Log import com.android.internal.widget.LockPatternUtils import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.animation.Expandable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig @@ -41,13 +43,17 @@ import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.KeyguardStateController import dagger.Lazy import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.withContext +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class KeyguardQuickAffordanceInteractor @Inject @@ -61,6 +67,8 @@ constructor( private val featureFlags: FeatureFlags, private val repository: Lazy<KeyguardQuickAffordanceRepository>, private val launchAnimator: DialogLaunchAnimator, + private val devicePolicyManager: DevicePolicyManager, + @Background private val backgroundDispatcher: CoroutineDispatcher, ) { private val isUsingRepository: Boolean get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES) @@ -74,9 +82,13 @@ constructor( get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES) /** Returns an observable for the quick affordance at the given position. */ - fun quickAffordance( + suspend fun quickAffordance( position: KeyguardQuickAffordancePosition ): Flow<KeyguardQuickAffordanceModel> { + if (isFeatureDisabledByDevicePolicy()) { + return flowOf(KeyguardQuickAffordanceModel.Hidden) + } + return combine( quickAffordanceAlwaysVisible(position), keyguardInteractor.isDozing, @@ -148,13 +160,20 @@ constructor( * * @return `true` if the affordance was selected successfully; `false` otherwise. */ - fun select(slotId: String, affordanceId: String): Boolean { + suspend fun select(slotId: String, affordanceId: String): Boolean { check(isUsingRepository) + if (isFeatureDisabledByDevicePolicy()) { + return false + } val slots = repository.get().getSlotPickerRepresentations() val slot = slots.find { it.id == slotId } ?: return false val selections = - repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList() + repository + .get() + .getCurrentSelections() + .getOrDefault(slotId, emptyList()) + .toMutableList() val alreadySelected = selections.remove(affordanceId) if (!alreadySelected) { while (selections.size > 0 && selections.size >= slot.maxSelectedAffordances) { @@ -183,8 +202,11 @@ constructor( * @return `true` if the affordance was successfully removed; `false` otherwise (for example, if * the affordance was not on the slot to begin with). */ - fun unselect(slotId: String, affordanceId: String?): Boolean { + suspend fun unselect(slotId: String, affordanceId: String?): Boolean { check(isUsingRepository) + if (isFeatureDisabledByDevicePolicy()) { + return false + } val slots = repository.get().getSlotPickerRepresentations() if (slots.find { it.id == slotId } == null) { @@ -203,7 +225,11 @@ constructor( } val selections = - repository.get().getCurrentSelections().getOrDefault(slotId, emptyList()).toMutableList() + repository + .get() + .getCurrentSelections() + .getOrDefault(slotId, emptyList()) + .toMutableList() return if (selections.remove(affordanceId)) { repository .get() @@ -219,6 +245,10 @@ constructor( /** Returns affordance IDs indexed by slot ID, for all known slots. */ suspend fun getSelections(): Map<String, List<KeyguardQuickAffordancePickerRepresentation>> { + if (isFeatureDisabledByDevicePolicy()) { + return emptyMap() + } + val slots = repository.get().getSlotPickerRepresentations() val selections = repository.get().getCurrentSelections() val affordanceById = @@ -343,13 +373,17 @@ constructor( return repository.get().getAffordancePickerRepresentations() } - fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> { + suspend fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> { check(isUsingRepository) + if (isFeatureDisabledByDevicePolicy()) { + return emptyList() + } + return repository.get().getSlotPickerRepresentations() } - fun getPickerFlags(): List<KeyguardPickerFlag> { + suspend fun getPickerFlags(): List<KeyguardPickerFlag> { return listOf( KeyguardPickerFlag( name = Contract.FlagsTable.FLAG_NAME_REVAMPED_WALLPAPER_UI, @@ -357,7 +391,9 @@ constructor( ), KeyguardPickerFlag( name = Contract.FlagsTable.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED, - value = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES), + value = + !isFeatureDisabledByDevicePolicy() && + featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES), ), KeyguardPickerFlag( name = Contract.FlagsTable.FLAG_NAME_CUSTOM_CLOCKS_ENABLED, @@ -374,6 +410,17 @@ constructor( ) } + private suspend fun isFeatureDisabledByDevicePolicy(): Boolean { + val flags = + withContext(backgroundDispatcher) { + devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId) + } + val flagsToCheck = + DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL or + DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL + return flagsToCheck and flags != 0 + } + companion object { private const val TAG = "KeyguardQuickAffordanceInteractor" private const val DELIMITER = "::" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt index 6610983a6ff6..c709fd18298c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.domain.interactor +import android.content.Context import android.content.res.ColorStateList import android.hardware.biometrics.BiometricSourceType import android.os.Handler @@ -23,9 +24,13 @@ import android.os.Trace import android.os.UserHandle import android.os.UserManager import android.view.View +import android.util.Log +import com.android.keyguard.KeyguardConstants import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.DejankUtils +import com.android.systemui.R import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main @@ -62,8 +67,9 @@ constructor( private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor, private val falsingCollector: FalsingCollector, private val dismissCallbackRegistry: DismissCallbackRegistry, + private val context: Context, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, keyguardBypassController: KeyguardBypassController, - keyguardUpdateMonitor: KeyguardUpdateMonitor, ) { /** Whether we want to wait for face auth. */ private val primaryBouncerFaceDelay = @@ -90,7 +96,6 @@ constructor( } val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull() - val screenTurnedOff: Flow<Unit> = repository.onScreenTurnedOff.filter { it }.map {} val show: Flow<KeyguardBouncerModel> = repository.primaryBouncerShow.filterNotNull() val hide: Flow<Unit> = repository.primaryBouncerHide.filter { it }.map {} val startingToHide: Flow<Unit> = repository.primaryBouncerStartingToHide.filter { it }.map {} @@ -115,6 +120,24 @@ constructor( } /** Allow for interaction when just about fully visible */ val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 } + val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing + + init { + keyguardUpdateMonitor.registerCallback( + object : KeyguardUpdateMonitorCallback() { + override fun onBiometricRunningStateChanged( + running: Boolean, + biometricSourceType: BiometricSourceType? + ) { + updateSideFpsVisibility() + } + + override fun onStrongAuthStateChanged(userId: Int) { + updateSideFpsVisibility() + } + } + ) + } // TODO(b/243685699): Move isScrimmed logic to data layer. // TODO(b/243695312): Encapsulate all of the show logic for the bouncer. @@ -122,7 +145,6 @@ constructor( @JvmOverloads fun show(isScrimmed: Boolean) { // Reset some states as we show the bouncer. - repository.setOnScreenTurnedOff(false) repository.setKeyguardAuthenticated(null) repository.setPrimaryHide(false) repository.setPrimaryStartingToHide(false) @@ -262,11 +284,6 @@ constructor( repository.setKeyguardAuthenticated(strongAuth) } - /** Tell the bouncer the screen has turned off. */ - fun onScreenTurnedOff() { - repository.setOnScreenTurnedOff(true) - } - /** Update the position of the bouncer when showing. */ fun setKeyguardPosition(position: Float) { repository.setKeyguardPosition(position) @@ -301,6 +318,35 @@ constructor( repository.setPrimaryStartDisappearAnimation(finishRunnable) } + /** Determine whether to show the side fps animation. */ + fun updateSideFpsVisibility() { + val sfpsEnabled: Boolean = + context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer) + val fpsDetectionRunning: Boolean = keyguardUpdateMonitor.isFingerprintDetectionRunning + val isUnlockingWithFpAllowed: Boolean = + keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed + val bouncerVisible = repository.primaryBouncerVisible.value + val toShow = + (repository.primaryBouncerVisible.value && + sfpsEnabled && + fpsDetectionRunning && + isUnlockingWithFpAllowed && + !isAnimatingAway()) + + if (KeyguardConstants.DEBUG) { + Log.d( + TAG, + ("sideFpsToShow=$toShow\n" + + "bouncerVisible=$bouncerVisible\n" + + "configEnabled=$sfpsEnabled\n" + + "fpsDetectionRunning=$fpsDetectionRunning\n" + + "isUnlockingWithFpAllowed=$isUnlockingWithFpAllowed\n" + + "isAnimatingAway=${isAnimatingAway()}") + ) + } + repository.setSideFpsShowing(toShow) + } + /** Returns whether bouncer is fully showing. */ fun isFullyShowing(): Boolean { return (repository.primaryBouncerShowingSoon.value || @@ -344,4 +390,8 @@ constructor( DejankUtils.removeCallbacks(showRunnable) mainHandler.removeCallbacks(showRunnable) } + + companion object { + private const val TAG = "PrimaryBouncerInteractor" + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt index 56f911f8b1da..7db567b2a0e9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt @@ -34,6 +34,7 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.ActivityStarter import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch @@ -121,7 +122,6 @@ object KeyguardBouncerViewBinder { launch { viewModel.hide.collect { securityContainerController.cancelDismissAction() - securityContainerController.onPause() securityContainerController.reset() } } @@ -155,13 +155,18 @@ object KeyguardBouncerViewBinder { launch { viewModel.isBouncerVisible.collect { isVisible -> - val visibility = if (isVisible) View.VISIBLE else View.INVISIBLE - view.visibility = visibility - securityContainerController.onBouncerVisibilityChanged(visibility) + view.visibility = if (isVisible) View.VISIBLE else View.INVISIBLE + securityContainerController.onBouncerVisibilityChanged(isVisible) } } launch { + viewModel.isBouncerVisible + .filter { !it } + .collect { securityContainerController.onPause() } + } + + launch { viewModel.isInteractable.collect { isInteractable -> securityContainerController.setInteractable(isInteractable) } @@ -204,10 +209,14 @@ object KeyguardBouncerViewBinder { } launch { - viewModel.screenTurnedOff.collect { - if (view.visibility == View.VISIBLE) { - securityContainerController.onPause() - } + viewModel.shouldUpdateSideFps.collect { + viewModel.updateSideFpsVisibility() + } + } + + launch { + viewModel.sideFpsShowing.collect { + securityContainerController.updateSideFpsVisibility(it) } } awaitCancellation() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt index b8b3a8e5db20..97e94d8f3232 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt @@ -24,7 +24,9 @@ import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge /** Models UI state for the lock screen bouncer; handles user input. */ class KeyguardBouncerViewModel @@ -66,8 +68,16 @@ constructor( /** Observe whether keyguard is authenticated already. */ val keyguardAuthenticated: Flow<Boolean> = interactor.keyguardAuthenticated - /** Observe whether screen is turned off. */ - val screenTurnedOff: Flow<Unit> = interactor.screenTurnedOff + /** Observe whether the side fps is showing. */ + val sideFpsShowing: Flow<Boolean> = interactor.sideFpsShowing + + /** Observe whether we should update fps is showing. */ + val shouldUpdateSideFps: Flow<Unit> = + merge( + interactor.startingToHide, + interactor.isVisible.map {}, + interactor.startingDisappearAnimation.filterNotNull().map {} + ) /** Observe whether we want to update resources. */ fun notifyUpdateResources() { @@ -84,6 +94,10 @@ constructor( interactor.onMessageShown() } + fun updateSideFpsVisibility() { + interactor.updateSideFpsVisibility() + } + /** Observe whether back button is enabled. */ fun observeOnIsBackButtonEnabled(systemUiVisibility: () -> Int): Flow<Int> { return interactor.isBackButtonEnabled.map { enabled -> diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt index dc7a4f18adbc..0b57175defe7 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt @@ -88,6 +88,8 @@ data class SmartspaceMediaData( } } +/** Key to indicate whether this card should be used to re-show recent media */ +const val EXTRA_KEY_TRIGGER_RESUME = "SHOULD_TRIGGER_RESUME" /** Key for extras [SmartspaceMediaData.cardAction] indicating why the card was sent */ const val EXTRA_KEY_TRIGGER_SOURCE = "MEDIA_RECOMMENDATION_TRIGGER_SOURCE" /** Value for [EXTRA_KEY_TRIGGER_SOURCE] when the card is sent on headphone connection */ diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt index 27f7b9736807..97717a64ce26 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt @@ -23,6 +23,7 @@ import com.android.internal.annotations.VisibleForTesting import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.controls.models.player.MediaData +import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_RESUME import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData import com.android.systemui.media.controls.util.MediaFlags import com.android.systemui.media.controls.util.MediaUiEventLogger @@ -138,14 +139,23 @@ constructor( val sorted = userEntries.toSortedMap(compareBy { userEntries.get(it)?.lastActive ?: -1 }) val timeSinceActive = timeSinceActiveForMostRecentMedia(sorted) var smartspaceMaxAgeMillis = SMARTSPACE_MAX_AGE - data.cardAction?.let { - val smartspaceMaxAgeSeconds = it.extras.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0) + data.cardAction?.extras?.let { + val smartspaceMaxAgeSeconds = it.getLong(RESUMABLE_MEDIA_MAX_AGE_SECONDS_KEY, 0) if (smartspaceMaxAgeSeconds > 0) { smartspaceMaxAgeMillis = TimeUnit.SECONDS.toMillis(smartspaceMaxAgeSeconds) } } - val shouldReactivate = !hasActiveMedia() && hasAnyMedia() && data.isActive + // Check if smartspace has explicitly specified whether to re-activate resumable media. + // The default behavior is to trigger if the smartspace data is active. + val shouldTriggerResume = + if (data.cardAction?.extras?.containsKey(EXTRA_KEY_TRIGGER_RESUME) == true) { + data.cardAction.extras.getBoolean(EXTRA_KEY_TRIGGER_RESUME, true) + } else { + true + } + val shouldReactivate = + shouldTriggerResume && !hasActiveMedia() && hasAnyMedia() && data.isActive if (timeSinceActive < smartspaceMaxAgeMillis) { // It could happen there are existing active media resume cards, then we don't need to diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt index 4ff082ad6e06..0b0535df6228 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt @@ -98,7 +98,7 @@ class ReceiverChipRippleView(context: Context?, attrs: AttributeSet?) : RippleVi // Calculates the actual starting percentage according to ripple shader progress set method. // Check calculations in [RippleShader.progress] fun calculateStartingPercentage(newHeight: Float): Float { - val ratio = rippleShader.currentHeight / newHeight + val ratio = rippleShader.rippleSize.currentHeight / newHeight val remainingPercentage = (1 - ratio).toDouble().pow(1 / 3.toDouble()).toFloat() return 1 - remainingPercentage } diff --git a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java index 245cf89a8337..27510720ae2f 100644 --- a/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java +++ b/packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java @@ -26,7 +26,10 @@ public class ProcessWrapper { @Inject public ProcessWrapper() {} - public int getUserHandleIdentifier() { - return android.os.Process.myUserHandle().getIdentifier(); + /** + * Returns {@code true} if System User is running the current process. + */ + public boolean isSystemUser() { + return android.os.Process.myUserHandle().isSystem(); } } diff --git a/packages/SystemUI/src/com/android/systemui/process/condition/UserProcessCondition.java b/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java index 5a21ea075ea3..80fbf9115065 100644 --- a/packages/SystemUI/src/com/android/systemui/process/condition/UserProcessCondition.java +++ b/packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java @@ -17,29 +17,26 @@ package com.android.systemui.process.condition; import com.android.systemui.process.ProcessWrapper; -import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.condition.Condition; import javax.inject.Inject; /** - * {@link UserProcessCondition} provides a signal when the process handle belongs to the current - * user. + * {@link SystemProcessCondition} checks to make sure the current process is being ran by the + * System User. */ -public class UserProcessCondition extends Condition { +public class SystemProcessCondition extends Condition { private final ProcessWrapper mProcessWrapper; - private final UserTracker mUserTracker; @Inject - public UserProcessCondition(ProcessWrapper processWrapper, UserTracker userTracker) { + public SystemProcessCondition(ProcessWrapper processWrapper) { + super(); mProcessWrapper = processWrapper; - mUserTracker = userTracker; } @Override protected void start() { - updateCondition(mUserTracker.getUserId() - == mProcessWrapper.getUserHandleIdentifier()); + updateCondition(mProcessWrapper.isSystemUser()); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 8ad102ece9b3..ae965d37557f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -51,9 +51,7 @@ import com.android.systemui.animation.Interpolators; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.compose.ComposeFacade; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; import com.android.systemui.media.controls.ui.MediaHost; -import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.qs.QSContainerController; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -61,6 +59,7 @@ import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSFragmentComponent; import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder; import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel; +import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -88,14 +87,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private final Rect mQsBounds = new Rect(); private final SysuiStatusBarStateController mStatusBarStateController; - private final FalsingManager mFalsingManager; private final KeyguardBypassController mBypassController; private boolean mQsExpanded; private boolean mHeaderAnimating; private boolean mStackScrollerOverscrolling; - private long mDelay; - private QSAnimator mQSAnimator; private HeightListener mPanelView; private QSSquishinessController mQSSquishinessController; @@ -116,8 +112,7 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private final MediaHost mQqsMediaHost; private final QSFragmentComponent.Factory mQsComponentFactory; private final QSFragmentDisableFlagsLogger mQsFragmentDisableFlagsLogger; - private final QSTileHost mHost; - private final FeatureFlags mFeatureFlags; + private final QSLogger mLogger; private final FooterActionsController mFooterActionsController; private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory; private final ListeningAndVisibilityLifecycleOwner mListeningAndVisibilityLifecycleOwner; @@ -150,11 +145,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca */ private boolean mTransitioningToFullShade; - /** - * Whether the next Quick settings - */ - private boolean mAnimateNextQsUpdate; - private final DumpManager mDumpManager; /** @@ -178,14 +168,13 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca @Inject public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, - QSTileHost qsTileHost, SysuiStatusBarStateController statusBarStateController, CommandQueue commandQueue, @Named(QS_PANEL) MediaHost qsMediaHost, @Named(QUICK_QS_PANEL) MediaHost qqsMediaHost, KeyguardBypassController keyguardBypassController, QSFragmentComponent.Factory qsComponentFactory, QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger, - FalsingManager falsingManager, DumpManager dumpManager, FeatureFlags featureFlags, + DumpManager dumpManager, QSLogger qsLogger, FooterActionsController footerActionsController, FooterActionsViewModel.Factory footerActionsViewModelFactory) { mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; @@ -193,13 +182,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca mQqsMediaHost = qqsMediaHost; mQsComponentFactory = qsComponentFactory; mQsFragmentDisableFlagsLogger = qsFragmentDisableFlagsLogger; + mLogger = qsLogger; commandQueue.observe(getLifecycle(), this); - mHost = qsTileHost; - mFalsingManager = falsingManager; mBypassController = keyguardBypassController; mStatusBarStateController = statusBarStateController; mDumpManager = dumpManager; - mFeatureFlags = featureFlags; mFooterActionsController = footerActionsController; mFooterActionsViewModelFactory = footerActionsViewModelFactory; mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner(); @@ -716,8 +703,10 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca private void setAlphaAnimationProgress(float progress) { final View view = getView(); if (progress == 0 && view.getVisibility() != View.INVISIBLE) { + mLogger.logVisibility("QS fragment", View.INVISIBLE); view.setVisibility(View.INVISIBLE); } else if (progress > 0 && view.getVisibility() != View.VISIBLE) { + mLogger.logVisibility("QS fragment", View.VISIBLE); view.setVisibility((View.VISIBLE)); } view.setAlpha(interpolateAlphaAnimationProgress(progress)); @@ -914,7 +903,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca getView().getViewTreeObserver().removeOnPreDrawListener(this); getView().animate() .translationY(0f) - .setStartDelay(mDelay) .setDuration(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .setListener(mAnimateHeaderSlidingInListener) diff --git a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt index d32ef327e90e..23c41db6d5a6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/logging/QSLogger.kt @@ -17,15 +17,14 @@ package com.android.systemui.qs.logging import android.service.quicksettings.Tile +import android.view.View import com.android.systemui.log.dagger.QSLog import com.android.systemui.plugins.log.ConstantStringsLogger import com.android.systemui.plugins.log.ConstantStringsLoggerImpl import com.android.systemui.plugins.log.LogBuffer -import com.android.systemui.plugins.log.LogLevel import com.android.systemui.plugins.log.LogLevel.DEBUG import com.android.systemui.plugins.log.LogLevel.ERROR import com.android.systemui.plugins.log.LogLevel.VERBOSE -import com.android.systemui.plugins.log.LogMessage import com.android.systemui.plugins.qs.QSTile import com.android.systemui.statusbar.StatusBarState import com.google.errorprone.annotations.CompileTimeConstant @@ -332,4 +331,25 @@ class QSLogger @Inject constructor(@QSLog private val buffer: LogBuffer) : else -> "wrong state" } } + + fun logVisibility(viewName: String, @View.Visibility visibility: Int) { + buffer.log( + TAG, + DEBUG, + { + str1 = viewName + str2 = toVisibilityString(visibility) + }, + { "$str1 visibility: $str2" } + ) + } + + private fun toVisibilityString(visibility: Int): String { + return when (visibility) { + View.VISIBLE -> "VISIBLE" + View.INVISIBLE -> "INVISIBLE" + View.GONE -> "GONE" + else -> "undefined" + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 27b0590f0204..c6c1a1bc071f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -2207,7 +2207,12 @@ public final class NotificationPanelViewController implements Dumpable { && mQsController.getFullyExpanded()) { // Upon initialisation when we are not layouted yet we don't want to announce that we // are fully expanded, hence the != 0.0f check. - return mResources.getString(R.string.accessibility_desc_quick_settings); + if (mSplitShadeEnabled) { + // In split shade, QS is expanded but it also shows notifications + return mResources.getString(R.string.accessibility_desc_qs_notification_shade); + } else { + return mResources.getString(R.string.accessibility_desc_quick_settings); + } } else if (mBarState == KEYGUARD) { return mResources.getString(R.string.accessibility_desc_lock_screen); } else { diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java index 1f0cbf9af51c..74a61a3efebe 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java @@ -16,7 +16,7 @@ package com.android.systemui.shade; -import static android.os.Trace.TRACE_TAG_ALWAYS; +import static android.os.Trace.TRACE_TAG_APP; import static android.view.WindowInsets.Type.systemBars; import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG; @@ -328,7 +328,7 @@ public class NotificationShadeWindowView extends FrameLayout { @Override public void requestLayout() { - Trace.instant(TRACE_TAG_ALWAYS, "NotificationShadeWindowView#requestLayout"); + Trace.instant(TRACE_TAG_APP, "NotificationShadeWindowView#requestLayout"); super.requestLayout(); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index 524b656f5653..43da50ab3c9a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -835,6 +835,7 @@ public class QuickSettingsController { @VisibleForTesting void setExpandImmediate(boolean expandImmediate) { if (expandImmediate != mExpandImmediate) { + mShadeLog.logQsExpandImmediateChanged(expandImmediate); mExpandImmediate = expandImmediate; mShadeExpansionStateManager.notifyExpandImmediateChange(expandImmediate); } @@ -1377,6 +1378,9 @@ public class QuickSettingsController { } private void collapseOrExpandQs() { + if (mSplitShadeEnabled) { + return; // QS is always expanded in split shade + } onExpansionStarted(); if (getExpanded()) { flingQs(0, FLING_COLLAPSE, null, true); diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt index 827bd1162c2c..d34e127b194b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt @@ -153,6 +153,17 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { ) } + fun logQsExpandImmediateChanged(newValue: Boolean) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + bool1 = newValue + }, + { "qsExpandImmediate=$bool1" } + ) + } + fun logQsExpansionChanged( message: String, qsExpanded: Boolean, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index 6ef6165bcbb3..29510d00a8d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -52,18 +52,19 @@ import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.WeatherData import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.settings.UserTracker import com.android.systemui.shared.regionsampling.RegionSampler import com.android.systemui.shared.regionsampling.UpdateColorCallback import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DATE_SMARTSPACE_DATA_PLUGIN import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.WEATHER_SMARTSPACE_DATA_PLUGIN -import com.android.systemui.plugins.Weather import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.util.concurrency.Execution import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.time.SystemClock import java.io.PrintWriter import java.time.Instant import java.util.Optional @@ -81,6 +82,7 @@ constructor( private val smartspaceManager: SmartspaceManager, private val activityStarter: ActivityStarter, private val falsingManager: FalsingManager, + private val systemClock: SystemClock, private val secureSettings: SecureSettings, private val userTracker: UserTracker, private val contentResolver: ContentResolver, @@ -152,6 +154,18 @@ constructor( // The weather data plugin takes unfiltered targets and performs the filtering internally. weatherPlugin?.onTargetsAvailable(targets) + val now = Instant.ofEpochMilli(systemClock.currentTimeMillis()) + val weatherTarget = targets.find { t -> + t.featureType == SmartspaceTarget.FEATURE_WEATHER && + now.isAfter(Instant.ofEpochMilli(t.creationTimeMillis)) && + now.isBefore(Instant.ofEpochMilli(t.expiryTimeMillis)) + } + if (weatherTarget != null) { + val weatherData = WeatherData.fromBundle(weatherTarget.baseAction.extras) + if (weatherData != null) { + keyguardUpdateMonitor.sendWeatherData(weatherData) + } + } val filteredTargets = targets.filter(::filterSmartspaceTarget) plugin?.onTargetsAvailable(filteredTargets) @@ -173,17 +187,6 @@ constructor( } isContentUpdatedOnce = true } - - val now = Instant.now() - val weatherTarget = targets.find { t -> - t.featureType == SmartspaceTarget.FEATURE_WEATHER && - now.isAfter(Instant.ofEpochMilli(t.creationTimeMillis)) && - now.isBefore(Instant.ofEpochMilli(t.expiryTimeMillis)) - } - if (weatherTarget != null) { - val weatherData = Weather.fromBundle(weatherTarget.baseAction.extras) - keyguardUpdateMonitor.sendWeatherData(weatherData) - } } private val userTrackerCallback = object : UserTracker.Callback { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java index df35c9e6832a..aa9a6c2c4cc6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java @@ -164,6 +164,11 @@ public class NotifCollection implements Dumpable, PipelineDumpable { private Queue<NotifEvent> mEventQueue = new ArrayDeque<>(); + private final Runnable mRebuildListRunnable = () -> { + if (mBuildListener != null) { + mBuildListener.onBuildList(mReadOnlyNotificationSet, "asynchronousUpdate"); + } + }; private boolean mAttached = false; private boolean mAmDispatchingToOtherCode; @@ -458,7 +463,7 @@ public class NotifCollection implements Dumpable, PipelineDumpable { int modificationType) { Assert.isMainThread(); mEventQueue.add(new ChannelChangedEvent(pkgName, user, channel, modificationType)); - dispatchEventsAndRebuildList("onNotificationChannelModified"); + dispatchEventsAndAsynchronouslyRebuildList(); } private void onNotificationsInitialized() { @@ -613,15 +618,39 @@ public class NotifCollection implements Dumpable, PipelineDumpable { private void dispatchEventsAndRebuildList(String reason) { Trace.beginSection("NotifCollection.dispatchEventsAndRebuildList"); + if (mMainHandler.hasCallbacks(mRebuildListRunnable)) { + mMainHandler.removeCallbacks(mRebuildListRunnable); + } + + dispatchEvents(); + + if (mBuildListener != null) { + mBuildListener.onBuildList(mReadOnlyNotificationSet, reason); + } + Trace.endSection(); + } + + private void dispatchEventsAndAsynchronouslyRebuildList() { + Trace.beginSection("NotifCollection.dispatchEventsAndAsynchronouslyRebuildList"); + + dispatchEvents(); + + if (!mMainHandler.hasCallbacks(mRebuildListRunnable)) { + mMainHandler.postDelayed(mRebuildListRunnable, 1000L); + } + + Trace.endSection(); + } + + private void dispatchEvents() { + Trace.beginSection("NotifCollection.dispatchEvents"); + mAmDispatchingToOtherCode = true; while (!mEventQueue.isEmpty()) { mEventQueue.remove().dispatchTo(mNotifCollectionListeners); } mAmDispatchingToOtherCode = false; - if (mBuildListener != null) { - mBuildListener.onBuildList(mReadOnlyNotificationSet, reason); - } Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt index cc1103de8ec0..abe067039cd9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLogger.kt @@ -20,6 +20,7 @@ package com.android.systemui.statusbar.notification.logging import android.app.StatsManager import android.util.Log import android.util.StatsEvent +import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -143,67 +144,70 @@ constructor( runBlocking(mainDispatcher) { traceSection("NML#getNotifications") { notificationPipeline.allNotifs } } +} - /** Aggregates memory usage data by package and style, returning sums. */ - private fun aggregateMemoryUsageData( - notificationMemoryUse: List<NotificationMemoryUsage> - ): Map<Pair<String, Int>, NotificationMemoryUseAtomBuilder> { - return notificationMemoryUse - .groupingBy { Pair(it.packageName, it.objectUsage.style) } - .aggregate { - _, - accumulator: NotificationMemoryUseAtomBuilder?, - element: NotificationMemoryUsage, - first -> - val use = - if (first) { - NotificationMemoryUseAtomBuilder(element.uid, element.objectUsage.style) - } else { - accumulator!! - } - - use.count++ - // If the views of the notification weren't inflated, the list of memory usage - // parameters will be empty. - if (element.viewUsage.isNotEmpty()) { - use.countWithInflatedViews++ +/** Aggregates memory usage data by package and style, returning sums. */ +@VisibleForTesting +internal fun aggregateMemoryUsageData( + notificationMemoryUse: List<NotificationMemoryUsage> +): Map<Pair<String, Int>, NotificationMemoryLogger.NotificationMemoryUseAtomBuilder> { + return notificationMemoryUse + .groupingBy { Pair(it.packageName, it.objectUsage.style) } + .aggregate { + _, + accumulator: NotificationMemoryLogger.NotificationMemoryUseAtomBuilder?, + element: NotificationMemoryUsage, + first -> + val use = + if (first) { + NotificationMemoryLogger.NotificationMemoryUseAtomBuilder( + element.uid, + element.objectUsage.style + ) + } else { + accumulator!! } - use.smallIconObject += element.objectUsage.smallIcon - if (element.objectUsage.smallIcon > 0) { - use.smallIconBitmapCount++ - } + use.count++ + // If the views of the notification weren't inflated, the list of memory usage + // parameters will be empty. + if (element.viewUsage.isNotEmpty()) { + use.countWithInflatedViews++ + } - use.largeIconObject += element.objectUsage.largeIcon - if (element.objectUsage.largeIcon > 0) { - use.largeIconBitmapCount++ - } + use.smallIconObject += element.objectUsage.smallIcon + if (element.objectUsage.smallIcon > 0) { + use.smallIconBitmapCount++ + } - use.bigPictureObject += element.objectUsage.bigPicture - if (element.objectUsage.bigPicture > 0) { - use.bigPictureBitmapCount++ - } + use.largeIconObject += element.objectUsage.largeIcon + if (element.objectUsage.largeIcon > 0) { + use.largeIconBitmapCount++ + } - use.extras += element.objectUsage.extras - use.extenders += element.objectUsage.extender - - // Use totals count which are more accurate when aggregated - // in this manner. - element.viewUsage - .firstOrNull { vu -> vu.viewType == ViewType.TOTAL } - ?.let { - use.smallIconViews += it.smallIcon - use.largeIconViews += it.largeIcon - use.systemIconViews += it.systemIcons - use.styleViews += it.style - use.customViews += it.style - use.softwareBitmaps += it.softwareBitmapsPenalty - } - - return@aggregate use + use.bigPictureObject += element.objectUsage.bigPicture + if (element.objectUsage.bigPicture > 0) { + use.bigPictureBitmapCount++ } - } - /** Rounds the passed value to the nearest KB - e.g. 700B rounds to 1KB. */ - private fun toKb(value: Int): Int = (value.toFloat() / 1024f).roundToInt() + use.extras += element.objectUsage.extras + use.extenders += element.objectUsage.extender + + // Use totals count which are more accurate when aggregated + // in this manner. + element.viewUsage + .firstOrNull { vu -> vu.viewType == ViewType.TOTAL } + ?.let { + use.smallIconViews += it.smallIcon + use.largeIconViews += it.largeIcon + use.systemIconViews += it.systemIcons + use.styleViews += it.style + use.customViews += it.customViews + use.softwareBitmaps += it.softwareBitmapsPenalty + } + + return@aggregate use + } } +/** Rounds the passed value to the nearest KB - e.g. 700B rounds to 1KB. */ +private fun toKb(value: Int): Int = (value.toFloat() / 1024f).roundToInt() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java index 21f4cb566e28..49f17b664a20 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java @@ -88,6 +88,7 @@ public class FooterView extends StackScrollerDecorView { mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer); updateResources(); updateText(); + updateColors(); } public void setFooterLabelTextAndIcon(@StringRes int text, @DrawableRes int icon) { 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 1fb7eb5106e6..d2087ba6ca1c 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 @@ -16,7 +16,7 @@ package com.android.systemui.statusbar.notification.stack; -import static android.os.Trace.TRACE_TAG_ALWAYS; +import static android.os.Trace.TRACE_TAG_APP; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL; @@ -1121,7 +1121,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable @Override public void requestLayout() { - Trace.instant(TRACE_TAG_ALWAYS, "NotificationStackScrollLayout#requestLayout"); + Trace.instant(TRACE_TAG_APP, "NotificationStackScrollLayout#requestLayout"); super.requestLayout(); } 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 39281da09749..b0e4668e2a45 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -790,7 +790,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Override public void onFinishedGoingToSleep() { - mPrimaryBouncerInteractor.onScreenTurnedOff(); + mPrimaryBouncerInteractor.hide(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 52eef421eb8c..b847d67b448e 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -1398,6 +1398,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, @Override public void onAnimationCancel(@NonNull Animator animation) { mInteractionJankMonitor.cancel(CUJ_VOLUME_CONTROL); + Log.d(TAG, "onAnimationCancel"); } @Override @@ -1471,6 +1472,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mHandler.removeMessages(H.DISMISS); mHandler.removeMessages(H.SHOW); if (mIsAnimatingDismiss) { + Log.d(TAG, "dismissH: isAnimatingDismiss"); return; } mIsAnimatingDismiss = true; diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index bffbe17fb2eb..bbc7bc92e819 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -32,11 +32,9 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -44,7 +42,6 @@ import static org.mockito.Mockito.when; import android.content.res.Configuration; import android.content.res.Resources; import android.hardware.biometrics.BiometricOverlayConstants; -import android.hardware.biometrics.BiometricSourceType; import android.media.AudioManager; import android.telephony.TelephonyManager; import android.testing.AndroidTestingRunner; @@ -53,7 +50,6 @@ import android.testing.TestableResources; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.View; import android.view.WindowInsetsController; import android.widget.FrameLayout; @@ -96,10 +92,8 @@ import java.util.Optional; @TestableLooper.RunWithLooper() public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { private static final int TARGET_USER_ID = 100; - @Rule public MockitoRule mRule = MockitoJUnit.rule(); - @Mock private KeyguardSecurityContainer mView; @Mock @@ -368,134 +362,12 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { } @Test - public void onBouncerVisibilityChanged_allConditionsGood_sideFpsHintShown() { - setupConditionsToEnableSideFpsHint(); - reset(mSideFpsController); - - mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE); - - verify(mSideFpsController).show(SideFpsUiRequestSource.PRIMARY_BOUNCER, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD); - verify(mSideFpsController, never()).hide(any()); - } - - @Test - public void onBouncerVisibilityChanged_fpsSensorNotRunning_sideFpsHintHidden() { - setupConditionsToEnableSideFpsHint(); - setFingerprintDetectionRunning(false); - reset(mSideFpsController); - - mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE); - - verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER); - verify(mSideFpsController, never()).show(any(), anyInt()); - } - - @Test - public void onBouncerVisibilityChanged_withoutSidedSecurity_sideFpsHintHidden() { - setupConditionsToEnableSideFpsHint(); - setSideFpsHintEnabledFromResources(false); - reset(mSideFpsController); - - mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE); - - verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER); - verify(mSideFpsController, never()).show(any(), anyInt()); - } - - @Test - public void onBouncerVisibilityChanged_unlockingWithFingerprintNotAllowed_sideFpsHintHidden() { - setupConditionsToEnableSideFpsHint(); - setUnlockingWithFingerprintAllowed(false); - reset(mSideFpsController); - - mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE); - - verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER); - verify(mSideFpsController, never()).show(any(), anyInt()); - } - - @Test - public void onBouncerVisibilityChanged_sideFpsHintShown_sideFpsHintHidden() { - setupGetSecurityView(); - setupConditionsToEnableSideFpsHint(); - mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE); - verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD); - reset(mSideFpsController); - - mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.INVISIBLE); - - verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER); - verify(mSideFpsController, never()).show(any(), anyInt()); - } - - @Test public void onBouncerVisibilityChanged_resetsScale() { - mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.INVISIBLE); - + mKeyguardSecurityContainerController.onBouncerVisibilityChanged(false); verify(mView).resetScale(); } @Test - public void onStartingToHide_sideFpsHintShown_sideFpsHintHidden() { - setupGetSecurityView(); - setupConditionsToEnableSideFpsHint(); - mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE); - verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD); - reset(mSideFpsController); - - mKeyguardSecurityContainerController.onStartingToHide(); - - verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER); - verify(mSideFpsController, never()).show(any(), anyInt()); - } - - @Test - public void onPause_sideFpsHintShown_sideFpsHintHidden() { - setupGetSecurityView(); - setupConditionsToEnableSideFpsHint(); - mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE); - verify(mSideFpsController, atLeastOnce()).show(SideFpsUiRequestSource.PRIMARY_BOUNCER, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD); - reset(mSideFpsController); - - mKeyguardSecurityContainerController.onPause(); - - verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER); - verify(mSideFpsController, never()).show(any(), anyInt()); - } - - @Test - public void onResume_sideFpsHintShouldBeShown_sideFpsHintShown() { - setupGetSecurityView(); - setupConditionsToEnableSideFpsHint(); - mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE); - reset(mSideFpsController); - - mKeyguardSecurityContainerController.onResume(0); - - verify(mSideFpsController).show(SideFpsUiRequestSource.PRIMARY_BOUNCER, - BiometricOverlayConstants.REASON_AUTH_KEYGUARD); - verify(mSideFpsController, never()).hide(any()); - } - - @Test - public void onResume_sideFpsHintShouldNotBeShown_sideFpsHintHidden() { - setupGetSecurityView(); - setupConditionsToEnableSideFpsHint(); - setSideFpsHintEnabledFromResources(false); - mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE); - reset(mSideFpsController); - - mKeyguardSecurityContainerController.onResume(0); - - verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER); - verify(mSideFpsController, never()).show(any(), anyInt()); - } - - @Test public void showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() { // GIVEN the current security method is SimPin when(mKeyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false); @@ -719,39 +591,31 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { any(KeyguardSecurityCallback.class)); } + @Test + public void testSideFpsControllerShow() { + mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ true); + verify(mSideFpsController).show( + SideFpsUiRequestSource.PRIMARY_BOUNCER, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD); + } + + @Test + public void testSideFpsControllerHide() { + mKeyguardSecurityContainerController.updateSideFpsVisibility(/* isVisible= */ false); + verify(mSideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER); + } + private KeyguardSecurityContainer.SwipeListener getRegisteredSwipeListener() { mKeyguardSecurityContainerController.onViewAttached(); verify(mView).setSwipeListener(mSwipeListenerArgumentCaptor.capture()); return mSwipeListenerArgumentCaptor.getValue(); } - private void setupConditionsToEnableSideFpsHint() { - attachView(); - setSideFpsHintEnabledFromResources(true); - setFingerprintDetectionRunning(true); - setUnlockingWithFingerprintAllowed(true); - } - private void attachView() { mKeyguardSecurityContainerController.onViewAttached(); verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallback.capture()); } - private void setFingerprintDetectionRunning(boolean running) { - when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(running); - mKeyguardUpdateMonitorCallback.getValue().onBiometricRunningStateChanged(running, - BiometricSourceType.FINGERPRINT); - } - - private void setSideFpsHintEnabledFromResources(boolean enabled) { - mTestableResources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, - enabled); - } - - private void setUnlockingWithFingerprintAllowed(boolean allowed) { - when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(allowed); - } - private void setupGetSecurityView() { when(mKeyguardSecurityViewFlipperController.getSecurityView( any(), any(KeyguardSecurityCallback.class))) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java index 6b80494a0c30..dc90e2d0a656 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java @@ -28,6 +28,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN; import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED; import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE; +import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING; import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING; import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT; import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT; @@ -1166,10 +1167,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase { assertThat(mKeyguardUpdateMonitor.isFingerprintLockedOut()).isEqualTo(fpLocked); assertThat(mKeyguardUpdateMonitor.isFaceLockedOut()).isEqualTo(faceLocked); - // Fingerprint should be restarted once its cancelled bc on lockout, the device - // can still detectFingerprint (and if it's not locked out, fingerprint can listen) + // Fingerprint should be cancelled on lockout if going to lockout state, else + // restarted if it's not assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState) - .isEqualTo(BIOMETRIC_STATE_CANCELLING_RESTARTING); + .isEqualTo(fpLocked + ? BIOMETRIC_STATE_CANCELLING : BIOMETRIC_STATE_CANCELLING_RESTARTING); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt index c73ff1dab3d8..54c9d392ad1c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt @@ -86,8 +86,9 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle mock(PrimaryBouncerCallbackInteractor::class.java), mock(FalsingCollector::class.java), mock(DismissCallbackRegistry::class.java), + context, + mKeyguardUpdateMonitor, mock(KeyguardBypassController::class.java), - mKeyguardUpdateMonitor ) mAlternateBouncerInteractor = AlternateBouncerInteractor( diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java index e4df754ec96a..8cb91304808d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineClassifierTest.java @@ -106,7 +106,7 @@ public class BrightLineClassifierTest extends SysuiTestCase { mClassifiers.add(mClassifierB); when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList); when(mKeyguardStateController.isShowing()).thenReturn(true); - when(mFalsingDataProvider.isFolded()).thenReturn(true); + when(mFalsingDataProvider.isUnfolded()).thenReturn(false); mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider, mMetricsLogger, mClassifiers, mSingleTapClassfier, mLongTapClassifier, mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java index ae38eb67c431..315774aad71a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java @@ -89,7 +89,7 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { mClassifiers.add(mClassifierA); when(mFalsingDataProvider.getRecentMotionEvents()).thenReturn(mMotionEventList); when(mKeyguardStateController.isShowing()).thenReturn(true); - when(mFalsingDataProvider.isFolded()).thenReturn(true); + when(mFalsingDataProvider.isUnfolded()).thenReturn(false); mBrightLineFalsingManager = new BrightLineFalsingManager(mFalsingDataProvider, mMetricsLogger, mClassifiers, mSingleTapClassifier, mLongTapClassifier, mDoubleTapClassifier, mHistoryTracker, mKeyguardStateController, @@ -185,7 +185,7 @@ public class BrightLineFalsingManagerTest extends SysuiTestCase { @Test public void testSkipUnfolded() { assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue(); - when(mFalsingDataProvider.isFolded()).thenReturn(false); + when(mFalsingDataProvider.isUnfolded()).thenReturn(true); assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isFalse(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java index c451a1e754c9..2edc3d361316 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java @@ -324,12 +324,18 @@ public class FalsingDataProviderTest extends ClassifierTest { @Test public void test_FoldedState_Folded() { when(mFoldStateListener.getFolded()).thenReturn(true); - assertThat(mDataProvider.isFolded()).isTrue(); + assertThat(mDataProvider.isUnfolded()).isFalse(); } @Test public void test_FoldedState_Unfolded() { when(mFoldStateListener.getFolded()).thenReturn(false); - assertThat(mDataProvider.isFolded()).isFalse(); + assertThat(mDataProvider.isUnfolded()).isTrue(); + } + + @Test + public void test_FoldedState_NotFoldable() { + when(mFoldStateListener.getFolded()).thenReturn(null); + assertThat(mDataProvider.isUnfolded()).isFalse(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt index a12315b63fa7..2e9800606edf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt @@ -26,6 +26,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -58,4 +59,16 @@ class ServerFlagReaderImplTest : SysuiTestCase() { verify(changeListener).onChange(flag) } + + @Test + fun testChange_ignoresListenersDuringTest() { + val serverFlagReader = ServerFlagReaderImpl(NAMESPACE, deviceConfig, executor, true) + val flag = ReleasedFlag(1, "1", "test") + serverFlagReader.listenForChanges(listOf(flag), changeListener) + + deviceConfig.setProperty(NAMESPACE, "flag_override_1", "1", false) + executor.runAllReady() + + verify(changeListener, never()).onChange(flag) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt new file mode 100644 index 000000000000..f6ff4b214035 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyboard.data.repository + +import android.hardware.input.InputManager +import android.view.InputDevice +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.nullable +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(JUnit4::class) +class KeyboardRepositoryTest : SysuiTestCase() { + + @Captor + private lateinit var deviceListenerCaptor: ArgumentCaptor<InputManager.InputDeviceListener> + @Mock private lateinit var inputManager: InputManager + + private lateinit var underTest: KeyboardRepository + private lateinit var dispatcher: CoroutineDispatcher + private lateinit var testScope: TestScope + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf()) + whenever(inputManager.getInputDevice(any())).then { invocation -> + val id = invocation.arguments.first() + INPUT_DEVICES_MAP[id] + } + dispatcher = StandardTestDispatcher() + testScope = TestScope(dispatcher) + underTest = KeyboardRepositoryImpl(testScope.backgroundScope, dispatcher, inputManager) + } + + @Test + fun emitsDisconnected_ifNothingIsConnected() = + testScope.runTest { + val initialState = underTest.keyboardConnected.first() + assertThat(initialState).isFalse() + } + + @Test + fun emitsConnected_ifKeyboardAlreadyConnectedAtTheStart() = + testScope.runTest { + whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(PHYSICAL_FULL_KEYBOARD_ID)) + val initialValue = underTest.keyboardConnected.first() + assertThat(initialValue).isTrue() + } + + @Test + fun emitsConnected_whenNewPhysicalKeyboardConnects() = + testScope.runTest { + val deviceListener = captureDeviceListener() + val isKeyboardConnected by collectLastValue(underTest.keyboardConnected) + + deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) + + assertThat(isKeyboardConnected).isTrue() + } + + @Test + fun emitsDisconnected_whenKeyboardDisconnects() = + testScope.runTest { + val deviceListener = captureDeviceListener() + val isKeyboardConnected by collectLastValue(underTest.keyboardConnected) + + deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) + assertThat(isKeyboardConnected).isTrue() + + deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID) + assertThat(isKeyboardConnected).isFalse() + } + + private suspend fun captureDeviceListener(): InputManager.InputDeviceListener { + underTest.keyboardConnected.first() + verify(inputManager).registerInputDeviceListener(deviceListenerCaptor.capture(), nullable()) + return deviceListenerCaptor.value + } + + @Test + fun emitsDisconnected_whenVirtualOrNotFullKeyboardConnects() = + testScope.runTest { + val deviceListener = captureDeviceListener() + val isKeyboardConnected by collectLastValue(underTest.keyboardConnected) + + deviceListener.onInputDeviceAdded(PHYSICAL_NOT_FULL_KEYBOARD_ID) + assertThat(isKeyboardConnected).isFalse() + + deviceListener.onInputDeviceAdded(VIRTUAL_FULL_KEYBOARD_ID) + assertThat(isKeyboardConnected).isFalse() + } + + @Test + fun emitsDisconnected_whenKeyboardDisconnectsAndWasAlreadyConnectedAtTheStart() = + testScope.runTest { + val deviceListener = captureDeviceListener() + val isKeyboardConnected by collectLastValue(underTest.keyboardConnected) + + deviceListener.onInputDeviceRemoved(PHYSICAL_FULL_KEYBOARD_ID) + assertThat(isKeyboardConnected).isFalse() + } + + @Test + fun emitsConnected_whenAnotherDeviceDisconnects() = + testScope.runTest { + val deviceListener = captureDeviceListener() + val isKeyboardConnected by collectLastValue(underTest.keyboardConnected) + + deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) + deviceListener.onInputDeviceRemoved(VIRTUAL_FULL_KEYBOARD_ID) + + assertThat(isKeyboardConnected).isTrue() + } + + @Test + fun emitsConnected_whenOnePhysicalKeyboardDisconnectsButAnotherRemainsConnected() = + testScope.runTest { + val deviceListener = captureDeviceListener() + val isKeyboardConnected by collectLastValue(underTest.keyboardConnected) + + deviceListener.onInputDeviceAdded(PHYSICAL_FULL_KEYBOARD_ID) + deviceListener.onInputDeviceAdded(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) + deviceListener.onInputDeviceRemoved(ANOTHER_PHYSICAL_FULL_KEYBOARD_ID) + + assertThat(isKeyboardConnected).isTrue() + } + + @Test + fun passesKeyboardBacklightValues_fromBacklightListener() { + // TODO(b/268645734): implement when implementing backlight listener + } + + private companion object { + private const val PHYSICAL_FULL_KEYBOARD_ID = 1 + private const val VIRTUAL_FULL_KEYBOARD_ID = 2 + private const val PHYSICAL_NOT_FULL_KEYBOARD_ID = 3 + private const val ANOTHER_PHYSICAL_FULL_KEYBOARD_ID = 4 + + private val INPUT_DEVICES_MAP: Map<Int, InputDevice> = + mapOf( + PHYSICAL_FULL_KEYBOARD_ID to inputDevice(virtual = false, fullKeyboard = true), + VIRTUAL_FULL_KEYBOARD_ID to inputDevice(virtual = true, fullKeyboard = true), + PHYSICAL_NOT_FULL_KEYBOARD_ID to inputDevice(virtual = false, fullKeyboard = false), + ANOTHER_PHYSICAL_FULL_KEYBOARD_ID to + inputDevice(virtual = false, fullKeyboard = true) + ) + + private fun inputDevice(virtual: Boolean, fullKeyboard: Boolean): InputDevice = + mock<InputDevice>().also { + whenever(it.isVirtual).thenReturn(virtual) + whenever(it.isFullKeyboard).thenReturn(fullKeyboard) + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt index 15a454b3a24e..a4e5bcaecde4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard +import android.app.admin.DevicePolicyManager import android.content.ContentValues import android.content.pm.PackageManager import android.content.pm.ProviderInfo @@ -61,7 +62,6 @@ import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest @@ -90,6 +90,7 @@ class CustomizationProviderTest : SysuiTestCase() { @Mock private lateinit var previewSurfacePackage: SurfaceControlViewHost.SurfacePackage @Mock private lateinit var launchAnimator: DialogLaunchAnimator @Mock private lateinit var commandQueue: CommandQueue + @Mock private lateinit var devicePolicyManager: DevicePolicyManager private lateinit var underTest: CustomizationProvider private lateinit var testScope: TestScope @@ -102,7 +103,7 @@ class CustomizationProviderTest : SysuiTestCase() { whenever(backgroundHandler.looper).thenReturn(TestableLooper.get(this).looper) underTest = CustomizationProvider() - val testDispatcher = StandardTestDispatcher() + val testDispatcher = UnconfinedTestDispatcher() testScope = TestScope(testDispatcher) val localUserSelectionManager = KeyguardQuickAffordanceLocalUserSelectionManager( @@ -183,6 +184,8 @@ class CustomizationProviderTest : SysuiTestCase() { featureFlags = featureFlags, repository = { quickAffordanceRepository }, launchAnimator = launchAnimator, + devicePolicyManager = devicePolicyManager, + backgroundDispatcher = testDispatcher, ) underTest.previewManager = KeyguardRemotePreviewManager( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index 23e06ec181c0..84ec125bfa55 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor +import android.app.admin.DevicePolicyManager import android.content.Intent import android.os.UserHandle import androidx.test.filters.SmallTest @@ -54,7 +55,10 @@ import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -70,6 +74,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(Parameterized::class) class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { @@ -219,8 +224,10 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { @Mock private lateinit var expandable: Expandable @Mock private lateinit var launchAnimator: DialogLaunchAnimator @Mock private lateinit var commandQueue: CommandQueue + @Mock private lateinit var devicePolicyManager: DevicePolicyManager private lateinit var underTest: KeyguardQuickAffordanceInteractor + private lateinit var testScope: TestScope @JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false @JvmField @Parameter(1) var canShowWhileLocked: Boolean = false @@ -292,6 +299,8 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false) set(Flags.FACE_AUTH_REFACTOR, true) } + val testDispatcher = StandardTestDispatcher() + testScope = TestScope(testDispatcher) underTest = KeyguardQuickAffordanceInteractor( keyguardInteractor = @@ -322,58 +331,61 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { featureFlags = featureFlags, repository = { quickAffordanceRepository }, launchAnimator = launchAnimator, + devicePolicyManager = devicePolicyManager, + backgroundDispatcher = testDispatcher, ) } @Test - fun onQuickAffordanceTriggered() = runBlockingTest { - setUpMocks( - needStrongAuthAfterBoot = needStrongAuthAfterBoot, - keyguardIsUnlocked = keyguardIsUnlocked, - ) + fun onQuickAffordanceTriggered() = + testScope.runTest { + setUpMocks( + needStrongAuthAfterBoot = needStrongAuthAfterBoot, + keyguardIsUnlocked = keyguardIsUnlocked, + ) - homeControls.setState( - lockScreenState = - KeyguardQuickAffordanceConfig.LockScreenState.Visible( - icon = DRAWABLE, - ) - ) - homeControls.onTriggeredResult = - if (startActivity) { - KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity( - intent = INTENT, - canShowWhileLocked = canShowWhileLocked, - ) - } else { - KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled - } + homeControls.setState( + lockScreenState = + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + icon = DRAWABLE, + ) + ) + homeControls.onTriggeredResult = + if (startActivity) { + KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity( + intent = INTENT, + canShowWhileLocked = canShowWhileLocked, + ) + } else { + KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } - underTest.onQuickAffordanceTriggered( - configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS, - expandable = expandable, - ) + underTest.onQuickAffordanceTriggered( + configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS, + expandable = expandable, + ) - if (startActivity) { - if (needsToUnlockFirst) { - verify(activityStarter) - .postStartActivityDismissingKeyguard( - any(), - /* delay= */ eq(0), - same(animationController), - ) + if (startActivity) { + if (needsToUnlockFirst) { + verify(activityStarter) + .postStartActivityDismissingKeyguard( + any(), + /* delay= */ eq(0), + same(animationController), + ) + } else { + verify(activityStarter) + .startActivity( + any(), + /* dismissShade= */ eq(true), + same(animationController), + /* showOverLockscreenWhenLocked= */ eq(true), + ) + } } else { - verify(activityStarter) - .startActivity( - any(), - /* dismissShade= */ eq(true), - same(animationController), - /* showOverLockscreenWhenLocked= */ eq(true), - ) + verifyZeroInteractions(activityStarter) } - } else { - verifyZeroInteractions(activityStarter) } - } private fun setUpMocks( needStrongAuthAfterBoot: Boolean = true, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index 1b8c6273e2d8..62c9e5ffbb51 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.domain.interactor +import android.app.admin.DevicePolicyManager import android.os.UserHandle import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -78,6 +79,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var launchAnimator: DialogLaunchAnimator @Mock private lateinit var commandQueue: CommandQueue + @Mock private lateinit var devicePolicyManager: DevicePolicyManager private lateinit var underTest: KeyguardQuickAffordanceInteractor @@ -184,6 +186,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { featureFlags = featureFlags, repository = { quickAffordanceRepository }, launchAnimator = launchAnimator, + devicePolicyManager = devicePolicyManager, + backgroundDispatcher = testDispatcher, ) } @@ -239,6 +243,44 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { } @Test + fun `quickAffordance - hidden when all features are disabled by device policy`() = + testScope.runTest { + whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL) + quickAccessWallet.setState( + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + icon = ICON, + ) + ) + + val collectedValue by + collectLastValue( + underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END) + ) + + assertThat(collectedValue).isInstanceOf(KeyguardQuickAffordanceModel.Hidden::class.java) + } + + @Test + fun `quickAffordance - hidden when shortcuts feature is disabled by device policy`() = + testScope.runTest { + whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL) + quickAccessWallet.setState( + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + icon = ICON, + ) + ) + + val collectedValue by + collectLastValue( + underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END) + ) + + assertThat(collectedValue).isInstanceOf(KeyguardQuickAffordanceModel.Hidden::class.java) + } + + @Test fun `quickAffordance - bottom start affordance hidden while dozing`() = testScope.runTest { repository.setDozing(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt index 46ed829e0574..6b7fd616e678 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt @@ -19,11 +19,13 @@ package com.android.systemui.keyguard.domain.interactor import android.os.Looper import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import android.testing.TestableResources import android.view.View import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityModel import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.DejankUtils +import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.keyguard.DismissCallbackRegistry @@ -69,6 +71,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor private val mainHandler = FakeHandler(Looper.getMainLooper()) private lateinit var underTest: PrimaryBouncerInteractor + private lateinit var resources: TestableResources @Before fun setUp() { @@ -84,18 +87,19 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { mPrimaryBouncerCallbackInteractor, falsingCollector, dismissCallbackRegistry, - keyguardBypassController, + context, keyguardUpdateMonitor, + keyguardBypassController, ) `when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null) `when`(repository.primaryBouncerShow.value).thenReturn(null) `when`(bouncerView.delegate).thenReturn(bouncerViewDelegate) + resources = context.orCreateTestableResources } @Test fun testShow_isScrimmed() { underTest.show(true) - verify(repository).setOnScreenTurnedOff(false) verify(repository).setKeyguardAuthenticated(null) verify(repository).setPrimaryHide(false) verify(repository).setPrimaryStartingToHide(false) @@ -207,12 +211,6 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { } @Test - fun testOnScreenTurnedOff() { - underTest.onScreenTurnedOff() - verify(repository).setOnScreenTurnedOff(true) - } - - @Test fun testSetKeyguardPosition() { underTest.setKeyguardPosition(0f) verify(repository).setKeyguardPosition(0f) @@ -286,4 +284,98 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() { `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false) assertThat(underTest.willDismissWithAction()).isFalse() } + + @Test + fun testSideFpsVisibility() { + updateSideFpsVisibilityParameters( + isVisible = true, + sfpsEnabled = true, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true, + isAnimatingAway = false + ) + underTest.updateSideFpsVisibility() + verify(repository).setSideFpsShowing(true) + } + + @Test + fun testSideFpsVisibility_notVisible() { + updateSideFpsVisibilityParameters( + isVisible = false, + sfpsEnabled = true, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true, + isAnimatingAway = false + ) + underTest.updateSideFpsVisibility() + verify(repository).setSideFpsShowing(false) + } + + @Test + fun testSideFpsVisibility_sfpsNotEnabled() { + updateSideFpsVisibilityParameters( + isVisible = true, + sfpsEnabled = false, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true, + isAnimatingAway = false + ) + underTest.updateSideFpsVisibility() + verify(repository).setSideFpsShowing(false) + } + + @Test + fun testSideFpsVisibility_fpsDetectionNotRunning() { + updateSideFpsVisibilityParameters( + isVisible = true, + sfpsEnabled = true, + fpsDetectionRunning = false, + isUnlockingWithFpAllowed = true, + isAnimatingAway = false + ) + underTest.updateSideFpsVisibility() + verify(repository).setSideFpsShowing(false) + } + + @Test + fun testSideFpsVisibility_UnlockingWithFpNotAllowed() { + updateSideFpsVisibilityParameters( + isVisible = true, + sfpsEnabled = true, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = false, + isAnimatingAway = false + ) + underTest.updateSideFpsVisibility() + verify(repository).setSideFpsShowing(false) + } + + @Test + fun testSideFpsVisibility_AnimatingAway() { + updateSideFpsVisibilityParameters( + isVisible = true, + sfpsEnabled = true, + fpsDetectionRunning = true, + isUnlockingWithFpAllowed = true, + isAnimatingAway = true + ) + underTest.updateSideFpsVisibility() + verify(repository).setSideFpsShowing(false) + } + + private fun updateSideFpsVisibilityParameters( + isVisible: Boolean, + sfpsEnabled: Boolean, + fpsDetectionRunning: Boolean, + isUnlockingWithFpAllowed: Boolean, + isAnimatingAway: Boolean + ) { + `when`(repository.primaryBouncerVisible.value).thenReturn(isVisible) + resources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, sfpsEnabled) + `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(fpsDetectionRunning) + `when`(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed) + .thenReturn(isUnlockingWithFpAllowed) + `when`(repository.primaryBouncerStartingDisappearAnimation.value) + .thenReturn(if (isAnimatingAway) Runnable {} else null) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt index 75b74b0cfe28..f675e7997eb4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt @@ -26,7 +26,6 @@ import com.android.systemui.classifier.FalsingCollector import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.BouncerView -import com.android.systemui.keyguard.data.BouncerViewDelegate import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController @@ -44,7 +43,6 @@ import org.mockito.MockitoAnnotations class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() { private lateinit var repository: FakeKeyguardBouncerRepository @Mock private lateinit var bouncerView: BouncerView - @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor @@ -69,8 +67,9 @@ class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() { primaryBouncerCallbackInteractor, falsingCollector, dismissCallbackRegistry, - keyguardBypassController, + context, keyguardUpdateMonitor, + keyguardBypassController, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 6afeddda18ab..8bd8be565eee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.app.admin.DevicePolicyManager import android.content.Intent import android.os.UserHandle import androidx.test.filters.SmallTest @@ -87,6 +88,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var launchAnimator: DialogLaunchAnimator @Mock private lateinit var commandQueue: CommandQueue + @Mock private lateinit var devicePolicyManager: DevicePolicyManager private lateinit var underTest: KeyguardBottomAreaViewModel @@ -140,6 +142,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { bouncerRepository = FakeKeyguardBouncerRepository(), ) whenever(userTracker.userHandle).thenReturn(mock()) + whenever(userTracker.userId).thenReturn(10) whenever(lockPatternUtils.getStrongAuthForUser(anyInt())) .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED) val testDispatcher = StandardTestDispatcher() @@ -205,6 +208,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { featureFlags = featureFlags, repository = { quickAffordanceRepository }, launchAnimator = launchAnimator, + devicePolicyManager = devicePolicyManager, + backgroundDispatcher = testDispatcher, ), bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository), burnInHelperWrapper = burnInHelperWrapper, @@ -240,6 +245,39 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { } @Test + fun `startButton - hidden when device policy disables all keyguard features`() = + testScope.runTest { + whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL) + repository.setKeyguardShowing(true) + val latest by collectLastValue(underTest.startButton) + + val testConfig = + TestConfig( + isVisible = true, + isClickable = true, + isActivated = true, + icon = mock(), + canShowWhileLocked = false, + intent = Intent("action"), + ) + val configKey = + setUpQuickAffordanceModel( + position = KeyguardQuickAffordancePosition.BOTTOM_START, + testConfig = testConfig, + ) + + assertQuickAffordanceViewModel( + viewModel = latest, + testConfig = + TestConfig( + isVisible = false, + ), + configKey = configKey, + ) + } + + @Test fun `startButton - in preview mode - visible even when keyguard not showing`() = testScope.runTest { underTest.enablePreviewMode( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt index 586af626d29e..65e4c10265cd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt @@ -16,15 +16,23 @@ package com.android.systemui.keyguard.ui.viewmodel +import android.os.Looper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardSecurityModel +import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.BouncerView +import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel +import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.utils.os.FakeHandler import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.runCurrent @@ -33,7 +41,6 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito import org.mockito.MockitoAnnotations @SmallTest @@ -41,31 +48,69 @@ import org.mockito.MockitoAnnotations @kotlinx.coroutines.ExperimentalCoroutinesApi class KeyguardBouncerViewModelTest : SysuiTestCase() { lateinit var underTest: KeyguardBouncerViewModel + lateinit var bouncerInteractor: PrimaryBouncerInteractor @Mock lateinit var bouncerView: BouncerView - @Mock lateinit var bouncerInteractor: PrimaryBouncerInteractor + @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel + @Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor + @Mock private lateinit var falsingCollector: FalsingCollector + @Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry + @Mock private lateinit var keyguardBypassController: KeyguardBypassController + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + private val mainHandler = FakeHandler(Looper.getMainLooper()) + val repository = FakeKeyguardBouncerRepository() @Before fun setup() { MockitoAnnotations.initMocks(this) + bouncerInteractor = + PrimaryBouncerInteractor( + repository, + bouncerView, + mainHandler, + keyguardStateController, + keyguardSecurityModel, + primaryBouncerCallbackInteractor, + falsingCollector, + dismissCallbackRegistry, + context, + keyguardUpdateMonitor, + keyguardBypassController, + ) underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor) } @Test - fun setMessage() = - runTest { - val flow = MutableStateFlow<BouncerShowMessageModel?>(null) - var message: BouncerShowMessageModel? = null - Mockito.`when`(bouncerInteractor.showMessage) - .thenReturn(flow as Flow<BouncerShowMessageModel>) - // Reinitialize the view model. - underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor) + fun setMessage() = runTest { + var message: BouncerShowMessageModel? = null + val job = underTest.bouncerShowMessage.onEach { message = it }.launchIn(this) - flow.value = BouncerShowMessageModel(message = "abc", colorStateList = null) + repository.setShowMessage(BouncerShowMessageModel("abc", null)) + // Run the tasks that are pending at this point of virtual time. + runCurrent() + assertThat(message?.message).isEqualTo("abc") + job.cancel() + } + + @Test + fun shouldUpdateSideFps() = runTest { + var count = 0 + val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this) + repository.setPrimaryVisible(true) + // Run the tasks that are pending at this point of virtual time. + runCurrent() + assertThat(count).isEqualTo(1) + job.cancel() + } - val job = underTest.bouncerShowMessage.onEach { message = it }.launchIn(this) - // Run the tasks that are pending at this point of virtual time. - runCurrent() - assertThat(message?.message).isEqualTo("abc") - job.cancel() - } + @Test + fun sideFpsShowing() = runTest { + var sideFpsIsShowing = false + val job = underTest.sideFpsShowing.onEach { sideFpsIsShowing = it }.launchIn(this) + repository.setSideFpsShowing(true) + // Run the tasks that are pending at this point of virtual time. + runCurrent() + assertThat(sideFpsIsShowing).isEqualTo(true) + job.cancel() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt index eb6235ca8a6a..8532ffed85fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.media.controls.pipeline import android.app.smartspace.SmartspaceAction +import android.os.Bundle import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest @@ -25,6 +26,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.media.controls.MediaTestUtils import com.android.systemui.media.controls.models.player.MediaData +import com.android.systemui.media.controls.models.recommendation.EXTRA_KEY_TRIGGER_RESUME import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData import com.android.systemui.media.controls.ui.MediaPlayerData import com.android.systemui.media.controls.util.MediaFlags @@ -75,6 +77,7 @@ class MediaDataFilterTest : SysuiTestCase() { @Mock private lateinit var smartspaceMediaRecommendationItem: SmartspaceAction @Mock private lateinit var logger: MediaUiEventLogger @Mock private lateinit var mediaFlags: MediaFlags + @Mock private lateinit var cardAction: SmartspaceAction private lateinit var mediaDataFilter: MediaDataFilter private lateinit var dataMain: MediaData @@ -122,6 +125,7 @@ class MediaDataFilterTest : SysuiTestCase() { whenever(smartspaceData.headphoneConnectionTimeMillis) .thenReturn(clock.currentTimeMillis() - 100) whenever(smartspaceData.instanceId).thenReturn(SMARTSPACE_INSTANCE_ID) + whenever(smartspaceData.cardAction).thenReturn(cardAction) } private fun setUser(id: Int) { @@ -574,4 +578,55 @@ class MediaDataFilterTest : SysuiTestCase() { verify(mediaDataManager, never()) .dismissSmartspaceRecommendation(eq(SMARTSPACE_KEY), anyLong()) } + + @Test + fun testSmartspaceLoaded_shouldTriggerResume_doesTrigger() { + // WHEN we have media that was recently played, but not currently active + val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) + mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) + verify(listener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) + + // AND we get a smartspace signal with extra to trigger resume + val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, true) } + whenever(cardAction.extras).thenReturn(extras) + mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) + + // THEN we should tell listeners to treat the media as active instead + val dataCurrentAndActive = dataCurrent.copy(active = true) + verify(listener) + .onMediaDataLoaded( + eq(KEY), + eq(KEY), + eq(dataCurrentAndActive), + eq(true), + eq(100), + eq(true) + ) + assertThat(mediaDataFilter.hasActiveMediaOrRecommendation()).isTrue() + // And send the smartspace data, but not prioritized + verify(listener) + .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) + } + + @Test + fun testSmartspaceLoaded_notShouldTriggerResume_doesNotTrigger() { + // WHEN we have media that was recently played, but not currently active + val dataCurrent = dataMain.copy(active = false, lastActive = clock.elapsedRealtime()) + mediaDataFilter.onMediaDataLoaded(KEY, null, dataCurrent) + verify(listener) + .onMediaDataLoaded(eq(KEY), eq(null), eq(dataCurrent), eq(true), eq(0), eq(false)) + + // AND we get a smartspace signal with extra to not trigger resume + val extras = Bundle().apply { putBoolean(EXTRA_KEY_TRIGGER_RESUME, false) } + whenever(cardAction.extras).thenReturn(extras) + mediaDataFilter.onSmartspaceMediaDataLoaded(SMARTSPACE_KEY, smartspaceData) + + // THEN listeners are not updated to show media + verify(listener, never()) + .onMediaDataLoaded(eq(KEY), eq(KEY), any(), eq(true), eq(100), eq(true)) + // But the smartspace update is still propagated + verify(listener) + .onSmartspaceMediaDataLoaded(eq(SMARTSPACE_KEY), eq(smartspaceData), eq(false)) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/process/condition/UserProcessConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java index 2293fc577029..fb7197706ddc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/process/condition/UserProcessConditionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java @@ -26,7 +26,6 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; import com.android.systemui.process.ProcessWrapper; -import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.condition.Condition; import com.android.systemui.shared.condition.Monitor; import com.android.systemui.util.concurrency.FakeExecutor; @@ -41,10 +40,7 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @SmallTest -public class UserProcessConditionTest extends SysuiTestCase { - @Mock - UserTracker mUserTracker; - +public class SystemProcessConditionTest extends SysuiTestCase { @Mock ProcessWrapper mProcessWrapper; @@ -59,15 +55,14 @@ public class UserProcessConditionTest extends SysuiTestCase { } /** - * Verifies condition reports false when tracker reports a different user id than the - * identifier from the process handle. + * Verifies condition reports false when tracker reports the process is being ran by the + * system user. */ @Test - public void testConditionFailsWithDifferentIds() { + public void testConditionFailsWithNonSystemProcess() { - final Condition condition = new UserProcessCondition(mProcessWrapper, mUserTracker); - when(mProcessWrapper.getUserHandleIdentifier()).thenReturn(0); - when(mUserTracker.getUserId()).thenReturn(1); + final Condition condition = new SystemProcessCondition(mProcessWrapper); + when(mProcessWrapper.isSystemUser()).thenReturn(false); final Monitor monitor = new Monitor(mExecutor); @@ -81,15 +76,14 @@ public class UserProcessConditionTest extends SysuiTestCase { } /** - * Verifies condition reports false when tracker reports a different user id than the - * identifier from the process handle. + * Verifies condition reports true when tracker reports the process is being ran by the + * system user. */ @Test - public void testConditionSucceedsWithSameIds() { + public void testConditionSucceedsWithSystemProcess() { - final Condition condition = new UserProcessCondition(mProcessWrapper, mUserTracker); - when(mProcessWrapper.getUserHandleIdentifier()).thenReturn(0); - when(mUserTracker.getUserId()).thenReturn(0); + final Condition condition = new SystemProcessCondition(mProcessWrapper); + when(mProcessWrapper.isSystemUser()).thenReturn(true); final Monitor monitor = new Monitor(mExecutor); @@ -101,5 +95,4 @@ public class UserProcessConditionTest extends SysuiTestCase { verify(mCallback).onConditionsChanged(true); } - } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index 4caa50fa847d..89606bf6be3d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -52,14 +52,13 @@ import com.android.systemui.R; import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.animation.ShadeInterpolation; import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.media.controls.ui.MediaHost; -import com.android.systemui.plugins.FalsingManager; import com.android.systemui.qs.customize.QSCustomizerController; import com.android.systemui.qs.dagger.QSFragmentComponent; import com.android.systemui.qs.external.TileServiceRequestController; import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder; import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel; +import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.StatusBarState; @@ -86,7 +85,6 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { @Mock private MediaHost mQSMediaHost; @Mock private MediaHost mQQSMediaHost; @Mock private KeyguardBypassController mBypassController; - @Mock private FalsingManager mFalsingManager; @Mock private TileServiceRequestController.Builder mTileServiceRequestControllerBuilder; @Mock private TileServiceRequestController mTileServiceRequestController; @Mock private QSCustomizerController mQsCustomizerController; @@ -503,11 +501,9 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { setUpMedia(); setUpOther(); - FakeFeatureFlags featureFlags = new FakeFeatureFlags(); return new QSFragment( new RemoteInputQuickSettingsDisabler( context, commandQueue, mock(ConfigurationController.class)), - mock(QSTileHost.class), mStatusBarStateController, commandQueue, mQSMediaHost, @@ -515,9 +511,8 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { mBypassController, mQsComponentFactory, mock(QSFragmentDisableFlagsLogger.class), - mFalsingManager, mock(DumpManager.class), - featureFlags, + mock(QSLogger.class), mock(FooterActionsController.class), mFooterActionsViewModelFactory); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java index 8601d6c0a357..04b372c4a361 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java @@ -45,6 +45,7 @@ import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.UserTracker; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -53,6 +54,7 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest +@Ignore("b/269171747") public class ReduceBrightColorsTileTest extends SysuiTestCase { @Mock private QSTileHost mHost; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt index 7fdcfb210804..2de57051d4f2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.lockscreen +import android.app.smartspace.SmartspaceAction import android.app.smartspace.SmartspaceManager import android.app.smartspace.SmartspaceSession import android.app.smartspace.SmartspaceSession.OnTargetsAvailableListener @@ -26,6 +27,7 @@ import android.content.pm.UserInfo import android.database.ContentObserver import android.graphics.drawable.Drawable import android.net.Uri +import android.os.Bundle import android.os.Handler import android.os.UserHandle import android.provider.Settings @@ -43,6 +45,7 @@ import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.WeatherData import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener import com.android.systemui.settings.UserTracker @@ -54,6 +57,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceP import com.android.systemui.util.concurrency.FakeExecution import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argThat import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.settings.SecureSettings @@ -69,6 +73,7 @@ import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.spy +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import java.util.Optional @@ -76,6 +81,13 @@ import java.util.concurrent.Executor @SmallTest class LockscreenSmartspaceControllerTest : SysuiTestCase() { + companion object { + const val SMARTSPACE_TIME_TOO_EARLY = 1000L + const val SMARTSPACE_TIME_JUST_RIGHT = 4000L + const val SMARTSPACE_TIME_TOO_LATE = 9000L + const val SMARTSPACE_CREATION_TIME = 1234L + const val SMARTSPACE_EXPIRY_TIME = 5678L + } @Mock private lateinit var featureFlags: FeatureFlags @Mock @@ -224,6 +236,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { smartspaceManager, activityStarter, falsingManager, + clock, secureSettings, userTracker, contentResolver, @@ -529,6 +542,190 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test + fun testSessionListener_ifWeatherExtraMissing_thenWeatherDataNotSent() { + connectSession() + clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) + // WHEN we receive a list of targets + val targets = listOf( + makeTarget(1, userHandlePrimary, isSensitive = true), + makeTarget(2, userHandlePrimary, featureType = SmartspaceTarget.FEATURE_WEATHER) + + ) + sessionListener.onTargetsAvailable(targets) + verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any()) + } + + @Test + fun testSessionListener_ifWeatherExtraIsMissingValues_thenWeatherDataNotSent() { + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) + // WHEN we receive a list of targets + val targets = listOf( + makeTarget(1, userHandlePrimary, isSensitive = true), + makeWeatherTargetWithExtras( + id = 2, + userHandle = userHandlePrimary, + description = null, + state = WeatherData.WeatherStateIcon.SUNNY.id, + temperature = "32", + useCelsius = null) + + ) + + sessionListener.onTargetsAvailable(targets) + + verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any()) + } + + @Test + fun testSessionListener_ifTooEarly_thenWeatherDataNotSent() { + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_TOO_EARLY) + // WHEN we receive a list of targets + val targets = listOf( + makeWeatherTargetWithExtras( + id = 1, + userHandle = userHandleManaged, + description = "Sunny", + state = WeatherData.WeatherStateIcon.SUNNY.id, + temperature = "32", + useCelsius = false) + ) + sessionListener.onTargetsAvailable(targets) + verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any()) + } + + @Test + fun testSessionListener_ifOnTime_thenWeatherDataSent() { + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) + // WHEN we receive a list of targets + val targets = listOf( + makeWeatherTargetWithExtras( + id = 1, + userHandle = userHandleManaged, + description = "Snow Showers", + state = WeatherData.WeatherStateIcon.SNOW_SHOWERS_SNOW.id, + temperature = "-1", + useCelsius = false) + ) + sessionListener.onTargetsAvailable(targets) + verify(keyguardUpdateMonitor).sendWeatherData(argThat { w -> + w.description == "Snow Showers" && + w.state == WeatherData.WeatherStateIcon.SNOW_SHOWERS_SNOW && + w.temperature == -1 && !w.useCelsius + }) + } + + @Test + fun testSessionListener_ifTooLate_thenWeatherDataNotSent() { + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_TOO_LATE) + // WHEN we receive a list of targets + val targets = listOf( + makeWeatherTargetWithExtras( + id = 1, + userHandle = userHandleManaged, + description = "Sunny", + state = WeatherData.WeatherStateIcon.SUNNY.id, + temperature = "72", + useCelsius = false) + ) + sessionListener.onTargetsAvailable(targets) + verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any()) + } + + @Test + fun testSessionListener_onlyFirstWeatherDataSent() { + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) + // WHEN we receive a list of targets + val targets = listOf( + makeWeatherTargetWithExtras( + id = 1, + userHandle = userHandleManaged, + description = "Sunny", + state = WeatherData.WeatherStateIcon.SUNNY.id, + temperature = "72", + useCelsius = false), + makeWeatherTargetWithExtras( + id = 2, + userHandle = userHandleManaged, + description = "Showers", + state = WeatherData.WeatherStateIcon.SHOWERS_RAIN.id, + temperature = "62", + useCelsius = true) + ) + sessionListener.onTargetsAvailable(targets) + verify(keyguardUpdateMonitor).sendWeatherData(argThat { w -> + w.description == "Sunny" && + w.state == WeatherData.WeatherStateIcon.SUNNY && + w.temperature == 72 && !w.useCelsius + }) + } + + @Test + fun testSessionListener_ifDecouplingEnabled_weatherDataUpdates() { + `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true) + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) + // WHEN we receive a list of targets + val targets = listOf( + makeTarget(1, userHandlePrimary, isSensitive = true), + makeTarget(2, userHandlePrimary), + makeTarget(3, userHandleManaged), + makeWeatherTargetWithExtras( + id = 4, + userHandle = userHandlePrimary, + description = "Flurries", + state = WeatherData.WeatherStateIcon.FLURRIES.id, + temperature = "0", + useCelsius = true) + ) + + sessionListener.onTargetsAvailable(targets) + + verify(keyguardUpdateMonitor).sendWeatherData(argThat { w -> + w.description == "Flurries" && + w.state == WeatherData.WeatherStateIcon.FLURRIES && + w.temperature == 0 && w.useCelsius + }) + } + + @Test + fun testSessionListener_ifDecouplingDisabled_weatherDataUpdates() { + `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false) + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) + // WHEN we receive a list of targets + val targets = listOf( + makeWeatherTargetWithExtras( + id = 1, + userHandle = userHandlePrimary, + description = "Sunny", + state = WeatherData.WeatherStateIcon.SUNNY.id, + temperature = "32", + useCelsius = false), + makeTarget(2, userHandlePrimary, isSensitive = true) + ) + + sessionListener.onTargetsAvailable(targets) + + verify(keyguardUpdateMonitor).sendWeatherData(argThat { w -> + w.description == "Sunny" && + w.state == WeatherData.WeatherStateIcon.SUNNY && + w.temperature == 32 && !w.useCelsius + }) + } + + @Test fun testSettingsAreReloaded() { // GIVEN a connected session where the privacy settings later flip to false connectSession() @@ -740,7 +937,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { return userInfo } - fun makeTarget( + private fun makeTarget( id: Int, userHandle: UserHandle, isSensitive: Boolean = false, @@ -755,6 +952,38 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { .build() } + private fun makeWeatherTargetWithExtras( + id: Int, + userHandle: UserHandle, + description: String?, + state: Int?, + temperature: String?, + useCelsius: Boolean? + ): SmartspaceTarget { + val mockWeatherBundle = mock(Bundle::class.java).apply { + `when`(getString(WeatherData.DESCRIPTION_KEY)).thenReturn(description) + if (state != null) + `when`(getInt(eq(WeatherData.STATE_KEY), any())).thenReturn(state) + `when`(getString(WeatherData.TEMPERATURE_KEY)).thenReturn(temperature) + `when`(containsKey(WeatherData.USE_CELSIUS_KEY)).thenReturn(useCelsius != null) + if (useCelsius != null) + `when`(getBoolean(WeatherData.USE_CELSIUS_KEY)).thenReturn(useCelsius) + } + + val mockBaseAction = mock(SmartspaceAction::class.java) + `when`(mockBaseAction.extras).thenReturn(mockWeatherBundle) + return SmartspaceTarget.Builder( + "targetWithWeatherExtras$id", + ComponentName("testpackage", "testclass$id"), + userHandle) + .setSensitive(false) + .setFeatureType(SmartspaceTarget.FEATURE_WEATHER) + .setBaseAction(mockBaseAction) + .setExpiryTimeMillis(SMARTSPACE_EXPIRY_TIME) + .setCreationTimeMillis(SMARTSPACE_CREATION_TIME) + .build() + } + private fun setAllowPrivateNotifications(user: UserHandle, value: Boolean) { `when`(secureSettings.getIntForUser( eq(PRIVATE_LOCKSCREEN_SETTING), diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java index 94e3e6cb5f8e..edb2965eabfa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java @@ -105,6 +105,7 @@ import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; +import org.mockito.stubbing.Answer; import java.util.Arrays; import java.util.Collection; @@ -376,6 +377,90 @@ public class NotifCollectionTest extends SysuiTestCase { } @Test + public void testScheduleBuildNotificationListWhenChannelChanged() { + // GIVEN + final NotificationEntryBuilder neb = buildNotif(TEST_PACKAGE, 48); + final NotificationChannel channel = new NotificationChannel( + "channelId", + "channelName", + NotificationManager.IMPORTANCE_DEFAULT); + neb.setChannel(channel); + + final NotifEvent notif = mNoMan.postNotif(neb); + final NotificationEntry entry = mCollectionListener.getEntry(notif.key); + + when(mMainHandler.hasCallbacks(any())).thenReturn(false); + + clearInvocations(mBuildListener); + + // WHEN + mNotifHandler.onNotificationChannelModified(TEST_PACKAGE, + entry.getSbn().getUser(), channel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + + // THEN + verify(mMainHandler).postDelayed(any(), eq(1000L)); + } + + @Test + public void testCancelScheduledBuildNotificationListEventWhenNotifUpdatedSynchronously() { + // GIVEN + final NotificationEntry entry1 = buildNotif(TEST_PACKAGE, 1) + .setGroup(mContext, "group_1") + .build(); + final NotificationEntry entry2 = buildNotif(TEST_PACKAGE, 2) + .setGroup(mContext, "group_1") + .setContentTitle(mContext, "New version") + .build(); + final NotificationEntry entry3 = buildNotif(TEST_PACKAGE, 3) + .setGroup(mContext, "group_1") + .build(); + + final List<CoalescedEvent> entriesToBePosted = Arrays.asList( + new CoalescedEvent(entry1.getKey(), 0, entry1.getSbn(), entry1.getRanking(), null), + new CoalescedEvent(entry2.getKey(), 1, entry2.getSbn(), entry2.getRanking(), null), + new CoalescedEvent(entry3.getKey(), 2, entry3.getSbn(), entry3.getRanking(), null) + ); + + when(mMainHandler.hasCallbacks(any())).thenReturn(true); + + // WHEN + mNotifHandler.onNotificationBatchPosted(entriesToBePosted); + + // THEN + verify(mMainHandler).removeCallbacks(any()); + } + + @Test + public void testBuildNotificationListWhenChannelChanged() { + // GIVEN + final NotificationEntryBuilder neb = buildNotif(TEST_PACKAGE, 48); + final NotificationChannel channel = new NotificationChannel( + "channelId", + "channelName", + NotificationManager.IMPORTANCE_DEFAULT); + neb.setChannel(channel); + + final NotifEvent notif = mNoMan.postNotif(neb); + final NotificationEntry entry = mCollectionListener.getEntry(notif.key); + + when(mMainHandler.hasCallbacks(any())).thenReturn(false); + when(mMainHandler.postDelayed(any(), eq(1000L))).thenAnswer((Answer) invocation -> { + final Runnable runnable = invocation.getArgument(0); + runnable.run(); + return null; + }); + + clearInvocations(mBuildListener); + + // WHEN + mNotifHandler.onNotificationChannelModified(TEST_PACKAGE, + entry.getSbn().getUser(), channel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED); + + // THEN + verifyBuiltList(List.of(entry)); + } + + @Test public void testRankingsAreUpdatedForOtherNotifs() { // GIVEN a collection with one notif NotifEvent notif1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 3) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt index bd039031cecc..33a838ed5183 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryLoggerTest.kt @@ -20,6 +20,7 @@ import android.app.Notification import android.app.StatsManager import android.graphics.Bitmap import android.graphics.drawable.Icon +import android.stats.sysui.NotificationEnums import android.testing.AndroidTestingRunner import android.util.StatsEvent import androidx.test.filters.SmallTest @@ -31,10 +32,12 @@ import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Expect import com.google.common.truth.Truth.assertThat import java.lang.RuntimeException import kotlinx.coroutines.Dispatchers import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock @@ -45,6 +48,8 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) class NotificationMemoryLoggerTest : SysuiTestCase() { + @Rule @JvmField val expect = Expect.create() + private val bgExecutor = FakeExecutor(FakeSystemClock()) private val immediate = Dispatchers.Main.immediate @@ -132,6 +137,123 @@ class NotificationMemoryLoggerTest : SysuiTestCase() { .isEqualTo(StatsManager.PULL_SKIP) } + @Test + fun aggregateMemoryUsageData_returnsCorrectlyAggregatedSamePackageData() { + val usage = getPresetMemoryUsages() + val aggregateUsage = aggregateMemoryUsageData(usage) + + assertThat(aggregateUsage).hasSize(3) + assertThat(aggregateUsage) + .containsKey(Pair("package 1", NotificationEnums.STYLE_BIG_PICTURE)) + + // Aggregated fields + val aggregatedData = + aggregateUsage[Pair("package 1", NotificationEnums.STYLE_BIG_PICTURE)]!! + val presetUsage1 = usage[0] + val presetUsage2 = usage[1] + assertAggregatedData( + aggregatedData, + 2, + 2, + smallIconObject = + presetUsage1.objectUsage.smallIcon + presetUsage2.objectUsage.smallIcon, + smallIconBitmapCount = 2, + largeIconObject = + presetUsage1.objectUsage.largeIcon + presetUsage2.objectUsage.largeIcon, + largeIconBitmapCount = 2, + bigPictureObject = + presetUsage1.objectUsage.bigPicture + presetUsage2.objectUsage.bigPicture, + bigPictureBitmapCount = 2, + extras = presetUsage1.objectUsage.extras + presetUsage2.objectUsage.extras, + extenders = presetUsage1.objectUsage.extender + presetUsage2.objectUsage.extender, + // Only totals need to be summarized. + smallIconViews = + presetUsage1.viewUsage[0].smallIcon + presetUsage2.viewUsage[0].smallIcon, + largeIconViews = + presetUsage1.viewUsage[0].largeIcon + presetUsage2.viewUsage[0].largeIcon, + systemIconViews = + presetUsage1.viewUsage[0].systemIcons + presetUsage2.viewUsage[0].systemIcons, + styleViews = presetUsage1.viewUsage[0].style + presetUsage2.viewUsage[0].style, + customViews = + presetUsage1.viewUsage[0].customViews + presetUsage2.viewUsage[0].customViews, + softwareBitmaps = + presetUsage1.viewUsage[0].softwareBitmapsPenalty + + presetUsage2.viewUsage[0].softwareBitmapsPenalty, + seenCount = 0 + ) + } + + @Test + fun aggregateMemoryUsageData_correctlySeparatesDifferentStyles() { + val usage = getPresetMemoryUsages() + val aggregateUsage = aggregateMemoryUsageData(usage) + + assertThat(aggregateUsage).hasSize(3) + assertThat(aggregateUsage) + .containsKey(Pair("package 1", NotificationEnums.STYLE_BIG_PICTURE)) + assertThat(aggregateUsage).containsKey(Pair("package 1", NotificationEnums.STYLE_BIG_TEXT)) + + // Different style should be separate + val separateStyleData = + aggregateUsage[Pair("package 1", NotificationEnums.STYLE_BIG_TEXT)]!! + val presetUsage = usage[2] + assertAggregatedData( + separateStyleData, + 1, + 1, + presetUsage.objectUsage.smallIcon, + 1, + presetUsage.objectUsage.largeIcon, + 1, + presetUsage.objectUsage.bigPicture, + 1, + presetUsage.objectUsage.extras, + presetUsage.objectUsage.extender, + presetUsage.viewUsage[0].smallIcon, + presetUsage.viewUsage[0].largeIcon, + presetUsage.viewUsage[0].systemIcons, + presetUsage.viewUsage[0].style, + presetUsage.viewUsage[0].customViews, + presetUsage.viewUsage[0].softwareBitmapsPenalty, + 0 + ) + } + + @Test + fun aggregateMemoryUsageData_correctlySeparatesDifferentProcess() { + val usage = getPresetMemoryUsages() + val aggregateUsage = aggregateMemoryUsageData(usage) + + assertThat(aggregateUsage).hasSize(3) + assertThat(aggregateUsage) + .containsKey(Pair("package 2", NotificationEnums.STYLE_BIG_PICTURE)) + + // Different UID/package should also be separate + val separatePackageData = + aggregateUsage[Pair("package 2", NotificationEnums.STYLE_BIG_PICTURE)]!! + val presetUsage = usage[3] + assertAggregatedData( + separatePackageData, + 1, + 1, + presetUsage.objectUsage.smallIcon, + 1, + presetUsage.objectUsage.largeIcon, + 1, + presetUsage.objectUsage.bigPicture, + 1, + presetUsage.objectUsage.extras, + presetUsage.objectUsage.extender, + presetUsage.viewUsage[0].smallIcon, + presetUsage.viewUsage[0].largeIcon, + presetUsage.viewUsage[0].systemIcons, + presetUsage.viewUsage[0].style, + presetUsage.viewUsage[0].customViews, + presetUsage.viewUsage[0].softwareBitmapsPenalty, + 0 + ) + } + private fun createLoggerWithNotifications( notifications: List<Notification> ): NotificationMemoryLogger { @@ -143,4 +265,182 @@ class NotificationMemoryLoggerTest : SysuiTestCase() { whenever(pipeline.allNotifs).thenReturn(notifications) return NotificationMemoryLogger(pipeline, statsManager, immediate, bgExecutor) } + + /** + * Short hand for making sure the passed NotificationMemoryUseAtomBuilder object contains + * expected values. + */ + private fun assertAggregatedData( + value: NotificationMemoryLogger.NotificationMemoryUseAtomBuilder, + count: Int, + countWithInflatedViews: Int, + smallIconObject: Int, + smallIconBitmapCount: Int, + largeIconObject: Int, + largeIconBitmapCount: Int, + bigPictureObject: Int, + bigPictureBitmapCount: Int, + extras: Int, + extenders: Int, + smallIconViews: Int, + largeIconViews: Int, + systemIconViews: Int, + styleViews: Int, + customViews: Int, + softwareBitmaps: Int, + seenCount: Int + ) { + expect.withMessage("count").that(value.count).isEqualTo(count) + expect + .withMessage("countWithInflatedViews") + .that(value.countWithInflatedViews) + .isEqualTo(countWithInflatedViews) + expect.withMessage("smallIconObject").that(value.smallIconObject).isEqualTo(smallIconObject) + expect + .withMessage("smallIconBitmapCount") + .that(value.smallIconBitmapCount) + .isEqualTo(smallIconBitmapCount) + expect.withMessage("largeIconObject").that(value.largeIconObject).isEqualTo(largeIconObject) + expect + .withMessage("largeIconBitmapCount") + .that(value.largeIconBitmapCount) + .isEqualTo(largeIconBitmapCount) + expect + .withMessage("bigPictureObject") + .that(value.bigPictureObject) + .isEqualTo(bigPictureObject) + expect + .withMessage("bigPictureBitmapCount") + .that(value.bigPictureBitmapCount) + .isEqualTo(bigPictureBitmapCount) + expect.withMessage("extras").that(value.extras).isEqualTo(extras) + expect.withMessage("extenders").that(value.extenders).isEqualTo(extenders) + expect.withMessage("smallIconViews").that(value.smallIconViews).isEqualTo(smallIconViews) + expect.withMessage("largeIconViews").that(value.largeIconViews).isEqualTo(largeIconViews) + expect.withMessage("systemIconViews").that(value.systemIconViews).isEqualTo(systemIconViews) + expect.withMessage("styleViews").that(value.styleViews).isEqualTo(styleViews) + expect.withMessage("customViews").that(value.customViews).isEqualTo(customViews) + expect.withMessage("softwareBitmaps").that(value.softwareBitmaps).isEqualTo(softwareBitmaps) + expect.withMessage("seenCount").that(value.seenCount).isEqualTo(seenCount) + } + + /** Generates a static set of [NotificationMemoryUsage] objects. */ + private fun getPresetMemoryUsages() = + listOf( + // A pair of notifications that have to be aggregated, same UID and style + NotificationMemoryUsage( + "package 1", + 384, + "key1", + Notification.Builder(context).setStyle(Notification.BigPictureStyle()).build(), + NotificationObjectUsage( + 23, + 45, + 67, + NotificationEnums.STYLE_BIG_PICTURE, + 12, + 483, + 4382, + true + ), + listOf( + NotificationViewUsage(ViewType.TOTAL, 493, 584, 4833, 584, 4888, 5843), + NotificationViewUsage( + ViewType.PRIVATE_CONTRACTED_VIEW, + 100, + 250, + 300, + 594, + 6000, + 5843 + ) + ) + ), + NotificationMemoryUsage( + "package 1", + 384, + "key2", + Notification.Builder(context).setStyle(Notification.BigPictureStyle()).build(), + NotificationObjectUsage( + 77, + 54, + 34, + NotificationEnums.STYLE_BIG_PICTURE, + 77, + 432, + 2342, + true + ), + listOf( + NotificationViewUsage(ViewType.TOTAL, 3245, 1234, 7653, 543, 765, 7655), + NotificationViewUsage( + ViewType.PRIVATE_CONTRACTED_VIEW, + 160, + 350, + 300, + 5544, + 66500, + 5433 + ) + ) + ), + // Different style is different aggregation + NotificationMemoryUsage( + "package 1", + 384, + "key2", + Notification.Builder(context).setStyle(Notification.BigTextStyle()).build(), + NotificationObjectUsage( + 77, + 54, + 34, + NotificationEnums.STYLE_BIG_TEXT, + 77, + 432, + 2342, + true + ), + listOf( + NotificationViewUsage(ViewType.TOTAL, 3245, 1234, 7653, 543, 765, 7655), + NotificationViewUsage( + ViewType.PRIVATE_CONTRACTED_VIEW, + 160, + 350, + 300, + 5544, + 66500, + 5433 + ) + ) + ), + // Different package is also different aggregation + NotificationMemoryUsage( + "package 2", + 684, + "key2", + Notification.Builder(context).setStyle(Notification.BigPictureStyle()).build(), + NotificationObjectUsage( + 32, + 654, + 234, + NotificationEnums.STYLE_BIG_PICTURE, + 211, + 776, + 435, + true + ), + listOf( + NotificationViewUsage(ViewType.TOTAL, 4355, 6543, 4322, 5435, 6546, 65485), + NotificationViewUsage( + ViewType.PRIVATE_CONTRACTED_VIEW, + 6546, + 7657, + 4353, + 6546, + 76575, + 54654 + ) + ) + ) + ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt new file mode 100644 index 000000000000..89cc18cc5d67 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.surfaceeffects.ripple + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class RippleShaderTest : SysuiTestCase() { + + private lateinit var rippleShader: RippleShader + + @Before + fun setup() { + rippleShader = RippleShader() + } + + @Test + fun setMaxSize_hasCorrectSizes() { + val expectedMaxWidth = 300f + val expectedMaxHeight = 500f + + rippleShader.rippleSize.setMaxSize(expectedMaxWidth, expectedMaxHeight) + + assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(2) + assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(rippleShader.rippleSize.initialSize) + val maxSize = rippleShader.rippleSize.sizes[1] + assertThat(maxSize.t).isEqualTo(1f) + assertThat(maxSize.width).isEqualTo(expectedMaxWidth) + assertThat(maxSize.height).isEqualTo(expectedMaxHeight) + } + + @Test + fun setSizeAtProgresses_hasCorrectSizes() { + val expectedSize0 = RippleShader.SizeAtProgress(t = 0f, width = 100f, height = 100f) + val expectedSize1 = RippleShader.SizeAtProgress(t = 0.2f, width = 1500f, height = 1200f) + val expectedSize2 = RippleShader.SizeAtProgress(t = 0.4f, width = 200f, height = 70f) + + rippleShader.rippleSize.setSizeAtProgresses(expectedSize0, expectedSize1, expectedSize2) + + assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(3) + assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(expectedSize0) + assertThat(rippleShader.rippleSize.sizes[1]).isEqualTo(expectedSize1) + assertThat(rippleShader.rippleSize.sizes[2]).isEqualTo(expectedSize2) + } + + @Test + fun setSizeAtProgresses_sizeListIsSortedByT() { + val expectedSize0 = RippleShader.SizeAtProgress(t = 0f, width = 100f, height = 100f) + val expectedSize1 = RippleShader.SizeAtProgress(t = 0.2f, width = 1500f, height = 1200f) + val expectedSize2 = RippleShader.SizeAtProgress(t = 0.4f, width = 200f, height = 70f) + val expectedSize3 = RippleShader.SizeAtProgress(t = 0.8f, width = 300f, height = 900f) + val expectedSize4 = RippleShader.SizeAtProgress(t = 1f, width = 500f, height = 300f) + + // Add them in unsorted order + rippleShader.rippleSize.setSizeAtProgresses( + expectedSize0, + expectedSize3, + expectedSize2, + expectedSize4, + expectedSize1 + ) + + assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(5) + assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(expectedSize0) + assertThat(rippleShader.rippleSize.sizes[1]).isEqualTo(expectedSize1) + assertThat(rippleShader.rippleSize.sizes[2]).isEqualTo(expectedSize2) + assertThat(rippleShader.rippleSize.sizes[3]).isEqualTo(expectedSize3) + assertThat(rippleShader.rippleSize.sizes[4]).isEqualTo(expectedSize4) + } + + @Test + fun update_getsCorrectNextTargetSize() { + val expectedSize0 = RippleShader.SizeAtProgress(t = 0f, width = 100f, height = 100f) + val expectedSize1 = RippleShader.SizeAtProgress(t = 0.2f, width = 1500f, height = 1200f) + val expectedSize2 = RippleShader.SizeAtProgress(t = 0.4f, width = 200f, height = 70f) + val expectedSize3 = RippleShader.SizeAtProgress(t = 0.8f, width = 300f, height = 900f) + val expectedSize4 = RippleShader.SizeAtProgress(t = 1f, width = 500f, height = 300f) + + rippleShader.rippleSize.setSizeAtProgresses( + expectedSize0, + expectedSize1, + expectedSize2, + expectedSize3, + expectedSize4 + ) + + rippleShader.rippleSize.update(0.5f) + // Progress is between 0.4 and 0.8 (expectedSize3 and 4), so the index should be 3. + assertThat(rippleShader.rippleSize.currentSizeIndex).isEqualTo(3) + } + + @Test + fun update_sizeListIsEmpty_setsInitialSize() { + assertThat(rippleShader.rippleSize.sizes).isEmpty() + + rippleShader.rippleSize.update(0.3f) + + assertThat(rippleShader.rippleSize.sizes.size).isEqualTo(1) + assertThat(rippleShader.rippleSize.sizes[0]).isEqualTo(rippleShader.rippleSize.initialSize) + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt index 337421974562..9cdce20bbf1e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt @@ -21,6 +21,7 @@ import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.E import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow /** Fake implementation of [KeyguardRepository] */ @@ -44,8 +45,6 @@ class FakeKeyguardBouncerRepository : KeyguardBouncerRepository { override val panelExpansionAmount = _panelExpansionAmount.asStateFlow() private val _keyguardPosition = MutableStateFlow(0f) override val keyguardPosition = _keyguardPosition.asStateFlow() - private val _onScreenTurnedOff = MutableStateFlow(false) - override val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow() private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null) override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow() private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null) @@ -61,6 +60,8 @@ class FakeKeyguardBouncerRepository : KeyguardBouncerRepository { override var lastAlternateBouncerVisibleTime: Long = 0L private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false) override val alternateBouncerUIAvailable = _isAlternateBouncerUIAvailable.asStateFlow() + private val _sideFpsShowing: MutableStateFlow<Boolean> = MutableStateFlow(false) + override val sideFpsShowing: StateFlow<Boolean> = _sideFpsShowing.asStateFlow() override fun setPrimaryScrimmed(isScrimmed: Boolean) { _primaryBouncerScrimmed.value = isScrimmed @@ -122,7 +123,7 @@ class FakeKeyguardBouncerRepository : KeyguardBouncerRepository { _isBackButtonEnabled.value = isBackButtonEnabled } - override fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) { - _onScreenTurnedOff.value = onScreenTurnedOff + override fun setSideFpsShowing(isShowing: Boolean) { + _sideFpsShowing.value = isShowing } } diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index cf880eba20f7..6410142278b5 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1218,6 +1218,10 @@ import java.util.concurrent.atomic.AtomicBoolean; sendILMsg(MSG_IL_BTA2DP_TIMEOUT, SENDMSG_QUEUE, a2dpCodec, address, delayMs); } + /*package*/ void setLeAudioTimeout(String address, int device, int delayMs) { + sendILMsg(MSG_IL_BTLEAUDIO_TIMEOUT, SENDMSG_QUEUE, device, address, delayMs); + } + /*package*/ void setAvrcpAbsoluteVolumeSupported(boolean supported) { synchronized (mDeviceStateLock) { mBtHelper.setAvrcpAbsoluteVolumeSupported(supported); @@ -1422,6 +1426,13 @@ import java.util.concurrent.atomic.AtomicBoolean; mDeviceInventory.onMakeA2dpDeviceUnavailableNow((String) msg.obj, msg.arg1); } break; + case MSG_IL_BTLEAUDIO_TIMEOUT: + // msg.obj == address of LE Audio device + synchronized (mDeviceStateLock) { + mDeviceInventory.onMakeLeAudioDeviceUnavailableNow( + (String) msg.obj, msg.arg1); + } + break; case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: final BluetoothDevice btDevice = (BluetoothDevice) msg.obj; synchronized (mDeviceStateLock) { @@ -1649,11 +1660,14 @@ import java.util.concurrent.atomic.AtomicBoolean; // process set volume for Le Audio, obj is BleVolumeInfo private static final int MSG_II_SET_LE_AUDIO_OUT_VOLUME = 46; + private static final int MSG_IL_BTLEAUDIO_TIMEOUT = 49; + private static boolean isMessageHandledUnderWakelock(int msgId) { switch(msgId) { case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: case MSG_L_SET_BT_ACTIVE_DEVICE: case MSG_IL_BTA2DP_TIMEOUT: + case MSG_IL_BTLEAUDIO_TIMEOUT: case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: case MSG_TOGGLE_HDMI: case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT: @@ -1744,6 +1758,7 @@ import java.util.concurrent.atomic.AtomicBoolean; case MSG_L_SET_BT_ACTIVE_DEVICE: case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: case MSG_IL_BTA2DP_TIMEOUT: + case MSG_IL_BTLEAUDIO_TIMEOUT: case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: if (sLastDeviceConnectMsgTime >= time) { // add a little delay to make sure messages are ordered as expected diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 35da73ef58c0..a74f4154eb85 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -374,7 +374,7 @@ public class AudioDeviceInventory { case BluetoothProfile.LE_AUDIO: case BluetoothProfile.LE_AUDIO_BROADCAST: if (switchToUnavailable) { - makeLeAudioDeviceUnavailable(address, btInfo.mAudioSystemDevice); + makeLeAudioDeviceUnavailableNow(address, btInfo.mAudioSystemDevice); } else if (switchToAvailable) { makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice), streamType, btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10, @@ -486,6 +486,12 @@ public class AudioDeviceInventory { } } + /*package*/ void onMakeLeAudioDeviceUnavailableNow(String address, int device) { + synchronized (mDevicesLock) { + makeLeAudioDeviceUnavailableNow(address, device); + } + } + /*package*/ void onReportNewRoutes() { int n = mRoutesObservers.beginBroadcast(); if (n > 0) { @@ -883,10 +889,11 @@ public class AudioDeviceInventory { new MediaMetrics.Item(mMetricsId + "disconnectLeAudio") .record(); if (toRemove.size() > 0) { - final int delay = checkSendBecomingNoisyIntentInt(device, 0, + final int delay = checkSendBecomingNoisyIntentInt(device, + AudioService.CONNECTION_STATE_DISCONNECTED, AudioSystem.DEVICE_NONE); toRemove.stream().forEach(deviceAddress -> - makeLeAudioDeviceUnavailable(deviceAddress, device) + makeLeAudioDeviceUnavailableLater(deviceAddress, device, delay) ); } } @@ -1187,9 +1194,21 @@ public class AudioDeviceInventory { */ mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource); - AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(device, address, name), + final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( + device, address, name), AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); + if (res != AudioSystem.AUDIO_STATUS_OK) { + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "APM failed to make available LE Audio device addr=" + address + + " error=" + res).printLog(TAG)); + // TODO: connection failed, stop here + // TODO: return; + } else { + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "LE Audio device addr=" + address + " now available").printLog(TAG)); + } + mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address), new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); mDeviceBroker.postAccessoryPlugMediaUnmute(device); @@ -1210,11 +1229,23 @@ public class AudioDeviceInventory { } @GuardedBy("mDevicesLock") - private void makeLeAudioDeviceUnavailable(String address, int device) { + private void makeLeAudioDeviceUnavailableNow(String address, int device) { if (device != AudioSystem.DEVICE_NONE) { - AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(device, address), + final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( + device, address), AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); + + if (res != AudioSystem.AUDIO_STATUS_OK) { + AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent( + "APM failed to make unavailable LE Audio device addr=" + address + + " error=" + res).printLog(TAG)); + // TODO: failed to disconnect, stop here + // TODO: return; + } else { + AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent( + "LE Audio device addr=" + address + " made unavailable")).printLog(TAG)); + } mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address)); } @@ -1222,6 +1253,14 @@ public class AudioDeviceInventory { } @GuardedBy("mDevicesLock") + private void makeLeAudioDeviceUnavailableLater(String address, int device, int delayMs) { + // the device will be made unavailable later, so consider it disconnected right away + mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address)); + // send the delayed message to make the device unavailable later + mDeviceBroker.setLeAudioTimeout(address, device, delayMs); + } + + @GuardedBy("mDevicesLock") private void setCurrentAudioRouteNameIfPossible(String name, boolean fromA2dp) { synchronized (mCurAudioRoutes) { if (TextUtils.equals(mCurAudioRoutes.bluetoothName, name)) { diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index f95982138564..691ce93b3f7b 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -291,9 +291,9 @@ public class BtHelper { if (mA2dp == null) { return AudioSystem.AUDIO_FORMAT_DEFAULT; } - final BluetoothCodecStatus btCodecStatus = null; + BluetoothCodecStatus btCodecStatus = null; try { - mA2dp.getCodecStatus(device); + btCodecStatus = mA2dp.getCodecStatus(device); } catch (Exception e) { Log.e(TAG, "Exception while getting status of " + device, e); } @@ -489,7 +489,7 @@ public class BtHelper { } // @GuardedBy("AudioDeviceBroker.mSetModeLock") - @GuardedBy("AudioDeviceBroker.mDeviceStateLock") + //@GuardedBy("AudioDeviceBroker.mDeviceStateLock") /*package*/ synchronized void resetBluetoothSco() { mScoAudioState = SCO_STATE_INACTIVE; broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); @@ -532,7 +532,7 @@ public class BtHelper { } } - //@GuardedBy("AudioDeviceBroker.mDeviceStateLock") + @GuardedBy("AudioDeviceBroker.mDeviceStateLock") /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) { if (profile == BluetoothProfile.HEADSET) { onHeadsetProfileConnected((BluetoothHeadset) proxy); @@ -564,7 +564,7 @@ public class BtHelper { } // @GuardedBy("AudioDeviceBroker.mSetModeLock") - @GuardedBy("AudioDeviceBroker.mDeviceStateLock") + //@GuardedBy("AudioDeviceBroker.mDeviceStateLock") /*package*/ synchronized void onHeadsetProfileConnected(BluetoothHeadset headset) { // Discard timeout message mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService(); diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 598e2b990ea5..94b67cedf86c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -452,6 +452,13 @@ public class FingerprintService extends SystemService { return -1; } + if (!Utils.isUserEncryptedOrLockdown(mLockPatternUtils, userId)) { + // If this happens, something in KeyguardUpdateMonitor is wrong. This should only + // ever be invoked when the user is encrypted or lockdown. + Slog.e(TAG, "detectFingerprint invoked when user is not encrypted or lockdown"); + return -1; + } + final Pair<Integer, ServiceProvider> provider = getSingleProvider(); if (provider == null) { Slog.w(TAG, "Null provider for detectFingerprint"); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 4341634fb890..909c531c5e92 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -434,6 +434,8 @@ public final class DisplayManagerService extends SystemService { private boolean mIsDocked; private boolean mIsDreaming; + private boolean mBootCompleted = false; + private final BroadcastReceiver mIdleModeReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -573,6 +575,12 @@ public final class DisplayManagerService extends SystemService { } } } else if (phase == PHASE_BOOT_COMPLETED) { + synchronized (mSyncRoot) { + mBootCompleted = true; + for (int i = 0; i < mDisplayPowerControllers.size(); i++) { + mDisplayPowerControllers.valueAt(i).onBootCompleted(); + } + } mDisplayModeDirector.onBootCompleted(); mLogicalDisplayMapper.onBootCompleted(); } @@ -2680,7 +2688,7 @@ public final class DisplayManagerService extends SystemService { final DisplayPowerController displayPowerController = new DisplayPowerController( mContext, mDisplayPowerCallbacks, mPowerHandler, mSensorManager, mDisplayBlanker, display, mBrightnessTracker, brightnessSetting, - () -> handleBrightnessChange(display), hbmMetadata); + () -> handleBrightnessChange(display), hbmMetadata, mBootCompleted); mDisplayPowerControllers.append(display.getDisplayIdLocked(), displayPowerController); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 8025fa60ca2b..5ecc7f2b7bb8 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -133,6 +133,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private static final int MSG_BRIGHTNESS_RAMP_DONE = 12; private static final int MSG_STATSD_HBM_BRIGHTNESS = 13; private static final int MSG_SWITCH_USER = 14; + private static final int MSG_BOOT_COMPLETED = 15; private static final int PROXIMITY_UNKNOWN = -1; private static final int PROXIMITY_NEGATIVE = 0; @@ -506,6 +507,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private boolean mIsEnabled; private boolean mIsInTransition; + private boolean mBootCompleted; + /** * Creates the display power controller. */ @@ -513,7 +516,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call DisplayPowerCallbacks callbacks, Handler handler, SensorManager sensorManager, DisplayBlanker blanker, LogicalDisplay logicalDisplay, BrightnessTracker brightnessTracker, BrightnessSetting brightnessSetting, - Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata) { + Runnable onBrightnessChangeRunnable, HighBrightnessModeMetadata hbmMetadata, + boolean bootCompleted) { mLogicalDisplay = logicalDisplay; mDisplayId = mLogicalDisplay.getDisplayIdLocked(); final String displayIdStr = "[" + mDisplayId + "]"; @@ -655,6 +659,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mTemporaryAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT; mPendingAutoBrightnessAdjustment = PowerManager.BRIGHTNESS_INVALID_FLOAT; + mBootCompleted = bootCompleted; } private void applyReduceBrightColorsSplineAdjustment() { @@ -1312,7 +1317,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (mScreenOffBrightnessSensorController != null) { mScreenOffBrightnessSensorController.setLightSensorEnabled(mUseAutoBrightness - && (state == Display.STATE_OFF || (state == Display.STATE_DOZE + && mIsEnabled && (state == Display.STATE_OFF || (state == Display.STATE_DOZE && !mAllowAutoBrightnessWhileDozingConfig))); } @@ -1370,7 +1375,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Initialize things the first time the power state is changed. if (mustInitialize) { - initialize(state); + initialize(readyToUpdateDisplayState() ? state : Display.STATE_UNKNOWN); } // Animate the screen state change unless already animating. @@ -2050,7 +2055,8 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } } - if (!reportOnly && mPowerState.getScreenState() != state) { + if (!reportOnly && mPowerState.getScreenState() != state + && readyToUpdateDisplayState()) { Trace.traceCounter(Trace.TRACE_TAG_POWER, "ScreenState", state); // TODO(b/153319140) remove when we can get this from the above trace invocation SystemProperties.set("debug.tracing.screen_state", String.valueOf(state)); @@ -2497,6 +2503,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mBrightnessSetting.setBrightness(brightnessValue); } + void onBootCompleted() { + mHandler.obtainMessage(MSG_BOOT_COMPLETED).sendToTarget(); + } + private void updateScreenBrightnessSetting(float brightnessValue) { if (!isValidBrightnessValue(brightnessValue) || brightnessValue == mCurrentScreenBrightnessSetting) { @@ -2642,6 +2652,17 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } }; + /** + * Indicates whether the display state is ready to update. If this is the default display, we + * want to update it right away so that we can draw the boot animation on it. If it is not + * the default display, drawing the boot animation on it would look incorrect, so we need + * to wait until boot is completed. + * @return True if the display state is ready to update + */ + private boolean readyToUpdateDisplayState() { + return mDisplayId == Display.DEFAULT_DISPLAY || mBootCompleted; + } + public void dump(final PrintWriter pw) { synchronized (mLock) { pw.println(); @@ -3177,6 +3198,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call case MSG_SWITCH_USER: handleOnSwitchUser(msg.arg1); break; + + case MSG_BOOT_COMPLETED: + mBootCompleted = true; + updatePowerState(); + break; } } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index b5fceb50f1dc..ff10cbc19d5f 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -8543,6 +8543,9 @@ public class NotificationManagerService extends SystemService { if (interceptBefore && !record.isIntercepted() && record.isNewEnoughForAlerting(System.currentTimeMillis())) { buzzBeepBlinkLocked(record); + + // Log alert after change in intercepted state to Zen Log as well + ZenLog.traceAlertOnUpdatedIntercept(record); } } if (changed) { diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index d3443066155f..1501d69cea3c 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -114,6 +114,8 @@ public final class NotificationRecord { // is this notification currently being intercepted by Zen Mode? private boolean mIntercept; + // has the intercept value been set explicitly? we only want to log it if new or changed + private boolean mInterceptSet; // is this notification hidden since the app pkg is suspended? private boolean mHidden; @@ -914,6 +916,7 @@ public final class NotificationRecord { public boolean setIntercepted(boolean intercept) { mIntercept = intercept; + mInterceptSet = true; return mIntercept; } @@ -934,6 +937,10 @@ public final class NotificationRecord { return mIntercept; } + public boolean hasInterceptBeenSet() { + return mInterceptSet; + } + public boolean isNewEnoughForAlerting(long now) { return getFreshnessMs(now) <= MAX_SOUND_DELAY_MS; } diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java index c0bc474cd8fa..35b94e7ccd63 100644 --- a/services/core/java/com/android/server/notification/ZenLog.java +++ b/services/core/java/com/android/server/notification/ZenLog.java @@ -68,20 +68,23 @@ public class ZenLog { private static final int TYPE_MATCHES_CALL_FILTER = 18; private static final int TYPE_RECORD_CALLER = 19; private static final int TYPE_CHECK_REPEAT_CALLER = 20; + private static final int TYPE_ALERT_ON_UPDATED_INTERCEPT = 21; private static int sNext; private static int sSize; public static void traceIntercepted(NotificationRecord record, String reason) { - if (record != null && record.isIntercepted()) return; // already logged append(TYPE_INTERCEPTED, record.getKey() + "," + reason); } public static void traceNotIntercepted(NotificationRecord record, String reason) { - if (record != null && record.isUpdate) return; // already logged append(TYPE_NOT_INTERCEPTED, record.getKey() + "," + reason); } + public static void traceAlertOnUpdatedIntercept(NotificationRecord record) { + append(TYPE_ALERT_ON_UPDATED_INTERCEPT, record.getKey()); + } + public static void traceSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller, int ringerModeInternalIn, int ringerModeInternalOut) { append(TYPE_SET_RINGER_MODE_EXTERNAL, caller + ",e:" + @@ -219,6 +222,7 @@ public class ZenLog { case TYPE_MATCHES_CALL_FILTER: return "matches_call_filter"; case TYPE_RECORD_CALLER: return "record_caller"; case TYPE_CHECK_REPEAT_CALLER: return "check_repeat_caller"; + case TYPE_ALERT_ON_UPDATED_INTERCEPT: return "alert_on_updated_intercept"; default: return "unknown"; } } diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java index db0ce2ef6fe2..5b7b0c12026b 100644 --- a/services/core/java/com/android/server/notification/ZenModeFiltering.java +++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java @@ -155,85 +155,85 @@ public class ZenModeFiltering { if (isCritical(record)) { // Zen mode is ignored for critical notifications. - ZenLog.traceNotIntercepted(record, "criticalNotification"); + maybeLogInterceptDecision(record, false, "criticalNotification"); return false; } // Make an exception to policy for the notification saying that policy has changed if (NotificationManager.Policy.areAllVisualEffectsSuppressed(policy.suppressedVisualEffects) && "android".equals(record.getSbn().getPackageName()) && SystemMessageProto.SystemMessage.NOTE_ZEN_UPGRADE == record.getSbn().getId()) { - ZenLog.traceNotIntercepted(record, "systemDndChangedNotification"); + maybeLogInterceptDecision(record, false, "systemDndChangedNotification"); return false; } switch (zen) { case Global.ZEN_MODE_NO_INTERRUPTIONS: // #notevenalarms - ZenLog.traceIntercepted(record, "none"); + maybeLogInterceptDecision(record, true, "none"); return true; case Global.ZEN_MODE_ALARMS: if (isAlarm(record)) { // Alarms only - ZenLog.traceNotIntercepted(record, "alarm"); + maybeLogInterceptDecision(record, false, "alarm"); return false; } - ZenLog.traceIntercepted(record, "alarmsOnly"); + maybeLogInterceptDecision(record, true, "alarmsOnly"); return true; case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS: // allow user-prioritized packages through in priority mode if (record.getPackagePriority() == Notification.PRIORITY_MAX) { - ZenLog.traceNotIntercepted(record, "priorityApp"); + maybeLogInterceptDecision(record, false, "priorityApp"); return false; } if (isAlarm(record)) { if (!policy.allowAlarms()) { - ZenLog.traceIntercepted(record, "!allowAlarms"); + maybeLogInterceptDecision(record, true, "!allowAlarms"); return true; } - ZenLog.traceNotIntercepted(record, "allowedAlarm"); + maybeLogInterceptDecision(record, false, "allowedAlarm"); return false; } if (isEvent(record)) { if (!policy.allowEvents()) { - ZenLog.traceIntercepted(record, "!allowEvents"); + maybeLogInterceptDecision(record, true, "!allowEvents"); return true; } - ZenLog.traceNotIntercepted(record, "allowedEvent"); + maybeLogInterceptDecision(record, false, "allowedEvent"); return false; } if (isReminder(record)) { if (!policy.allowReminders()) { - ZenLog.traceIntercepted(record, "!allowReminders"); + maybeLogInterceptDecision(record, true, "!allowReminders"); return true; } - ZenLog.traceNotIntercepted(record, "allowedReminder"); + maybeLogInterceptDecision(record, false, "allowedReminder"); return false; } if (isMedia(record)) { if (!policy.allowMedia()) { - ZenLog.traceIntercepted(record, "!allowMedia"); + maybeLogInterceptDecision(record, true, "!allowMedia"); return true; } - ZenLog.traceNotIntercepted(record, "allowedMedia"); + maybeLogInterceptDecision(record, false, "allowedMedia"); return false; } if (isSystem(record)) { if (!policy.allowSystem()) { - ZenLog.traceIntercepted(record, "!allowSystem"); + maybeLogInterceptDecision(record, true, "!allowSystem"); return true; } - ZenLog.traceNotIntercepted(record, "allowedSystem"); + maybeLogInterceptDecision(record, false, "allowedSystem"); return false; } if (isConversation(record)) { if (policy.allowConversations()) { if (policy.priorityConversationSenders == CONVERSATION_SENDERS_ANYONE) { - ZenLog.traceNotIntercepted(record, "conversationAnyone"); + maybeLogInterceptDecision(record, false, "conversationAnyone"); return false; } else if (policy.priorityConversationSenders == NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT && record.getChannel().isImportantConversation()) { - ZenLog.traceNotIntercepted(record, "conversationMatches"); + maybeLogInterceptDecision(record, false, "conversationMatches"); return false; } } @@ -244,31 +244,59 @@ public class ZenModeFiltering { if (policy.allowRepeatCallers() && REPEAT_CALLERS.isRepeat( mContext, extras(record), record.getPhoneNumbers())) { - ZenLog.traceNotIntercepted(record, "repeatCaller"); + maybeLogInterceptDecision(record, false, "repeatCaller"); return false; } if (!policy.allowCalls()) { - ZenLog.traceIntercepted(record, "!allowCalls"); + maybeLogInterceptDecision(record, true, "!allowCalls"); return true; } return shouldInterceptAudience(policy.allowCallsFrom(), record); } if (isMessage(record)) { if (!policy.allowMessages()) { - ZenLog.traceIntercepted(record, "!allowMessages"); + maybeLogInterceptDecision(record, true, "!allowMessages"); return true; } return shouldInterceptAudience(policy.allowMessagesFrom(), record); } - ZenLog.traceIntercepted(record, "!priority"); + maybeLogInterceptDecision(record, true, "!priority"); return true; default: - ZenLog.traceNotIntercepted(record, "unknownZenMode"); + maybeLogInterceptDecision(record, false, "unknownZenMode"); return false; } } + // Consider logging the decision of shouldIntercept for the given record. + // This will log the outcome if one of the following is true: + // - it's the first time the intercept decision is set for the record + // - OR it's not the first time, but the intercept decision changed + private static void maybeLogInterceptDecision(NotificationRecord record, boolean intercept, + String reason) { + boolean interceptBefore = record.isIntercepted(); + if (record.hasInterceptBeenSet() && (interceptBefore == intercept)) { + // this record has already been evaluated for whether it should be intercepted, and + // the decision has not changed. + return; + } + + // add a note to the reason indicating whether it's new or updated + String annotatedReason = reason; + if (!record.hasInterceptBeenSet()) { + annotatedReason = "new:" + reason; + } else if (interceptBefore != intercept) { + annotatedReason = "updated:" + reason; + } + + if (intercept) { + ZenLog.traceIntercepted(record, annotatedReason); + } else { + ZenLog.traceNotIntercepted(record, annotatedReason); + } + } + /** * Check if the notification is too critical to be suppressed. * @@ -285,10 +313,10 @@ public class ZenModeFiltering { private static boolean shouldInterceptAudience(int source, NotificationRecord record) { float affinity = record.getContactAffinity(); if (!audienceMatches(source, affinity)) { - ZenLog.traceIntercepted(record, "!audienceMatches,affinity=" + affinity); + maybeLogInterceptDecision(record, true, "!audienceMatches,affinity=" + affinity); return true; } - ZenLog.traceNotIntercepted(record, "affinity=" + affinity); + maybeLogInterceptDecision(record, false, "affinity=" + affinity); return false; } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 8aca912999cc..df33163016eb 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -7880,6 +7880,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } /** + * @return The {@code true} if the current instance has {@link mCompatDisplayInsets} without + * considering the inheritance implemented in {@link #getCompatDisplayInsets()} + */ + boolean hasCompatDisplayInsetsWithoutInheritance() { + return mCompatDisplayInsets != null; + } + + /** * @return {@code true} if this activity is in size compatibility mode that uses the different * density than its parent or its bounds don't fit in parent naturally. */ @@ -7887,7 +7895,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mInSizeCompatModeForBounds) { return true; } - if (mCompatDisplayInsets == null || !shouldCreateCompatDisplayInsets() + if (getCompatDisplayInsets() == null || !shouldCreateCompatDisplayInsets() // The orientation is different from parent when transforming. || isFixedRotationTransforming()) { return false; @@ -7958,11 +7966,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. private void updateCompatDisplayInsets() { - if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { - mCompatDisplayInsets = mLetterboxUiController.getInheritedCompatDisplayInsets(); - return; - } - if (mCompatDisplayInsets != null || !shouldCreateCompatDisplayInsets()) { + if (getCompatDisplayInsets() != null || !shouldCreateCompatDisplayInsets()) { // The override configuration is set only once in size compatibility mode. return; } @@ -8025,9 +8029,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override float getCompatScale() { - if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { - return mLetterboxUiController.getInheritedSizeCompatScale(); - } return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale(); } @@ -8071,7 +8072,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A resolveFixedOrientationConfiguration(newParentConfiguration); } - if (mCompatDisplayInsets != null) { + if (getCompatDisplayInsets() != null) { resolveSizeCompatModeConfiguration(newParentConfiguration); } else if (inMultiWindowMode() && !isFixedOrientationLetterboxAllowed) { // We ignore activities' requested orientation in multi-window modes. They may be @@ -8089,7 +8090,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A resolveAspectRatioRestriction(newParentConfiguration); } - if (isFixedOrientationLetterboxAllowed || mCompatDisplayInsets != null + if (isFixedOrientationLetterboxAllowed || getCompatDisplayInsets() != null // In fullscreen, can be letterboxed for aspect ratio. || !inMultiWindowMode()) { updateResolvedBoundsPosition(newParentConfiguration); @@ -8097,7 +8098,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean isIgnoreOrientationRequest = mDisplayContent != null && mDisplayContent.getIgnoreOrientationRequest(); - if (mCompatDisplayInsets == null // for size compat mode set in updateCompatDisplayInsets + if (getCompatDisplayInsets() == null + // for size compat mode set in updateCompatDisplayInsets // Fixed orientation letterboxing is possible on both large screen devices // with ignoreOrientationRequest enabled and on phones in split screen even with // ignoreOrientationRequest disabled. @@ -8143,7 +8145,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A info.neverSandboxDisplayApis(sConstrainDisplayApisConfig), info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig), !matchParentBounds(), - mCompatDisplayInsets != null, + getCompatDisplayInsets() != null, shouldCreateCompatDisplayInsets()); } resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds); @@ -8442,8 +8444,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A || orientationRespectedWithInsets)) { return; } + final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets(); - if (mCompatDisplayInsets != null && !mCompatDisplayInsets.mIsInFixedOrientationLetterbox) { + if (compatDisplayInsets != null && !compatDisplayInsets.mIsInFixedOrientationLetterbox) { // App prefers to keep its original size. // If the size compat is from previous fixed orientation letterboxing, we may want to // have fixed orientation letterbox again, otherwise it will show the size compat @@ -8498,9 +8501,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mIsAspectRatioApplied = applyAspectRatio(resolvedBounds, containingBoundsWithInsets, containingBounds, desiredAspectRatio); - if (mCompatDisplayInsets != null) { - mCompatDisplayInsets.getBoundsByRotation( - mTmpBounds, newParentConfig.windowConfiguration.getRotation()); + if (compatDisplayInsets != null) { + compatDisplayInsets.getBoundsByRotation(mTmpBounds, + newParentConfig.windowConfiguration.getRotation()); if (resolvedBounds.width() != mTmpBounds.width() || resolvedBounds.height() != mTmpBounds.height()) { // The app shouldn't be resized, we only do fixed orientation letterboxing if the @@ -8514,7 +8517,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Calculate app bounds using fixed orientation bounds because they will be needed later // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}. getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(), - newParentConfig, mCompatDisplayInsets); + newParentConfig, compatDisplayInsets); mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds); } @@ -8571,13 +8574,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ? requestedOrientation // We should use the original orientation of the activity when possible to avoid // forcing the activity in the opposite orientation. - : mCompatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED - ? mCompatDisplayInsets.mOriginalRequestedOrientation + : getCompatDisplayInsets().mOriginalRequestedOrientation != ORIENTATION_UNDEFINED + ? getCompatDisplayInsets().mOriginalRequestedOrientation : newParentConfiguration.orientation; int rotation = newParentConfiguration.windowConfiguration.getRotation(); final boolean isFixedToUserRotation = mDisplayContent == null || mDisplayContent.getDisplayRotation().isFixedToUserRotation(); - if (!isFixedToUserRotation && !mCompatDisplayInsets.mIsFloating) { + if (!isFixedToUserRotation && !getCompatDisplayInsets().mIsFloating) { // Use parent rotation because the original display can be rotated. resolvedConfig.windowConfiguration.setRotation(rotation); } else { @@ -8593,11 +8596,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // rely on them to contain the original and unchanging width and height of the app. final Rect containingAppBounds = new Rect(); final Rect containingBounds = mTmpBounds; - mCompatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation, + getCompatDisplayInsets().getContainerBounds(containingAppBounds, containingBounds, rotation, orientation, orientationRequested, isFixedToUserRotation); resolvedBounds.set(containingBounds); // The size of floating task is fixed (only swap), so the aspect ratio is already correct. - if (!mCompatDisplayInsets.mIsFloating) { + if (!getCompatDisplayInsets().mIsFloating) { mIsAspectRatioApplied = applyAspectRatio(resolvedBounds, containingAppBounds, containingBounds); } @@ -8606,7 +8609,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // are calculated in compat container space. The actual position on screen will be applied // later, so the calculation is simpler that doesn't need to involve offset from parent. getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, - mCompatDisplayInsets); + getCompatDisplayInsets()); // Use current screen layout as source because the size of app is independent to parent. resolvedConfig.screenLayout = TaskFragment.computeScreenLayoutOverride( getConfiguration().screenLayout, resolvedConfig.screenWidthDp, @@ -8641,14 +8644,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Calculates the scale the size compatibility bounds into the region which is available // to application. - final int contentW = resolvedAppBounds.width(); - final int contentH = resolvedAppBounds.height(); - final int viewportW = containerAppBounds.width(); - final int viewportH = containerAppBounds.height(); final float lastSizeCompatScale = mSizeCompatScale; - // Only allow to scale down. - mSizeCompatScale = (contentW <= viewportW && contentH <= viewportH) - ? 1f : Math.min((float) viewportW / contentW, (float) viewportH / contentH); + updateSizeCompatScale(resolvedAppBounds, containerAppBounds); + final int containerTopInset = containerAppBounds.top - containerBounds.top; final boolean topNotAligned = containerTopInset != resolvedAppBounds.top - resolvedBounds.top; @@ -8688,6 +8686,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A isInSizeCompatModeForBounds(resolvedAppBounds, containerAppBounds); } + void updateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) { + // Only allow to scale down. + mSizeCompatScale = mLetterboxUiController.findOpaqueNotFinishingActivityBelow() + .map(activityRecord -> activityRecord.mSizeCompatScale) + .orElseGet(() -> { + final int contentW = resolvedAppBounds.width(); + final int contentH = resolvedAppBounds.height(); + final int viewportW = containerAppBounds.width(); + final int viewportH = containerAppBounds.height(); + return (contentW <= viewportW && contentH <= viewportH) ? 1f : Math.min( + (float) viewportW / contentW, (float) viewportH / contentH); + }); + } + private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity @@ -8750,10 +8762,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override public Rect getBounds() { - if (mSizeCompatBounds != null) { - return mSizeCompatBounds; - } - return super.getBounds(); + // TODO(b/268458693): Refactor configuration inheritance in case of translucent activities + final Rect superBounds = super.getBounds(); + return mLetterboxUiController.findOpaqueNotFinishingActivityBelow() + .map(ActivityRecord::getBounds) + .orElseGet(() -> { + if (mSizeCompatBounds != null) { + return mSizeCompatBounds; + } + return superBounds; + }); } @Override @@ -8778,7 +8796,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Max bounds should be sandboxed when an activity should have compatDisplayInsets, and it // will keep the same bounds and screen configuration when it was first launched regardless // how its parent window changes, so that the sandbox API will provide a consistent result. - if (mCompatDisplayInsets != null || shouldCreateCompatDisplayInsets()) { + if (getCompatDisplayInsets() != null || shouldCreateCompatDisplayInsets()) { return true; } @@ -8820,7 +8838,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mTransitionController.collect(this); } } - if (mCompatDisplayInsets != null) { + if (getCompatDisplayInsets() != null) { Configuration overrideConfig = getRequestedOverrideConfiguration(); // Adapt to changes in orientation locking. The app is still non-resizable, but // it can change which orientation is fixed. If the fixed orientation changes, @@ -8896,7 +8914,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mVisibleRequested) { // It may toggle the UI for user to restart the size compatibility mode activity. display.handleActivitySizeCompatModeIfNeeded(this); - } else if (mCompatDisplayInsets != null && !visibleIgnoringKeyguard + } else if (getCompatDisplayInsets() != null && !visibleIgnoringKeyguard && (app == null || !app.hasVisibleActivities())) { // visibleIgnoringKeyguard is checked to avoid clearing mCompatDisplayInsets during // displays change. Displays are turned off during the change so mVisibleRequested diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index 513667024caa..fa49a6ba6c2b 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -22,6 +22,7 @@ import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ALLOW import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_CAMERA_COMPAT_TREATMENT; import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_COMPAT_FAKE_FOCUS; import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY; +import static com.android.server.wm.LetterboxConfigurationDeviceConfig.KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY; import android.annotation.IntDef; import android.annotation.NonNull; @@ -296,7 +297,6 @@ final class LetterboxConfiguration { R.bool.config_isCompatFakeFocusEnabled); mIsPolicyForIgnoringRequestedOrientationEnabled = mContext.getResources().getBoolean( R.bool.config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled); - mIsDisplayRotationImmersiveAppCompatPolicyEnabled = mContext.getResources().getBoolean( R.bool.config_letterboxIsDisplayRotationImmersiveAppCompatPolicyEnabled); mDeviceConfig.updateFlagActiveStatus( @@ -311,7 +311,9 @@ final class LetterboxConfiguration { mDeviceConfig.updateFlagActiveStatus( /* isActive */ mIsCompatFakeFocusEnabled, /* key */ KEY_ENABLE_COMPAT_FAKE_FOCUS); - + mDeviceConfig.updateFlagActiveStatus( + /* isActive */ mTranslucentLetterboxingEnabled, + /* key */ KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY); mLetterboxConfigurationPersister = letterboxConfigurationPersister; mLetterboxConfigurationPersister.start(); } @@ -1003,7 +1005,7 @@ final class LetterboxConfiguration { boolean isTranslucentLetterboxingEnabled() { return mTranslucentLetterboxingOverrideEnabled || (mTranslucentLetterboxingEnabled - && isTranslucentLetterboxingAllowed()); + && mDeviceConfig.getFlag(KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY)); } void setTranslucentLetterboxingEnabled(boolean translucentLetterboxingEnabled) { @@ -1051,13 +1053,6 @@ final class LetterboxConfiguration { isDeviceInTabletopMode, nextVerticalPosition); } - // TODO(b/262378106): Cache a runtime flag and implement - // DeviceConfig.OnPropertiesChangedListener - static boolean isTranslucentLetterboxingAllowed() { - return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER, - "enable_translucent_activity_letterbox", false); - } - /** Whether fake sending focus is enabled for unfocused apps in splitscreen */ boolean isCompatFakeFocusEnabled() { return mIsCompatFakeFocusEnabled && mDeviceConfig.getFlag(KEY_ENABLE_COMPAT_FAKE_FOCUS); diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java index b364872e56e7..df3c8f0fdccc 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java +++ b/services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java @@ -48,6 +48,11 @@ final class LetterboxConfigurationDeviceConfig static final String KEY_ENABLE_COMPAT_FAKE_FOCUS = "enable_compat_fake_focus"; private static final boolean DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS = true; + static final String KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY = + "enable_letterbox_translucent_activity"; + + private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY = true; + @VisibleForTesting static final Map<String, Boolean> sKeyToDefaultValueMap = Map.of( KEY_ENABLE_CAMERA_COMPAT_TREATMENT, @@ -57,7 +62,9 @@ final class LetterboxConfigurationDeviceConfig KEY_ALLOW_IGNORE_ORIENTATION_REQUEST, DEFAULT_VALUE_ALLOW_IGNORE_ORIENTATION_REQUEST, KEY_ENABLE_COMPAT_FAKE_FOCUS, - DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS + DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS, + KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY, + DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY ); // Whether camera compatibility treatment is enabled. @@ -82,6 +89,10 @@ final class LetterboxConfigurationDeviceConfig // which isn't guaranteed by default in multi-window modes. private boolean mIsCompatFakeFocusAllowed = DEFAULT_VALUE_ENABLE_COMPAT_FAKE_FOCUS; + // Whether the letterbox strategy for transparent activities is allowed + private boolean mIsTranslucentLetterboxingAllowed = + DEFAULT_VALUE_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY; + // Set of active device configs that need to be updated in // DeviceConfig.OnPropertiesChangedListener#onPropertiesChanged. private final ArraySet<String> mActiveDeviceConfigsSet = new ArraySet<>(); @@ -129,6 +140,8 @@ final class LetterboxConfigurationDeviceConfig return mIsAllowIgnoreOrientationRequest; case KEY_ENABLE_COMPAT_FAKE_FOCUS: return mIsCompatFakeFocusAllowed; + case KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY: + return mIsTranslucentLetterboxingAllowed; default: throw new AssertionError("Unexpected flag name: " + key); } @@ -141,20 +154,20 @@ final class LetterboxConfigurationDeviceConfig } switch (key) { case KEY_ENABLE_CAMERA_COMPAT_TREATMENT: - mIsCameraCompatTreatmentEnabled = - getDeviceConfig(key, defaultValue); + mIsCameraCompatTreatmentEnabled = getDeviceConfig(key, defaultValue); break; case KEY_ENABLE_DISPLAY_ROTATION_IMMERSIVE_APP_COMPAT_POLICY: mIsDisplayRotationImmersiveAppCompatPolicyEnabled = getDeviceConfig(key, defaultValue); break; case KEY_ALLOW_IGNORE_ORIENTATION_REQUEST: - mIsAllowIgnoreOrientationRequest = - getDeviceConfig(key, defaultValue); + mIsAllowIgnoreOrientationRequest = getDeviceConfig(key, defaultValue); break; case KEY_ENABLE_COMPAT_FAKE_FOCUS: - mIsCompatFakeFocusAllowed = - getDeviceConfig(key, defaultValue); + mIsCompatFakeFocusAllowed = getDeviceConfig(key, defaultValue); + break; + case KEY_ENABLE_LETTERBOX_TRANSLUCENT_ACTIVITY: + mIsTranslucentLetterboxingAllowed = getDeviceConfig(key, defaultValue); break; default: throw new AssertionError("Unexpected flag name: " + key); diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 5a481f438827..e1dbe01aca61 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -193,12 +193,6 @@ final class LetterboxUiController { // The app compat state for the opaque activity if any private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; - // If true it means that the opaque activity beneath a translucent one is in SizeCompatMode. - private boolean mIsInheritedInSizeCompatMode; - - // This is the SizeCompatScale of the opaque activity beneath a translucent one - private float mInheritedSizeCompatScale; - // The CompatDisplayInsets of the opaque activity beneath the translucent one. private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets; @@ -735,8 +729,21 @@ final class LetterboxUiController { : mActivityRecord.inMultiWindowMode() ? mActivityRecord.getTask().getBounds() : mActivityRecord.getRootTask().getParent().getBounds(); + // In case of translucent activities an option is to use the WindowState#getFrame() of + // the first opaque activity beneath. In some cases (e.g. an opaque activity is using + // non MATCH_PARENT layouts or a Dialog theme) this might not provide the correct + // information and in particular it might provide a value for a smaller area making + // the letterbox overlap with the translucent activity's frame. + // If we use WindowState#getFrame() for the translucent activity's letterbox inner + // frame, the letterbox will then be overlapped with the translucent activity's frame. + // Because the surface layer of letterbox is lower than an activity window, this + // won't crop the content, but it may affect other features that rely on values stored + // in mLetterbox, e.g. transitions, a status bar scrim and recents preview in Launcher + // For this reason we use ActivityRecord#getBounds() that the translucent activity + // inherits from the first opaque activity beneath and also takes care of the scaling + // in case of activities in size compat mode. final Rect innerFrame = hasInheritedLetterboxBehavior() - ? mActivityRecord.getWindowConfiguration().getBounds() : w.getFrame(); + ? mActivityRecord.getBounds() : w.getFrame(); mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint); } else if (mLetterbox != null) { mLetterbox.hide(); @@ -962,8 +969,8 @@ final class LetterboxUiController { && (parentConfiguration.orientation == ORIENTATION_LANDSCAPE && mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT) // Check whether the activity fills the parent vertically. - && parentConfiguration.windowConfiguration.getBounds().height() - == mActivityRecord.getBounds().height(); + && parentConfiguration.windowConfiguration.getAppBounds().height() + <= mActivityRecord.getBounds().height(); } @VisibleForTesting @@ -1386,10 +1393,10 @@ final class LetterboxUiController { mLetterboxConfigListener.onRemoved(); clearInheritedConfig(); } - // In case mActivityRecord.getCompatDisplayInsets() is not null we don't apply the + // In case mActivityRecord.hasCompatDisplayInsetsWithoutOverride() we don't apply the // opaque activity constraints because we're expecting the activity is already letterboxed. - if (mActivityRecord.getTask() == null || mActivityRecord.getCompatDisplayInsets() != null - || mActivityRecord.fillsParent()) { + if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent() + || mActivityRecord.hasCompatDisplayInsetsWithoutInheritance()) { return; } final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity( @@ -1417,6 +1424,7 @@ final class LetterboxUiController { // We need to initialize appBounds to avoid NPE. The actual value will // be set ahead when resolving the Configuration for the activity. mutatedConfiguration.windowConfiguration.setAppBounds(new Rect()); + inheritConfiguration(firstOpaqueActivityBeneath); return mutatedConfiguration; }); } @@ -1457,16 +1465,12 @@ final class LetterboxUiController { return mInheritedAppCompatState; } - float getInheritedSizeCompatScale() { - return mInheritedSizeCompatScale; - } - @Configuration.Orientation int getInheritedOrientation() { return mInheritedOrientation; } - public ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() { + ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() { return mInheritedCompatDisplayInsets; } @@ -1486,7 +1490,7 @@ final class LetterboxUiController { * @return The first not finishing opaque activity beneath the current translucent activity * if it exists and the strategy is enabled. */ - private Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() { + Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() { if (!hasInheritedLetterboxBehavior() || mActivityRecord.getTask() == null) { return Optional.empty(); } @@ -1508,8 +1512,6 @@ final class LetterboxUiController { } mInheritedOrientation = firstOpaque.getRequestedConfigurationOrientation(); mInheritedAppCompatState = firstOpaque.getAppCompatState(); - mIsInheritedInSizeCompatMode = firstOpaque.inSizeCompatMode(); - mInheritedSizeCompatScale = firstOpaque.getCompatScale(); mInheritedCompatDisplayInsets = firstOpaque.getCompatDisplayInsets(); } @@ -1519,8 +1521,6 @@ final class LetterboxUiController { mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED; mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; - mIsInheritedInSizeCompatMode = false; - mInheritedSizeCompatScale = 1f; mInheritedCompatDisplayInsets = null; } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 3aa80c4ccefb..adaaa259e680 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -3455,6 +3455,11 @@ class Task extends TaskFragment { && top.getOrganizedTask() == this && top.isState(RESUMED); // Whether the direct top activity is in size compat mode on foreground. info.topActivityInSizeCompat = isTopActivityResumed && top.inSizeCompatMode(); + if (info.topActivityInSizeCompat + && mWmService.mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) { + // We hide the restart button in case of transparent activities. + info.topActivityInSizeCompat = top.fillsParent(); + } // Whether the direct top activity is eligible for letterbox education. info.topActivityEligibleForLetterboxEducation = isTopActivityResumed && top.isEligibleForLetterboxEducation(); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index de84655986ef..318fff275f1f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -274,7 +274,8 @@ public class SizeCompatTests extends WindowTestsBase { public void testTranslucentActivitiesWhenUnfolding() { mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); setUpDisplaySizeWithApp(2800, 1400); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mActivity.mDisplayContent.setIgnoreOrientationRequest( + true /* ignoreOrientationRequest */); mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier( 1.0f /*letterboxVerticalPositionMultiplier*/); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); @@ -285,18 +286,23 @@ public class SizeCompatTests extends WindowTestsBase { .build(); doReturn(false).when(translucentActivity).fillsParent(); mTask.addChild(translucentActivity); + assertEquals(translucentActivity.getBounds(), mActivity.getBounds()); mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); spyOn(mActivity); // Halffold - setFoldablePosture(translucentActivity, true /* isHalfFolded */, false /* isTabletop */); + setFoldablePosture(translucentActivity, true /* isHalfFolded */, + false /* isTabletop */); verify(mActivity).recomputeConfiguration(); + assertEquals(translucentActivity.getBounds(), mActivity.getBounds()); clearInvocations(mActivity); // Unfold - setFoldablePosture(translucentActivity, false /* isHalfFolded */, false /* isTabletop */); + setFoldablePosture(translucentActivity, false /* isHalfFolded */, + false /* isTabletop */); verify(mActivity).recomputeConfiguration(); + assertEquals(translucentActivity.getBounds(), mActivity.getBounds()); } @Test diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 6ea416b54811..468bf1719d89 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -510,7 +510,6 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser private boolean mUsbAccessoryConnected; private boolean mSourcePower; private boolean mSinkPower; - private boolean mConfigured; private boolean mAudioAccessoryConnected; private boolean mAudioAccessorySupported; @@ -543,7 +542,12 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser private final UsbPermissionManager mPermissionManager; private NotificationManager mNotificationManager; + /** + * Do not debounce for the first disconnect after resetUsbGadget. + */ + protected boolean mResetUsbGadgetDisableDebounce; protected boolean mConnected; + protected boolean mConfigured; protected long mScreenUnlockedFunctions; protected boolean mBootCompleted; protected boolean mCurrentFunctionsApplied; @@ -650,15 +654,29 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser Slog.e(TAG, "unknown state " + state); return; } - if (configured == 0) removeMessages(MSG_UPDATE_STATE); if (connected == 1) removeMessages(MSG_FUNCTION_SWITCH_TIMEOUT); Message msg = Message.obtain(this, MSG_UPDATE_STATE); msg.arg1 = connected; msg.arg2 = configured; - // debounce disconnects to avoid problems bringing up USB tethering - sendMessageDelayed(msg, + if (DEBUG) { + Slog.i(TAG, "mResetUsbGadgetDisableDebounce:" + mResetUsbGadgetDisableDebounce + + " connected:" + connected + "configured:" + configured); + } + if (mResetUsbGadgetDisableDebounce) { + // Do not debounce disconnect after resetUsbGadget. + sendMessage(msg); + if (connected == 1) mResetUsbGadgetDisableDebounce = false; + } else { + if (configured == 0) { + removeMessages(MSG_UPDATE_STATE); + if (DEBUG) Slog.i(TAG, "removeMessages MSG_UPDATE_STATE"); + } + if (connected == 1) removeMessages(MSG_FUNCTION_SWITCH_TIMEOUT); + // debounce disconnects to avoid problems bringing up USB tethering. + sendMessageDelayed(msg, (connected == 0) ? (mScreenLocked ? DEVICE_STATE_UPDATE_DELAY : DEVICE_STATE_UPDATE_DELAY_EXT) : 0); + } } public void updateHostState(UsbPort port, UsbPortStatus status) { @@ -904,7 +922,10 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser case MSG_UPDATE_STATE: mConnected = (msg.arg1 == 1); mConfigured = (msg.arg2 == 1); - + if (DEBUG) { + Slog.i(TAG, "handleMessage MSG_UPDATE_STATE " + "mConnected:" + mConnected + + " mConfigured:" + mConfigured); + } updateUsbNotification(false); updateAdbNotification(false); if (mBootCompleted) { @@ -2010,12 +2031,19 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } try { + // MSG_ACCESSORY_MODE_ENTER_TIMEOUT has to be removed to allow exiting + // AOAP mode during resetUsbGadget. + removeMessages(MSG_ACCESSORY_MODE_ENTER_TIMEOUT); + if (mConfigured) { + mResetUsbGadgetDisableDebounce = true; + } android.hardware.usb.gadget.V1_1.IUsbGadget gadgetProxy = android.hardware.usb.gadget.V1_1.IUsbGadget .castFrom(mGadgetProxy); gadgetProxy.reset(); } catch (RemoteException e) { Slog.e(TAG, "reset Usb Gadget failed", e); + mResetUsbGadgetDisableDebounce = false; } } break; |