Merge "Add unit test for Keyboards & HomeScaffold"
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index da54da16..58ee59d 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -31,6 +31,10 @@
*
* The insets frame will by default as the window frame size. If the providers are set, the
* calculation result based on the source size will be used as the insets frame.
+ *
+ * The InsetsFrameProvider should be self-contained. Nothing describing the window itself, such as
+ * contentInsets, visibleInsets, etc. won't affect the insets providing to other windows when this
+ * is set.
* @hide
*/
public class InsetsFrameProvider implements Parcelable {
diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java
index 573db0d..384dacf 100644
--- a/core/java/android/window/StartingWindowRemovalInfo.java
+++ b/core/java/android/window/StartingWindowRemovalInfo.java
@@ -61,6 +61,12 @@
*/
public boolean deferRemoveForIme;
+ /**
+ * The rounded corner radius
+ * @hide
+ */
+ public float roundedCornerRadius;
+
public StartingWindowRemovalInfo() {
}
@@ -80,6 +86,7 @@
mainFrame = source.readTypedObject(Rect.CREATOR);
playRevealAnimation = source.readBoolean();
deferRemoveForIme = source.readBoolean();
+ roundedCornerRadius = source.readFloat();
}
@Override
@@ -89,6 +96,7 @@
dest.writeTypedObject(mainFrame, flags);
dest.writeBoolean(playRevealAnimation);
dest.writeBoolean(deferRemoveForIme);
+ dest.writeFloat(roundedCornerRadius);
}
@Override
@@ -96,6 +104,7 @@
return "StartingWindowRemovalInfo{taskId=" + taskId
+ " frame=" + mainFrame
+ " playRevealAnimation=" + playRevealAnimation
+ + " roundedCornerRadius=" + roundedCornerRadius
+ " deferRemoveForIme=" + deferRemoveForIme + "}";
}
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index d1d14f6..44923b6 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -117,7 +117,8 @@
public void testSendHint() {
Session s = createSession();
assumeNotNull(s);
- s.sendHint(Session.CPU_LOAD_UP);
+ s.sendHint(Session.CPU_LOAD_RESET);
+ // ensure we can also send within the rate limit without exception
s.sendHint(Session.CPU_LOAD_RESET);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
index 8bba4404..20da877 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
@@ -50,13 +50,17 @@
private final float mIconStartAlpha;
private final float mBrandingStartAlpha;
private final TransactionPool mTransactionPool;
+ // TODO(b/261167708): Clean enter animation code after moving Letterbox code to Shell
+ private final float mRoundedCornerRadius;
private Runnable mFinishCallback;
SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash,
- Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish) {
+ Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish,
+ float roundedCornerRadius) {
mSplashScreenView = view;
mFirstWindowSurface = leash;
+ mRoundedCornerRadius = roundedCornerRadius;
if (frame != null) {
mFirstWindowFrame.set(frame);
}
@@ -97,7 +101,7 @@
SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface,
mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration,
mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay,
- mAppRevealDuration, this);
+ mAppRevealDuration, this, mRoundedCornerRadius);
}
private void reset() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
index 3098e55..a7e4385 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -63,6 +63,24 @@
/**
* Creates and starts the animator to fade out the icon, reveal the app, and shift up main
+ * window with rounded corner radius.
+ */
+ static void startAnimations(ViewGroup splashScreenView,
+ SurfaceControl firstWindowSurface, int mainWindowShiftLength,
+ TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
+ int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
+ int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener,
+ float roundedCornerRadius) {
+ ValueAnimator animator =
+ createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength,
+ transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
+ iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
+ animatorListener, roundedCornerRadius);
+ animator.start();
+ }
+
+ /**
+ * Creates and starts the animator to fade out the icon, reveal the app, and shift up main
* window.
* @hide
*/
@@ -71,12 +89,10 @@
TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
- ValueAnimator animator =
- createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength,
- transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
- iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
- animatorListener);
- animator.start();
+ startAnimations(splashScreenView, firstWindowSurface, mainWindowShiftLength,
+ transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
+ iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
+ animatorListener, 0f /* roundedCornerRadius */);
}
/**
@@ -87,7 +103,8 @@
SurfaceControl firstWindowSurface, int mMainWindowShiftLength,
TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
- int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener) {
+ int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener,
+ float roundedCornerRadius) {
// reveal app
final float transparentRatio = 0.8f;
final int globalHeight = splashScreenView.getHeight();
@@ -124,7 +141,7 @@
shiftUpAnimation = new ShiftUpAnimation(0, -mMainWindowShiftLength, occludeHoleView,
firstWindowSurface, splashScreenView, transactionPool, firstWindowFrame,
- mMainWindowShiftLength);
+ mMainWindowShiftLength, roundedCornerRadius);
}
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
@@ -289,8 +306,8 @@
public ShiftUpAnimation(float fromYDelta, float toYDelta, View occludeHoleView,
SurfaceControl firstWindowSurface, ViewGroup splashScreenView,
TransactionPool transactionPool, Rect firstWindowFrame,
- int mainWindowShiftLength) {
- mFromYDelta = fromYDelta;
+ int mainWindowShiftLength, float roundedCornerRadius) {
+ mFromYDelta = fromYDelta - roundedCornerRadius;
mToYDelta = toYDelta;
mOccludeHoleView = occludeHoleView;
mApplier = new SyncRtSurfaceTransactionApplier(occludeHoleView);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 0a2027f..ebb957b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -1168,10 +1168,11 @@
* Create and play the default exit animation for splash screen view.
*/
void applyExitAnimation(SplashScreenView view, SurfaceControl leash,
- Rect frame, Runnable finishCallback, long createTime) {
+ Rect frame, Runnable finishCallback, long createTime, float roundedCornerRadius) {
final Runnable playAnimation = () -> {
final SplashScreenExitAnimation animation = new SplashScreenExitAnimation(mContext,
- view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback);
+ view, leash, frame, mMainWindowShiftLength, mTransactionPool, finishCallback,
+ roundedCornerRadius);
animation.startAnimations();
};
if (view.getIconView() == null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index ae3f8e9..4f07bfe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -546,7 +546,7 @@
mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
removalInfo.windowAnimationLeash, removalInfo.mainFrame,
() -> removeWindowInner(record.mDecorView, true),
- record.mCreateTime);
+ record.mCreateTime, removalInfo.roundedCornerRadius);
} else {
// the SplashScreenView has been copied to client, hide the view to skip
// default exit animation
diff --git a/native/android/Android.bp b/native/android/Android.bp
index f1b1d79..254eb44 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -95,6 +95,7 @@
"libpowermanager",
"android.hardware.configstore@1.0",
"android.hardware.configstore-utils",
+ "android.hardware.power-V4-ndk",
"libnativedisplay",
],
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 40eb507..9e97bd3 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -16,6 +16,7 @@
#define LOG_TAG "perf_hint"
+#include <aidl/android/hardware/power/SessionHint.h>
#include <android/os/IHintManager.h>
#include <android/os/IHintSession.h>
#include <android/performance_hint.h>
@@ -25,14 +26,21 @@
#include <performance_hint_private.h>
#include <utils/SystemClock.h>
+#include <chrono>
#include <utility>
#include <vector>
using namespace android;
using namespace android::os;
+using namespace std::chrono_literals;
+
+using AidlSessionHint = aidl::android::hardware::power::SessionHint;
+
struct APerformanceHintSession;
+constexpr int64_t SEND_HINT_TIMEOUT = std::chrono::nanoseconds(100ms).count();
+
struct APerformanceHintManager {
public:
static APerformanceHintManager* getInstance();
@@ -75,6 +83,8 @@
int64_t mFirstTargetMetTimestamp;
// Last target hit timestamp
int64_t mLastTargetMetTimestamp;
+ // Last hint reported from sendHint indexed by hint value
+ std::vector<int64_t> mLastHintSentTimestamp;
// Cached samples
std::vector<int64_t> mActualDurationsNanos;
std::vector<int64_t> mTimestampsNanos;
@@ -147,7 +157,12 @@
mPreferredRateNanos(preferredRateNanos),
mTargetDurationNanos(targetDurationNanos),
mFirstTargetMetTimestamp(0),
- mLastTargetMetTimestamp(0) {}
+ mLastTargetMetTimestamp(0) {
+ const std::vector<AidlSessionHint> sessionHintRange{ndk::enum_range<AidlSessionHint>().begin(),
+ ndk::enum_range<AidlSessionHint>().end()};
+
+ mLastHintSentTimestamp = std::vector<int64_t>(sessionHintRange.size(), 0);
+}
APerformanceHintSession::~APerformanceHintSession() {
binder::Status ret = mHintSession->close();
@@ -224,10 +239,16 @@
}
int APerformanceHintSession::sendHint(int32_t hint) {
- if (hint < 0) {
- ALOGE("%s: session hint value must be greater than zero", __FUNCTION__);
+ if (hint < 0 || hint >= static_cast<int32_t>(mLastHintSentTimestamp.size())) {
+ ALOGE("%s: invalid session hint %d", __FUNCTION__, hint);
return EINVAL;
}
+ int64_t now = elapsedRealtimeNano();
+
+ // Limit sendHint to a pre-detemined rate for safety
+ if (now < (mLastHintSentTimestamp[hint] + SEND_HINT_TIMEOUT)) {
+ return 0;
+ }
binder::Status ret = mHintSession->sendHint(hint);
@@ -235,6 +256,7 @@
ALOGE("%s: HintSession sendHint failed: %s", __FUNCTION__, ret.exceptionMessage().c_str());
return EPIPE;
}
+ mLastHintSentTimestamp[hint] = now;
return 0;
}
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 1881e60..0c2d3b6 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -122,9 +122,16 @@
result = APerformanceHint_reportActualWorkDuration(session, -1L);
EXPECT_EQ(EINVAL, result);
- // Send both valid and invalid session hints
int hintId = 2;
- EXPECT_CALL(*iSession, sendHint(Eq(2))).Times(Exactly(1));
+ EXPECT_CALL(*iSession, sendHint(Eq(hintId))).Times(Exactly(1));
+ result = APerformanceHint_sendHint(session, hintId);
+ EXPECT_EQ(0, result);
+ usleep(110000); // Sleep for longer than the update timeout.
+ EXPECT_CALL(*iSession, sendHint(Eq(hintId))).Times(Exactly(1));
+ result = APerformanceHint_sendHint(session, hintId);
+ EXPECT_EQ(0, result);
+ // Expect to get rate limited if we try to send faster than the limiter allows
+ EXPECT_CALL(*iSession, sendHint(Eq(hintId))).Times(Exactly(0));
result = APerformanceHint_sendHint(session, hintId);
EXPECT_EQ(0, result);
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
index ae325f8..e3e1220 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/DrawablePainter.kt
@@ -20,6 +20,7 @@
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
+import android.os.Build
import android.os.Handler
import android.os.Looper
import android.view.View
@@ -117,13 +118,17 @@
return true
}
- override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean =
- drawable.setLayoutDirection(
- when (layoutDirection) {
- LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
- LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
- }
- )
+ override fun applyLayoutDirection(layoutDirection: LayoutDirection): Boolean {
+ if (Build.VERSION.SDK_INT >= 23) {
+ return drawable.setLayoutDirection(
+ when (layoutDirection) {
+ LayoutDirection.Ltr -> View.LAYOUT_DIRECTION_LTR
+ LayoutDirection.Rtl -> View.LAYOUT_DIRECTION_RTL
+ }
+ )
+ }
+ return false
+ }
override val intrinsicSize: Size get() = drawableIntrinsicSize
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/FlowExt.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/FlowExt.kt
deleted file mode 100644
index dbf8836..0000000
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/FlowExt.kt
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright 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.
- */
-
-package com.android.settingslib.spa.framework.compose
-
-import android.annotation.SuppressLint
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.produceState
-import androidx.compose.ui.platform.LocalLifecycleOwner
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.repeatOnLifecycle
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.withContext
-import kotlin.coroutines.CoroutineContext
-import kotlin.coroutines.EmptyCoroutineContext
-
-/**
- * *************************************************************************************************
- * This file was forked from AndroidX:
- * lifecycle/lifecycle-runtime-compose/src/main/java/androidx/lifecycle/compose/FlowExt.kt
- * TODO: Replace with AndroidX when it's usable.
- */
-
-/**
- * Collects values from this [StateFlow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * The [StateFlow.value] is used as an initial value. Every time there would be new value posted
- * into the [StateFlow] the returned [State] will be updated causing recomposition of every
- * [State.value] usage whenever the [lifecycleOwner]'s lifecycle is at least [minActiveState].
- *
- * This [StateFlow] is collected every time the [lifecycleOwner]'s lifecycle reaches the
- * [minActiveState] Lifecycle state. The collection stops when the [lifecycleOwner]'s lifecycle
- * falls below [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.StateFlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param lifecycleOwner [LifecycleOwner] whose `lifecycle` is used to restart collecting `this`
- * flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@SuppressLint("StateFlowValueCalledInComposition")
-@Composable
-fun <T> StateFlow<T>.collectAsStateWithLifecycle(
- lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
- minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
- context: CoroutineContext = EmptyCoroutineContext
-): State<T> = collectAsStateWithLifecycle(
- initialValue = this.value,
- lifecycle = lifecycleOwner.lifecycle,
- minActiveState = minActiveState,
- context = context
-)
-
-/**
- * Collects values from this [StateFlow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * The [StateFlow.value] is used as an initial value. Every time there would be new value posted
- * into the [StateFlow] the returned [State] will be updated causing recomposition of every
- * [State.value] usage whenever the [lifecycle] is at least [minActiveState].
- *
- * This [StateFlow] is collected every time [lifecycle] reaches the [minActiveState] Lifecycle
- * state. The collection stops when [lifecycle] falls below [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.StateFlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param lifecycle [Lifecycle] used to restart collecting `this` flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@SuppressLint("StateFlowValueCalledInComposition")
-@Composable
-fun <T> StateFlow<T>.collectAsStateWithLifecycle(
- lifecycle: Lifecycle,
- minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
- context: CoroutineContext = EmptyCoroutineContext
-): State<T> = collectAsStateWithLifecycle(
- initialValue = this.value,
- lifecycle = lifecycle,
- minActiveState = minActiveState,
- context = context
-)
-
-/**
- * Collects values from this [Flow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * Every time there would be new value posted into the [Flow] the returned [State] will be updated
- * causing recomposition of every [State.value] usage whenever the [lifecycleOwner]'s lifecycle is
- * at least [minActiveState].
- *
- * This [Flow] is collected every time the [lifecycleOwner]'s lifecycle reaches the [minActiveState]
- * Lifecycle state. The collection stops when the [lifecycleOwner]'s lifecycle falls below
- * [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.FlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param initialValue The initial value given to the returned [State.value].
- * @param lifecycleOwner [LifecycleOwner] whose `lifecycle` is used to restart collecting `this`
- * flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@Composable
-fun <T> Flow<T>.collectAsStateWithLifecycle(
- initialValue: T,
- lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
- minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
- context: CoroutineContext = EmptyCoroutineContext
-): State<T> = collectAsStateWithLifecycle(
- initialValue = initialValue,
- lifecycle = lifecycleOwner.lifecycle,
- minActiveState = minActiveState,
- context = context
-)
-
-/**
- * Collects values from this [Flow] and represents its latest value via [State] in a
- * lifecycle-aware manner.
- *
- * Every time there would be new value posted into the [Flow] the returned [State] will be updated
- * causing recomposition of every [State.value] usage whenever the [lifecycle] is at
- * least [minActiveState].
- *
- * This [Flow] is collected every time [lifecycle] reaches the [minActiveState] Lifecycle
- * state. The collection stops when [lifecycle] falls below [minActiveState].
- *
- * @sample androidx.lifecycle.compose.samples.FlowCollectAsStateWithLifecycle
- *
- * Warning: [Lifecycle.State.INITIALIZED] is not allowed in this API. Passing it as a
- * parameter will throw an [IllegalArgumentException].
- *
- * @param initialValue The initial value given to the returned [State.value].
- * @param lifecycle [Lifecycle] used to restart collecting `this` flow.
- * @param minActiveState [Lifecycle.State] in which the upstream flow gets collected. The
- * collection will stop if the lifecycle falls below that state, and will restart if it's in that
- * state again.
- * @param context [CoroutineContext] to use for collecting.
- */
-@Composable
-fun <T> Flow<T>.collectAsStateWithLifecycle(
- initialValue: T,
- lifecycle: Lifecycle,
- minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
- context: CoroutineContext = EmptyCoroutineContext
-): State<T> {
- return produceState(initialValue, this, lifecycle, minActiveState, context) {
- lifecycle.repeatOnLifecycle(minActiveState) {
- if (context == EmptyCoroutineContext) {
- this@collectAsStateWithLifecycle.collect { this@produceState.value = it }
- } else withContext(context) {
- this@collectAsStateWithLifecycle.collect { this@produceState.value = it }
- }
- }
- }
-}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
index 4df7794..392089a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/Pager.kt
@@ -19,8 +19,6 @@
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
-import androidx.compose.foundation.layout.calculateEndPadding
-import androidx.compose.foundation.layout.calculateStartPadding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
@@ -36,7 +34,6 @@
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
@@ -123,7 +120,7 @@
contentPadding: PaddingValues = PaddingValues(0.dp),
horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
key: ((page: Int) -> Any)? = null,
- content: @Composable() (PagerScope.(page: Int) -> Unit),
+ content: @Composable PagerScope.(page: Int) -> Unit,
) {
Pager(
count = count,
@@ -175,24 +172,8 @@
.collect { state.updateCurrentPageBasedOnLazyListState() }
}
val density = LocalDensity.current
- val layoutDirection = LocalLayoutDirection.current
- LaunchedEffect(density, contentPadding, isVertical, layoutDirection, reverseLayout, state) {
- with(density) {
- // this should be exposed on LazyListLayoutInfo instead. b/200920410
- state.afterContentPadding = if (isVertical) {
- if (!reverseLayout) {
- contentPadding.calculateBottomPadding()
- } else {
- contentPadding.calculateTopPadding()
- }
- } else {
- if (!reverseLayout) {
- contentPadding.calculateEndPadding(layoutDirection)
- } else {
- contentPadding.calculateStartPadding(layoutDirection)
- }
- }.roundToPx()
- }
+ LaunchedEffect(density, state, itemSpacing) {
+ with(density) { state.itemSpacing = itemSpacing.roundToPx() }
}
val pagerScope = remember(state) { PagerScopeImpl(state) }
@@ -203,6 +184,7 @@
ConsumeFlingNestedScrollConnection(
consumeHorizontal = !isVertical,
consumeVertical = isVertical,
+ pagerState = state,
)
}
@@ -268,6 +250,7 @@
private class ConsumeFlingNestedScrollConnection(
private val consumeHorizontal: Boolean,
private val consumeVertical: Boolean,
+ private val pagerState: PagerState,
) : NestedScrollConnection {
override fun onPostScroll(
consumed: Offset,
@@ -281,9 +264,15 @@
}
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
- // We can consume all post fling velocity on the main-axis
- // so that it doesn't propagate up to the Pager
- return available.consume(consumeHorizontal, consumeVertical)
+ return if (pagerState.currentPageOffset != 0f) {
+ // The Pager is already scrolling. This means that a nested scroll child was
+ // scrolled to end, and the Pager can use this fling
+ Velocity.Zero
+ } else {
+ // A nested scroll child is still scrolling. We can consume all post fling
+ // velocity on the main-axis so that it doesn't propagate up to the Pager
+ available.consume(consumeHorizontal, consumeVertical)
+ }
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt
index 21ba117..480335d 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PagerState.kt
@@ -85,12 +85,14 @@
return layoutInfo.visibleItemsInfo.maxByOrNull {
val start = maxOf(it.offset, 0)
val end = minOf(
- it.offset + it.size, layoutInfo.viewportEndOffset - afterContentPadding)
+ it.offset + it.size,
+ layoutInfo.viewportEndOffset - layoutInfo.afterContentPadding
+ )
end - start
}
}
- internal var afterContentPadding = 0
+ internal var itemSpacing by mutableStateOf(0)
private val currentPageLayoutInfo: LazyListItemInfo?
get() = lazyListState.layoutInfo.visibleItemsInfo.lastOrNull {
@@ -135,9 +137,7 @@
*/
val currentPageOffset: Float by derivedStateOf {
currentPageLayoutInfo?.let {
- // We coerce since itemSpacing can make the offset > 1f.
- // We don't want to count spacing in the offset so cap it to 1f
- (-it.offset / it.size.toFloat()).coerceIn(-1f, 1f)
+ (-it.offset / (it.size + itemSpacing).toFloat()).coerceIn(-0.5f, 0.5f)
} ?: 0f
}
@@ -187,28 +187,26 @@
// offset from the size
lazyListState.animateScrollToItem(
index = page,
- scrollOffset = (target.size * pageOffset).roundToInt()
+ scrollOffset = ((target.size + itemSpacing) * pageOffset).roundToInt()
)
} else if (layoutInfo.visibleItemsInfo.isNotEmpty()) {
// If we don't, we use the current page size as a guide
- val currentSize = layoutInfo.visibleItemsInfo.first().size
+ val currentSize = layoutInfo.visibleItemsInfo.first().size + itemSpacing
lazyListState.animateScrollToItem(
index = page,
scrollOffset = (currentSize * pageOffset).roundToInt()
)
// The target should be visible now
- target = lazyListState.layoutInfo.visibleItemsInfo.firstOrNull {
- it.index == page
- }
+ target = layoutInfo.visibleItemsInfo.firstOrNull { it.index == page }
- if (target != null && target.size != currentSize) {
+ if (target != null && target.size + itemSpacing != currentSize) {
// If the size we used for calculating the offset differs from the actual
// target page size, we need to scroll again. This doesn't look great,
// but there's not much else we can do.
lazyListState.animateScrollToItem(
index = page,
- scrollOffset = (target.size * pageOffset).roundToInt()
+ scrollOffset = ((target.size + itemSpacing) * pageOffset).roundToInt()
)
}
}
@@ -248,7 +246,7 @@
if (pageOffset.absoluteValue > 0.0001f) {
currentPageLayoutInfo?.let {
scroll {
- scrollBy(it.size * pageOffset)
+ scrollBy((it.size + itemSpacing) * pageOffset)
}
}
}
@@ -295,7 +293,7 @@
}
private fun requireCurrentPageOffset(value: Float, name: String) {
- require(value in -1f..1f) { "$name must be >= 0 and <= 1" }
+ require(value in -1f..1f) { "$name must be >= -1 and <= 1" }
}
companion object {
diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
index c4a17d8..45f9b23 100644
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ b/packages/SettingsLib/Spa/tests/build.gradle
@@ -76,7 +76,13 @@
sourceDirectories.from = files("../spa/src")
classDirectories.from = fileTree(
dir: "../spa/build/tmp/kotlin-classes/debug",
- excludes: ["com/android/settingslib/spa/debug/**"],
+ excludes: [
+ "com/android/settingslib/spa/debug/**",
+
+ // Excludes files forked from Accompanist.
+ "com/android/settingslib/spa/framework/compose/DrawablePainter*",
+ "com/android/settingslib/spa/framework/compose/Pager*",
+ ],
)
executionData.from = fileTree(dir: "$buildDir/outputs/code_coverage/debugAndroidTest/connected")
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 33c00fb..9349966 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -432,6 +432,11 @@
AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER |
AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0;
changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
+ } else if (stream == AudioManager.STREAM_VOICE_CALL) {
+ final boolean routedToBluetooth =
+ (mAudio.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)
+ & AudioManager.DEVICE_OUT_BLE_HEADSET) != 0;
+ changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
}
return changed;
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 833a6a4..1bc0d08 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1771,6 +1771,7 @@
if (ss.level == row.requestedLevel) {
row.requestedLevel = -1;
}
+ final boolean isVoiceCallStream = row.stream == AudioManager.STREAM_VOICE_CALL;
final boolean isA11yStream = row.stream == STREAM_ACCESSIBILITY;
final boolean isRingStream = row.stream == AudioManager.STREAM_RING;
final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM;
@@ -1815,8 +1816,12 @@
} else if (isRingSilent || zenMuted) {
iconRes = row.iconMuteRes;
} else if (ss.routedToBluetooth) {
- iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
- : R.drawable.ic_volume_media_bt;
+ if (isVoiceCallStream) {
+ iconRes = R.drawable.ic_volume_bt_sco;
+ } else {
+ iconRes = isStreamMuted(ss) ? R.drawable.ic_volume_media_bt_mute
+ : R.drawable.ic_volume_media_bt;
+ }
} else if (isStreamMuted(ss)) {
iconRes = ss.muted ? R.drawable.ic_volume_media_off : row.iconMuteRes;
} else {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 3769f52..915ea1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -170,6 +170,34 @@
}
@Test
+ public void testVolumeChangeW_deviceOutFromBLEHeadset_doStateChanged() {
+ mVolumeController.setDeviceInteractive(false);
+ when(mWakefullnessLifcycle.getWakefulness()).thenReturn(
+ WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+ when(mAudioManager.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)).thenReturn(
+ AudioManager.DEVICE_OUT_BLE_HEADSET);
+
+ mVolumeController.onVolumeChangedW(
+ AudioManager.STREAM_VOICE_CALL, AudioManager.FLAG_SHOW_UI);
+
+ verify(mCallback, times(1)).onStateChanged(any());
+ }
+
+ @Test
+ public void testVolumeChangeW_deviceOutFromA2DP_doStateChanged() {
+ mVolumeController.setDeviceInteractive(false);
+ when(mWakefullnessLifcycle.getWakefulness()).thenReturn(
+ WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+ when(mAudioManager.getDevicesForStream(AudioManager.STREAM_VOICE_CALL)).thenReturn(
+ AudioManager.DEVICE_OUT_BLUETOOTH_A2DP);
+
+ mVolumeController.onVolumeChangedW(
+ AudioManager.STREAM_VOICE_CALL, AudioManager.FLAG_SHOW_UI);
+
+ verify(mCallback, never()).onStateChanged(any());
+ }
+
+ @Test
public void testOnRemoteVolumeChanged_newStream_noNullPointer() {
MediaSession.Token token = new MediaSession.Token(Process.myUid(), null);
mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(token, 0);
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 3f8333a..a7e95fb 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -80,7 +80,6 @@
import android.os.IProgressListener;
import android.os.IRemoteCallback;
import android.os.IUserManager;
-import android.os.Looper;
import android.os.Message;
import android.os.PowerWhitelistManager;
import android.os.Process;
@@ -437,6 +436,12 @@
/** @see #getLastUserUnlockingUptime */
private volatile long mLastUserUnlockingUptime = 0;
+ /**
+ * Pending user starts waiting for shutdown step to complete.
+ */
+ @GuardedBy("mLock")
+ private final List<PendingUserStart> mPendingUserStarts = new ArrayList<>();
+
private final UserLifecycleListener mUserLifecycleListener = new UserLifecycleListener() {
@Override
public void onUserCreated(UserInfo user, Object token) {
@@ -1184,9 +1189,13 @@
} else {
stopped = true;
// User can no longer run.
+ Slogf.i(TAG, "Removing user state from UserController.mStartedUsers for user #"
+ + userId + " as a result of user being stopped");
mStartedUsers.remove(userId);
+
mUserLru.remove(Integer.valueOf(userId));
updateStartedUserArrayLU();
+
if (allowDelayedLocking && !keyEvictedCallbacks.isEmpty()) {
Slogf.wtf(TAG,
"Delayed locking enabled while KeyEvictedCallbacks not empty, userId:"
@@ -1200,7 +1209,10 @@
}
}
if (stopped) {
+ Slogf.i(TAG, "Removing user state from UserManager.mUserStates for user #" + userId
+ + " as a result of user being stopped");
mInjector.getUserManagerInternal().removeUserState(userId);
+
mInjector.activityManagerOnUserStopped(userId);
// Clean up all state and processes associated with the user.
// Kill all the processes for the user.
@@ -1228,10 +1240,13 @@
USER_LIFECYCLE_EVENT_STATE_FINISH);
clearSessionId(userId);
- if (!lockUser) {
- return;
+ if (lockUser) {
+ dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
}
- dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
+
+ // Resume any existing pending user start,
+ // which was paused while the SHUTDOWN flow of the user was in progress.
+ resumePendingUserStarts(userId);
} else {
logUserLifecycleEvent(userId, USER_LIFECYCLE_EVENT_STOP_USER,
USER_LIFECYCLE_EVENT_STATE_NONE);
@@ -1239,6 +1254,31 @@
}
}
+ /**
+ * Resume any existing pending user start for the specified userId which was paused
+ * while the shutdown flow of the user was in progress.
+ * Remove all the handled user starts from mPendingUserStarts.
+ * @param userId the id of the user
+ */
+ private void resumePendingUserStarts(@UserIdInt int userId) {
+ synchronized (mLock) {
+ final List<PendingUserStart> handledUserStarts = new ArrayList<>();
+
+ for (PendingUserStart userStart: mPendingUserStarts) {
+ if (userStart.userId == userId) {
+ Slogf.i(TAG, "resumePendingUserStart for" + userStart);
+ mHandler.post(() -> startUser(userStart.userId,
+ userStart.isForeground, userStart.unlockListener));
+
+ handledUserStarts.add(userStart);
+ }
+ }
+ // remove all the pending user starts which are now handled
+ mPendingUserStarts.removeAll(handledUserStarts);
+ }
+ }
+
+
private void dispatchUserLocking(@UserIdInt int userId,
@Nullable List<KeyEvictedCallback> keyEvictedCallbacks) {
// Evict the user's credential encryption key. Performed on FgThread to make it
@@ -1252,6 +1292,7 @@
}
}
try {
+ Slogf.i(TAG, "Locking CE storage for user #" + userId);
mInjector.getStorageManager().lockUserKey(userId);
} catch (RemoteException re) {
throw re.rethrowAsRuntimeException();
@@ -1657,10 +1698,11 @@
updateStartedUserArrayLU();
needStart = true;
updateUmState = true;
- } else if (uss.state == UserState.STATE_SHUTDOWN && !isCallingOnHandlerThread()) {
+ } else if (uss.state == UserState.STATE_SHUTDOWN) {
Slogf.i(TAG, "User #" + userId
- + " is shutting down - will start after full stop");
- mHandler.post(() -> startUser(userId, foreground, unlockListener));
+ + " is shutting down - will start after full shutdown");
+ mPendingUserStarts.add(new PendingUserStart(userId,
+ foreground, unlockListener));
t.traceEnd(); // updateStartedUserArrayStarting
return true;
}
@@ -1818,10 +1860,6 @@
return true;
}
- private boolean isCallingOnHandlerThread() {
- return Looper.myLooper() == mHandler.getLooper();
- }
-
/**
* Start user, if its not already running, and bring it to foreground.
*/
@@ -3390,6 +3428,32 @@
}
}
+ /**
+ * Helper class for keeping track of user starts which are paused while user's
+ * shutdown is taking place.
+ */
+ private static class PendingUserStart {
+ public final @UserIdInt int userId;
+ public final boolean isForeground;
+ public final IProgressListener unlockListener;
+
+ PendingUserStart(int userId, boolean foreground,
+ IProgressListener unlockListener) {
+ this.userId = userId;
+ this.isForeground = foreground;
+ this.unlockListener = unlockListener;
+ }
+
+ @Override
+ public String toString() {
+ return "PendingUserStart{"
+ + "userId=" + userId
+ + ", isForeground=" + isForeground
+ + ", unlockListener=" + unlockListener
+ + '}';
+ }
+ }
+
@VisibleForTesting
static class Injector {
private final ActivityManagerService mService;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 7dd8770..eb04687 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4386,6 +4386,13 @@
values.touchscreen,
values.uiMode);
+ // Note: certain tests currently run as platform_app which is not allowed
+ // to set debug system properties. To ensure that system properties are set
+ // only when allowed, we check the current UID.
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ SystemProperties.set("debug.tracing.mcc", Integer.toString(values.mcc));
+ SystemProperties.set("debug.tracing.mnc", Integer.toString(values.mnc));
+ }
if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) {
final LocaleList locales = values.getLocales();
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 1fef3c2..9ac1762 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1202,7 +1202,6 @@
return null;
}
return (displayFrames, windowContainer, inOutFrame) -> {
- inOutFrame.inset(win.mGivenContentInsets);
final LayoutParams lp = win.mAttrs.forRotation(displayFrames.mRotation);
final InsetsFrameProvider ifp = lp.providedInsets[index];
InsetsFrameProvider.calculateInsetsFrame(displayFrames.mUnrestricted,
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 0a2e877..274d7ff 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -691,6 +691,8 @@
if (mainWindow == null || mainWindow.mRemoved) {
removalInfo.playRevealAnimation = false;
} else if (removalInfo.playRevealAnimation && playShiftUpAnimation) {
+ removalInfo.roundedCornerRadius =
+ topActivity.mLetterboxUiController.getRoundedCornersRadius(mainWindow);
removalInfo.windowAnimationLeash = applyStartingWindowAnimation(mainWindow);
removalInfo.mainFrame = mainWindow.getRelativeFrame();
}