diff options
76 files changed, 2062 insertions, 718 deletions
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/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 1f9b6cf6c64f..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> 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/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 2a6fbd2cee8c..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 @@ -1695,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) { @@ -1779,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/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/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/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/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 8e11a990a4dc..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; @@ -3286,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/keyguard/mediator/ScreenOnCoordinator.kt b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt index 0f00a040b094..603471b1de41 100644 --- a/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt +++ b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt @@ -73,6 +73,10 @@ class ScreenOnCoordinator @Inject constructor( @BinderThread fun onScreenTurnedOn() { foldAodAnimationController?.onScreenTurnedOn() + } + + @BinderThread + fun onScreenTurnedOff() { pendingTasks.reset() } } 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/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/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/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java index 47872d2d68bb..4d40db0a0cfd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java @@ -638,6 +638,7 @@ public class KeyguardService extends Service { checkPermission(); mKeyguardViewMediator.onScreenTurnedOff(); mKeyguardLifecyclesDispatcher.dispatch(KeyguardLifecyclesDispatcher.SCREEN_TURNED_OFF); + mScreenOnCoordinator.onScreenTurnedOff(); } @Override // Binder interface 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/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/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 2175a3358396..c6c1a1bc071f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -1197,6 +1197,7 @@ public final class NotificationPanelViewController implements Dumpable { } private void onSplitShadeEnabledChanged() { + mShadeLog.logSplitShadeChanged(mSplitShadeEnabled); // when we switch between split shade and regular shade we want to enforce setting qs to // the default state: expanded for split shade and collapsed otherwise if (!isOnKeyguard() && mPanelExpanded) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index d041212d24c7..43da50ab3c9a 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -1710,12 +1710,16 @@ public class QuickSettingsController { */ private void flingQs(float vel, int type, final Runnable onFinishRunnable, boolean isClick) { + mShadeLog.flingQs(type, isClick); float target; switch (type) { case FLING_EXPAND: target = getMaxExpansionHeight(); break; case FLING_COLLAPSE: + if (mSplitShadeEnabled) { // TODO:(b/269742565) remove below log + Log.wtfStack(TAG, "FLING_COLLAPSE called in split shade"); + } target = getMinExpansionHeight(); break; case FLING_HIDE: diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt index aa8c5b65e0fe..d34e127b194b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt @@ -20,6 +20,9 @@ import android.view.MotionEvent import com.android.systemui.log.dagger.ShadeLog import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.plugins.log.LogLevel +import com.android.systemui.shade.NotificationPanelViewController.FLING_COLLAPSE +import com.android.systemui.shade.NotificationPanelViewController.FLING_EXPAND +import com.android.systemui.shade.NotificationPanelViewController.FLING_HIDE import com.google.errorprone.annotations.CompileTimeConstant import javax.inject.Inject @@ -241,18 +244,40 @@ class ShadeLogger @Inject constructor(@ShadeLog private val buffer: LogBuffer) { ) } - fun logLastFlingWasExpanding( - expand: Boolean - ) { + fun logLastFlingWasExpanding(expand: Boolean) { buffer.log( - TAG, - LogLevel.VERBOSE, - { - bool1 = expand - }, - { - "NPVC mLastFlingWasExpanding set to: $bool1" - } + TAG, + LogLevel.VERBOSE, + { bool1 = expand }, + { "NPVC mLastFlingWasExpanding set to: $bool1" } + ) + } + + fun flingQs(flingType: Int, isClick: Boolean) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { + str1 = flingTypeToString(flingType) + bool1 = isClick + }, + { "QS fling with type $str1, originated from click: $isClick" } + ) + } + + private fun flingTypeToString(flingType: Int) = when (flingType) { + FLING_EXPAND -> "FLING_EXPAND" + FLING_COLLAPSE -> "FLING_COLLAPSE" + FLING_HIDE -> "FLING_HIDE" + else -> "UNKNOWN" + } + + fun logSplitShadeChanged(splitShadeEnabled: Boolean) { + buffer.log( + TAG, + LogLevel.VERBOSE, + { bool1 = splitShadeEnabled }, + { "Split shade state changed: split shade ${if (bool1) "enabled" else "disabled"}" } ) } } 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/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/mediator/ScreenOnCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt index e9a2789bb5c8..9fe32f1e378b 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt @@ -32,6 +32,7 @@ import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito.`when` +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import java.util.Optional @@ -83,6 +84,33 @@ class ScreenOnCoordinatorTest : SysuiTestCase() { } @Test + fun testTasksReady_onScreenTurningOnAndTurnedOnEventsCalledTogether_callsDrawnCallback() { + screenOnCoordinator.onScreenTurningOn(runnable) + screenOnCoordinator.onScreenTurnedOn() + + onUnfoldOverlayReady() + onFoldAodReady() + waitHandlerIdle(testHandler) + + // Should be called when both unfold overlay and keyguard drawn ready + verify(runnable).run() + } + + @Test + fun testTasksReady_onScreenTurnedOnAndTurnedOffBeforeCompletion_doesNotCallDrawnCallback() { + screenOnCoordinator.onScreenTurningOn(runnable) + screenOnCoordinator.onScreenTurnedOn() + screenOnCoordinator.onScreenTurnedOff() + + onUnfoldOverlayReady() + onFoldAodReady() + waitHandlerIdle(testHandler) + + // Should not be called because this screen turning on call is not valid anymore + verify(runnable, never()).run() + } + + @Test fun testUnfoldTransitionDisabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback() { // Recreate with empty unfoldComponent screenOnCoordinator = ScreenOnCoordinator( 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/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/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/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/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/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/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 7957ed63a3e2..5ecc7f2b7bb8 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1317,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))); } 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 e3437683e957..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(); @@ -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; |