summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java166
-rw-r--r--libs/WindowManager/Shell/Android.bp1
-rw-r--r--libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background.xml21
-rw-r--r--libs/WindowManager/Shell/res/drawable/letterbox_education_dismiss_button_background_ripple.xml31
-rw-r--r--libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background.xml22
-rw-r--r--libs/WindowManager/Shell/res/drawable/letterbox_restart_button_background_ripple.xml32
-rw-r--r--libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background.xml23
-rw-r--r--libs/WindowManager/Shell/res/drawable/letterbox_restart_dismiss_button_background_ripple.xml34
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropConstants.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java14
-rw-r--r--packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java4
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt3
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableImageView.kt2
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/view/LaunchableTextView.kt2
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleAnimation.kt2
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt146
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt120
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt15
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt2
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt85
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt107
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java12
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java61
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java94
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/data/model/BacklightModel.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/data/repository/KeyboardRepository.kt100
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/models/recommendation/SmartspaceMediaData.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/process/ProcessWrapper.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/process/condition/SystemProcessCondition.java (renamed from packages/SystemUI/src/com/android/systemui/process/condition/UserProcessCondition.java)15
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java166
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt191
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt108
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModelTest.kt83
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataFilterTest.kt55
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/process/condition/UserProcessConditionTest.java)29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt231
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/ripple/RippleShaderTest.kt121
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt9
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java94
-rw-r--r--services/core/java/com/android/server/wm/LetterboxConfiguration.java15
-rw-r--r--services/core/java/com/android/server/wm/LetterboxConfigurationDeviceConfig.java27
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java40
-rw-r--r--services/core/java/com/android/server/wm/Task.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java12
-rw-r--r--services/usb/java/com/android/server/usb/UsbDeviceManager.java38
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;