Merge "Revert "Split settingslib_main_switch"" into main
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index b0332c3..ffb79b3 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1642,6 +1642,7 @@
*
* <p>
*/
+ // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public Policy getNotificationPolicy() {
INotificationManager service = getService();
try {
@@ -1660,6 +1661,7 @@
*
* @param policy The new desired policy.
*/
+ // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public void setNotificationPolicy(@NonNull Policy policy) {
checkRequired("policy", policy);
INotificationManager service = getService();
@@ -2608,6 +2610,7 @@
* Only available if policy access is granted to this package. See
* {@link #isNotificationPolicyAccessGranted}.
*/
+ // TODO(b/309457271): Update documentation with VANILLA_ICE_CREAM behavior.
public final void setInterruptionFilter(@InterruptionFilter int interruptionFilter) {
final INotificationManager service = getService();
try {
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index ff4dfc7..305b751 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1004,6 +1004,8 @@
priorityCategories |= Policy.PRIORITY_CATEGORY_CONVERSATIONS;
conversationSenders = getConversationSendersWithDefault(
zenPolicy.getPriorityConversationSenders(), conversationSenders);
+ } else {
+ conversationSenders = CONVERSATION_SENDERS_NONE;
}
if (zenPolicy.isCategoryAllowed(ZenPolicy.PRIORITY_CATEGORY_CALLS,
@@ -1102,7 +1104,7 @@
return (policy.suppressedVisualEffects & visualEffect) == 0;
}
- private int getNotificationPolicySenders(@ZenPolicy.PeopleType int senders,
+ private static int getNotificationPolicySenders(@ZenPolicy.PeopleType int senders,
int defaultPolicySender) {
switch (senders) {
case ZenPolicy.PEOPLE_TYPE_ANYONE:
@@ -1116,7 +1118,7 @@
}
}
- private int getConversationSendersWithDefault(@ZenPolicy.ConversationSenders int senders,
+ private static int getConversationSendersWithDefault(@ZenPolicy.ConversationSenders int senders,
int defaultPolicySender) {
switch (senders) {
case ZenPolicy.CONVERSATION_SENDERS_ANYONE:
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
new file mode 100644
index 0000000..b63a969
--- /dev/null
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.window.flags"
+
+flag {
+ name: "allows_screen_size_decoupled_from_status_bar_and_cutout"
+ namespace: "large_screen_experiences_app_compat"
+ description: "When necessary, configuration decoupled from status bar and display cutout"
+ bug: "291870756"
+ is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 15c188d..cec83de 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5245,6 +5245,11 @@
<!-- Zen mode - name of default automatic calendar time-based rule that is triggered every night (when sleeping). [CHAR LIMIT=40] -->
<string name="zen_mode_default_every_night_name">Sleeping</string>
+ <!-- Zen mode - Condition summary when a rule is activated due to a call to setInterruptionFilter(). [CHAR_LIMIT=NONE] -->
+ <string name="zen_mode_implicit_activated">On</string>
+ <!-- Zen mode - Condition summary when a rule is deactivated due to a call to setInterruptionFilter(). [CHAR_LIMIT=NONE] -->
+ <string name="zen_mode_implicit_deactivated">Off</string>
+
<!-- Indication that the current volume and other effects (vibration) are being suppressed by a third party, such as a notification listener. [CHAR LIMIT=30] -->
<string name="muted_by"><xliff:g id="third_party">%1$s</xliff:g> is muting some sounds</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e19d548..16ad5c9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2574,6 +2574,8 @@
<java-symbol type="string" name="zen_mode_default_weekends_name" />
<java-symbol type="string" name="zen_mode_default_events_name" />
<java-symbol type="string" name="zen_mode_default_every_night_name" />
+ <java-symbol type="string" name="zen_mode_implicit_activated" />
+ <java-symbol type="string" name="zen_mode_implicit_deactivated" />
<java-symbol type="string" name="display_rotation_camera_compat_toast_after_rotation" />
<java-symbol type="string" name="display_rotation_camera_compat_toast_in_multi_window" />
<java-symbol type="array" name="config_system_condition_providers" />
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index d5b29e3..d5fab44 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -822,14 +822,23 @@
+ "participant");
}
- // Make sure other open changes are visible as entering PIP. Some may be hidden in
- // Transitions#setupStartState because the transition type is OPEN (such as auto-enter).
+ // Make sure other non-pip changes are handled correctly.
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
if (change == enterPip) continue;
if (TransitionUtil.isOpeningType(change.getMode())) {
+ // For other open changes that are visible when entering PIP, some may be hidden in
+ // Transitions#setupStartState because the transition type is OPEN (such as
+ // auto-enter).
final SurfaceControl leash = change.getLeash();
startTransaction.show(leash).setAlpha(leash, 1.f);
+ } else if (TransitionUtil.isClosingType(change.getMode())) {
+ // For other close changes that are invisible as entering PIP, hide them immediately
+ // to avoid showing a freezing surface.
+ // Ideally, we should let other handler to handle them (likely RemoteHandler by
+ // Launcher).
+ final SurfaceControl leash = change.getLeash();
+ startTransaction.hide(leash);
}
}
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index e706eb0..d55d28d 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -527,52 +527,65 @@
return Frame(surface->logicalWidth(), surface->logicalHeight(), bufferAge);
}
-struct DestroySemaphoreInfo {
+class SharedSemaphoreInfo : public LightRefBase<SharedSemaphoreInfo> {
PFN_vkDestroySemaphore mDestroyFunction;
VkDevice mDevice;
VkSemaphore mSemaphore;
+ GrBackendSemaphore mGrBackendSemaphore;
- DestroySemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device,
- VkSemaphore semaphore)
- : mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) {}
+ SharedSemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device,
+ VkSemaphore semaphore)
+ : mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) {
+ mGrBackendSemaphore.initVulkan(semaphore);
+ }
- ~DestroySemaphoreInfo() { mDestroyFunction(mDevice, mSemaphore, nullptr); }
+ ~SharedSemaphoreInfo() { mDestroyFunction(mDevice, mSemaphore, nullptr); }
+
+ friend class LightRefBase<SharedSemaphoreInfo>;
+ friend class sp<SharedSemaphoreInfo>;
+
+public:
+ VkSemaphore semaphore() const { return mSemaphore; }
+
+ GrBackendSemaphore* grBackendSemaphore() { return &mGrBackendSemaphore; }
};
static void destroy_semaphore(void* context) {
- DestroySemaphoreInfo* info = reinterpret_cast<DestroySemaphoreInfo*>(context);
- delete info;
+ SharedSemaphoreInfo* info = reinterpret_cast<SharedSemaphoreInfo*>(context);
+ info->decStrong(0);
}
VulkanManager::VkDrawResult VulkanManager::finishFrame(SkSurface* surface) {
ATRACE_NAME("Vulkan finish frame");
- VkExportSemaphoreCreateInfo exportInfo;
- exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
- exportInfo.pNext = nullptr;
- exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
-
- VkSemaphoreCreateInfo semaphoreInfo;
- semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
- semaphoreInfo.pNext = &exportInfo;
- semaphoreInfo.flags = 0;
- VkSemaphore semaphore;
- VkResult err = mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore);
- ALOGE_IF(VK_SUCCESS != err, "VulkanManager::makeSwapSemaphore(): Failed to create semaphore");
-
- GrBackendSemaphore backendSemaphore;
- backendSemaphore.initVulkan(semaphore);
-
+ sp<SharedSemaphoreInfo> sharedSemaphore;
GrFlushInfo flushInfo;
- if (err == VK_SUCCESS) {
- flushInfo.fNumSemaphores = 1;
- flushInfo.fSignalSemaphores = &backendSemaphore;
- flushInfo.fFinishedProc = destroy_semaphore;
- flushInfo.fFinishedContext =
- new DestroySemaphoreInfo(mDestroySemaphore, mDevice, semaphore);
- } else {
- semaphore = VK_NULL_HANDLE;
+
+ {
+ VkExportSemaphoreCreateInfo exportInfo;
+ exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
+ exportInfo.pNext = nullptr;
+ exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+
+ VkSemaphoreCreateInfo semaphoreInfo;
+ semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+ semaphoreInfo.pNext = &exportInfo;
+ semaphoreInfo.flags = 0;
+ VkSemaphore semaphore;
+ VkResult err = mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore);
+ ALOGE_IF(VK_SUCCESS != err,
+ "VulkanManager::makeSwapSemaphore(): Failed to create semaphore");
+
+ if (err == VK_SUCCESS) {
+ sharedSemaphore = sp<SharedSemaphoreInfo>::make(mDestroySemaphore, mDevice, semaphore);
+ flushInfo.fNumSemaphores = 1;
+ flushInfo.fSignalSemaphores = sharedSemaphore->grBackendSemaphore();
+ flushInfo.fFinishedProc = destroy_semaphore;
+ sharedSemaphore->incStrong(0);
+ flushInfo.fFinishedContext = sharedSemaphore.get();
+ }
}
+
GrDirectContext* context = GrAsDirectContext(surface->recordingContext());
ALOGE_IF(!context, "Surface is not backed by gpu");
GrSemaphoresSubmitted submitted = context->flush(
@@ -581,37 +594,34 @@
VkDrawResult drawResult{
.submissionTime = systemTime(),
};
- if (semaphore != VK_NULL_HANDLE) {
- if (submitted == GrSemaphoresSubmitted::kYes) {
- if (mFrameBoundaryANDROID) {
- // retrieve VkImage used as render target
- VkImage image = VK_NULL_HANDLE;
- GrBackendRenderTarget backendRenderTarget =
- SkSurfaces::GetBackendRenderTarget(
- surface, SkSurfaces::BackendHandleAccess::kFlushRead);
- if (backendRenderTarget.isValid()) {
- GrVkImageInfo info;
- if (GrBackendRenderTargets::GetVkImageInfo(backendRenderTarget, &info)) {
- image = info.fImage;
- } else {
- ALOGE("Frame boundary: backend is not vulkan");
- }
+ if (sharedSemaphore) {
+ if (submitted == GrSemaphoresSubmitted::kYes && mFrameBoundaryANDROID) {
+ // retrieve VkImage used as render target
+ VkImage image = VK_NULL_HANDLE;
+ GrBackendRenderTarget backendRenderTarget = SkSurfaces::GetBackendRenderTarget(
+ surface, SkSurfaces::BackendHandleAccess::kFlushRead);
+ if (backendRenderTarget.isValid()) {
+ GrVkImageInfo info;
+ if (GrBackendRenderTargets::GetVkImageInfo(backendRenderTarget, &info)) {
+ image = info.fImage;
} else {
- ALOGE("Frame boundary: invalid backend render target");
+ ALOGE("Frame boundary: backend is not vulkan");
}
- // frameBoundaryANDROID needs to know about mSwapSemaphore, but
- // it won't wait on it.
- mFrameBoundaryANDROID(mDevice, semaphore, image);
+ } else {
+ ALOGE("Frame boundary: invalid backend render target");
}
+ // frameBoundaryANDROID needs to know about mSwapSemaphore, but
+ // it won't wait on it.
+ mFrameBoundaryANDROID(mDevice, sharedSemaphore->semaphore(), image);
}
VkSemaphoreGetFdInfoKHR getFdInfo;
getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
getFdInfo.pNext = nullptr;
- getFdInfo.semaphore = semaphore;
+ getFdInfo.semaphore = sharedSemaphore->semaphore();
getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
int fenceFd = -1;
- err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd);
+ VkResult err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd);
ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd");
drawResult.presentFence.reset(fenceFd);
} else {
@@ -732,15 +742,15 @@
return INVALID_OPERATION;
}
- GrBackendSemaphore backendSemaphore;
- backendSemaphore.initVulkan(semaphore);
+ auto sharedSemaphore = sp<SharedSemaphoreInfo>::make(mDestroySemaphore, mDevice, semaphore);
// Even if Skia fails to submit the semaphore, it will still call the destroy_semaphore callback
GrFlushInfo flushInfo;
flushInfo.fNumSemaphores = 1;
- flushInfo.fSignalSemaphores = &backendSemaphore;
+ flushInfo.fSignalSemaphores = sharedSemaphore->grBackendSemaphore();
flushInfo.fFinishedProc = destroy_semaphore;
- flushInfo.fFinishedContext = new DestroySemaphoreInfo(mDestroySemaphore, mDevice, semaphore);
+ sharedSemaphore->incStrong(0);
+ flushInfo.fFinishedContext = sharedSemaphore.get();
GrSemaphoresSubmitted submitted = grContext->flush(flushInfo);
grContext->submit();
diff --git a/media/java/android/media/AudioDeviceAttributes.java b/media/java/android/media/AudioDeviceAttributes.java
index 5a27435..2b349d4 100644
--- a/media/java/android/media/AudioDeviceAttributes.java
+++ b/media/java/android/media/AudioDeviceAttributes.java
@@ -325,7 +325,7 @@
+ " role:" + roleToString(mRole)
+ " type:" + (mRole == ROLE_OUTPUT ? AudioSystem.getOutputDeviceName(mNativeType)
: AudioSystem.getInputDeviceName(mNativeType))
- + " addr:" + mAddress
+ + " addr:" + Utils.anonymizeBluetoothAddress(mNativeType, mAddress)
+ " name:" + mName
+ " profiles:" + mAudioProfiles.toString()
+ " descriptors:" + mAudioDescriptors.toString());
diff --git a/media/java/android/media/AudioDevicePort.java b/media/java/android/media/AudioDevicePort.java
index 9211c53..73bc6f9 100644
--- a/media/java/android/media/AudioDevicePort.java
+++ b/media/java/android/media/AudioDevicePort.java
@@ -156,7 +156,7 @@
AudioSystem.getOutputDeviceName(mType));
return "{" + super.toString()
+ ", mType: " + type
- + ", mAddress: " + mAddress
+ + ", mAddress: " + Utils.anonymizeBluetoothAddress(mType, mAddress)
+ "}";
}
}
diff --git a/media/java/android/media/Utils.java b/media/java/android/media/Utils.java
index ecb6b3d..d07f611 100644
--- a/media/java/android/media/Utils.java
+++ b/media/java/android/media/Utils.java
@@ -657,4 +657,35 @@
// on the fly.
private final boolean mForceRemoveConsistency; // default false
}
+
+ /**
+ * Convert a Bluetooth MAC address to an anonymized one when exposed to a non privileged app
+ * Must match the implementation of BluetoothUtils.toAnonymizedAddress()
+ * @param address MAC address to be anonymized
+ * @return anonymized MAC address
+ */
+ public static @Nullable String anonymizeBluetoothAddress(@Nullable String address) {
+ if (address == null) {
+ return null;
+ }
+ if (address.length() != "AA:BB:CC:DD:EE:FF".length()) {
+ return address;
+ }
+ return "XX:XX:XX:XX" + address.substring("XX:XX:XX:XX".length());
+ }
+
+ /**
+ * Convert a Bluetooth MAC address to an anonymized one if the internal device type corresponds
+ * to a Bluetooth.
+ * @param deviceType the internal type of the audio device
+ * @param address MAC address to be anonymized
+ * @return anonymized MAC address
+ */
+ public static @Nullable String anonymizeBluetoothAddress(
+ int deviceType, @Nullable String address) {
+ if (!AudioSystem.isBluetoothDevice(deviceType)) {
+ return address;
+ }
+ return anonymizeBluetoothAddress(address);
+ }
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
index 1c92696..223e99e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/common/UserProfilePager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -57,19 +57,24 @@
private fun UserManager.getUserGroups(): List<UserGroup> {
val userGroupList = mutableListOf<UserGroup>()
- val profileToShowInSettings = getProfiles(UserHandle.myUserId())
- .map { userInfo -> userInfo to getUserProperties(userInfo.userHandle) }
+ val showInSettingsMap = getProfiles(UserHandle.myUserId()).groupBy { showInSettings(it) }
- profileToShowInSettings
- .filter { it.second.showInSettings == UserProperties.SHOW_IN_SETTINGS_WITH_PARENT }
- .takeIf { it.isNotEmpty() }
- ?.map { it.first }
- ?.let { userInfos -> userGroupList += UserGroup(userInfos) }
+ showInSettingsMap[UserProperties.SHOW_IN_SETTINGS_WITH_PARENT]?.let {
+ userGroupList += UserGroup(it)
+ }
- profileToShowInSettings
- .filter { it.second.showInSettings == UserProperties.SHOW_IN_SETTINGS_SEPARATE &&
- (!it.second.hideInSettingsInQuietMode || !it.first.isQuietModeEnabled) }
- .forEach { userGroupList += UserGroup(userInfos = listOf(it.first)) }
+ showInSettingsMap[UserProperties.SHOW_IN_SETTINGS_SEPARATE]?.forEach {
+ userGroupList += UserGroup(listOf(it))
+ }
return userGroupList
}
+
+private fun UserManager.showInSettings(userInfo: UserInfo): Int {
+ val userProperties = getUserProperties(userInfo.userHandle)
+ return if (userInfo.isQuietModeEnabled && userProperties.hideInSettingsInQuietMode) {
+ UserProperties.SHOW_IN_SETTINGS_NO
+ } else {
+ userProperties.showInSettings
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/common/UserProfilePagerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/common/UserProfilePagerTest.kt
new file mode 100644
index 0000000..e450364
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/common/UserProfilePagerTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.settingslib.spaprivileged.template.common
+
+import android.content.Context
+import android.content.pm.UserInfo
+import android.content.pm.UserProperties
+import android.os.UserManager
+import androidx.compose.material3.Text
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.framework.common.userManager
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+
+@RunWith(AndroidJUnit4::class)
+class UserProfilePagerTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ private val mockUserManager = mock<UserManager> {
+ on { getProfiles(any()) } doReturn listOf(USER_0)
+ on { getUserProperties(USER_0.userHandle) } doReturn
+ UserProperties.Builder()
+ .setShowInSettings(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT)
+ .build()
+ }
+
+ private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
+ on { userManager } doReturn mockUserManager
+ }
+
+ @Test
+ fun userProfilePager() {
+ composeTestRule.setContent {
+ CompositionLocalProvider(LocalContext provides context) {
+ UserProfilePager { userGroup ->
+ Text(text = userGroup.userInfos.joinToString { it.id.toString() })
+ }
+ }
+ }
+
+ composeTestRule.onNodeWithText(USER_0.id.toString()).assertIsDisplayed()
+ }
+
+ private companion object {
+ val USER_0 = UserInfo(0, "", 0)
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index aae61bd..b77a60b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -506,7 +506,10 @@
// There is no ongoing transition.
if (state !is TransitionState.Transition || state.fromScene == state.toScene) {
- return idleValue
+ // Even if this element SceneTransitionLayout is not animated, the layout itself might be
+ // animated (e.g. by another parent SceneTransitionLayout), in which case this element still
+ // need to participate in the layout phase.
+ return currentValue()
}
// A transition was started but it's not ready yet (not all elements have been composed/laid
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
index 216608a..5d8eaf7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt
@@ -2,9 +2,10 @@
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.unit.IntSize
interface DraggableHandler {
- fun onDragStarted(startedPosition: Offset, pointersDown: Int = 1)
+ fun onDragStarted(layoutSize: IntSize, startedPosition: Offset, pointersDown: Int = 1)
fun onDelta(pixels: Float)
fun onDragStopped(velocity: Float)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index d0a5f5b..d48781a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -38,6 +38,7 @@
import androidx.compose.ui.input.pointer.util.VelocityTracker
import androidx.compose.ui.input.pointer.util.addPointerInputChange
import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.util.fastForEach
@@ -60,7 +61,7 @@
orientation: Orientation,
enabled: Boolean,
startDragImmediately: Boolean,
- onDragStarted: (startedPosition: Offset, pointersDown: Int) -> Unit,
+ onDragStarted: (layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) -> Unit,
onDragDelta: (Float) -> Unit,
onDragStopped: (velocity: Float) -> Unit,
): Modifier = composed {
@@ -83,7 +84,7 @@
val onDragStart: (Offset, Int) -> Unit = { startedPosition, pointersDown ->
velocityTracker.resetTracking()
- onDragStarted(startedPosition, pointersDown)
+ onDragStarted(size, startedPosition, pointersDown)
}
val onDragCancel: () -> Unit = { onDragStopped(/* velocity= */ 0f) }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index eb5168b..1a79522 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -25,8 +25,9 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateMap
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
-import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.layout.intermediateLayout
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.zIndex
@@ -44,14 +45,24 @@
var content by mutableStateOf(content)
var userActions by mutableStateOf(actions)
var zIndex by mutableFloatStateOf(zIndex)
- var size by mutableStateOf(IntSize.Zero)
+ var targetSize by mutableStateOf(IntSize.Zero)
/** The shared values in this scene that are not tied to a specific element. */
val sharedValues = SnapshotStateMap<ValueKey, Element.SharedValue<*>>()
@Composable
+ @OptIn(ExperimentalComposeUiApi::class)
fun Content(modifier: Modifier = Modifier) {
- Box(modifier.zIndex(zIndex).onPlaced { size = it.size }.testTag(key.testTag)) {
+ Box(
+ modifier
+ .zIndex(zIndex)
+ .intermediateLayout { measurable, constraints ->
+ targetSize = lookaheadSize
+ val placeable = measurable.measure(constraints)
+ layout(placeable.width, placeable.height) { placeable.place(0, 0) }
+ }
+ .testTag(key.testTag)
+ ) {
scope.content()
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index 9a3a0ae..9d71801 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -27,6 +27,7 @@
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.round
@@ -90,7 +91,7 @@
internal var gestureWithPriority: Any? = null
- internal fun onDragStarted(pointersDown: Int, startedPosition: Offset?) {
+ internal fun onDragStarted(pointersDown: Int, layoutSize: IntSize, startedPosition: Offset?) {
if (isDrivingTransition) {
// This [transition] was already driving the animation: simply take over it.
// Stop animating and start from where the current offset.
@@ -126,14 +127,14 @@
// we will also have to make sure that we correctly handle overscroll.
swipeTransition.absoluteDistance =
when (orientation) {
- Orientation.Horizontal -> layoutImpl.size.width
- Orientation.Vertical -> layoutImpl.size.height
+ Orientation.Horizontal -> layoutSize.width
+ Orientation.Vertical -> layoutSize.height
}.toFloat()
val fromEdge =
startedPosition?.let { position ->
layoutImpl.edgeDetector.edge(
- layoutImpl.size,
+ layoutSize,
position.round(),
layoutImpl.density,
orientation,
@@ -513,9 +514,9 @@
private class SceneDraggableHandler(
private val gestureHandler: SceneGestureHandler,
) : DraggableHandler {
- override fun onDragStarted(startedPosition: Offset, pointersDown: Int) {
+ override fun onDragStarted(layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) {
gestureHandler.gestureWithPriority = this
- gestureHandler.onDragStarted(pointersDown, startedPosition)
+ gestureHandler.onDragStarted(pointersDown, layoutSize, startedPosition)
}
override fun onDelta(pixels: Float) {
@@ -647,7 +648,11 @@
canContinueScroll = { true },
onStart = {
gestureHandler.gestureWithPriority = this
- gestureHandler.onDragStarted(pointersDown = 1, startedPosition = null)
+ gestureHandler.onDragStarted(
+ pointersDown = 1,
+ layoutSize = gestureHandler.currentScene.targetSize,
+ startedPosition = null,
+ )
},
onScroll = { offsetAvailable ->
if (gestureHandler.gestureWithPriority != this) {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 6edd1b6..0b06953 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -30,13 +30,15 @@
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateMap
+import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.layout.LookaheadScope
-import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.layout.intermediateLayout
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.util.fastForEach
+import com.android.compose.ui.util.lerp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
@@ -64,12 +66,6 @@
private val horizontalGestureHandler: SceneGestureHandler
private val verticalGestureHandler: SceneGestureHandler
- /**
- * The size of this layout. Note that this could be [IntSize.Zero] if this layour does not have
- * any scene configured or right before the first measure pass of the layout.
- */
- @VisibleForTesting var size by mutableStateOf(IntSize.Zero)
-
init {
setScenes(builder)
@@ -157,15 +153,46 @@
}
@Composable
+ @OptIn(ExperimentalComposeUiApi::class)
internal fun Content(modifier: Modifier) {
Box(
modifier
// Handle horizontal and vertical swipes on this layout.
// Note: order here is important and will give a slight priority to the vertical
// swipes.
- .swipeToScene(gestureHandler(Orientation.Horizontal))
- .swipeToScene(gestureHandler(Orientation.Vertical))
- .onSizeChanged { size = it }
+ .swipeToScene(horizontalGestureHandler)
+ .swipeToScene(verticalGestureHandler)
+ // Animate the size of this layout.
+ .intermediateLayout { measurable, constraints ->
+ // Measure content normally.
+ val placeable = measurable.measure(constraints)
+
+ val width: Int
+ val height: Int
+ val state = state.transitionState
+ if (state !is TransitionState.Transition || state.fromScene == state.toScene) {
+ width = placeable.width
+ height = placeable.height
+ } else {
+ // Interpolate the size.
+ val fromSize = scene(state.fromScene).targetSize
+ val toSize = scene(state.toScene).targetSize
+
+ // Optimization: make sure we don't read state.progress if fromSize ==
+ // toSize to avoid running this code every frame when the layout size does
+ // not change.
+ if (fromSize == toSize) {
+ width = fromSize.width
+ height = fromSize.height
+ } else {
+ val size = lerp(fromSize, toSize, state.progress)
+ width = size.width.coerceAtLeast(0)
+ height = size.height.coerceAtLeast(0)
+ }
+ }
+
+ layout(width, height) { placeable.place(0, 0) }
+ }
) {
LookaheadScope {
val scenesToCompose =
@@ -230,4 +257,9 @@
}
internal fun isSceneReady(scene: SceneKey): Boolean = readyScenes.containsKey(scene)
+
+ @VisibleForTesting
+ fun setScenesTargetSizeForTest(size: IntSize) {
+ scenes.values.forEach { it.targetSize = size }
+ }
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
index 840800d..70534dd 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
@@ -38,7 +38,7 @@
transition: TransitionState.Transition,
value: Offset
): Offset {
- val sceneSize = scene.size
+ val sceneSize = scene.targetSize
val elementSize = sceneValues.targetSize
if (elementSize == Element.SizeUnspecified) {
return value
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index 1e3d011..7ab2096 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -46,6 +46,7 @@
import org.junit.runner.RunWith
private const val SCREEN_SIZE = 100f
+private val LAYOUT_SIZE = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt())
@RunWith(AndroidJUnit4::class)
class SceneGestureHandlerTest {
@@ -80,7 +81,7 @@
edgeDetector = DefaultEdgeDetector,
coroutineScope = coroutineScope,
)
- .also { it.size = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt()) },
+ .apply { setScenesTargetSizeForTest(LAYOUT_SIZE) },
orientation = Orientation.Vertical,
coroutineScope = coroutineScope,
)
@@ -128,18 +129,21 @@
runMonotonicClockTest { TestGestureScope(coroutineScope = this).block() }
}
+ private fun DraggableHandler.onDragStarted() =
+ onDragStarted(layoutSize = LAYOUT_SIZE, startedPosition = Offset.Zero)
+
@Test
fun testPreconditions() = runGestureTest { assertScene(currentScene = SceneA, isIdle = true) }
@Test
fun onDragStarted_shouldStartATransition() = runGestureTest {
- draggable.onDragStarted(startedPosition = Offset.Zero)
+ draggable.onDragStarted()
assertScene(currentScene = SceneA, isIdle = false)
}
@Test
fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest {
- draggable.onDragStarted(startedPosition = Offset.Zero)
+ draggable.onDragStarted()
assertScene(currentScene = SceneA, isIdle = false)
val transition = transitionState as Transition
@@ -152,7 +156,7 @@
@Test
fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest {
- draggable.onDragStarted(startedPosition = Offset.Zero)
+ draggable.onDragStarted()
assertScene(currentScene = SceneA, isIdle = false)
draggable.onDelta(pixels = deltaInPixels10)
@@ -170,7 +174,7 @@
@Test
fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest {
- draggable.onDragStarted(startedPosition = Offset.Zero)
+ draggable.onDragStarted()
assertScene(currentScene = SceneA, isIdle = false)
draggable.onDelta(pixels = deltaInPixels10)
@@ -188,7 +192,7 @@
@Test
fun onDragStoppedAfterStarted_returnImmediatelyToIdle() = runGestureTest {
- draggable.onDragStarted(startedPosition = Offset.Zero)
+ draggable.onDragStarted()
assertScene(currentScene = SceneA, isIdle = false)
draggable.onDragStopped(velocity = 0f)
@@ -197,7 +201,7 @@
@Test
fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
- draggable.onDragStarted(startedPosition = Offset.Zero)
+ draggable.onDragStarted()
assertScene(currentScene = SceneA, isIdle = false)
draggable.onDelta(pixels = deltaInPixels10)
@@ -217,7 +221,7 @@
assertScene(currentScene = SceneC, isIdle = false)
// Start a new gesture while the offset is animating
- draggable.onDragStarted(startedPosition = Offset.Zero)
+ draggable.onDragStarted()
assertThat(sceneGestureHandler.isAnimatingOffset).isFalse()
}
@@ -421,6 +425,7 @@
draggable.onDelta(deltaInPixels10)
assertScene(currentScene = SceneA, isIdle = true)
}
+
@Test
fun beforeDraggableStart_stop_shouldBeIgnored() = runGestureTest {
draggable.onDragStopped(velocityThreshold)
@@ -437,7 +442,7 @@
@Test
fun startNestedScrollWhileDragging() = runGestureTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = Always)
- draggable.onDragStarted(Offset.Zero)
+ draggable.onDragStarted()
assertScene(currentScene = SceneA, isIdle = false)
val transition = transitionState as Transition
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 5afd420..321cf63 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -18,6 +18,8 @@
import androidx.activity.ComponentActivity
import androidx.compose.animation.core.FastOutSlowInEasing
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@@ -48,6 +50,7 @@
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.test.assertSizeIsEqualTo
import com.android.compose.test.subjects.DpOffsetSubject
import com.android.compose.test.subjects.assertThat
import com.google.common.truth.Truth.assertThat
@@ -307,6 +310,26 @@
assertThat(layoutState.transitionState.currentScene).isEqualTo(TestScenes.SceneA)
}
+ @Test
+ fun layoutSizeIsAnimated() {
+ val layoutTag = "layout"
+ rule.testTransition(
+ fromSceneContent = { Box(Modifier.size(200.dp, 100.dp)) },
+ toSceneContent = { Box(Modifier.size(120.dp, 140.dp)) },
+ transition = {
+ // 4 frames of animation.
+ spec = tween(4 * 16, easing = LinearEasing)
+ },
+ layoutModifier = Modifier.testTag(layoutTag),
+ ) {
+ before { rule.onNodeWithTag(layoutTag).assertSizeIsEqualTo(200.dp, 100.dp) }
+ at(16) { rule.onNodeWithTag(layoutTag).assertSizeIsEqualTo(180.dp, 110.dp) }
+ at(32) { rule.onNodeWithTag(layoutTag).assertSizeIsEqualTo(160.dp, 120.dp) }
+ at(48) { rule.onNodeWithTag(layoutTag).assertSizeIsEqualTo(140.dp, 130.dp) }
+ after { rule.onNodeWithTag(layoutTag).assertSizeIsEqualTo(120.dp, 140.dp) }
+ }
+ }
+
private fun SemanticsNodeInteraction.offsetRelativeTo(
other: SemanticsNodeInteraction,
): DpOffset {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
index 2a27763..8cffcf6 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/EdgeTranslateTest.kt
@@ -48,7 +48,7 @@
rule.testTransition(
// The layout under test is 300dp x 300dp.
layoutModifier = Modifier.size(300.dp),
- fromSceneContent = {},
+ fromSceneContent = { Box(Modifier.fillMaxSize()) },
toSceneContent = {
// Foo is 100dp x 100dp in the center of the layout, so at offset = (100dp, 100dp)
Box(Modifier.fillMaxSize()) {
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
index e0ae1be..06de296 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestTransition.kt
@@ -16,7 +16,6 @@
package com.android.compose.animation.scene
-import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -104,7 +103,7 @@
currentScene,
onChangeScene,
transitions { from(fromScene, to = toScene, transition) },
- layoutModifier.fillMaxSize(),
+ layoutModifier,
) {
scene(fromScene, content = fromSceneContent)
scene(toScene, content = toSceneContent)
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index d64a131..ced96f1 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -441,9 +441,6 @@
// TODO(b/270437894): Tracking Bug
val MEDIA_REMOTE_RESUME = unreleasedFlag("media_remote_resume")
- // TODO(b/304506662): Tracking Bug
- val MEDIA_DEVICE_NAME_FIX = releasedFlag("media_device_name_fix")
-
// 1000 - dock
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag("simulate_dock_through_charging")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index 078feff..c2aedca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -30,6 +30,7 @@
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
import javax.inject.Inject
@@ -44,6 +45,7 @@
sharedNotificationContainer: SharedNotificationContainer,
sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
controller: NotificationStackScrollLayoutController,
+ notificationStackSizeCalculator: NotificationStackSizeCalculator,
private val smartspaceViewModel: KeyguardSmartspaceViewModel,
) :
NotificationStackScrollLayoutSection(
@@ -53,6 +55,7 @@
sharedNotificationContainer,
sharedNotificationContainerViewModel,
controller,
+ notificationStackSizeCalculator,
) {
override fun applyConstraints(constraintSet: ConstraintSet) {
if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
index 00966f2..ea2bdf7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt
@@ -27,6 +27,7 @@
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
@@ -40,6 +41,7 @@
private val sharedNotificationContainer: SharedNotificationContainer,
private val sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
private val controller: NotificationStackScrollLayoutController,
+ private val notificationStackSizeCalculator: NotificationStackSizeCalculator,
) : KeyguardSection() {
private val placeHolderId = R.id.nssl_placeholder
private var disposableHandle: DisposableHandle? = null
@@ -69,6 +71,7 @@
sharedNotificationContainer,
sharedNotificationContainerViewModel,
controller,
+ notificationStackSizeCalculator,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
index bf95c77..dc2ad8d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
@@ -30,6 +30,7 @@
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
import javax.inject.Inject
@@ -44,6 +45,7 @@
sharedNotificationContainer: SharedNotificationContainer,
sharedNotificationContainerViewModel: SharedNotificationContainerViewModel,
controller: NotificationStackScrollLayoutController,
+ notificationStackSizeCalculator: NotificationStackSizeCalculator,
private val smartspaceViewModel: KeyguardSmartspaceViewModel,
) :
NotificationStackScrollLayoutSection(
@@ -53,6 +55,7 @@
sharedNotificationContainer,
sharedNotificationContainerViewModel,
controller,
+ notificationStackSizeCalculator,
) {
override fun applyConstraints(constraintSet: ConstraintSet) {
if (!featureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
index 2034d97..dcbf670 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDeviceManager.kt
@@ -37,8 +37,6 @@
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.models.player.MediaDeviceData
import com.android.systemui.media.controls.util.MediaControllerFactory
@@ -70,7 +68,6 @@
@Main private val fgExecutor: Executor,
@Background private val bgExecutor: Executor,
dumpManager: DumpManager,
- private val featureFlags: FeatureFlagsClassic,
) : MediaDataManager.Listener, Dumpable {
private val listeners: MutableSet<Listener> = mutableSetOf()
@@ -392,13 +389,6 @@
)
}
- if (!featureFlags.isEnabled(Flags.MEDIA_DEVICE_NAME_FIX)) {
- if (controller == null || routingSession != null) {
- return routingSession?.name?.toString() ?: device?.name
- }
- return null
- }
-
if (controller == null) {
// In resume state, we don't have a controller - just use the device name
return device?.name
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
index 1ec43c5..d277f32 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt
@@ -60,7 +60,7 @@
}
companion object {
- @JvmField val GUTS_ANIMATION_DURATION = 500L
+ @JvmField val GUTS_ANIMATION_DURATION = 234L
}
/** A listener when the current dimensions of the player change */
@@ -234,7 +234,8 @@
currentStartLocation,
currentEndLocation,
currentTransitionProgress,
- applyImmediately = false
+ applyImmediately = false,
+ isGutsAnimation = true,
)
}
@@ -254,7 +255,8 @@
currentStartLocation,
currentEndLocation,
currentTransitionProgress,
- applyImmediately = immediate
+ applyImmediately = immediate,
+ isGutsAnimation = true,
)
}
@@ -414,7 +416,10 @@
* it's not available, it will recreate one by measuring, which may be expensive.
*/
@VisibleForTesting
- fun obtainViewState(state: MediaHostState?): TransitionViewState? {
+ fun obtainViewState(
+ state: MediaHostState?,
+ isGutsAnimation: Boolean = false
+ ): TransitionViewState? {
if (state == null || state.measurementInput == null) {
return null
}
@@ -423,7 +428,7 @@
val viewState = viewStates[cacheKey]
if (viewState != null) {
// we already have cached this measurement, let's continue
- if (state.squishFraction <= 1f) {
+ if (state.squishFraction <= 1f && !isGutsAnimation) {
return squishViewState(viewState, state.squishFraction)
}
return viewState
@@ -455,13 +460,14 @@
// Given that we have a measurement and a view, let's get (guaranteed) viewstates
// from the start and end state and interpolate them
- val startViewState = obtainViewState(startState) as TransitionViewState
+ val startViewState = obtainViewState(startState, isGutsAnimation) as TransitionViewState
val endState = state.copy().also { it.expansion = 1.0f }
- val endViewState = obtainViewState(endState) as TransitionViewState
+ val endViewState = obtainViewState(endState, isGutsAnimation) as TransitionViewState
result =
layoutController.getInterpolatedState(startViewState, endViewState, state.expansion)
}
- if (state.squishFraction <= 1f) {
+ // Skip the adjustments of squish view state if UMO changes due to guts animation.
+ if (state.squishFraction <= 1f && !isGutsAnimation) {
return squishViewState(result, state.squishFraction)
}
return result
@@ -521,7 +527,8 @@
@MediaLocation startLocation: Int,
@MediaLocation endLocation: Int,
transitionProgress: Float,
- applyImmediately: Boolean
+ applyImmediately: Boolean,
+ isGutsAnimation: Boolean = false,
) =
traceSection("MediaViewController#setCurrentState") {
currentEndLocation = endLocation
@@ -537,7 +544,7 @@
// Obtain the view state that we'd want to be at the end
// The view might not be bound yet or has never been measured and in that case will be
// reset once the state is fully available
- var endViewState = obtainViewState(endHostState) ?: return
+ var endViewState = obtainViewState(endHostState, isGutsAnimation) ?: return
endViewState = updateViewStateSize(endViewState, endLocation, tmpState2)!!
layoutController.setMeasureState(endViewState)
@@ -548,7 +555,7 @@
}
val result: TransitionViewState
- var startViewState = obtainViewState(startHostState)
+ var startViewState = obtainViewState(startHostState, isGutsAnimation)
startViewState = updateViewStateSize(startViewState, startLocation, tmpState3)
if (!endHostState.visible) {
@@ -602,7 +609,8 @@
applyImmediately,
shouldAnimate,
animationDuration,
- animationDelay
+ animationDelay,
+ isGutsAnimation,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 285cb5a..e9c930a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1483,16 +1483,16 @@
}
private void updateMaxDisplayedNotifications(boolean recompute) {
+ if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+ return;
+ }
+
if (recompute) {
setMaxDisplayedNotifications(Math.max(computeMaxKeyguardNotifications(), 1));
} else {
if (SPEW_LOGCAT) Log.d(TAG, "Skipping computeMaxKeyguardNotifications() by request");
}
- if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
- return;
- }
-
if (isKeyguardShowing() && !mKeyguardBypassController.getBypassEnabled()) {
mNotificationStackScrollLayoutController.setMaxDisplayedNotifications(
mMaxAllowedKeyguardNotifications);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index e2e4556..8bab669 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -65,6 +65,10 @@
*/
@Deprecated("Use ShadeInteractor instead") val legacyShadeTracking: StateFlow<Boolean>
+ /** Specifically tracks the user expanding the shade on the lockscreen only */
+ @Deprecated("Use ShadeInteractor.isUserInteractingWithShade instead")
+ val legacyLockscreenShadeTracking: MutableStateFlow<Boolean>
+
/**
* QuickSettingsController.mTracking as a flow. "Tracking" means that the user is moving quick
* settings up or down with a pointer. Going forward, this concept will be replaced by checks
@@ -106,6 +110,9 @@
/** Sets whether the user is moving the shade with a pointer */
fun setLegacyShadeTracking(tracking: Boolean)
+ /** Sets whether the user is moving the shade with a pointer, on lockscreen only */
+ fun setLegacyLockscreenShadeTracking(tracking: Boolean)
+
/** Amount shade has expanded with regard to the UDFPS location */
val udfpsTransitionToFullShadeProgress: StateFlow<Float>
@@ -177,6 +184,8 @@
@Deprecated("Use ShadeInteractor instead")
override val legacyShadeTracking: StateFlow<Boolean> = _legacyShadeTracking.asStateFlow()
+ override val legacyLockscreenShadeTracking = MutableStateFlow(false)
+
private val _legacyQsTracking = MutableStateFlow(false)
@Deprecated("Use ShadeInteractor instead")
override val legacyQsTracking: StateFlow<Boolean> = _legacyQsTracking.asStateFlow()
@@ -212,6 +221,11 @@
_legacyShadeTracking.value = tracking
}
+ @Deprecated("Should only be called by NPVC and tests")
+ override fun setLegacyLockscreenShadeTracking(tracking: Boolean) {
+ legacyLockscreenShadeTracking.value = tracking
+ }
+
override fun setQsExpansion(qsExpansion: Float) {
_qsExpansion.value = qsExpansion
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index b2ffeb3..d687ef6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -72,7 +72,7 @@
userSetupRepository: UserSetupRepository,
userSwitcherInteractor: UserSwitcherInteractor,
sharedNotificationContainerInteractor: SharedNotificationContainerInteractor,
- repository: ShadeRepository,
+ private val repository: ShadeRepository,
) {
/** Emits true if the shade is currently allowed and false otherwise. */
val isShadeEnabled: StateFlow<Boolean> =
@@ -185,7 +185,15 @@
if (sceneContainerFlags.isEnabled()) {
sceneBasedInteracting(sceneInteractorProvider.get(), SceneKey.Shade)
} else {
- userInteractingFlow(repository.legacyShadeTracking, repository.legacyShadeExpansion)
+ combine(
+ userInteractingFlow(
+ repository.legacyShadeTracking,
+ repository.legacyShadeExpansion
+ ),
+ repository.legacyLockscreenShadeTracking
+ ) { legacyShadeTracking, legacyLockscreenShadeTracking ->
+ legacyShadeTracking || legacyLockscreenShadeTracking
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index bf722af..2e3f3f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -340,6 +340,7 @@
)
nsslController.resetScrollPosition()
nsslController.resetCheckSnoozeLeavebehind()
+ shadeRepository.setLegacyLockscreenShadeTracking(false)
setDragDownAmountAnimated(0f)
}
@@ -366,6 +367,7 @@
cancel()
}
}
+ shadeRepository.setLegacyLockscreenShadeTracking(true)
}
/** Do we need a falsing check currently? */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 3f080c2..0e83c78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -680,7 +680,7 @@
}
NotificationEntry entry = mCommonNotifCollectionLazy.get().getEntry(key);
if (mFeatureFlags.isEnabled(Flags.NOTIF_LS_BACKGROUND_THREAD)) {
- return entry != null
+ return entry != null && entry.getRanking().getChannel() != null
&& entry.getRanking().getChannel().getLockscreenVisibility()
== Notification.VISIBILITY_PRIVATE;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index a0ffba3..14ec08f35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -253,6 +253,7 @@
private NotificationLogger.OnChildLocationsChangedListener mListener;
private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
+ private Runnable mOnHeightChangedRunnable;
private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
private boolean mNeedsAnimation;
private boolean mTopPaddingNeedsAnimation;
@@ -1121,6 +1122,10 @@
if (mOnHeightChangedListener != null) {
mOnHeightChangedListener.onHeightChanged(view, needsAnimation);
}
+
+ if (mOnHeightChangedRunnable != null) {
+ mOnHeightChangedRunnable.run();
+ }
}
public boolean isPulseExpanding() {
@@ -4252,6 +4257,10 @@
this.mOnHeightChangedListener = onHeightChangedListener;
}
+ void setOnHeightChangedRunnable(Runnable r) {
+ this.mOnHeightChangedRunnable = r;
+ }
+
void onChildAnimationFinished() {
setAnimationRunning(false);
requestChildrenUpdate();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 99b3a00..2cf0c26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -20,6 +20,7 @@
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
import static com.android.app.animation.Interpolators.STANDARD;
+
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
@@ -34,7 +35,6 @@
import android.animation.ObjectAnimator;
import android.content.res.Configuration;
-import android.content.res.Resources;
import android.graphics.Point;
import android.os.Trace;
import android.os.UserHandle;
@@ -65,9 +65,7 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
-import com.android.systemui.common.ui.ConfigurationState;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.flags.Flags;
@@ -94,7 +92,6 @@
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -112,7 +109,6 @@
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.dagger.SilentHeader;
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
-import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ShelfNotificationIconViewStore;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -122,11 +118,9 @@
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationSnooze;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -960,6 +954,13 @@
mView.setOnHeightChangedListener(listener);
}
+ /**
+ * Invoked in addition to {@see #setOnHeightChangedListener}
+ */
+ public void setOnHeightChangedRunnable(Runnable r) {
+ mView.setOnHeightChangedRunnable(r);
+ }
+
public void setOverscrollTopChangedListener(
OnOverscrollTopChangedListener listener) {
mView.setOverscrollTopChangedListener(listener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
index 57cea5d..eb1c17a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
@@ -18,13 +18,15 @@
package com.android.systemui.statusbar.notification.stack.domain.interactor
import android.content.Context
-import com.android.systemui.res.R
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.SplitShadeStateController
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@@ -43,6 +45,10 @@
private val _topPosition = MutableStateFlow(0f)
val topPosition = _topPosition.asStateFlow()
+ private val _notificationStackChanged = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+ /** An internal modification was made to notifications */
+ val notificationStackChanged = _notificationStackChanged.asSharedFlow()
+
val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
configurationRepository.onAnyConfigurationChange
.onStart { emit(Unit) }
@@ -72,6 +78,11 @@
_topPosition.value = top
}
+ /** An internal modification was made to notifications */
+ fun notificationStackChanged() {
+ _notificationStackChanged.tryEmit(Unit)
+ }
+
data class ConfigurationBasedDimensions(
val useSplitShade: Boolean,
val useLargeScreenHeader: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index a1a0cca..0ff1bec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -20,6 +20,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
import kotlinx.coroutines.DisposableHandle
@@ -33,6 +34,7 @@
view: SharedNotificationContainer,
viewModel: SharedNotificationContainerViewModel,
controller: NotificationStackScrollLayoutController,
+ notificationStackSizeCalculator: NotificationStackSizeCalculator,
): DisposableHandle {
val disposableHandle =
view.repeatWhenAttached {
@@ -54,9 +56,16 @@
}
launch {
- viewModel.maxNotifications.collect {
- controller.setMaxDisplayedNotifications(it)
- }
+ viewModel
+ .getMaxNotifications { space ->
+ notificationStackSizeCalculator.computeMaxKeyguardNotifications(
+ controller.getView(),
+ space,
+ 0f, // Vertical space for shelf is already accounted for
+ controller.getShelfHeight().toFloat(),
+ )
+ }
+ .collect { controller.setMaxDisplayedNotifications(it) }
}
launch {
@@ -70,9 +79,12 @@
}
}
+ controller.setOnHeightChangedRunnable(Runnable { viewModel.notificationStackChanged() })
+
return object : DisposableHandle {
override fun dispose() {
disposableHandle.dispose()
+ controller.setOnHeightChangedRunnable(null)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index b86b5dc..d6b6f75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -22,28 +22,26 @@
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
-import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.combineTransform
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
/** View-model for the shared notification container, used by both the shade and keyguard spaces */
class SharedNotificationContainerViewModel
@Inject
constructor(
- interactor: SharedNotificationContainerInteractor,
+ private val interactor: SharedNotificationContainerInteractor,
keyguardInteractor: KeyguardInteractor,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
- notificationStackSizeCalculator: NotificationStackSizeCalculator,
- controller: NotificationStackScrollLayoutController,
- shadeInteractor: ShadeInteractor,
+ private val shadeInteractor: ShadeInteractor,
) {
private val statesForConstrainedNotifications =
setOf(
@@ -151,24 +149,46 @@
* When on keyguard, there is limited space to display notifications so calculate how many could
* be shown. Otherwise, there is no limit since the vertical space will be scrollable.
*
- * TODO: b/296606746 - Need to rerun logic when notifs change
+ * When expanding or when the user is interacting with the shade, keep the count stable; do not
+ * emit a value.
*/
- val maxNotifications: Flow<Int> =
- combine(isOnLockscreen, shadeInteractor.shadeExpansion, position) {
- onLockscreen,
- shadeExpansion,
- positionInfo ->
- if (onLockscreen && shadeExpansion < 1f) {
- notificationStackSizeCalculator.computeMaxKeyguardNotifications(
- controller.getView(),
- positionInfo.bottom - positionInfo.top,
- 0f, // Vertical space for shelf is already accounted for
- controller.getShelfHeight().toFloat(),
- )
- } else {
- -1 // No limit
+ fun getMaxNotifications(calculateSpace: (Float) -> Int): Flow<Int> {
+ // When to limit notifications: on lockscreen with an unexpanded shade. Also, recalculate
+ // when the notification stack has changed internally
+ val limitedNotifications =
+ combineTransform(
+ isOnLockscreen,
+ position,
+ shadeInteractor.shadeExpansion,
+ interactor.notificationStackChanged.onStart { emit(Unit) },
+ ) { isOnLockscreen, position, shadeExpansion, _ ->
+ if (isOnLockscreen && shadeExpansion == 0f) {
+ emit(calculateSpace(position.bottom - position.top))
+ }
}
- }
+
+ // When to show unlimited notifications: When the shade is fully expanded and the user is
+ // not actively dragging the shade
+ val unlimitedNotifications =
+ combineTransform(
+ shadeInteractor.shadeExpansion,
+ shadeInteractor.isUserInteracting,
+ ) { shadeExpansion, isUserInteracting ->
+ if (shadeExpansion == 1f && !isUserInteracting) {
+ emit(-1)
+ }
+ }
+
+ return merge(
+ limitedNotifications,
+ unlimitedNotifications,
+ )
+ .distinctUntilChanged()
+ }
+
+ fun notificationStackChanged() {
+ interactor.notificationStackChanged()
+ }
data class ConfigurationBasedDimensions(
val marginStart: Int,
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt
index db4ab7e..5b91615 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt
@@ -25,6 +25,16 @@
* The fraction after which we start fading in when going from a gone widget to a visible one
*/
private const val GONE_FADE_FRACTION = 0.8f
+/**
+ * The fraction after which we start fading in going from a gone widget to a visible one in guts
+ * animation.
+ */
+private const val GONE_FADE_GUTS_FRACTION = 0.286f
+/**
+ * The fraction before which we fade out when going from a visible widget to a gone one in guts
+ * animation.
+ */
+private const val VISIBLE_FADE_GUTS_FRACTION = 0.355f
/**
* The amont we're scaling appearing views
@@ -45,6 +55,7 @@
private var animationStartState: TransitionViewState? = null
private var state = TransitionViewState()
private var animator: ValueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f)
+ private var isGutsAnimation = false
private var currentHeight: Int = 0
private var currentWidth: Int = 0
var sizeChangedListener: ((Int, Int) -> Unit)? = null
@@ -152,15 +163,6 @@
// this looks quite ugly
val nowGone: Boolean
if (widgetStart.gone) {
-
- // Only fade it in at the very end
- alphaProgress = MathUtils.map(GONE_FADE_FRACTION, 1.0f, 0.0f, 1.0f, progress)
- nowGone = progress < GONE_FADE_FRACTION
-
- // Scale it just a little, not all the way
- val endScale = widgetEnd.scale
- newScale = MathUtils.lerp(GONE_SCALE_AMOUNT * endScale, endScale, progress)
-
// don't clip
widthProgress = 1.0f
@@ -168,25 +170,52 @@
resultMeasureWidth = widgetEnd.measureWidth
resultMeasureHeight = widgetEnd.measureHeight
- // Let's make sure we're centering the view in the gone view instead of having
- // the left at 0
- resultX = MathUtils.lerp(widgetStart.x - resultMeasureWidth / 2.0f,
- widgetEnd.x,
- progress)
- resultY = MathUtils.lerp(widgetStart.y - resultMeasureHeight / 2.0f,
- widgetEnd.y,
- progress)
+ if (isGutsAnimation) {
+ // if animation is open/close guts, fade in starts early.
+ alphaProgress = MathUtils.map(
+ GONE_FADE_GUTS_FRACTION,
+ 1.0f,
+ 0.0f,
+ 1.0f,
+ progress
+ )
+ nowGone = progress < GONE_FADE_GUTS_FRACTION
+
+ // Do not change scale of widget.
+ newScale = 1.0f
+
+ // We do not want any horizontal or vertical movement.
+ resultX = widgetStart.x
+ resultY = widgetStart.y
+ } else {
+ // Only fade it in at the very end
+ alphaProgress = MathUtils.map(
+ GONE_FADE_FRACTION,
+ 1.0f,
+ 0.0f,
+ 1.0f,
+ progress
+ )
+ nowGone = progress < GONE_FADE_FRACTION
+
+ // Scale it just a little, not all the way
+ val endScale = widgetEnd.scale
+ newScale = MathUtils.lerp(GONE_SCALE_AMOUNT * endScale, endScale, progress)
+
+ // Let's make sure we're centering the view in the gone view instead of
+ // having the left at 0
+ resultX = MathUtils.lerp(
+ widgetStart.x - resultMeasureWidth / 2.0f,
+ widgetEnd.x,
+ progress
+ )
+ resultY = MathUtils.lerp(
+ widgetStart.y - resultMeasureHeight / 2.0f,
+ widgetEnd.y,
+ progress
+ )
+ }
} else {
-
- // Fadeout in the very beginning
- alphaProgress = MathUtils.map(0.0f, 1.0f - GONE_FADE_FRACTION, 0.0f, 1.0f,
- progress)
- nowGone = progress > 1.0f - GONE_FADE_FRACTION
-
- // Scale it just a little, not all the way
- val startScale = widgetStart.scale
- newScale = MathUtils.lerp(startScale, startScale * GONE_SCALE_AMOUNT, progress)
-
// Don't clip
widthProgress = 0.0f
@@ -194,14 +223,54 @@
resultMeasureWidth = widgetStart.measureWidth
resultMeasureHeight = widgetStart.measureHeight
- // Let's make sure we're centering the view in the gone view instead of having
- // the left at 0
- resultX = MathUtils.lerp(widgetStart.x,
- widgetEnd.x - resultMeasureWidth / 2.0f,
- progress)
- resultY = MathUtils.lerp(widgetStart.y,
- widgetEnd.y - resultMeasureHeight / 2.0f,
- progress)
+ // Fadeout in the very beginning
+ if (isGutsAnimation) {
+ alphaProgress = MathUtils.map(
+ 0.0f,
+ VISIBLE_FADE_GUTS_FRACTION,
+ 0.0f,
+ 1.0f,
+ progress
+ )
+ nowGone = progress > VISIBLE_FADE_GUTS_FRACTION
+
+ // Do not change scale of widget during open/close guts animation.
+ newScale = 1.0f
+
+ // We do not want any horizontal or vertical movement.
+ resultX = widgetEnd.x
+ resultY = widgetEnd.y
+ } else {
+ alphaProgress = MathUtils.map(
+ 0.0f,
+ 1.0f - GONE_FADE_FRACTION,
+ 0.0f,
+ 1.0f,
+ progress
+ )
+ nowGone = progress > 1.0f - GONE_FADE_FRACTION
+
+ // Scale it just a little, not all the way
+ val startScale = widgetStart.scale
+ newScale = MathUtils.lerp(
+ startScale,
+ startScale * GONE_SCALE_AMOUNT,
+ progress
+ )
+
+ // Let's make sure we're centering the view in the gone view instead of
+ // having the left at 0
+ resultX = MathUtils.lerp(
+ widgetStart.x,
+ widgetEnd.x - resultMeasureWidth / 2.0f,
+ progress
+ )
+ resultY = MathUtils.lerp(
+ widgetStart.y,
+ widgetEnd.y - resultMeasureHeight / 2.0f,
+ progress
+ )
+ }
}
resultWidgetState.gone = nowGone
} else {
@@ -279,8 +348,10 @@
applyImmediately: Boolean,
animate: Boolean,
duration: Long = 0,
- delay: Long = 0
+ delay: Long = 0,
+ isGuts: Boolean,
) {
+ isGutsAnimation = isGuts
val animated = animate && currentState.width != 0
this.state = state.copy()
if (applyImmediately || transitionLayout == null) {
@@ -291,6 +362,8 @@
animationStartState = currentState.copy()
animator.duration = duration
animator.startDelay = delay
+ animator.interpolator =
+ if (isGutsAnimation) Interpolators.LINEAR else Interpolators.FAST_OUT_SLOW_IN
animator.start()
} else if (!animator.isRunning) {
applyStateToLayout(this.state)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
index b101acf..437a35f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDeviceManagerTest.kt
@@ -38,8 +38,6 @@
import com.android.settingslib.media.PhoneMediaDevice
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.flags.Flags
import com.android.systemui.media.controls.MediaTestUtils
import com.android.systemui.media.controls.models.player.MediaData
import com.android.systemui.media.controls.models.player.MediaDeviceData
@@ -112,7 +110,6 @@
private lateinit var session: MediaSession
private lateinit var mediaData: MediaData
@JvmField @Rule val mockito = MockitoJUnit.rule()
- private val featureFlags = FakeFeatureFlagsClassic()
@Before
fun setUp() {
@@ -131,7 +128,6 @@
fakeFgExecutor,
fakeBgExecutor,
dumpster,
- featureFlags,
)
manager.addListener(listener)
@@ -150,7 +146,6 @@
MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, token = session.sessionToken)
whenever(controllerFactory.create(session.sessionToken)).thenReturn(controller)
setupLeAudioConfiguration(false)
- featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, false)
}
@After
@@ -463,7 +458,6 @@
@Test
fun mr2ReturnsSystemRouteWithNullName_isPhone_usePhoneName() {
- featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
// When the routing session name is null, and is a system session for a PhoneMediaDevice
val phoneDevice = mock(PhoneMediaDevice::class.java)
whenever(phoneDevice.iconWithoutBackground).thenReturn(icon)
@@ -489,7 +483,6 @@
@Test
fun mr2ReturnsSystemRouteWithNullName_useSelectedRouteName() {
- featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
// When the routing session does not have a name, and is a system session
whenever(route.name).thenReturn(null)
whenever(mr2.getSelectedRoutes(any())).thenReturn(listOf(selectedRoute))
@@ -725,101 +718,6 @@
assertThat(data.showBroadcastButton).isFalse()
}
- // Duplicates of above tests with MEDIA_DEVICE_NAME_FIX enabled
-
- @Test
- fun loadMediaDataWithNullToken_withNameFix() {
- featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
- manager.onMediaDataLoaded(KEY, null, mediaData.copy(token = null))
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
- val data = captureDeviceData(KEY)
- assertThat(data.enabled).isTrue()
- assertThat(data.name).isEqualTo(DEVICE_NAME)
- }
-
- @Test
- fun onAboutToConnectDeviceAdded_findsDeviceInfoFromAddress_withNameFix() {
- featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
- manager.onMediaDataLoaded(KEY, null, mediaData)
- // Run and reset the executors and listeners so we only focus on new events.
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
- reset(listener)
-
- // Ensure we'll get device info when using the address
- val fullMediaDevice = mock(MediaDevice::class.java)
- val address = "fakeAddress"
- val nameFromDevice = "nameFromDevice"
- val iconFromDevice = mock(Drawable::class.java)
- whenever(lmm.getMediaDeviceById(eq(address))).thenReturn(fullMediaDevice)
- whenever(fullMediaDevice.name).thenReturn(nameFromDevice)
- whenever(fullMediaDevice.iconWithoutBackground).thenReturn(iconFromDevice)
-
- // WHEN the about-to-connect device changes to non-null
- val deviceCallback = captureCallback()
- val nameFromParam = "nameFromParam"
- val iconFromParam = mock(Drawable::class.java)
- deviceCallback.onAboutToConnectDeviceAdded(address, nameFromParam, iconFromParam)
- assertThat(fakeFgExecutor.runAllReady()).isEqualTo(1)
-
- // THEN the about-to-connect device based on the address is returned
- val data = captureDeviceData(KEY)
- assertThat(data.enabled).isTrue()
- assertThat(data.name).isEqualTo(nameFromDevice)
- assertThat(data.name).isNotEqualTo(nameFromParam)
- assertThat(data.icon).isEqualTo(iconFromDevice)
- assertThat(data.icon).isNotEqualTo(iconFromParam)
- }
-
- @Test
- fun deviceNameFromMR2RouteInfo_withNameFix() {
- featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
- // GIVEN that MR2Manager returns a valid routing session
- whenever(route.name).thenReturn(REMOTE_DEVICE_NAME)
- // WHEN a notification is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
- // THEN it uses the route name (instead of device name)
- val data = captureDeviceData(KEY)
- assertThat(data.enabled).isTrue()
- assertThat(data.name).isEqualTo(REMOTE_DEVICE_NAME)
- }
-
- @Test
- fun deviceDisabledWhenMR2ReturnsNullRouteInfo_withNameFix() {
- featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
- // GIVEN that MR2Manager returns null for routing session
- whenever(mr2.getRoutingSessionForMediaController(any())).thenReturn(null)
- // WHEN a notification is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
- // THEN the device is disabled and name is set to null
- val data = captureDeviceData(KEY)
- assertThat(data.enabled).isFalse()
- assertThat(data.name).isNull()
- }
-
- @Test
- fun mr2ReturnsNonSystemRouteWithNullName_useLocalDeviceName_withNameFix() {
- featureFlags.set(Flags.MEDIA_DEVICE_NAME_FIX, true)
- // GIVEN that MR2Manager returns a routing session that does not have a name
- whenever(route.name).thenReturn(null)
- whenever(route.isSystemSession).thenReturn(false)
- // WHEN a notification is added
- manager.onMediaDataLoaded(KEY, null, mediaData)
- fakeBgExecutor.runAllReady()
- fakeFgExecutor.runAllReady()
- // THEN the device is enabled and uses the current connected device name
- val data = captureDeviceData(KEY)
- assertThat(data.name).isEqualTo(DEVICE_NAME)
- assertThat(data.enabled).isTrue()
- }
-
- // End duplicate tests
-
private fun captureCallback(): LocalMediaManager.DeviceCallback {
val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java)
verify(lmm).registerCallback(captor.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
index e920687..20b19fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt
@@ -158,6 +158,15 @@
}
@Test
+ fun updateLegacyLockscreenShadeTracking() =
+ testScope.runTest {
+ assertThat(underTest.legacyLockscreenShadeTracking.value).isEqualTo(false)
+
+ underTest.setLegacyLockscreenShadeTracking(true)
+ assertThat(underTest.legacyLockscreenShadeTracking.value).isEqualTo(true)
+ }
+
+ @Test
fun updateLegacyQsTracking() =
testScope.runTest {
assertThat(underTest.legacyQsTracking.value).isEqualTo(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index a1425f8..ae32142 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -323,6 +323,20 @@
}
@Test
+ public void testCurrentUserPrivateNotificationsNullChannel() {
+ // GIVEN current user allows private notifications to show
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 1,
+ mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+ mCurrentUserNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking())
+ .setChannel(null)
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+ // THEN the notification is not redacted
+ assertFalse(mLockscreenUserManager.needsRedaction(mCurrentUserNotif));
+ }
+
+ @Test
public void testWorkPrivateNotificationsRedacted() {
// GIVEN work profile doesn't private notifications to show
mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 6203531..e91d6d7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -83,6 +83,7 @@
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -906,6 +907,20 @@
assertEquals(bottomImeInset, mStackScrollerInternal.mBottomInset);
}
+ @Test
+ public void testSetMaxDisplayedNotifications_notifiesListeners() {
+ ExpandableView.OnHeightChangedListener listener =
+ mock(ExpandableView.OnHeightChangedListener.class);
+ Runnable runnable = mock(Runnable.class);
+ mStackScroller.setOnHeightChangedListener(listener);
+ mStackScroller.setOnHeightChangedRunnable(runnable);
+
+ mStackScroller.setMaxDisplayedNotifications(50);
+
+ verify(listener).onHeightChanged(mNotificationShelf, false);
+ verify(runnable).run();
+ }
+
private void setBarStateForTest(int state) {
// Can't inject this through the listener or we end up on the actual implementation
// rather than the mock because the spy just coppied the anonymous inner /shruggie.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 22553df..db8f217 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -40,13 +40,8 @@
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.FakeShadeRepository
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
-import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.user.domain.UserDomainLayerModule
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import dagger.BindsInstance
import dagger.Component
@@ -84,25 +79,13 @@
}
}
- private val notificationStackSizeCalculator: NotificationStackSizeCalculator = mock()
- private val notificationStackScrollLayoutController: NotificationStackScrollLayoutController =
- mock {
- whenever(view).thenReturn(mock())
- whenever(shelfHeight).thenReturn(0)
- }
-
private val testComponent: TestComponent =
DaggerSharedNotificationContainerViewModelTest_TestComponent.factory()
.create(
test = this,
featureFlags =
FakeFeatureFlagsClassicModule { set(Flags.FULL_SCREEN_USER_SWITCHER, true) },
- mocks =
- TestMocksModule(
- notificationStackSizeCalculator = notificationStackSizeCalculator,
- notificationStackScrollLayoutController =
- notificationStackScrollLayoutController,
- )
+ mocks = TestMocksModule(),
)
@Test
@@ -336,17 +319,9 @@
@Test
fun maxNotificationsOnLockscreen() =
testComponent.runTest {
- whenever(
- notificationStackSizeCalculator.computeMaxKeyguardNotifications(
- any(),
- any(),
- any(),
- any()
- )
- )
- .thenReturn(10)
-
- val maxNotifications by collectLastValue(underTest.maxNotifications)
+ var notificationCount = 10
+ val maxNotifications by
+ collectLastValue(underTest.getMaxNotifications { notificationCount })
showLockscreen()
@@ -356,21 +331,52 @@
SharedNotificationContainerPosition(top = 1f, bottom = 2f)
assertThat(maxNotifications).isEqualTo(10)
+
+ // Also updates when directly requested (as it would from NotificationStackScrollLayout)
+ notificationCount = 25
+ sharedNotificationContainerInteractor.notificationStackChanged()
+ assertThat(maxNotifications).isEqualTo(25)
+ }
+
+ @Test
+ fun maxNotificationsOnLockscreen_DoesNotUpdateWhenUserInteracting() =
+ testComponent.runTest {
+ var notificationCount = 10
+ val maxNotifications by
+ collectLastValue(underTest.getMaxNotifications { notificationCount })
+
+ showLockscreen()
+
+ overrideResource(R.bool.config_use_split_notification_shade, false)
+ configurationRepository.onAnyConfigurationChange()
+ keyguardInteractor.sharedNotificationContainerPosition.value =
+ SharedNotificationContainerPosition(top = 1f, bottom = 2f)
+
+ assertThat(maxNotifications).isEqualTo(10)
+
+ // Shade expanding... still 10
+ shadeRepository.setLockscreenShadeExpansion(0.5f)
+ assertThat(maxNotifications).isEqualTo(10)
+
+ notificationCount = 25
+
+ // When shade is expanding by user interaction
+ shadeRepository.setLegacyLockscreenShadeTracking(true)
+
+ // Should still be 10, since the user is interacting
+ assertThat(maxNotifications).isEqualTo(10)
+
+ shadeRepository.setLegacyLockscreenShadeTracking(false)
+ shadeRepository.setLockscreenShadeExpansion(0f)
+
+ // Stopped tracking, show 25
+ assertThat(maxNotifications).isEqualTo(25)
}
@Test
fun maxNotificationsOnShade() =
testComponent.runTest {
- whenever(
- notificationStackSizeCalculator.computeMaxKeyguardNotifications(
- any(),
- any(),
- any(),
- any()
- )
- )
- .thenReturn(10)
- val maxNotifications by collectLastValue(underTest.maxNotifications)
+ val maxNotifications by collectLastValue(underTest.getMaxNotifications { 10 })
// Show lockscreen with shade expanded
showLockscreenWithShadeExpanded()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
index 800593f..02318ab 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt
@@ -59,6 +59,8 @@
private val _legacyIsQsExpanded = MutableStateFlow(false)
@Deprecated("Use ShadeInteractor instead") override val legacyIsQsExpanded = _legacyIsQsExpanded
+ override val legacyLockscreenShadeTracking = MutableStateFlow(false)
+
@Deprecated("Use ShadeInteractor instead")
override fun setLegacyIsQsExpanded(legacyIsQsExpanded: Boolean) {
_legacyIsQsExpanded.value = legacyIsQsExpanded
@@ -81,6 +83,11 @@
_legacyShadeTracking.value = tracking
}
+ @Deprecated("Should only be called by NPVC and tests")
+ override fun setLegacyLockscreenShadeTracking(tracking: Boolean) {
+ legacyLockscreenShadeTracking.value = tracking
+ }
+
fun setShadeModel(model: ShadeModel) {
_shadeModel.value = model
}
diff --git a/services/companion/java/com/android/server/companion/virtual/OWNERS b/services/companion/java/com/android/server/companion/virtual/OWNERS
index 83143a4..5295ec8 100644
--- a/services/companion/java/com/android/server/companion/virtual/OWNERS
+++ b/services/companion/java/com/android/server/companion/virtual/OWNERS
@@ -1,3 +1,5 @@
+# Bug component: 1171888
+
set noparent
ogunwale@google.com
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 15fc2dc..f6835fe 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -3651,7 +3651,26 @@
Watchdog.getInstance().pauseWatchingMonitorsFor(
SLOW_OPERATION_WATCHDOG_TIMEOUT_MS, "#close might be slow");
if (mMounted) {
- mVold.unmountAppFuse(uid, mountId);
+ BackgroundThread.getHandler().post(() -> {
+ try {
+ // We need to run the unmount on a separate thread to
+ // prevent a possible deadlock, where:
+ // 1. AppFuseThread (this thread) tries to call into vold
+ // 2. the vold lock is held by another thread, which called:
+ // mVold.openAppFuseFile()
+ // as part of that call, vold calls open() on the
+ // underlying file, which is a call that needs to be
+ // handled by the AppFuseThread, which is stuck waiting
+ // for the vold lock (see 1.)
+ // It is safe to do the unmount asynchronously, because the mount
+ // path we use is never reused during the current boot cycle;
+ // see mNextAppFuseName. Also,we have anyway stopped serving
+ // requests at this point.
+ mVold.unmountAppFuse(uid, mountId);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ });
mMounted = false;
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b99a98f..f92af67 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -599,6 +599,9 @@
private static final String INTENT_REMOTE_BUGREPORT_FINISHED =
"com.android.internal.intent.action.REMOTE_BUGREPORT_FINISHED";
+ public static final String DATA_FILE_PATH_HEADER = "Data File: ";
+ public static final String DATA_FILE_PATH_FOOTER = "End Data File\n";
+
// If set, we will push process association information in to procstats.
static final boolean TRACK_PROCSTATS_ASSOCIATIONS = true;
@@ -9595,17 +9598,33 @@
: Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0);
int dropboxMaxSize = Settings.Global.getInt(
mContext.getContentResolver(), maxBytesSetting, DROPBOX_DEFAULT_MAX_SIZE);
- int maxDataFileSize = dropboxMaxSize - sb.length()
- - lines * RESERVED_BYTES_PER_LOGCAT_LINE;
- if (dataFile != null && maxDataFileSize > 0) {
- try {
- sb.append(FileUtils.readTextFile(dataFile, maxDataFileSize,
- "\n\n[[TRUNCATED]]"));
- } catch (IOException e) {
- Slog.e(TAG, "Error reading " + dataFile, e);
+ if (dataFile != null) {
+ // Attach the stack traces file to the report so collectors can load them
+ // by file if they have access.
+ sb.append(DATA_FILE_PATH_HEADER)
+ .append(dataFile.getAbsolutePath()).append('\n');
+
+ int maxDataFileSize = dropboxMaxSize
+ - sb.length()
+ - lines * RESERVED_BYTES_PER_LOGCAT_LINE
+ - DATA_FILE_PATH_FOOTER.length();
+
+ if (maxDataFileSize > 0) {
+ // Inline dataFile contents if there is room.
+ try {
+ sb.append(FileUtils.readTextFile(dataFile, maxDataFileSize,
+ "\n\n[[TRUNCATED]]\n"));
+ } catch (IOException e) {
+ Slog.e(TAG, "Error reading " + dataFile, e);
+ }
}
+
+ // Always append the footer, even there wasn't enough space to inline the
+ // dataFile contents.
+ sb.append(DATA_FILE_PATH_FOOTER);
}
+
if (crashInfo != null && crashInfo.stackTrace != null) {
sb.append(crashInfo.stackTrace);
}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index e41b6ae..3ce92bc 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -265,13 +265,6 @@
@Nullable
public BroadcastRecord enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record,
int recordIndex, @NonNull BroadcastConsumer deferredStatesApplyConsumer) {
- // When updateDeferredStates() has already applied a deferred state to
- // all pending items, apply to this new broadcast too
- if (mLastDeferredStates && record.deferUntilActive
- && (record.getDeliveryState(recordIndex) == BroadcastRecord.DELIVERY_PENDING)) {
- deferredStatesApplyConsumer.accept(record, recordIndex);
- }
-
// Ignore FLAG_RECEIVER_REPLACE_PENDING if the sender specified the policy using the
// BroadcastOptions delivery group APIs.
if (record.isReplacePending()
@@ -294,6 +287,13 @@
// with implicit responsiveness expectations.
getQueueForBroadcast(record).addLast(newBroadcastArgs);
onBroadcastEnqueued(record, recordIndex);
+
+ // When updateDeferredStates() has already applied a deferred state to
+ // all pending items, apply to this new broadcast too
+ if (mLastDeferredStates && shouldBeDeferred()
+ && (record.getDeliveryState(recordIndex) == BroadcastRecord.DELIVERY_PENDING)) {
+ deferredStatesApplyConsumer.accept(record, recordIndex);
+ }
return null;
}
@@ -1235,32 +1235,45 @@
}
/**
- * Update {@link BroadcastRecord.DELIVERY_DEFERRED} states of all our
+ * Update {@link BroadcastRecord#DELIVERY_DEFERRED} states of all our
* pending broadcasts, when needed.
*/
void updateDeferredStates(@NonNull BroadcastConsumer applyConsumer,
@NonNull BroadcastConsumer clearConsumer) {
// When all we have pending is deferred broadcasts, and we're cached,
// then we want everything to be marked deferred
- final boolean wantDeferredStates = (mCountDeferred > 0)
- && (mCountDeferred == mCountEnqueued) && mProcessFreezable;
+ final boolean wantDeferredStates = shouldBeDeferred();
if (mLastDeferredStates != wantDeferredStates) {
mLastDeferredStates = wantDeferredStates;
if (wantDeferredStates) {
forEachMatchingBroadcast((r, i) -> {
- return r.deferUntilActive
- && (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_PENDING);
+ return (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_PENDING);
}, applyConsumer, false);
} else {
forEachMatchingBroadcast((r, i) -> {
- return r.deferUntilActive
- && (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_DEFERRED);
+ return (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_DEFERRED);
}, clearConsumer, false);
}
}
}
+ void clearDeferredStates(@NonNull BroadcastConsumer clearConsumer) {
+ if (mLastDeferredStates) {
+ mLastDeferredStates = false;
+ forEachMatchingBroadcast((r, i) -> {
+ return (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_DEFERRED);
+ }, clearConsumer, false);
+ }
+ }
+
+ @VisibleForTesting
+ boolean shouldBeDeferred() {
+ if (mRunnableAtInvalidated) updateRunnableAt();
+ return mRunnableAtReason == REASON_CACHED
+ || mRunnableAtReason == REASON_CACHED_INFINITE_DEFER;
+ }
+
/**
* Check overall health, confirming things are in a reasonable state and
* that we're not wedged.
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index b481697..5b54561 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -479,6 +479,10 @@
break;
}
+ // Clear the deferred state of broadcasts in this queue as we are just about to
+ // deliver broadcasts to this process.
+ queue.clearDeferredStates(mBroadcastConsumerDeferClear);
+
// We might not have heard about a newly running process yet, so
// consider refreshing if we think we're cold
updateWarmProcess(queue);
@@ -1567,12 +1571,14 @@
r.resultExtras = null;
};
- private final BroadcastConsumer mBroadcastConsumerDeferApply = (r, i) -> {
+ @VisibleForTesting
+ final BroadcastConsumer mBroadcastConsumerDeferApply = (r, i) -> {
setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_DEFERRED,
"mBroadcastConsumerDeferApply");
};
- private final BroadcastConsumer mBroadcastConsumerDeferClear = (r, i) -> {
+ @VisibleForTesting
+ final BroadcastConsumer mBroadcastConsumerDeferClear = (r, i) -> {
setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_PENDING,
"mBroadcastConsumerDeferClear");
};
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index a0a7b2b..d0ab287 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -1472,10 +1472,13 @@
}
return;
}
+ boolean processFreezableChangeReported = false;
if (opt.isPendingFreeze()) {
// Remove pending DO_FREEZE message
mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);
opt.setPendingFreeze(false);
+ reportProcessFreezableChangedLocked(app);
+ processFreezableChangeReported = true;
if (DEBUG_FREEZER) {
Slog.d(TAG_AM, "Cancel freezing " + pid + " " + app.processName);
}
@@ -1524,7 +1527,9 @@
if (processKilled) {
return;
}
- reportProcessFreezableChangedLocked(app);
+ if (!processFreezableChangeReported) {
+ reportProcessFreezableChangedLocked(app);
+ }
long freezeTime = opt.getFreezeUnfreezeTime();
diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java
index 51cb950..5c8dd0d 100644
--- a/services/core/java/com/android/server/audio/AdiDeviceState.java
+++ b/services/core/java/com/android/server/audio/AdiDeviceState.java
@@ -25,6 +25,7 @@
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
+import android.media.Utils;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@@ -167,8 +168,9 @@
public String toString() {
return "type: " + mDeviceType
+ " internal type: 0x" + Integer.toHexString(mInternalDeviceType)
- + " addr: " + mDeviceAddress + " bt audio type: "
- + AudioManager.audioDeviceCategoryToString(mAudioDeviceCategory)
+ + " addr: " + Utils.anonymizeBluetoothAddress(mInternalDeviceType, mDeviceAddress)
+ + " bt audio type: "
+ + AudioManager.audioDeviceCategoryToString(mAudioDeviceCategory)
+ " enabled: " + mSAEnabled + " HT: " + mHasHeadTracker
+ " HTenabled: " + mHeadTrackerEnabled;
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 7ba0827..e9b102b 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -48,6 +48,7 @@
import android.media.IStrategyPreferredDevicesDispatcher;
import android.media.MediaMetrics;
import android.media.MediaRecorder.AudioSource;
+import android.media.Utils;
import android.media.audiopolicy.AudioProductStrategy;
import android.media.permission.ClearCallingIdentityContext;
import android.media.permission.SafeCloseable;
@@ -477,7 +478,7 @@
return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
+ " (" + AudioSystem.getDeviceName(mDeviceType)
+ ") name:" + mDeviceName
- + " addr:" + mDeviceAddress
+ + " addr:" + Utils.anonymizeBluetoothAddress(mDeviceType, mDeviceAddress)
+ " codec: " + Integer.toHexString(mDeviceCodecFormat)
+ " peer addr:" + mPeerDeviceAddress
+ " group:" + mGroupId
@@ -532,7 +533,7 @@
mApmConnectedDevices.forEach((keyType, valueAddress) -> {
pw.println(" " + prefix + " type:0x" + Integer.toHexString(keyType)
+ " (" + AudioSystem.getDeviceName(keyType)
- + ") addr:" + valueAddress); });
+ + ") addr:" + Utils.anonymizeBluetoothAddress(keyType, valueAddress)); });
pw.println("\n" + prefix + "Preferred devices for capture preset:");
mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> {
pw.println(" " + prefix + "capturePreset:" + capturePreset
@@ -1789,7 +1790,8 @@
// TODO: return;
} else {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
- "A2DP device addr=" + address + " now available").printLog(TAG));
+ "A2DP device addr=" + Utils.anonymizeBluetoothAddress(address)
+ + " now available").printLog(TAG));
}
// Reset A2DP suspend state each time a new sink is connected
@@ -2027,7 +2029,8 @@
.equals(mApmConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) {
// removing A2DP device not currently used by AudioPolicy, log but don't act on it
AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
- "A2DP device " + address + " made unavailable, was not used")).printLog(TAG));
+ "A2DP device " + Utils.anonymizeBluetoothAddress(address)
+ + " made unavailable, was not used")).printLog(TAG));
mmi.set(MediaMetrics.Property.EARLY_RETURN,
"A2DP device made unavailable, was not used")
.record();
@@ -2043,13 +2046,15 @@
if (res != AudioSystem.AUDIO_STATUS_OK) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
- "APM failed to make unavailable A2DP device addr=" + address
+ "APM failed to make unavailable A2DP device addr="
+ + Utils.anonymizeBluetoothAddress(address)
+ " error=" + res).printLog(TAG));
// TODO: failed to disconnect, stop here
// TODO: return;
} else {
AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
- "A2DP device addr=" + address + " made unavailable")).printLog(TAG));
+ "A2DP device addr=" + Utils.anonymizeBluetoothAddress(address)
+ + " made unavailable")).printLog(TAG));
}
mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
@@ -2238,7 +2243,8 @@
// TODO: return;
} else {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
- "LE Audio device addr=" + address + " now available").printLog(TAG));
+ "LE Audio device addr=" + Utils.anonymizeBluetoothAddress(address)
+ + " now available").printLog(TAG));
}
// Reset LEA suspend state each time a new sink is connected
mDeviceBroker.clearLeAudioSuspended(true /* internalOnly */);
@@ -2282,7 +2288,8 @@
// TODO: return;
} else {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
- "LE Audio device addr=" + address + " made unavailable").printLog(TAG));
+ "LE Audio device addr=" + Utils.anonymizeBluetoothAddress(address)
+ + " made unavailable").printLog(TAG));
}
mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address));
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b2ee610..1e38c0f 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -136,6 +136,7 @@
import android.media.MediaRecorder.AudioSource;
import android.media.PlayerBase;
import android.media.Spatializer;
+import android.media.Utils;
import android.media.VolumeInfo;
import android.media.VolumePolicy;
import android.media.audiofx.AudioEffect;
@@ -7470,7 +7471,7 @@
sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:"
+ AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:"
- + device.getAddress() + " behavior:"
+ + Utils.anonymizeBluetoothAddress(device.getAddress()) + " behavior:"
+ AudioDeviceVolumeManager.volumeBehaviorName(deviceVolumeBehavior)
+ " pack:" + pkgName).printLog(TAG));
if (pkgName == null) {
@@ -9641,7 +9642,7 @@
private void avrcpSupportsAbsoluteVolume(String address, boolean support) {
// address is not used for now, but may be used when multiple a2dp devices are supported
sVolumeLogger.enqueue(new EventLogger.StringEvent("avrcpSupportsAbsoluteVolume addr="
- + address + " support=" + support).printLog(TAG));
+ + Utils.anonymizeBluetoothAddress(address) + " support=" + support).printLog(TAG));
mDeviceBroker.setAvrcpAbsoluteVolumeSupported(support);
setAvrcpAbsoluteVolumeSupported(support);
}
@@ -10539,11 +10540,11 @@
AudioDeviceAttributes retrieveBluetoothAddressUncheked(@NonNull AudioDeviceAttributes ada) {
Objects.requireNonNull(ada);
if (AudioSystem.isBluetoothDevice(ada.getInternalType())) {
- String anonymizedAddress = anonymizeBluetoothAddress(ada.getAddress());
+ String anonymizedAddress = Utils.anonymizeBluetoothAddress(ada.getAddress());
for (AdiDeviceState ads : mDeviceBroker.getImmutableDeviceInventory()) {
if (!(AudioSystem.isBluetoothDevice(ads.getInternalDeviceType())
&& (ada.getInternalType() == ads.getInternalDeviceType())
- && anonymizedAddress.equals(anonymizeBluetoothAddress(
+ && anonymizedAddress.equals(Utils.anonymizeBluetoothAddress(
ads.getDeviceAddress())))) {
continue;
}
@@ -10554,19 +10555,6 @@
return ada;
}
- /**
- * Convert a Bluetooth MAC address to an anonymized one when exposed to a non privileged app
- * Must match the implementation of BluetoothUtils.toAnonymizedAddress()
- * @param address Mac address to be anonymized
- * @return anonymized mac address
- */
- static String anonymizeBluetoothAddress(String address) {
- if (address == null || address.length() != "AA:BB:CC:DD:EE:FF".length()) {
- return null;
- }
- return "XX:XX:XX:XX" + address.substring("XX:XX:XX:XX".length());
- }
-
private List<AudioDeviceAttributes> anonymizeAudioDeviceAttributesList(
List<AudioDeviceAttributes> devices) {
if (isBluetoothPrividged()) {
@@ -10590,7 +10578,7 @@
return ada;
}
AudioDeviceAttributes res = new AudioDeviceAttributes(ada);
- res.setAddress(anonymizeBluetoothAddress(ada.getAddress()));
+ res.setAddress(Utils.anonymizeBluetoothAddress(ada.getAddress()));
return res;
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 53fbe8f..a12243b 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -22,7 +22,6 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.RouteInfo.RTN_THROW;
import static android.net.RouteInfo.RTN_UNREACHABLE;
import static android.net.VpnManager.NOTIFICATION_CHANNEL_VPN;
import static android.net.ipsec.ike.IkeSessionParams.ESP_ENCAP_TYPE_AUTO;
@@ -45,12 +44,10 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -113,7 +110,6 @@
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.CancellationSignal;
-import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
import android.os.INetworkManagementService;
@@ -152,7 +148,6 @@
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
-import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BinderUtils;
import com.android.net.module.util.LinkPropertiesUtils;
import com.android.net.module.util.NetdUtils;
@@ -202,7 +197,6 @@
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
/**
* @hide
@@ -1063,8 +1057,6 @@
// Store mPackage since it might be reset or might be replaced with the other VPN app.
final String oldPackage = mPackage;
final boolean isPackageChanged = !Objects.equals(packageName, oldPackage);
- // TODO: Remove "SdkLevel.isAtLeastT()" check once VpnManagerService is decoupled from
- // ConnectivityServiceTest.
// Only notify VPN apps that were already always-on, and only if the always-on provider
// changed, or the lockdown mode changed.
final boolean shouldNotifyOldPkg = isVpnApp(oldPackage) && mAlwaysOn
@@ -1078,12 +1070,6 @@
saveAlwaysOnPackage();
- // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
- // ConnectivityServiceTest.
- if (!SdkLevel.isAtLeastT()) {
- return true;
- }
-
if (shouldNotifyOldPkg) {
// If both of shouldNotifyOldPkg & isPackageChanged are true, that means the
// always-on of old package is disabled or the old package is replaced with the new
@@ -1984,9 +1970,7 @@
for (String app : packageNames) {
int uid = getAppUid(mContext, app, userId);
if (uid != -1) uids.add(uid);
- // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
- // ConnectivityServiceTest.
- if (Process.isApplicationUid(uid) && SdkLevel.isAtLeastT()) {
+ if (Process.isApplicationUid(uid)) {
uids.add(Process.toSdkSandboxUid(uid));
}
}
@@ -2297,15 +2281,6 @@
private INetworkManagementEventObserver mObserver = new BaseNetworkObserver() {
@Override
- public void interfaceStatusChanged(String interfaze, boolean up) {
- synchronized (Vpn.this) {
- if (!up && mVpnRunner != null && mVpnRunner instanceof LegacyVpnRunner) {
- ((LegacyVpnRunner) mVpnRunner).exitIfOuterInterfaceIs(interfaze);
- }
- }
- }
-
- @Override
public void interfaceRemoved(String interfaze) {
synchronized (Vpn.this) {
if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) {
@@ -2556,17 +2531,6 @@
private native boolean jniAddAddress(String interfaze, String address, int prefixLen);
private native boolean jniDelAddress(String interfaze, String address, int prefixLen);
- private static RouteInfo findIPv4DefaultRoute(LinkProperties prop) {
- for (RouteInfo route : prop.getAllRoutes()) {
- // Currently legacy VPN only works on IPv4.
- if (route.isDefaultRoute() && route.getGateway() instanceof Inet4Address) {
- return route;
- }
- }
-
- throw new IllegalStateException("Unable to find IPv4 default gateway");
- }
-
private void enforceNotRestrictedUser() {
final long token = Binder.clearCallingIdentity();
try {
@@ -2665,10 +2629,6 @@
throw new SecurityException("Restricted users cannot establish VPNs");
}
- final RouteInfo ipv4DefaultRoute = findIPv4DefaultRoute(egress);
- final String gateway = ipv4DefaultRoute.getGateway().getHostAddress();
- final String iface = ipv4DefaultRoute.getInterface();
-
// Load certificates.
String privateKey = "";
String userCert = "";
@@ -2700,8 +2660,6 @@
throw new IllegalStateException("Cannot load credentials");
}
- // Prepare arguments for racoon.
- String[] racoon = null;
switch (profile.type) {
case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
// Secret key is still just the alias (not the actual private key). The private key
@@ -2731,109 +2689,9 @@
// profile.
startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN);
return;
- case VpnProfile.TYPE_L2TP_IPSEC_PSK:
- racoon = new String[] {
- iface, profile.server, "udppsk", profile.ipsecIdentifier,
- profile.ipsecSecret, "1701",
- };
- break;
- case VpnProfile.TYPE_L2TP_IPSEC_RSA:
- racoon = new String[] {
- iface, profile.server, "udprsa", makeKeystoreEngineGrantString(privateKey),
- userCert, caCert, serverCert, "1701",
- };
- break;
- case VpnProfile.TYPE_IPSEC_XAUTH_PSK:
- racoon = new String[] {
- iface, profile.server, "xauthpsk", profile.ipsecIdentifier,
- profile.ipsecSecret, profile.username, profile.password, "", gateway,
- };
- break;
- case VpnProfile.TYPE_IPSEC_XAUTH_RSA:
- racoon = new String[] {
- iface, profile.server, "xauthrsa", makeKeystoreEngineGrantString(privateKey),
- userCert, caCert, serverCert, profile.username, profile.password, "", gateway,
- };
- break;
- case VpnProfile.TYPE_IPSEC_HYBRID_RSA:
- racoon = new String[] {
- iface, profile.server, "hybridrsa",
- caCert, serverCert, profile.username, profile.password, "", gateway,
- };
- break;
}
- // Prepare arguments for mtpd. MTU/MRU calculated conservatively. Only IPv4 supported
- // because LegacyVpn.
- // 1500 - 60 (Carrier-internal IPv6 + UDP + GTP) - 10 (PPP) - 16 (L2TP) - 8 (UDP)
- // - 77 (IPsec w/ SHA-2 512, 256b trunc-len, AES-CBC) - 8 (UDP encap) - 20 (IPv4)
- // - 28 (464xlat)
- String[] mtpd = null;
- switch (profile.type) {
- case VpnProfile.TYPE_PPTP:
- mtpd = new String[] {
- iface, "pptp", profile.server, "1723",
- "name", profile.username, "password", profile.password,
- "linkname", "vpn", "refuse-eap", "nodefaultroute",
- "usepeerdns", "idle", "1800", "mtu", "1270", "mru", "1270",
- (profile.mppe ? "+mppe" : "nomppe"),
- };
- if (profile.mppe) {
- // Disallow PAP authentication when MPPE is requested, as MPPE cannot work
- // with PAP anyway, and users may not expect PAP (plain text) to be used when
- // MPPE was requested.
- mtpd = Arrays.copyOf(mtpd, mtpd.length + 1);
- mtpd[mtpd.length - 1] = "-pap";
- }
- break;
- case VpnProfile.TYPE_L2TP_IPSEC_PSK:
- case VpnProfile.TYPE_L2TP_IPSEC_RSA:
- mtpd = new String[] {
- iface, "l2tp", profile.server, "1701", profile.l2tpSecret,
- "name", profile.username, "password", profile.password,
- "linkname", "vpn", "refuse-eap", "nodefaultroute",
- "usepeerdns", "idle", "1800", "mtu", "1270", "mru", "1270",
- };
- break;
- }
-
- VpnConfig config = new VpnConfig();
- config.legacy = true;
- config.user = profile.key;
- config.interfaze = iface;
- config.session = profile.name;
- config.isMetered = false;
- config.proxyInfo = profile.proxy;
- if (underlying != null) {
- config.underlyingNetworks = new Network[] { underlying };
- }
-
- config.addLegacyRoutes(profile.routes);
- if (!profile.dnsServers.isEmpty()) {
- config.dnsServers = Arrays.asList(profile.dnsServers.split(" +"));
- }
- if (!profile.searchDomains.isEmpty()) {
- config.searchDomains = Arrays.asList(profile.searchDomains.split(" +"));
- }
- startLegacyVpn(config, racoon, mtpd, profile);
- }
-
- private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd,
- VpnProfile profile) {
- stopVpnRunnerPrivileged();
-
- // Prepare for the new request.
- prepareInternal(VpnConfig.LEGACY_VPN);
- updateState(DetailedState.CONNECTING, "startLegacyVpn");
-
- // Start a new LegacyVpnRunner and we are done!
- mVpnRunner = new LegacyVpnRunner(config, racoon, mtpd, profile);
- startLegacyVpnRunner();
- }
-
- @VisibleForTesting
- protected void startLegacyVpnRunner() {
- mVpnRunner.start();
+ throw new UnsupportedOperationException("Legacy VPN is deprecated");
}
/**
@@ -2851,17 +2709,7 @@
return;
}
- final boolean isLegacyVpn = mVpnRunner instanceof LegacyVpnRunner;
mVpnRunner.exit();
-
- // LegacyVpn uses daemons that must be shut down before new ones are brought up.
- // The same limitation does not apply to Platform VPNs.
- if (isLegacyVpn) {
- synchronized (LegacyVpnRunner.TAG) {
- // wait for old thread to completely finish before spinning up
- // new instance, otherwise state updates can be out of order.
- }
- }
}
/**
@@ -4143,9 +3991,7 @@
// Ignore stale runner.
if (mVpnRunner != this) return;
- // TODO(b/230548427): Remove SDK check once VPN related stuff are
- // decoupled from ConnectivityServiceTest.
- if (SdkLevel.isAtLeastT() && category != null && isVpnApp(mPackage)) {
+ if (category != null && isVpnApp(mPackage)) {
sendEventToVpnManagerApp(category, errorClass, errorCode,
getPackage(), mSessionKey, makeVpnProfileStateLocked(),
mActiveNetwork,
@@ -4256,343 +4102,6 @@
}
}
- /**
- * Bringing up a VPN connection takes time, and that is all this thread
- * does. Here we have plenty of time. The only thing we need to take
- * care of is responding to interruptions as soon as possible. Otherwise
- * requests will pile up. This could be done in a Handler as a state
- * machine, but it is much easier to read in the current form.
- */
- private class LegacyVpnRunner extends VpnRunner {
- private static final String TAG = "LegacyVpnRunner";
-
- private final String[] mDaemons;
- private final String[][] mArguments;
- private final LocalSocket[] mSockets;
- private final String mOuterInterface;
- private final AtomicInteger mOuterConnection =
- new AtomicInteger(ConnectivityManager.TYPE_NONE);
- private final VpnProfile mProfile;
-
- private long mBringupStartTime = -1;
-
- /**
- * Watch for the outer connection (passing in the constructor) going away.
- */
- private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (!mEnableTeardown) return;
-
- if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
- if (intent.getIntExtra(ConnectivityManager.EXTRA_NETWORK_TYPE,
- ConnectivityManager.TYPE_NONE) == mOuterConnection.get()) {
- NetworkInfo info = (NetworkInfo)intent.getExtra(
- ConnectivityManager.EXTRA_NETWORK_INFO);
- if (info != null && !info.isConnectedOrConnecting()) {
- try {
- mObserver.interfaceStatusChanged(mOuterInterface, false);
- } catch (RemoteException e) {}
- }
- }
- }
- }
- };
-
- // GuardedBy("Vpn.this") (annotation can't be applied to constructor)
- LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd, VpnProfile profile) {
- super(TAG);
- if (racoon == null && mtpd == null) {
- throw new IllegalArgumentException(
- "Arguments to racoon and mtpd must not both be null");
- }
- mConfig = config;
- mDaemons = new String[] {"racoon", "mtpd"};
- // TODO: clear arguments from memory once launched
- mArguments = new String[][] {racoon, mtpd};
- mSockets = new LocalSocket[mDaemons.length];
-
- // This is the interface which VPN is running on,
- // mConfig.interfaze will change to point to OUR
- // internal interface soon. TODO - add inner/outer to mconfig
- // TODO - we have a race - if the outer iface goes away/disconnects before we hit this
- // we will leave the VPN up. We should check that it's still there/connected after
- // registering
- mOuterInterface = mConfig.interfaze;
-
- mProfile = profile;
-
- if (!TextUtils.isEmpty(mOuterInterface)) {
- for (Network network : mConnectivityManager.getAllNetworks()) {
- final LinkProperties lp = mConnectivityManager.getLinkProperties(network);
- if (lp != null && lp.getAllInterfaceNames().contains(mOuterInterface)) {
- final NetworkInfo netInfo = mConnectivityManager.getNetworkInfo(network);
- if (netInfo != null) {
- mOuterConnection.set(netInfo.getType());
- break;
- }
- }
- }
- }
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- mContext.registerReceiver(mBroadcastReceiver, filter);
- }
-
- /**
- * Checks if the parameter matches the underlying interface
- *
- * <p>If the underlying interface is torn down, the LegacyVpnRunner also should be. It has
- * no ability to migrate between interfaces (or Networks).
- */
- public void exitIfOuterInterfaceIs(String interfaze) {
- if (interfaze.equals(mOuterInterface)) {
- Log.i(TAG, "Legacy VPN is going down with " + interfaze);
- exitVpnRunner();
- }
- }
-
- /** Tears down this LegacyVpn connection */
- @Override
- public void exitVpnRunner() {
- // We assume that everything is reset after stopping the daemons.
- interrupt();
-
- // Always disconnect. This may be called again in cleanupVpnStateLocked() if
- // exitVpnRunner() was called from exit(), but it will be a no-op.
- agentDisconnect();
- try {
- mContext.unregisterReceiver(mBroadcastReceiver);
- } catch (IllegalArgumentException e) {}
- }
-
- @Override
- public void run() {
- // Wait for the previous thread since it has been interrupted.
- Log.v(TAG, "Waiting");
- synchronized (TAG) {
- Log.v(TAG, "Executing");
- try {
- bringup();
- waitForDaemonsToStop();
- interrupted(); // Clear interrupt flag if execute called exit.
- } catch (InterruptedException e) {
- } finally {
- for (LocalSocket socket : mSockets) {
- IoUtils.closeQuietly(socket);
- }
- // This sleep is necessary for racoon to successfully complete sending delete
- // message to server.
- try {
- Thread.sleep(50);
- } catch (InterruptedException e) {
- }
- for (String daemon : mDaemons) {
- mDeps.stopService(daemon);
- }
- }
- agentDisconnect();
- }
- }
-
- private void checkInterruptAndDelay(boolean sleepLonger) throws InterruptedException {
- long now = SystemClock.elapsedRealtime();
- if (now - mBringupStartTime <= 60000) {
- Thread.sleep(sleepLonger ? 200 : 1);
- } else {
- updateState(DetailedState.FAILED, "checkpoint");
- throw new IllegalStateException("VPN bringup took too long");
- }
- }
-
- private void checkAndFixupArguments(@NonNull final InetAddress endpointAddress) {
- final String endpointAddressString = endpointAddress.getHostAddress();
- // Perform some safety checks before inserting the address in place.
- // Position 0 in mDaemons and mArguments must be racoon, and position 1 must be mtpd.
- if (!"racoon".equals(mDaemons[0]) || !"mtpd".equals(mDaemons[1])) {
- throw new IllegalStateException("Unexpected daemons order");
- }
-
- // Respectively, the positions at which racoon and mtpd take the server address
- // argument are 1 and 2. Not all types of VPN require both daemons however, and
- // in that case the corresponding argument array is null.
- if (mArguments[0] != null) {
- if (!mProfile.server.equals(mArguments[0][1])) {
- throw new IllegalStateException("Invalid server argument for racoon");
- }
- mArguments[0][1] = endpointAddressString;
- }
-
- if (mArguments[1] != null) {
- if (!mProfile.server.equals(mArguments[1][2])) {
- throw new IllegalStateException("Invalid server argument for mtpd");
- }
- mArguments[1][2] = endpointAddressString;
- }
- }
-
- private void bringup() {
- // Catch all exceptions so we can clean up a few things.
- try {
- // resolve never returns null. If it does because of some bug, it will be
- // caught by the catch() block below and cleanup gracefully.
- final InetAddress endpointAddress = mDeps.resolve(mProfile.server);
-
- // Big hack : dynamically replace the address of the server in the arguments
- // with the resolved address.
- checkAndFixupArguments(endpointAddress);
-
- // Initialize the timer.
- mBringupStartTime = SystemClock.elapsedRealtime();
-
- // Wait for the daemons to stop.
- for (String daemon : mDaemons) {
- while (!mDeps.isServiceStopped(daemon)) {
- checkInterruptAndDelay(true);
- }
- }
-
- // Clear the previous state.
- final File state = mDeps.getStateFile();
- state.delete();
- if (state.exists()) {
- throw new IllegalStateException("Cannot delete the state");
- }
- new File("/data/misc/vpn/abort").delete();
-
- updateState(DetailedState.CONNECTING, "execute");
-
- // Start the daemon with arguments.
- for (int i = 0; i < mDaemons.length; ++i) {
- String[] arguments = mArguments[i];
- if (arguments == null) {
- continue;
- }
-
- // Start the daemon.
- String daemon = mDaemons[i];
- mDeps.startService(daemon);
-
- // Wait for the daemon to start.
- while (!mDeps.isServiceRunning(daemon)) {
- checkInterruptAndDelay(true);
- }
-
- // Create the control socket.
- mSockets[i] = new LocalSocket();
-
- // Wait for the socket to connect and send over the arguments.
- mDeps.sendArgumentsToDaemon(daemon, mSockets[i], arguments,
- this::checkInterruptAndDelay);
- }
-
- // Wait for the daemons to create the new state.
- while (!state.exists()) {
- // Check if a running daemon is dead.
- for (int i = 0; i < mDaemons.length; ++i) {
- String daemon = mDaemons[i];
- if (mArguments[i] != null && !mDeps.isServiceRunning(daemon)) {
- throw new IllegalStateException(daemon + " is dead");
- }
- }
- checkInterruptAndDelay(true);
- }
-
- // Now we are connected. Read and parse the new state.
- String[] parameters = FileUtils.readTextFile(state, 0, null).split("\n", -1);
- if (parameters.length != 7) {
- throw new IllegalStateException("Cannot parse the state: '"
- + String.join("', '", parameters) + "'");
- }
-
- // Set the interface and the addresses in the config.
- synchronized (Vpn.this) {
- mConfig.interfaze = parameters[0].trim();
-
- mConfig.addLegacyAddresses(parameters[1]);
- // Set the routes if they are not set in the config.
- if (mConfig.routes == null || mConfig.routes.isEmpty()) {
- mConfig.addLegacyRoutes(parameters[2]);
- }
-
- // Set the DNS servers if they are not set in the config.
- if (mConfig.dnsServers == null || mConfig.dnsServers.size() == 0) {
- String dnsServers = parameters[3].trim();
- if (!dnsServers.isEmpty()) {
- mConfig.dnsServers = Arrays.asList(dnsServers.split(" "));
- }
- }
-
- // Set the search domains if they are not set in the config.
- if (mConfig.searchDomains == null || mConfig.searchDomains.size() == 0) {
- String searchDomains = parameters[4].trim();
- if (!searchDomains.isEmpty()) {
- mConfig.searchDomains = Arrays.asList(searchDomains.split(" "));
- }
- }
-
- // Add a throw route for the VPN server endpoint, if one was specified.
- if (endpointAddress instanceof Inet4Address) {
- mConfig.routes.add(new RouteInfo(
- new IpPrefix(endpointAddress, 32), null /*gateway*/,
- null /*iface*/, RTN_THROW));
- } else if (endpointAddress instanceof Inet6Address) {
- mConfig.routes.add(new RouteInfo(
- new IpPrefix(endpointAddress, 128), null /*gateway*/,
- null /*iface*/, RTN_THROW));
- } else {
- Log.e(TAG, "Unknown IP address family for VPN endpoint: "
- + endpointAddress);
- }
-
- // Here is the last step and it must be done synchronously.
- // Set the start time
- mConfig.startTime = SystemClock.elapsedRealtime();
-
- // Check if the thread was interrupted while we were waiting on the lock.
- checkInterruptAndDelay(false);
-
- // Check if the interface is gone while we are waiting.
- if (!mDeps.isInterfacePresent(Vpn.this, mConfig.interfaze)) {
- throw new IllegalStateException(mConfig.interfaze + " is gone");
- }
-
- // Now INetworkManagementEventObserver is watching our back.
- mInterface = mConfig.interfaze;
- prepareStatusIntent();
-
- agentConnect();
-
- Log.i(TAG, "Connected!");
- }
- } catch (Exception e) {
- Log.i(TAG, "Aborting", e);
- updateState(DetailedState.FAILED, e.getMessage());
- exitVpnRunner();
- }
- }
-
- /**
- * Check all daemons every two seconds. Return when one of them is stopped.
- * The caller will move to the disconnected state when this function returns,
- * which can happen if a daemon failed or if the VPN was torn down.
- */
- private void waitForDaemonsToStop() throws InterruptedException {
- if (!mNetworkInfo.isConnected()) {
- return;
- }
- while (true) {
- Thread.sleep(2000);
- for (int i = 0; i < mDaemons.length; i++) {
- if (mArguments[i] != null && mDeps.isServiceStopped(mDaemons[i])) {
- return;
- }
- }
- }
- }
- }
-
private void verifyCallingUidAndPackage(String packageName) {
mDeps.verifyCallingUidAndPackage(mContext, packageName, mUserId);
}
@@ -4839,11 +4348,9 @@
// Build intent first because the sessionKey will be reset after performing
// VpnRunner.exit(). Also, cache mOwnerUID even if ownerUID will not be changed in
// VpnRunner.exit() to prevent design being changed in the future.
- // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
- // ConnectivityServiceTest.
final int ownerUid = mOwnerUID;
Intent intent = null;
- if (SdkLevel.isAtLeastT() && isVpnApp(mPackage)) {
+ if (isVpnApp(mPackage)) {
intent = buildVpnManagerEventIntent(
VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
-1 /* errorClass */, -1 /* errorCode*/, mPackage,
@@ -4884,12 +4391,8 @@
// The underlying network, NetworkCapabilities and LinkProperties are not
// necessary to send to VPN app since the purpose of this event is to notify
// VPN app that VPN is deactivated by the user.
- // TODO(b/230548427): Remove SDK check once VPN related stuff are decoupled from
- // ConnectivityServiceTest.
- if (SdkLevel.isAtLeastT()) {
- mEventChanges.log("[VMEvent] " + packageName + " stopped");
- sendEventToVpnManagerApp(intent, packageName);
- }
+ mEventChanges.log("[VMEvent] " + packageName + " stopped");
+ sendEventToVpnManagerApp(intent, packageName);
}
private boolean storeAppExclusionList(@NonNull String packageName,
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 1bdd402..707e990 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -179,6 +179,8 @@
import android.app.role.RoleManager;
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
+import android.companion.AssociationInfo;
+import android.companion.AssociationRequest;
import android.companion.ICompanionDeviceManager;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
@@ -546,6 +548,15 @@
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
static final long ENFORCE_NO_CLEAR_FLAG_ON_MEDIA_NOTIFICATION = 264179692L;
+ /**
+ * App calls to {@link android.app.NotificationManager#setInterruptionFilter} and
+ * {@link android.app.NotificationManager#setNotificationPolicy} manage DND through the
+ * creation and activation of an implicit {@link android.app.AutomaticZenRule}.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ static final long MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES = 308670109L;
+
private static final Duration POST_WAKE_LOCK_TIMEOUT = Duration.ofSeconds(30);
private IActivityManager mAm;
@@ -5343,6 +5354,12 @@
if (zen == -1) throw new IllegalArgumentException("Invalid filter: " + filter);
final int callingUid = Binder.getCallingUid();
final boolean isSystemOrSystemUi = isCallerIsSystemOrSystemUi();
+
+ if (android.app.Flags.modesApi() && !canManageGlobalZenPolicy(pkg, callingUid)) {
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, callingUid, zen);
+ return;
+ }
+
final long identity = Binder.clearCallingIdentity();
try {
mZenModeHelper.setManualZenMode(zen, null, pkg, "setInterruptionFilter",
@@ -5426,6 +5443,16 @@
}
}
+ private boolean canManageGlobalZenPolicy(String callingPkg, int callingUid) {
+ boolean isCompatChangeEnabled = Binder.withCleanCallingIdentity(
+ () -> CompatChanges.isChangeEnabled(MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES,
+ callingUid));
+ return !isCompatChangeEnabled
+ || isCallerIsSystemOrSystemUi()
+ || hasCompanionDevice(callingPkg, UserHandle.getUserId(callingUid),
+ AssociationRequest.DEVICE_PROFILE_WATCH);
+ }
+
private void enforcePolicyAccess(String pkg, String method) {
if (PackageManager.PERMISSION_GRANTED == getContext().checkCallingPermission(
android.Manifest.permission.MANAGE_NOTIFICATIONS)) {
@@ -5619,6 +5646,10 @@
@Override
public Policy getNotificationPolicy(String pkg) {
+ final int callingUid = Binder.getCallingUid();
+ if (android.app.Flags.modesApi() && !canManageGlobalZenPolicy(pkg, callingUid)) {
+ return mZenModeHelper.getNotificationPolicyFromImplicitZenRule(pkg);
+ }
final long identity = Binder.clearCallingIdentity();
try {
return mZenModeHelper.getNotificationPolicy();
@@ -5649,6 +5680,10 @@
enforcePolicyAccess(pkg, "setNotificationPolicy");
int callingUid = Binder.getCallingUid();
boolean isSystemOrSystemUi = isCallerIsSystemOrSystemUi();
+
+ boolean shouldApplyAsImplicitRule = android.app.Flags.modesApi()
+ && !canManageGlobalZenPolicy(pkg, callingUid);
+
final long identity = Binder.clearCallingIdentity();
try {
final ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(pkg,
@@ -5687,16 +5722,21 @@
policy = new Policy(policy.priorityCategories,
policy.priorityCallSenders, policy.priorityMessageSenders,
newVisualEffects, policy.priorityConversationSenders);
- ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion, policy);
- mZenModeHelper.setNotificationPolicy(policy, callingUid, isSystemOrSystemUi);
+
+ if (shouldApplyAsImplicitRule) {
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy);
+ } else {
+ ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion,
+ policy);
+ mZenModeHelper.setNotificationPolicy(policy, callingUid, isSystemOrSystemUi);
+ }
} catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set notification policy", e);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
-
-
@Override
public List<String> getEnabledNotificationListenerPackages() {
checkCallerIsSystem();
@@ -10556,6 +10596,12 @@
}
boolean hasCompanionDevice(ManagedServiceInfo info) {
+ return hasCompanionDevice(info.component.getPackageName(),
+ info.userid, /* withDeviceProfile= */ null);
+ }
+
+ private boolean hasCompanionDevice(String pkg, @UserIdInt int userId,
+ @Nullable @AssociationRequest.DeviceProfile String withDeviceProfile) {
if (mCompanionManager == null) {
mCompanionManager = getCompanionManager();
}
@@ -10565,17 +10611,19 @@
}
final long identity = Binder.clearCallingIdentity();
try {
- List<?> associations = mCompanionManager.getAssociations(
- info.component.getPackageName(), info.userid);
- if (!ArrayUtils.isEmpty(associations)) {
- return true;
+ List<AssociationInfo> associations = mCompanionManager.getAssociations(pkg, userId);
+ for (AssociationInfo association : associations) {
+ if (withDeviceProfile == null || withDeviceProfile.equals(
+ association.getDeviceProfile())) {
+ return true;
+ }
}
} catch (SecurityException se) {
// Not a privileged listener
} catch (RemoteException re) {
Slog.e(TAG, "Cannot reach companion device service", re);
} catch (Exception e) {
- Slog.e(TAG, "Cannot verify listener " + info, e);
+ Slog.e(TAG, "Cannot verify caller pkg=" + pkg + ", userId=" + userId, e);
} finally {
Binder.restoreCallingIdentity(identity);
}
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 8f5676b3..9106c33 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -118,7 +118,10 @@
protected boolean canSnooze(int numberToSnooze) {
synchronized (mLock) {
- if ((mSnoozedNotifications.size() + numberToSnooze) > CONCURRENT_SNOOZE_LIMIT) {
+ if ((mSnoozedNotifications.size() + numberToSnooze) > CONCURRENT_SNOOZE_LIMIT
+ || (mPersistedSnoozedNotifications.size()
+ + mPersistedSnoozedNotificationsWithContext.size() + numberToSnooze)
+ > CONCURRENT_SNOOZE_LIMIT) {
return false;
}
}
@@ -357,6 +360,9 @@
if (groupSummaryKey != null) {
NotificationRecord record = mSnoozedNotifications.remove(groupSummaryKey);
+ String trimmedKey = getTrimmedString(groupSummaryKey);
+ mPersistedSnoozedNotificationsWithContext.remove(trimmedKey);
+ mPersistedSnoozedNotifications.remove(trimmedKey);
if (record != null && !record.isCanceled) {
Runnable runnable = () -> {
diff --git a/services/core/java/com/android/server/notification/ZenAdapters.java b/services/core/java/com/android/server/notification/ZenAdapters.java
new file mode 100644
index 0000000..2a65aff
--- /dev/null
+++ b/services/core/java/com/android/server/notification/ZenAdapters.java
@@ -0,0 +1,78 @@
+/*
+ * 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.server.notification;
+
+import android.app.NotificationManager.Policy;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenPolicy;
+
+/**
+ * Converters between different Zen representations.
+ */
+class ZenAdapters {
+
+ static ZenPolicy notificationPolicyToZenPolicy(Policy policy) {
+ ZenPolicy.Builder zenPolicyBuilder = new ZenPolicy.Builder()
+ .allowAlarms(policy.allowAlarms())
+ .allowCalls(
+ policy.allowCalls()
+ ? ZenModeConfig.getZenPolicySenders(policy.allowCallsFrom())
+ : ZenPolicy.PEOPLE_TYPE_NONE)
+ .allowConversations(
+ policy.allowConversations()
+ ? notificationPolicyConversationSendersToZenPolicy(
+ policy.allowConversationsFrom())
+ : ZenPolicy.CONVERSATION_SENDERS_NONE)
+ .allowEvents(policy.allowEvents())
+ .allowMedia(policy.allowMedia())
+ .allowMessages(
+ policy.allowMessages()
+ ? ZenModeConfig.getZenPolicySenders(policy.allowMessagesFrom())
+ : ZenPolicy.PEOPLE_TYPE_NONE)
+ .allowReminders(policy.allowReminders())
+ .allowRepeatCallers(policy.allowRepeatCallers())
+ .allowSystem(policy.allowSystem());
+
+ if (policy.suppressedVisualEffects != Policy.SUPPRESSED_EFFECTS_UNSET) {
+ zenPolicyBuilder.showBadges(policy.showBadges())
+ .showFullScreenIntent(policy.showFullScreenIntents())
+ .showInAmbientDisplay(policy.showAmbient())
+ .showInNotificationList(policy.showInNotificationList())
+ .showLights(policy.showLights())
+ .showPeeking(policy.showPeeking())
+ .showStatusBarIcons(policy.showStatusBarIcons());
+ }
+
+ return zenPolicyBuilder.build();
+ }
+
+ @ZenPolicy.ConversationSenders
+ private static int notificationPolicyConversationSendersToZenPolicy(
+ int npPriorityConversationSenders) {
+ switch (npPriorityConversationSenders) {
+ case Policy.CONVERSATION_SENDERS_ANYONE:
+ return ZenPolicy.CONVERSATION_SENDERS_ANYONE;
+ case Policy.CONVERSATION_SENDERS_IMPORTANT:
+ return ZenPolicy.CONVERSATION_SENDERS_IMPORTANT;
+ case Policy.CONVERSATION_SENDERS_NONE:
+ return ZenPolicy.CONVERSATION_SENDERS_NONE;
+ case Policy.CONVERSATION_SENDERS_UNSET:
+ default:
+ return ZenPolicy.CONVERSATION_SENDERS_UNSET;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 762c1a1..c637df2 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -27,6 +27,7 @@
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
@@ -44,6 +45,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -349,11 +351,11 @@
ZenRule rule;
synchronized (mConfigLock) {
if (mConfig == null) return null;
- rule = mConfig.automaticRules.get(id);
+ rule = mConfig.automaticRules.get(id);
}
if (rule == null) return null;
if (canManageAutomaticZenRule(rule)) {
- return createAutomaticZenRule(rule);
+ return zenRuleToAutomaticZenRule(rule);
}
return null;
}
@@ -439,6 +441,167 @@
}
}
+ /**
+ * Create (or activate, or deactivate) an "implicit" {@link ZenRule} when an app that has
+ * Notification Policy Access but is not allowed to manage the global zen state
+ * calls {@link NotificationManager#setInterruptionFilter}.
+ *
+ * <p>When the {@code zenMode} is {@link Global#ZEN_MODE_OFF}, an existing implicit rule will be
+ * deactivated (if there is no implicit rule, the call will be ignored). For other modes, the
+ * rule's interruption filter will match the supplied {@code zenMode}. The policy of the last
+ * call to {@link NotificationManager#setNotificationPolicy} will be used (or, if never called,
+ * the global policy).
+ *
+ * <p>The created rule is owned by the calling package, but it has neither a
+ * {@link ConditionProviderService} nor an associated
+ * {@link AutomaticZenRule#configurationActivity}.
+ *
+ * @param zenMode one of the {@code Global#ZEN_MODE_x} values
+ */
+ void applyGlobalZenModeAsImplicitZenRule(String callingPkg, int callingUid, int zenMode) {
+ if (!android.app.Flags.modesApi()) {
+ Log.wtf(TAG, "applyGlobalZenModeAsImplicitZenRule called with flag off!");
+ return;
+ }
+ synchronized (mConfigLock) {
+ if (mConfig == null) {
+ return;
+ }
+ if (zenMode == Global.ZEN_MODE_OFF) {
+ // Deactivate implicit rule if it exists and is active; otherwise ignore.
+ ZenRule rule = mConfig.automaticRules.get(implicitRuleId(callingPkg));
+ if (rule != null) {
+ Condition deactivated = new Condition(rule.conditionId,
+ mContext.getString(R.string.zen_mode_implicit_deactivated),
+ Condition.STATE_FALSE);
+ setAutomaticZenRuleState(rule.id, deactivated,
+ callingUid, /* fromSystemOrSystemUi= */ false);
+ }
+ } else {
+ // Either create a new rule with a default ZenPolicy, or update an existing rule's
+ // filter value. In both cases, also activate (and unsnooze) it.
+ ZenModeConfig newConfig = mConfig.copy();
+ ZenRule rule = newConfig.automaticRules.get(implicitRuleId(callingPkg));
+ if (rule == null) {
+ rule = newImplicitZenRule(callingPkg);
+ newConfig.automaticRules.put(rule.id, rule);
+ }
+ rule.zenMode = zenMode;
+ rule.snoozing = false;
+ rule.condition = new Condition(rule.conditionId,
+ mContext.getString(R.string.zen_mode_implicit_activated),
+ Condition.STATE_TRUE);
+ setConfigLocked(newConfig, /* triggeringComponent= */ null,
+ "applyGlobalZenModeAsImplicitZenRule",
+ callingUid, /* fromSystemOrSystemUi= */ false);
+ }
+ }
+ }
+
+ /**
+ * Create (or update) an "implicit" {@link ZenRule} when an app that has Notification Policy
+ * Access but is not allowed to manage the global zen state calls
+ * {@link NotificationManager#setNotificationPolicy}.
+ *
+ * <p>The created rule is owned by the calling package and has the {@link ZenPolicy}
+ * corresponding to the supplied {@code policy}, but it has neither a
+ * {@link ConditionProviderService} nor an associated
+ * {@link AutomaticZenRule#configurationActivity}. Its zen mode will be set to
+ * {@link Global#ZEN_MODE_IMPORTANT_INTERRUPTIONS}.
+ */
+ void applyGlobalPolicyAsImplicitZenRule(String callingPkg, int callingUid,
+ NotificationManager.Policy policy) {
+ if (!android.app.Flags.modesApi()) {
+ Log.wtf(TAG, "applyGlobalPolicyAsImplicitZenRule called with flag off!");
+ return;
+ }
+ synchronized (mConfigLock) {
+ if (mConfig == null) {
+ return;
+ }
+ ZenModeConfig newConfig = mConfig.copy();
+ ZenRule rule = newConfig.automaticRules.get(implicitRuleId(callingPkg));
+ if (rule == null) {
+ rule = newImplicitZenRule(callingPkg);
+ rule.zenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ newConfig.automaticRules.put(rule.id, rule);
+ }
+ // TODO: b/308673679 - Keep user customization of this rule!
+ rule.zenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
+ setConfigLocked(newConfig, /* triggeringComponent= */ null,
+ "applyGlobalPolicyAsImplicitZenRule",
+ callingUid, /* fromSystemOrSystemUi= */ false);
+ }
+ }
+
+ /**
+ * Returns the {@link Policy} associated to the "implicit" {@link ZenRule} of a package that has
+ * Notification Policy Access but is not allowed to manage the global zen state.
+ *
+ * <p>If the implicit rule doesn't exist, or it doesn't specify a {@link ZenPolicy} (because the
+ * app never called {@link NotificationManager#setNotificationPolicy}) then the default policy
+ * is returned (i.e. same as {@link #getNotificationPolicy}.
+ *
+ * <p>Any unset values in the {@link ZenPolicy} will be mapped to their current defaults.
+ */
+ @Nullable
+ Policy getNotificationPolicyFromImplicitZenRule(String callingPkg) {
+ if (!android.app.Flags.modesApi()) {
+ Log.wtf(TAG, "getNotificationPolicyFromImplicitZenRule called with flag off!");
+ return getNotificationPolicy();
+ }
+ synchronized (mConfigLock) {
+ if (mConfig == null) {
+ return null;
+ }
+ ZenRule implicitRule = mConfig.automaticRules.get(implicitRuleId(callingPkg));
+ if (implicitRule != null && implicitRule.zenPolicy != null) {
+ return mConfig.toNotificationPolicy(implicitRule.zenPolicy);
+ } else {
+ return getNotificationPolicy();
+ }
+ }
+ }
+
+ /**
+ * Creates an empty {@link ZenRule} to be used as the implicit rule for {@code pkg}.
+ * Both {@link ZenRule#zenMode} and {@link ZenRule#zenPolicy} are unset.
+ */
+ private ZenRule newImplicitZenRule(String pkg) {
+ ZenRule rule = new ZenRule();
+ rule.id = implicitRuleId(pkg);
+ rule.pkg = pkg;
+ rule.creationTime = System.currentTimeMillis();
+
+ Binder.withCleanCallingIdentity(() -> {
+ try {
+ ApplicationInfo applicationInfo = mPm.getApplicationInfo(pkg, 0);
+ rule.name = applicationInfo.loadLabel(mPm).toString();
+ } catch (PackageManager.NameNotFoundException e) {
+ // Should not happen, since it's the app calling us (?)
+ Log.w(TAG, "Package not found for creating implicit zen rule");
+ rule.name = "Unknown";
+ }
+ });
+
+ rule.condition = null;
+ rule.conditionId = new Uri.Builder()
+ .scheme(Condition.SCHEME)
+ .authority("android")
+ .appendPath("implicit")
+ .appendPath(pkg)
+ .build();
+ rule.enabled = true;
+ rule.modified = false;
+ rule.component = null;
+ rule.configurationActivity = null;
+ return rule;
+ }
+
+ private static String implicitRuleId(String forPackage) {
+ return "implicit_" + forPackage;
+ }
+
public boolean removeAutomaticZenRule(String id, String reason, int callingUid,
boolean fromSystemOrSystemUi) {
ZenModeConfig newConfig;
@@ -626,7 +789,7 @@
}
// update default rule (if locale changed, name of rule will change)
currRule.name = defaultRule.name;
- updateAutomaticZenRule(defaultRule.id, createAutomaticZenRule(currRule),
+ updateAutomaticZenRule(defaultRule.id, zenRuleToAutomaticZenRule(currRule),
"locale changed", callingUid, fromSystemOrSystemUi);
}
}
@@ -669,7 +832,7 @@
return null;
}
- private void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
+ private static void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
boolean isNew) {
if (rule.enabled != automaticZenRule.isEnabled()) {
rule.snoozing = false;
@@ -699,7 +862,7 @@
}
}
- protected AutomaticZenRule createAutomaticZenRule(ZenRule rule) {
+ private static AutomaticZenRule zenRuleToAutomaticZenRule(ZenRule rule) {
AutomaticZenRule azr;
if (Flags.modesApi()) {
azr = new AutomaticZenRule.Builder(rule.name, rule.conditionId)
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 25b7ca1..dcac8c9 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -7,8 +7,6 @@
bug: "290381858"
}
-
-
flag {
name: "polite_notifications"
namespace: "systemui"
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a778415..c9703db 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4648,7 +4648,7 @@
// Expanding pip into new rotation, so create a rotation leash
// until the display is rotated.
topActivity.getOrCreateFixedRotationLeash(
- topActivity.getSyncTransaction());
+ topActivity.getPendingTransaction());
}
lastParentBeforePip.moveToFront("movePinnedActivityToOriginalTask");
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index c0bf2ce..3e23fab 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1052,12 +1052,12 @@
* @return true if we are *guaranteed* to enter-pip. This means we return false if there's
* a chance we won't thus legacy-entry (via pause+userLeaving) will return false.
*/
- private boolean checkEnterPipOnFinish(@NonNull ActivityRecord ar,
- @Nullable ActivityRecord resuming) {
+ private boolean checkEnterPipOnFinish(@NonNull ActivityRecord ar) {
if (!mCanPipOnFinish || !ar.isVisible() || ar.getTask() == null || !ar.isState(RESUMED)) {
return false;
}
+ final ActivityRecord resuming = getVisibleTransientLaunch(ar.getTaskDisplayArea());
if (ar.pictureInPictureArgs != null && ar.pictureInPictureArgs.isAutoEnterEnabled()) {
if (!ar.getTask().isVisibleRequested() || didCommitTransientLaunch()) {
// force enable pip-on-task-switch now that we've committed to actually launching
@@ -1196,9 +1196,7 @@
final boolean isScreenOff = ar.mDisplayContent == null
|| ar.mDisplayContent.getDisplayInfo().state == Display.STATE_OFF;
if ((!visibleAtTransitionEnd || isScreenOff) && !ar.isVisibleRequested()) {
- final ActivityRecord resuming = getVisibleTransientLaunch(
- ar.getTaskDisplayArea());
- final boolean commitVisibility = !checkEnterPipOnFinish(ar, resuming);
+ final boolean commitVisibility = !checkEnterPipOnFinish(ar);
// Avoid commit visibility if entering pip or else we will get a sudden
// "flash" / surface going invisible for a split second.
if (commitVisibility) {
@@ -1431,7 +1429,7 @@
if (candidateActivity.getTaskDisplayArea() != taskDisplayArea) {
continue;
}
- if (!candidateActivity.isVisible()) {
+ if (!candidateActivity.isVisibleRequested()) {
continue;
}
return candidateActivity;
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
index 4667710..b3a3650 100644
--- a/services/core/java/com/android/server/wm/WindowManagerFlags.java
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -49,5 +49,8 @@
final boolean mWallpaperOffsetAsync = Flags.wallpaperOffsetAsync();
+ final boolean mAllowsScreenSizeDecoupledFromStatusBarAndCutout =
+ Flags.allowsScreenSizeDecoupledFromStatusBarAndCutout();
+
/* End Available Flags */
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 55678c5..0a986c8 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -336,7 +336,6 @@
import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
import com.android.server.power.ShutdownThread;
import com.android.server.utils.PriorityDump;
-import com.android.window.flags.Flags;
import dalvik.annotation.optimization.NeverCompile;
@@ -1194,7 +1193,7 @@
.getBoolean(R.bool.config_skipActivityRelaunchWhenDocking);
final boolean isScreenSizeDecoupledFromStatusBarAndCutout = context.getResources()
.getBoolean(R.bool.config_decoupleStatusBarAndDisplayCutoutFromScreenSize)
- && Flags.closeToSquareConfigIncludesStatusBar();
+ && mFlags.mAllowsScreenSizeDecoupledFromStatusBarAndCutout;
if (!isScreenSizeDecoupledFromStatusBarAndCutout) {
mDecorTypes = WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars();
mConfigTypes = WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars();
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
new file mode 100644
index 0000000..72dc725
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BaseBroadcastQueueTest.java
@@ -0,0 +1,259 @@
+/*
+ * 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.server.am;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.annotation.NonNull;
+import android.app.usage.UsageStatsManagerInternal;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ResolveInfo;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.TestLooperManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.SparseArray;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.AlarmManagerInternal;
+import com.android.server.DropBoxManagerInternal;
+import com.android.server.LocalServices;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.Rule;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public abstract class BaseBroadcastQueueTest {
+
+ static final int USER_GUEST = 11;
+
+ static final String PACKAGE_ANDROID = "android";
+ static final String PACKAGE_PHONE = "com.android.phone";
+ static final String PACKAGE_RED = "com.example.red";
+ static final String PACKAGE_GREEN = "com.example.green";
+ static final String PACKAGE_BLUE = "com.example.blue";
+ static final String PACKAGE_YELLOW = "com.example.yellow";
+ static final String PACKAGE_ORANGE = "com.example.orange";
+
+ static final String PROCESS_SYSTEM = "system";
+
+ static final String CLASS_RED = "com.example.red.Red";
+ static final String CLASS_GREEN = "com.example.green.Green";
+ static final String CLASS_BLUE = "com.example.blue.Blue";
+ static final String CLASS_YELLOW = "com.example.yellow.Yellow";
+ static final String CLASS_ORANGE = "com.example.orange.Orange";
+
+ static final BroadcastProcessQueue.BroadcastPredicate BROADCAST_PREDICATE_ANY =
+ (r, i) -> true;
+
+ @Rule
+ public final ApplicationExitInfoTest.ServiceThreadRule
+ mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
+
+ @Mock
+ AppOpsService mAppOpsService;
+ @Mock
+ PackageManagerInternal mPackageManagerInt;
+ @Mock
+ UsageStatsManagerInternal mUsageStatsManagerInt;
+ @Mock
+ DropBoxManagerInternal mDropBoxManagerInt;
+ @Mock
+ AlarmManagerInternal mAlarmManagerInt;
+ @Mock
+ ProcessList mProcessList;
+
+ Context mContext;
+ ActivityManagerService mAms;
+ BroadcastConstants mConstants;
+ BroadcastSkipPolicy mSkipPolicy;
+ HandlerThread mHandlerThread;
+ TestLooperManager mLooper;
+ AtomicInteger mNextPid;
+
+ /**
+ * Map from PID to registered registered runtime receivers.
+ */
+ SparseArray<ReceiverList> mRegisteredReceivers = new SparseArray<>();
+
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ mHandlerThread = new HandlerThread(getTag());
+ mHandlerThread.start();
+ // Pause all event processing until a test chooses to resume
+ mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation()
+ .acquireLooperManager(mHandlerThread.getLooper()));
+ mNextPid = new AtomicInteger(100);
+
+ LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
+ LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+ LocalServices.removeServiceForTest(AlarmManagerInternal.class);
+ LocalServices.addService(AlarmManagerInternal.class, mAlarmManagerInt);
+ doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+ doNothing().when(mPackageManagerInt).notifyComponentUsed(any(), anyInt(), any(), any());
+ doAnswer((invocation) -> {
+ return getUidForPackage(invocation.getArgument(0));
+ }).when(mPackageManagerInt).getPackageUid(any(), anyLong(), eq(UserHandle.USER_SYSTEM));
+
+ final ActivityManagerService realAms = new ActivityManagerService(
+ new TestInjector(mContext), mServiceThreadRule.getThread());
+ realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+ realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+ realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
+ realAms.mOomAdjuster = spy(realAms.mOomAdjuster);
+ realAms.mPackageManagerInt = mPackageManagerInt;
+ realAms.mUsageStatsService = mUsageStatsManagerInt;
+ realAms.mProcessesReady = true;
+ mAms = spy(realAms);
+
+ mSkipPolicy = spy(new BroadcastSkipPolicy(mAms));
+ doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any());
+ doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any());
+
+ mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
+ }
+
+ public void tearDown() throws Exception {
+ mHandlerThread.quit();
+ }
+
+ static int getUidForPackage(@NonNull String packageName) {
+ switch (packageName) {
+ case PACKAGE_ANDROID: return android.os.Process.SYSTEM_UID;
+ case PACKAGE_PHONE: return android.os.Process.PHONE_UID;
+ case PACKAGE_RED: return android.os.Process.FIRST_APPLICATION_UID + 1;
+ case PACKAGE_GREEN: return android.os.Process.FIRST_APPLICATION_UID + 2;
+ case PACKAGE_BLUE: return android.os.Process.FIRST_APPLICATION_UID + 3;
+ case PACKAGE_YELLOW: return android.os.Process.FIRST_APPLICATION_UID + 4;
+ case PACKAGE_ORANGE: return android.os.Process.FIRST_APPLICATION_UID + 5;
+ default: throw new IllegalArgumentException();
+ }
+ }
+
+ static int getUidForPackage(@NonNull String packageName, int userId) {
+ return UserHandle.getUid(userId, getUidForPackage(packageName));
+ }
+
+ private class TestInjector extends ActivityManagerService.Injector {
+ TestInjector(Context context) {
+ super(context);
+ }
+
+ @Override
+ public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+ Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandlerThread.getThreadHandler();
+ }
+
+ @Override
+ public ProcessList getProcessList(ActivityManagerService service) {
+ return mProcessList;
+ }
+ }
+
+ abstract String getTag();
+
+ static ApplicationInfo makeApplicationInfo(String packageName) {
+ return makeApplicationInfo(packageName, packageName, UserHandle.USER_SYSTEM);
+ }
+
+ static ApplicationInfo makeApplicationInfo(String packageName, String processName, int userId) {
+ final ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = packageName;
+ ai.processName = processName;
+ ai.uid = getUidForPackage(packageName, userId);
+ return ai;
+ }
+
+ static ResolveInfo withPriority(ResolveInfo info, int priority) {
+ info.priority = priority;
+ return info;
+ }
+
+ static BroadcastFilter withPriority(BroadcastFilter filter, int priority) {
+ filter.setPriority(priority);
+ return filter;
+ }
+
+ static ResolveInfo makeManifestReceiver(String packageName, String name) {
+ return makeManifestReceiver(packageName, name, UserHandle.USER_SYSTEM);
+ }
+
+ static ResolveInfo makeManifestReceiver(String packageName, String name, int userId) {
+ return makeManifestReceiver(packageName, packageName, name, userId);
+ }
+
+ static ResolveInfo makeManifestReceiver(String packageName, String processName,
+ String name, int userId) {
+ final ResolveInfo ri = new ResolveInfo();
+ ri.activityInfo = new ActivityInfo();
+ ri.activityInfo.packageName = packageName;
+ ri.activityInfo.processName = processName;
+ ri.activityInfo.name = name;
+ ri.activityInfo.applicationInfo = makeApplicationInfo(packageName, processName, userId);
+ return ri;
+ }
+
+ BroadcastFilter makeRegisteredReceiver(ProcessRecord app) {
+ return makeRegisteredReceiver(app, 0);
+ }
+
+ BroadcastFilter makeRegisteredReceiver(ProcessRecord app, int priority) {
+ final ReceiverList receiverList = mRegisteredReceivers.get(app.getPid());
+ return makeRegisteredReceiver(receiverList, priority);
+ }
+
+ static BroadcastFilter makeRegisteredReceiver(ReceiverList receiverList, int priority) {
+ final IntentFilter filter = new IntentFilter();
+ filter.setPriority(priority);
+ final BroadcastFilter res = new BroadcastFilter(filter, receiverList,
+ receiverList.app.info.packageName, null, null, null, receiverList.uid,
+ receiverList.userId, false, false, true);
+ receiverList.add(res);
+ return res;
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 08f5d03..2378416 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -31,17 +31,6 @@
import static com.android.server.am.BroadcastProcessQueue.REASON_CONTAINS_PRIORITIZED;
import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
-import static com.android.server.am.BroadcastQueueTest.CLASS_BLUE;
-import static com.android.server.am.BroadcastQueueTest.CLASS_GREEN;
-import static com.android.server.am.BroadcastQueueTest.CLASS_RED;
-import static com.android.server.am.BroadcastQueueTest.CLASS_YELLOW;
-import static com.android.server.am.BroadcastQueueTest.PACKAGE_BLUE;
-import static com.android.server.am.BroadcastQueueTest.PACKAGE_GREEN;
-import static com.android.server.am.BroadcastQueueTest.PACKAGE_RED;
-import static com.android.server.am.BroadcastQueueTest.PACKAGE_YELLOW;
-import static com.android.server.am.BroadcastQueueTest.getUidForPackage;
-import static com.android.server.am.BroadcastQueueTest.makeManifestReceiver;
-import static com.android.server.am.BroadcastQueueTest.withPriority;
import static com.android.server.am.BroadcastRecord.isReceiverEquals;
import static com.google.common.truth.Truth.assertThat;
@@ -74,17 +63,15 @@
import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.BundleMerger;
import android.os.DropBoxManager;
-import android.os.HandlerThread;
import android.os.Process;
import android.os.SystemClock;
-import android.os.TestLooperManager;
import android.os.UserHandle;
-import android.provider.Settings;
import android.util.IndentingPrintWriter;
import android.util.Pair;
@@ -108,11 +95,12 @@
import java.util.Objects;
@SmallTest
-public final class BroadcastQueueModernImplTest {
+public final class BroadcastQueueModernImplTest extends BaseBroadcastQueueTest {
+ private static final String TAG = "BroadcastQueueModernImplTest";
+
private static final int TEST_UID = android.os.Process.FIRST_APPLICATION_UID;
private static final int TEST_UID2 = android.os.Process.FIRST_APPLICATION_UID + 1;
- @Mock ActivityManagerService mAms;
@Mock ProcessRecord mProcess;
@Mock BroadcastProcessQueue mQueue1;
@@ -120,11 +108,6 @@
@Mock BroadcastProcessQueue mQueue3;
@Mock BroadcastProcessQueue mQueue4;
- HandlerThread mHandlerThread;
- TestLooperManager mLooper;
-
- BroadcastConstants mConstants;
- private BroadcastSkipPolicy mSkipPolicy;
BroadcastQueueModernImpl mImpl;
BroadcastProcessQueue mHead;
@@ -136,22 +119,12 @@
@Before
public void setUp() throws Exception {
- mHandlerThread = new HandlerThread(getClass().getSimpleName());
- mHandlerThread.start();
+ super.setUp();
- // Pause all event processing until a test chooses to resume
- mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation()
- .acquireLooperManager(mHandlerThread.getLooper()));
-
- mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
mConstants.DELAY_URGENT_MILLIS = -120_000;
mConstants.DELAY_NORMAL_MILLIS = 10_000;
mConstants.DELAY_CACHED_MILLIS = 120_000;
- mSkipPolicy = spy(new BroadcastSkipPolicy(mAms));
- doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any());
- doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any());
-
final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) {
public void addBroadcastToHistoryLocked(BroadcastRecord original) {
// Ignored
@@ -169,7 +142,12 @@
@After
public void tearDown() throws Exception {
- mHandlerThread.quit();
+ super.tearDown();
+ }
+
+ @Override
+ public String getTag() {
+ return TAG;
}
/**
@@ -225,11 +203,6 @@
List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), false);
}
- private BroadcastRecord makeOrderedBroadcastRecord(Intent intent) {
- return makeBroadcastRecord(intent, BroadcastOptions.makeBasic(),
- List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), true);
- }
-
private BroadcastRecord makeBroadcastRecord(Intent intent, List receivers) {
return makeBroadcastRecord(intent, BroadcastOptions.makeBasic(), receivers, false);
}
@@ -246,8 +219,8 @@
private BroadcastRecord makeBroadcastRecord(Intent intent, BroadcastOptions options,
List receivers, IIntentReceiver resultTo, boolean ordered) {
- return new BroadcastRecord(mImpl, intent, mProcess, PACKAGE_RED, null, 21, 42, false, null,
- null, null, null, AppOpsManager.OP_NONE, options, receivers, null, resultTo,
+ return new BroadcastRecord(mImpl, intent, mProcess, PACKAGE_RED, null, 21, TEST_UID, false,
+ null, null, null, null, AppOpsManager.OP_NONE, options, receivers, null, resultTo,
Activity.RESULT_OK, null, null, ordered, false, false, UserHandle.USER_SYSTEM,
BackgroundStartPrivileges.NONE, false, null, PROCESS_STATE_UNKNOWN);
}
@@ -259,12 +232,12 @@
private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
BroadcastRecord record, int recordIndex, long enqueueTime) {
- queue.enqueueOrReplaceBroadcast(record, recordIndex, (r, i) -> {
- throw new UnsupportedOperationException();
- });
record.enqueueTime = enqueueTime;
record.enqueueRealTime = enqueueTime;
record.enqueueClockTime = enqueueTime;
+ queue.enqueueOrReplaceBroadcast(record, recordIndex, (r, i) -> {
+ throw new UnsupportedOperationException();
+ });
}
@Test
@@ -419,6 +392,7 @@
assertFalse(queue.isRunnable());
assertEquals(BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER,
queue.getRunnableAtReason());
+ assertTrue(queue.shouldBeDeferred());
assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
}
@@ -445,6 +419,7 @@
assertThat(cachedRunnableAt).isGreaterThan(notCachedRunnableAt);
assertTrue(queue.isRunnable());
assertEquals(BroadcastProcessQueue.REASON_CACHED, queue.getRunnableAtReason());
+ assertTrue(queue.shouldBeDeferred());
assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked());
}
@@ -526,11 +501,13 @@
queue.invalidateRunnableAt();
assertThat(queue.getRunnableAt()).isGreaterThan(airplaneRecord.enqueueTime);
assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason());
+ assertFalse(queue.shouldBeDeferred());
mConstants.MAX_PENDING_BROADCASTS = 1;
queue.invalidateRunnableAt();
assertThat(queue.getRunnableAt()).isAtMost(airplaneRecord.enqueueTime);
assertEquals(BroadcastProcessQueue.REASON_MAX_PENDING, queue.getRunnableAtReason());
+ assertFalse(queue.shouldBeDeferred());
}
@Test
@@ -549,10 +526,12 @@
queue.setProcessAndUidState(mProcess, true, false);
assertThat(queue.getRunnableAt()).isLessThan(timeTickRecord.enqueueTime);
assertEquals(BroadcastProcessQueue.REASON_FOREGROUND, queue.getRunnableAtReason());
+ assertFalse(queue.shouldBeDeferred());
queue.setProcessAndUidState(mProcess, false, false);
assertThat(queue.getRunnableAt()).isGreaterThan(timeTickRecord.enqueueTime);
assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason());
+ assertFalse(queue.shouldBeDeferred());
}
@Test
@@ -570,6 +549,7 @@
assertThat(queue.getRunnableAt()).isLessThan(timeTickRecord.enqueueTime);
assertEquals(BroadcastProcessQueue.REASON_TOP_PROCESS, queue.getRunnableAtReason());
+ assertFalse(queue.shouldBeDeferred());
doReturn(ActivityManager.PROCESS_STATE_SERVICE).when(mProcess).getSetProcState();
queue.setProcessAndUidState(mProcess, false, false);
@@ -580,6 +560,7 @@
List.of(makeMockRegisteredReceiver())), 0);
assertThat(queue.getRunnableAt()).isGreaterThan(timeTickRecord.enqueueTime);
assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason());
+ assertFalse(queue.shouldBeDeferred());
}
@Test
@@ -594,16 +575,19 @@
assertThat(queue.getRunnableAt()).isGreaterThan(timeTickRecord.enqueueTime);
assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason());
+ assertFalse(queue.shouldBeDeferred());
doReturn(true).when(mProcess).isPersistent();
queue.setProcessAndUidState(mProcess, false, false);
assertThat(queue.getRunnableAt()).isLessThan(timeTickRecord.enqueueTime);
assertEquals(BroadcastProcessQueue.REASON_PERSISTENT, queue.getRunnableAtReason());
+ assertFalse(queue.shouldBeDeferred());
doReturn(false).when(mProcess).isPersistent();
queue.setProcessAndUidState(mProcess, false, false);
assertThat(queue.getRunnableAt()).isGreaterThan(timeTickRecord.enqueueTime);
assertEquals(BroadcastProcessQueue.REASON_NORMAL, queue.getRunnableAtReason());
+ assertFalse(queue.shouldBeDeferred());
}
@Test
@@ -618,6 +602,7 @@
assertThat(queue.getRunnableAt()).isEqualTo(timeTickRecord.enqueueTime);
assertEquals(BroadcastProcessQueue.REASON_CORE_UID, queue.getRunnableAtReason());
+ assertFalse(queue.shouldBeDeferred());
}
@Test
@@ -636,10 +621,12 @@
assertEquals(Long.MAX_VALUE, queue.getRunnableAt());
assertEquals(BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER,
queue.getRunnableAtReason());
+ assertTrue(queue.shouldBeDeferred());
queue.setProcessAndUidState(mProcess, false, false);
assertThat(queue.getRunnableAt()).isEqualTo(timeTickRecord.enqueueTime);
assertEquals(BroadcastProcessQueue.REASON_CORE_UID, queue.getRunnableAtReason());
+ assertFalse(queue.shouldBeDeferred());
}
/**
@@ -1575,6 +1562,216 @@
verifyPendingRecords(redQueue, List.of(userPresent, timeTick));
}
+ @Test
+ public void testDeliveryDeferredForCached() throws Exception {
+ final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
+ final ProcessRecord redProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_RED));
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+ final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick,
+ List.of(makeRegisteredReceiver(greenProcess, 0)));
+
+ final Intent batteryChanged = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ final BroadcastOptions optionsBatteryChanged =
+ BroadcastOptions.makeWithDeferUntilActive(true);
+ final BroadcastRecord batteryChangedRecord = makeBroadcastRecord(batteryChanged,
+ optionsBatteryChanged,
+ List.of(makeRegisteredReceiver(greenProcess, 10),
+ makeRegisteredReceiver(redProcess, 0)),
+ false /* ordered */);
+
+ mImpl.enqueueBroadcastLocked(timeTickRecord);
+ mImpl.enqueueBroadcastLocked(batteryChangedRecord);
+
+ final BroadcastProcessQueue greenQueue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ final BroadcastProcessQueue redQueue = mImpl.getProcessQueue(PACKAGE_RED,
+ getUidForPackage(PACKAGE_RED));
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, greenQueue.getRunnableAtReason());
+ assertFalse(greenQueue.shouldBeDeferred());
+ assertEquals(BroadcastProcessQueue.REASON_BLOCKED, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ // Simulate process state change
+ greenQueue.setProcessAndUidState(greenProcess, false /* uidForeground */,
+ true /* processFreezable */);
+ greenQueue.updateDeferredStates(mImpl.mBroadcastConsumerDeferApply,
+ mImpl.mBroadcastConsumerDeferClear);
+
+ assertEquals(BroadcastProcessQueue.REASON_CACHED, greenQueue.getRunnableAtReason());
+ assertTrue(greenQueue.shouldBeDeferred());
+ // Once the broadcasts to green process are deferred, broadcasts to red process
+ // shouldn't be blocked anymore.
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ // All broadcasts to green process should be deferred.
+ greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_DEFERRED, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+
+ final Intent packageChanged = new Intent(Intent.ACTION_PACKAGE_CHANGED);
+ final BroadcastRecord packageChangedRecord = makeBroadcastRecord(packageChanged,
+ List.of(makeRegisteredReceiver(greenProcess, 0)));
+ mImpl.enqueueBroadcastLocked(packageChangedRecord);
+
+ assertEquals(BroadcastProcessQueue.REASON_CACHED, greenQueue.getRunnableAtReason());
+ assertTrue(greenQueue.shouldBeDeferred());
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ // All broadcasts to the green process, including the newly enqueued one, should be
+ // deferred.
+ greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_DEFERRED, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+
+ // Simulate process state change
+ greenQueue.setProcessAndUidState(greenProcess, false /* uidForeground */,
+ false /* processFreezable */);
+ greenQueue.updateDeferredStates(mImpl.mBroadcastConsumerDeferApply,
+ mImpl.mBroadcastConsumerDeferClear);
+
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, greenQueue.getRunnableAtReason());
+ assertFalse(greenQueue.shouldBeDeferred());
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ }
+
+ @Test
+ public void testDeliveryDeferredForCached_withInfiniteDeferred() throws Exception {
+ final ProcessRecord greenProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_GREEN));
+ final ProcessRecord redProcess = makeProcessRecord(makeApplicationInfo(PACKAGE_RED));
+
+ final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK);
+ final BroadcastOptions optionsTimeTick = BroadcastOptions.makeWithDeferUntilActive(true);
+ final BroadcastRecord timeTickRecord = makeBroadcastRecord(timeTick, optionsTimeTick,
+ List.of(makeRegisteredReceiver(greenProcess, 0)), false /* ordered */);
+
+ final Intent batteryChanged = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ final BroadcastOptions optionsBatteryChanged =
+ BroadcastOptions.makeWithDeferUntilActive(true);
+ final BroadcastRecord batteryChangedRecord = makeBroadcastRecord(batteryChanged,
+ optionsBatteryChanged,
+ List.of(makeRegisteredReceiver(greenProcess, 10),
+ makeRegisteredReceiver(redProcess, 0)),
+ false /* ordered */);
+
+ mImpl.enqueueBroadcastLocked(timeTickRecord);
+ mImpl.enqueueBroadcastLocked(batteryChangedRecord);
+
+ final BroadcastProcessQueue greenQueue = mImpl.getProcessQueue(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ final BroadcastProcessQueue redQueue = mImpl.getProcessQueue(PACKAGE_RED,
+ getUidForPackage(PACKAGE_RED));
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, greenQueue.getRunnableAtReason());
+ assertFalse(greenQueue.shouldBeDeferred());
+ assertEquals(BroadcastProcessQueue.REASON_BLOCKED, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ // Simulate process state change
+ greenQueue.setProcessAndUidState(greenProcess, false /* uidForeground */,
+ true /* processFreezable */);
+ greenQueue.updateDeferredStates(mImpl.mBroadcastConsumerDeferApply,
+ mImpl.mBroadcastConsumerDeferClear);
+
+ assertEquals(BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER,
+ greenQueue.getRunnableAtReason());
+ assertTrue(greenQueue.shouldBeDeferred());
+ // Once the broadcasts to green process are deferred, broadcasts to red process
+ // shouldn't be blocked anymore.
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ // All broadcasts to green process should be deferred.
+ greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_DEFERRED, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+
+ final Intent packageChanged = new Intent(Intent.ACTION_PACKAGE_CHANGED);
+ final BroadcastOptions optionsPackageChanged =
+ BroadcastOptions.makeWithDeferUntilActive(true);
+ final BroadcastRecord packageChangedRecord = makeBroadcastRecord(packageChanged,
+ optionsPackageChanged,
+ List.of(makeRegisteredReceiver(greenProcess, 0)), false /* ordered */);
+ mImpl.enqueueBroadcastLocked(packageChangedRecord);
+
+ assertEquals(BroadcastProcessQueue.REASON_CACHED_INFINITE_DEFER,
+ greenQueue.getRunnableAtReason());
+ assertTrue(greenQueue.shouldBeDeferred());
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ // All broadcasts to the green process, including the newly enqueued one, should be
+ // deferred.
+ greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_DEFERRED, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+
+ // Simulate process state change
+ greenQueue.setProcessAndUidState(greenProcess, false /* uidForeground */,
+ false /* processFreezable */);
+ greenQueue.updateDeferredStates(mImpl.mBroadcastConsumerDeferApply,
+ mImpl.mBroadcastConsumerDeferClear);
+
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, greenQueue.getRunnableAtReason());
+ assertFalse(greenQueue.shouldBeDeferred());
+ assertEquals(BroadcastProcessQueue.REASON_NORMAL, redQueue.getRunnableAtReason());
+ assertFalse(redQueue.shouldBeDeferred());
+
+ greenQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ redQueue.forEachMatchingBroadcast(BROADCAST_PREDICATE_ANY, (r, i) -> {
+ assertEquals("Unexpected state for " + r,
+ BroadcastRecord.DELIVERY_PENDING, r.getDeliveryState(i));
+ }, false /* andRemove */);
+ }
+
+ // TODO: Reuse BroadcastQueueTest.makeActiveProcessRecord()
+ private ProcessRecord makeProcessRecord(ApplicationInfo info) {
+ final ProcessRecord r = spy(new ProcessRecord(mAms, info, info.processName, info.uid));
+ r.setPid(mNextPid.incrementAndGet());
+ return r;
+ }
+
+ BroadcastFilter makeRegisteredReceiver(ProcessRecord app, int priority) {
+ final IIntentReceiver receiver = mock(IIntentReceiver.class);
+ final ReceiverList receiverList = new ReceiverList(mAms, app, app.getPid(), app.info.uid,
+ UserHandle.getUserId(app.info.uid), receiver);
+ return makeRegisteredReceiver(receiverList, priority);
+ }
+
private Intent createPackageChangedIntent(int uid, List<String> componentNameList) {
final Intent packageChangedIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
packageChangedIntent.putExtra(Intent.EXTRA_UID, uid);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 1c8e949..3364545 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -62,58 +62,36 @@
import android.app.IApplicationThread;
import android.app.UidObserver;
import android.app.usage.UsageEvents.Event;
-import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
-import android.content.Context;
import android.content.IIntentReceiver;
import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.DeadObjectException;
-import android.os.Handler;
-import android.os.HandlerThread;
import android.os.IBinder;
import android.os.PowerExemptionManager;
import android.os.SystemClock;
-import android.os.TestLooperManager;
import android.os.UserHandle;
-import android.provider.Settings;
import android.util.Log;
import android.util.Pair;
-import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.server.AlarmManagerInternal;
-import com.android.server.DropBoxManagerInternal;
-import com.android.server.LocalServices;
-import com.android.server.am.ActivityManagerService.Injector;
-import com.android.server.appop.AppOpsService;
-import com.android.server.wm.ActivityTaskManagerService;
-
import org.junit.After;
import org.junit.Assume;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
import org.mockito.verification.VerificationMode;
-import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.Writer;
@@ -126,7 +104,6 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.UnaryOperator;
@@ -136,13 +113,9 @@
@MediumTest
@RunWith(Parameterized.class)
@SuppressWarnings("GuardedBy")
-public class BroadcastQueueTest {
+public class BroadcastQueueTest extends BaseBroadcastQueueTest {
private static final String TAG = "BroadcastQueueTest";
- @Rule
- public final ApplicationExitInfoTest.ServiceThreadRule
- mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
-
private final Impl mImpl;
private enum Impl {
@@ -150,30 +123,8 @@
MODERN,
}
- private Context mContext;
- private HandlerThread mHandlerThread;
- private TestLooperManager mLooper;
- private AtomicInteger mNextPid;
-
- @Mock
- private AppOpsService mAppOpsService;
- @Mock
- private ProcessList mProcessList;
- @Mock
- private DropBoxManagerInternal mDropBoxManagerInt;
- @Mock
- private PackageManagerInternal mPackageManagerInt;
- @Mock
- private UsageStatsManagerInternal mUsageStatsManagerInt;
- @Mock
- private AlarmManagerInternal mAlarmManagerInt;
-
- private ActivityManagerService mAms;
private BroadcastQueue mQueue;
- BroadcastConstants mConstants;
- private BroadcastSkipPolicy mSkipPolicy;
private UidObserver mUidObserver;
- private UidObserver mUidCachedStateObserver;
/**
* Desired behavior of the next
@@ -183,11 +134,6 @@
ProcessStartBehavior.SUCCESS);
/**
- * Map from PID to registered registered runtime receivers.
- */
- private SparseArray<ReceiverList> mRegisteredReceivers = new SparseArray<>();
-
- /**
* Collection of all active processes during current test run.
*/
private List<ProcessRecord> mActiveProcesses = new ArrayList<>();
@@ -208,41 +154,8 @@
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
+ super.setUp();
- mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
-
- mHandlerThread = new HandlerThread(TAG);
- mHandlerThread.start();
-
- // Pause all event processing until a test chooses to resume
- mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation()
- .acquireLooperManager(mHandlerThread.getLooper()));
-
- mNextPid = new AtomicInteger(100);
-
- LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
- LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
- LocalServices.removeServiceForTest(PackageManagerInternal.class);
- LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
- LocalServices.removeServiceForTest(AlarmManagerInternal.class);
- LocalServices.addService(AlarmManagerInternal.class, mAlarmManagerInt);
- doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
- doNothing().when(mPackageManagerInt).notifyComponentUsed(any(), anyInt(), any(), any());
- doAnswer((invocation) -> {
- return getUidForPackage(invocation.getArgument(0));
- }).when(mPackageManagerInt).getPackageUid(any(), anyLong(), eq(UserHandle.USER_SYSTEM));
-
- final ActivityManagerService realAms = new ActivityManagerService(
- new TestInjector(mContext), mServiceThreadRule.getThread());
- realAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
- realAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
- realAms.mAtmInternal = spy(realAms.mActivityTaskManager.getAtmInternal());
- realAms.mOomAdjuster = spy(realAms.mOomAdjuster);
- realAms.mPackageManagerInt = mPackageManagerInt;
- realAms.mUsageStatsService = mUsageStatsManagerInt;
- realAms.mProcessesReady = true;
- mAms = spy(realAms);
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting startProcessLocked() for "
+ Arrays.toString(invocation.getArguments()));
@@ -321,21 +234,11 @@
return null;
}).when(mAms).registerUidObserver(any(), anyInt(),
eq(ActivityManager.PROCESS_STATE_TOP), any());
- doAnswer((invocation) -> {
- mUidCachedStateObserver = invocation.getArgument(0);
- return null;
- }).when(mAms).registerUidObserver(any(), anyInt(),
- eq(ActivityManager.PROCESS_STATE_LAST_ACTIVITY), any());
- mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS);
mConstants.TIMEOUT = 200;
mConstants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS = 500;
- mSkipPolicy = spy(new BroadcastSkipPolicy(mAms));
- doReturn(null).when(mSkipPolicy).shouldSkipMessage(any(), any());
- doReturn(false).when(mSkipPolicy).disallowBackgroundStart(any());
-
final BroadcastHistory emptyHistory = new BroadcastHistory(mConstants) {
public void addBroadcastToHistoryLocked(BroadcastRecord original) {
// Ignored
@@ -358,7 +261,7 @@
@After
public void tearDown() throws Exception {
- mHandlerThread.quit();
+ super.tearDown();
// Verify that all processes have finished handling broadcasts
for (ProcessRecord app : mActiveProcesses) {
@@ -369,26 +272,9 @@
}
}
- private class TestInjector extends Injector {
- TestInjector(Context context) {
- super(context);
- }
-
- @Override
- public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
- Handler handler) {
- return mAppOpsService;
- }
-
- @Override
- public Handler getUiHandler(ActivityManagerService service) {
- return mHandlerThread.getThreadHandler();
- }
-
- @Override
- public ProcessList getProcessList(ActivityManagerService service) {
- return mProcessList;
- }
+ @Override
+ public String getTag() {
+ return TAG;
}
private enum ProcessStartBehavior {
@@ -534,62 +420,6 @@
return Pair.create(app.getPid(), intent.getAction());
}
- static ApplicationInfo makeApplicationInfo(String packageName) {
- return makeApplicationInfo(packageName, packageName, UserHandle.USER_SYSTEM);
- }
-
- static ApplicationInfo makeApplicationInfo(String packageName, String processName, int userId) {
- final ApplicationInfo ai = new ApplicationInfo();
- ai.packageName = packageName;
- ai.processName = processName;
- ai.uid = getUidForPackage(packageName, userId);
- return ai;
- }
-
- static ResolveInfo withPriority(ResolveInfo info, int priority) {
- info.priority = priority;
- return info;
- }
-
- static BroadcastFilter withPriority(BroadcastFilter filter, int priority) {
- filter.setPriority(priority);
- return filter;
- }
-
- static ResolveInfo makeManifestReceiver(String packageName, String name) {
- return makeManifestReceiver(packageName, name, UserHandle.USER_SYSTEM);
- }
-
- static ResolveInfo makeManifestReceiver(String packageName, String name, int userId) {
- return makeManifestReceiver(packageName, packageName, name, userId);
- }
-
- static ResolveInfo makeManifestReceiver(String packageName, String processName, String name,
- int userId) {
- final ResolveInfo ri = new ResolveInfo();
- ri.activityInfo = new ActivityInfo();
- ri.activityInfo.packageName = packageName;
- ri.activityInfo.processName = processName;
- ri.activityInfo.name = name;
- ri.activityInfo.applicationInfo = makeApplicationInfo(packageName, processName, userId);
- return ri;
- }
-
- private BroadcastFilter makeRegisteredReceiver(ProcessRecord app) {
- return makeRegisteredReceiver(app, 0);
- }
-
- private BroadcastFilter makeRegisteredReceiver(ProcessRecord app, int priority) {
- final ReceiverList receiverList = mRegisteredReceivers.get(app.getPid());
- final IntentFilter filter = new IntentFilter();
- filter.setPriority(priority);
- final BroadcastFilter res = new BroadcastFilter(filter, receiverList,
- receiverList.app.info.packageName, null, null, null, receiverList.uid,
- receiverList.userId, false, false, true);
- receiverList.add(res);
- return res;
- }
-
private BroadcastRecord makeBroadcastRecord(Intent intent, ProcessRecord callerApp,
List<Object> receivers) {
return makeBroadcastRecord(intent, callerApp, BroadcastOptions.makeBasic(),
@@ -776,41 +606,6 @@
eq(userId), anyInt(), anyInt(), any());
}
- static final int USER_GUEST = 11;
-
- static final String PACKAGE_ANDROID = "android";
- static final String PACKAGE_PHONE = "com.android.phone";
- static final String PACKAGE_RED = "com.example.red";
- static final String PACKAGE_GREEN = "com.example.green";
- static final String PACKAGE_BLUE = "com.example.blue";
- static final String PACKAGE_YELLOW = "com.example.yellow";
- static final String PACKAGE_ORANGE = "com.example.orange";
-
- static final String PROCESS_SYSTEM = "system";
-
- static final String CLASS_RED = "com.example.red.Red";
- static final String CLASS_GREEN = "com.example.green.Green";
- static final String CLASS_BLUE = "com.example.blue.Blue";
- static final String CLASS_YELLOW = "com.example.yellow.Yellow";
- static final String CLASS_ORANGE = "com.example.orange.Orange";
-
- static int getUidForPackage(@NonNull String packageName) {
- switch (packageName) {
- case PACKAGE_ANDROID: return android.os.Process.SYSTEM_UID;
- case PACKAGE_PHONE: return android.os.Process.PHONE_UID;
- case PACKAGE_RED: return android.os.Process.FIRST_APPLICATION_UID + 1;
- case PACKAGE_GREEN: return android.os.Process.FIRST_APPLICATION_UID + 2;
- case PACKAGE_BLUE: return android.os.Process.FIRST_APPLICATION_UID + 3;
- case PACKAGE_YELLOW: return android.os.Process.FIRST_APPLICATION_UID + 4;
- case PACKAGE_ORANGE: return android.os.Process.FIRST_APPLICATION_UID + 5;
- default: throw new IllegalArgumentException();
- }
- }
-
- static int getUidForPackage(@NonNull String packageName, int userId) {
- return UserHandle.getUid(userId, getUidForPackage(packageName));
- }
-
/**
* Baseline verification of common debugging infrastructure, mostly to make
* sure it doesn't crash.
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 6792cfe..3803244 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -43,6 +43,7 @@
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MAX;
import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
@@ -72,6 +73,7 @@
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
+import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
@@ -163,6 +165,7 @@
import android.app.admin.DevicePolicyManagerInternal;
import android.app.usage.UsageStatsManagerInternal;
import android.companion.AssociationInfo;
+import android.companion.AssociationRequest;
import android.companion.ICompanionDeviceManager;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.BroadcastReceiver;
@@ -3820,6 +3823,7 @@
when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(singletonList(mock(AssociationInfo.class)));
mListener = mock(ManagedServices.ManagedServiceInfo.class);
+ mListener.component = new ComponentName(PKG, PKG);
when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
@@ -3870,6 +3874,7 @@
when(mCompanionMgr.getAssociations(PKG, mUserId))
.thenReturn(emptyList());
mListener = mock(ManagedServices.ManagedServiceInfo.class);
+ mListener.component = new ComponentName(PKG, PKG);
when(mListener.enabledAndUserMatches(anyInt())).thenReturn(false);
when(mListeners.checkServiceTokenLocked(any())).thenReturn(mListener);
try {
@@ -12777,6 +12782,145 @@
verify(mSnoozeHelper).clearData(anyInt());
}
+ @Test
+ @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+ public void setNotificationPolicy_mappedToImplicitRule() throws RemoteException {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mService.setCallerIsNormalPackage();
+ ZenModeHelper zenHelper = mock(ZenModeHelper.class);
+ mService.mZenModeHelper = zenHelper;
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+
+ NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
+ mBinderService.setNotificationPolicy("package", policy);
+
+ verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy));
+ }
+
+ @Test
+ @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+ public void setNotificationPolicy_systemCaller_setsGlobalPolicy() throws RemoteException {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
+ mService.mZenModeHelper = zenModeHelper;
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+ mService.isSystemUid = true;
+
+ NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
+ mBinderService.setNotificationPolicy("package", policy);
+
+ verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean());
+ }
+
+ @Test
+ @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+ public void setNotificationPolicy_watchCompanionApp_setsGlobalPolicy() throws RemoteException {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mService.setCallerIsNormalPackage();
+ ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
+ mService.mZenModeHelper = zenModeHelper;
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+ when(mCompanionMgr.getAssociations(anyString(), anyInt()))
+ .thenReturn(ImmutableList.of(
+ new AssociationInfo.Builder(1, mUserId, "package")
+ .setDisplayName("My watch")
+ .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WATCH)
+ .build()));
+
+ NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
+ mBinderService.setNotificationPolicy("package", policy);
+
+ verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean());
+ }
+
+ @Test
+ @DisableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+ public void setNotificationPolicy_withoutCompat_setsGlobalPolicy() throws RemoteException {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mService.setCallerIsNormalPackage();
+ ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
+ mService.mZenModeHelper = zenModeHelper;
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+
+ NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
+ mBinderService.setNotificationPolicy("package", policy);
+
+ verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean());
+ }
+
+ @Test
+ @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+ public void getNotificationPolicy_mappedFromImplicitRule() throws RemoteException {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mService.setCallerIsNormalPackage();
+ ZenModeHelper zenHelper = mock(ZenModeHelper.class);
+ mService.mZenModeHelper = zenHelper;
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+
+ mBinderService.getNotificationPolicy("package");
+
+ verify(zenHelper).getNotificationPolicyFromImplicitZenRule(eq("package"));
+ }
+
+ @Test
+ @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+ public void setInterruptionFilter_mappedToImplicitRule() throws RemoteException {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mService.setCallerIsNormalPackage();
+ ZenModeHelper zenHelper = mock(ZenModeHelper.class);
+ mService.mZenModeHelper = zenHelper;
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+
+ mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY);
+
+ verify(zenHelper).applyGlobalZenModeAsImplicitZenRule(eq("package"), anyInt(),
+ eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS));
+ }
+
+ @Test
+ @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+ public void setInterruptionFilter_systemCaller_setsGlobalPolicy() throws RemoteException {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mService.setCallerIsNormalPackage();
+ ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
+ mService.mZenModeHelper = zenModeHelper;
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+ mService.isSystemUid = true;
+
+ mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY);
+
+ verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
+ eq("package"), anyString(), anyInt(), anyBoolean());
+ }
+
+ @Test
+ @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+ public void setInterruptionFilter_watchCompanionApp_setsGlobalPolicy() throws RemoteException {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
+ mService.mZenModeHelper = zenModeHelper;
+ when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+ .thenReturn(true);
+ when(mCompanionMgr.getAssociations(anyString(), anyInt()))
+ .thenReturn(ImmutableList.of(
+ new AssociationInfo.Builder(1, mUserId, "package")
+ .setDisplayName("My watch")
+ .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WATCH)
+ .build()));
+
+ mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY);
+
+ verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
+ eq("package"), anyString(), anyInt(), anyBoolean());
+ }
+
private NotificationRecord createAndPostNotification(Notification.Builder nb, String testName)
throws RemoteException {
StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, testName, mUid, 0,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index 47f15b8..1e3b728 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -19,6 +19,7 @@
import static com.android.server.notification.SnoozeHelper.EXTRA_KEY;
import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -73,6 +74,14 @@
public class SnoozeHelperTest extends UiServiceTestCase {
private static final String TEST_CHANNEL_ID = "test_channel_id";
+ private static final String XML_TAG_NAME = "snoozed-notifications";
+ private static final String XML_SNOOZED_NOTIFICATION = "notification";
+ private static final String XML_SNOOZED_NOTIFICATION_CONTEXT = "context";
+ private static final String XML_SNOOZED_NOTIFICATION_KEY = "key";
+ private static final String XML_SNOOZED_NOTIFICATION_TIME = "time";
+ private static final String XML_SNOOZED_NOTIFICATION_CONTEXT_ID = "id";
+ private static final String XML_SNOOZED_NOTIFICATION_VERSION_LABEL = "version";
+
@Mock SnoozeHelper.Callback mCallback;
@Mock AlarmManager mAm;
@Mock ManagedServices.UserProfiles mUserProfiles;
@@ -316,6 +325,53 @@
}
@Test
+ public void testSnoozeLimit_maximumPersisted() throws XmlPullParserException, IOException {
+ final long snoozeTimeout = 1234;
+ final String snoozeContext = "ctx";
+ // Serialize & deserialize notifications so that only persisted lists are used
+ TypedXmlSerializer serializer = Xml.newFastSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ serializer.startDocument(null, true);
+ serializer.startTag(null, XML_TAG_NAME);
+ // Serialize maximum number of timed + context snoozed notifications, half of each
+ for (int i = 0; i < CONCURRENT_SNOOZE_LIMIT; i++) {
+ final boolean timedNotification = i % 2 == 0;
+ if (timedNotification) {
+ serializer.startTag(null, XML_SNOOZED_NOTIFICATION);
+ } else {
+ serializer.startTag(null, XML_SNOOZED_NOTIFICATION_CONTEXT);
+ }
+ serializer.attributeInt(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL, 1);
+ serializer.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, "key" + i);
+ if (timedNotification) {
+ serializer.attributeLong(null, XML_SNOOZED_NOTIFICATION_TIME, snoozeTimeout);
+ serializer.endTag(null, XML_SNOOZED_NOTIFICATION);
+ } else {
+ serializer.attribute(null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID, snoozeContext);
+ serializer.endTag(null, XML_SNOOZED_NOTIFICATION_CONTEXT);
+ }
+ }
+ serializer.endTag(null, XML_TAG_NAME);
+ serializer.endDocument();
+ serializer.flush();
+
+ TypedXmlPullParser parser = Xml.newFastPullParser();
+ parser.setInput(new BufferedInputStream(
+ new ByteArrayInputStream(baos.toByteArray())), "utf-8");
+ mSnoozeHelper.readXml(parser, 1);
+ // Verify that we can't snooze any more notifications
+ // and that the limit is caused by persisted notifications
+ assertThat(mSnoozeHelper.canSnooze(1)).isFalse();
+ assertThat(mSnoozeHelper.isSnoozed(UserHandle.USER_SYSTEM, "pkg", "key0")).isFalse();
+ assertThat(mSnoozeHelper.getSnoozeTimeForUnpostedNotification(UserHandle.USER_SYSTEM,
+ "pkg", "key0")).isEqualTo(snoozeTimeout);
+ assertThat(
+ mSnoozeHelper.getSnoozeContextForUnpostedNotification(UserHandle.USER_SYSTEM, "pkg",
+ "key1")).isEqualTo(snoozeContext);
+ }
+
+ @Test
public void testCancelByApp() throws Exception {
NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
@@ -611,6 +667,7 @@
@Test
public void repostGroupSummary_repostsSummary() throws Exception {
+ final int snoozeDuration = 1000;
IntArray profileIds = new IntArray();
profileIds.add(UserHandle.USER_SYSTEM);
when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
@@ -618,10 +675,14 @@
"pkg", 1, "one", UserHandle.SYSTEM, "group1", true);
NotificationRecord r2 = getNotificationRecord(
"pkg", 2, "two", UserHandle.SYSTEM, "group1", false);
- mSnoozeHelper.snooze(r, 1000);
- mSnoozeHelper.snooze(r2, 1000);
+ final long snoozeTime = System.currentTimeMillis() + snoozeDuration;
+ mSnoozeHelper.snooze(r, snoozeDuration);
+ mSnoozeHelper.snooze(r2, snoozeDuration);
assertEquals(2, mSnoozeHelper.getSnoozed().size());
assertEquals(2, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size());
+ // Verify that summary notification was added to the persisted list
+ assertThat(mSnoozeHelper.getSnoozeTimeForUnpostedNotification(UserHandle.USER_SYSTEM, "pkg",
+ r.getKey())).isAtLeast(snoozeTime);
mSnoozeHelper.repostGroupSummary("pkg", UserHandle.USER_SYSTEM, r.getGroupKey());
@@ -630,6 +691,39 @@
assertEquals(1, mSnoozeHelper.getSnoozed().size());
assertEquals(1, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size());
+ // Verify that summary notification was removed from the persisted list
+ assertThat(mSnoozeHelper.getSnoozeTimeForUnpostedNotification(UserHandle.USER_SYSTEM, "pkg",
+ r.getKey())).isEqualTo(0);
+ }
+
+ @Test
+ public void snoozeWithContext_repostGroupSummary_removesPersisted() throws Exception {
+ final String snoozeContext = "zzzzz";
+ IntArray profileIds = new IntArray();
+ profileIds.add(UserHandle.USER_SYSTEM);
+ when(mUserProfiles.getCurrentProfileIds()).thenReturn(profileIds);
+ NotificationRecord r = getNotificationRecord(
+ "pkg", 1, "one", UserHandle.SYSTEM, "group1", true);
+ NotificationRecord r2 = getNotificationRecord(
+ "pkg", 2, "two", UserHandle.SYSTEM, "group1", false);
+ mSnoozeHelper.snooze(r, snoozeContext);
+ mSnoozeHelper.snooze(r2, snoozeContext);
+ assertEquals(2, mSnoozeHelper.getSnoozed().size());
+ assertEquals(2, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size());
+ // Verify that summary notification was added to the persisted list
+ assertThat(mSnoozeHelper.getSnoozeContextForUnpostedNotification(UserHandle.USER_SYSTEM,
+ "pkg", r.getKey())).isEqualTo(snoozeContext);
+
+ mSnoozeHelper.repostGroupSummary("pkg", UserHandle.USER_SYSTEM, r.getGroupKey());
+
+ verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r, false);
+ verify(mCallback, never()).repost(UserHandle.USER_SYSTEM, r2, false);
+
+ assertEquals(1, mSnoozeHelper.getSnoozed().size());
+ assertEquals(1, mSnoozeHelper.getSnoozed(UserHandle.USER_SYSTEM, "pkg").size());
+ // Verify that summary notification was removed from the persisted list
+ assertThat(mSnoozeHelper.getSnoozeContextForUnpostedNotification(UserHandle.USER_SYSTEM,
+ "pkg", r.getKey())).isNull();
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
index 27e8f36..8f30f41 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/TestableNotificationManagerService.java
@@ -54,6 +54,15 @@
return mRankingHelper;
}
+ /**
+ * Sets {@link #isSystemUid} and {@link #isSystemAppId} to {@code false}, so that calls to NMS
+ * methods don't succeed {@link #isCallingUidSystem()} and similar checks.
+ */
+ void setCallerIsNormalPackage() {
+ isSystemUid = false;
+ isSystemAppId = false;
+ }
+
@Override
protected boolean isCallingUidSystem() {
countSystemChecks++;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
new file mode 100644
index 0000000..6cc1c43
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.server.notification;
+
+import static com.android.server.notification.ZenAdapters.notificationPolicyToZenPolicy;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.NotificationManager.Policy;
+import android.service.notification.ZenPolicy;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ZenAdaptersTest extends UiServiceTestCase {
+
+ @Test
+ public void notificationPolicyToZenPolicy_allCallers() {
+ Policy policy = new Policy(Policy.PRIORITY_CATEGORY_CALLS, Policy.PRIORITY_SENDERS_ANY, 0);
+
+ ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
+
+ assertThat(zenPolicy.getPriorityCategoryCalls()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(zenPolicy.getPriorityCallSenders()).isEqualTo(ZenPolicy.PEOPLE_TYPE_ANYONE);
+ }
+
+ @Test
+ public void notificationPolicyToZenPolicy_starredCallers() {
+ Policy policy = new Policy(Policy.PRIORITY_CATEGORY_CALLS, Policy.PRIORITY_SENDERS_STARRED,
+ 0);
+
+ ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
+
+ assertThat(zenPolicy.getPriorityCategoryCalls()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(zenPolicy.getPriorityCallSenders()).isEqualTo(ZenPolicy.PEOPLE_TYPE_STARRED);
+ }
+
+ @Test
+ public void notificationPolicyToZenPolicy_repeatCallers() {
+ Policy policy = new Policy(Policy.PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0);
+
+ ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
+
+ assertThat(zenPolicy.getPriorityCategoryCalls()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(zenPolicy.getPriorityCategoryRepeatCallers()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(zenPolicy.getPriorityCallSenders()).isEqualTo(ZenPolicy.PEOPLE_TYPE_NONE);
+ }
+
+ @Test
+ public void notificationPolicyToZenPolicy_noCallers() {
+ Policy policy = new Policy(0, 0, 0);
+
+ ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
+
+ assertThat(zenPolicy.getPriorityCategoryCalls()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(zenPolicy.getPriorityCallSenders()).isEqualTo(ZenPolicy.PEOPLE_TYPE_NONE);
+ }
+
+ @Test
+ public void notificationPolicyToZenPolicy_conversationsAllowedSendersUnset() {
+ Policy policy = new Policy(Policy.PRIORITY_CATEGORY_CONVERSATIONS, 0, 0);
+
+ ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
+
+ assertThat(zenPolicy.getPriorityCategoryConversations()).isEqualTo(ZenPolicy.STATE_UNSET);
+ }
+
+ @Test
+ public void notificationPolicyToZenPolicy_conversationsNotAllowedSendersUnset() {
+ Policy policy = new Policy(0, 0, 0);
+
+ ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
+
+ assertThat(zenPolicy.getPriorityCategoryConversations()).isEqualTo(
+ ZenPolicy.STATE_DISALLOW);
+ }
+
+ @Test
+ public void notificationPolicyToZenPolicy_setEffects() {
+ Policy policy = new Policy(0, 0, 0,
+ Policy.SUPPRESSED_EFFECT_BADGE | Policy.SUPPRESSED_EFFECT_LIGHTS);
+
+ ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
+
+ assertThat(zenPolicy.getVisualEffectBadge()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+ assertThat(zenPolicy.getVisualEffectLights()).isEqualTo(ZenPolicy.STATE_DISALLOW);
+
+ assertThat(zenPolicy.getVisualEffectAmbient()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(zenPolicy.getVisualEffectFullScreenIntent()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(zenPolicy.getVisualEffectNotificationList()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(zenPolicy.getVisualEffectPeek()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ assertThat(zenPolicy.getVisualEffectStatusBar()).isEqualTo(ZenPolicy.STATE_ALLOW);
+ }
+
+ @Test
+ public void notificationPolicyToZenPolicy_unsetEffects() {
+ Policy policy = new Policy(0, 0, 0);
+
+ ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
+
+ assertThat(zenPolicy.getVisualEffectAmbient()).isEqualTo(ZenPolicy.STATE_UNSET);
+ assertThat(zenPolicy.getVisualEffectBadge()).isEqualTo(ZenPolicy.STATE_UNSET);
+ assertThat(zenPolicy.getVisualEffectFullScreenIntent()).isEqualTo(ZenPolicy.STATE_UNSET);
+ assertThat(zenPolicy.getVisualEffectLights()).isEqualTo(ZenPolicy.STATE_UNSET);
+ assertThat(zenPolicy.getVisualEffectNotificationList()).isEqualTo(ZenPolicy.STATE_UNSET);
+ assertThat(zenPolicy.getVisualEffectPeek()).isEqualTo(ZenPolicy.STATE_UNSET);
+ assertThat(zenPolicy.getVisualEffectStatusBar()).isEqualTo(ZenPolicy.STATE_UNSET);
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index e8201fd..37aeb57 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -22,6 +22,7 @@
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DISABLED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ENABLED;
import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
+import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS;
@@ -32,6 +33,7 @@
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM;
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
+import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS;
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_STARRED;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_BADGE;
@@ -40,8 +42,11 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.provider.Settings.Global.ZEN_MODE_OFF;
import static android.service.notification.Condition.STATE_FALSE;
import static android.service.notification.Condition.STATE_TRUE;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS;
+import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS;
import static com.android.os.dnd.DNDProtoEnums.PEOPLE_STARRED;
@@ -50,6 +55,8 @@
import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
@@ -72,6 +79,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.AppGlobals;
import android.app.AppOpsManager;
@@ -82,6 +90,7 @@
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
@@ -92,6 +101,7 @@
import android.media.AudioSystem;
import android.media.VolumePolicy;
import android.net.Uri;
+import android.os.Parcel;
import android.os.Process;
import android.os.UserHandle;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -100,6 +110,7 @@
import android.service.notification.Condition;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenModeConfig.ScheduleInfo;
+import android.service.notification.ZenModeConfig.ZenRule;
import android.service.notification.ZenModeDiff;
import android.service.notification.ZenPolicy;
import android.test.suitebuilder.annotation.SmallTest;
@@ -124,6 +135,7 @@
import com.android.server.notification.ManagedServices.UserProfiles;
import com.google.common.collect.ImmutableList;
+import com.google.common.truth.Correspondence;
import com.google.protobuf.InvalidProtocolBufferException;
import org.junit.Before;
@@ -157,27 +169,29 @@
private static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE";
private static final String SCHEDULE_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE";
private static final String CUSTOM_PKG_NAME = "not.android";
+ private static final String CUSTOM_APP_LABEL = "This is not Android";
private static final int CUSTOM_PKG_UID = 1;
private static final String CUSTOM_RULE_ID = "custom_rule";
- private final String NAME = "name";
- private final ComponentName OWNER = new ComponentName("pkg", "cls");
- private final ComponentName CONFIG_ACTIVITY = new ComponentName("pkg", "act");
- private final ZenPolicy POLICY = new ZenPolicy.Builder().allowAlarms(true).build();
- private final Uri CONDITION_ID = new Uri.Builder().scheme("scheme")
+ private static final String NAME = "name";
+ private static final ComponentName OWNER = new ComponentName("pkg", "cls");
+ private static final ComponentName CONFIG_ACTIVITY = new ComponentName("pkg", "act");
+ private static final ZenPolicy POLICY = new ZenPolicy.Builder().allowAlarms(true).build();
+ private static final Uri CONDITION_ID = new Uri.Builder().scheme("scheme")
.authority("authority")
.appendPath("path")
.appendPath("test")
.build();
- private final Condition CONDITION = new Condition(CONDITION_ID, "", Condition.STATE_TRUE);
- private final String TRIGGER_DESC = "Every Night, 10pm to 6am";
- private final int TYPE = TYPE_BEDTIME;
- private final boolean ALLOW_MANUAL = true;
- private final int ICON_RES_ID = 1234;
- private final int INTERRUPTION_FILTER = Settings.Global.ZEN_MODE_ALARMS;
- private final boolean ENABLED = true;
- private final int CREATION_TIME = 123;
+ private static final Condition CONDITION = new Condition(CONDITION_ID, "",
+ Condition.STATE_TRUE);
+ private static final String TRIGGER_DESC = "Every Night, 10pm to 6am";
+ private static final int TYPE = TYPE_BEDTIME;
+ private static final boolean ALLOW_MANUAL = true;
+ private static final int ICON_RES_ID = 1234;
+ private static final int INTERRUPTION_FILTER = Settings.Global.ZEN_MODE_ALARMS;
+ private static final boolean ENABLED = true;
+ private static final int CREATION_TIME = 123;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -227,6 +241,10 @@
.thenReturn(CUSTOM_PKG_UID);
when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(
new String[] {pkg});
+ ApplicationInfo mockAppInfo = mock(ApplicationInfo.class);
+ when(mockAppInfo.loadLabel(any())).thenReturn(CUSTOM_APP_LABEL);
+ when(mPackageManager.getApplicationInfo(eq(CUSTOM_PKG_NAME), anyInt()))
+ .thenReturn(mockAppInfo);
mZenModeHelper.mPm = mPackageManager;
mZenModeEventLogger.reset();
@@ -334,7 +352,7 @@
@Test
public void testZenOff_NoMuteApplied() {
- mZenModeHelper.mZenMode = Settings.Global.ZEN_MODE_OFF;
+ mZenModeHelper.mZenMode = ZEN_MODE_OFF;
mZenModeHelper.setPriorityOnlyDndExemptPackages(new String[] {PKG_O});
mZenModeHelper.mConsolidatedPolicy = new Policy(Policy.PRIORITY_CATEGORY_ALARMS
| PRIORITY_CATEGORY_MEDIA, 0, 0, 0, 0, 0);
@@ -635,7 +653,7 @@
// 3. apply zen off - verify zen is set to previous ringer (normal)
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
- mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF;
+ mZenModeHelper.mZenMode = ZEN_MODE_OFF;
mZenModeHelper.applyZenToRingerMode();
verify(mAudioManager, atLeastOnce()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
mZenModeHelper.TAG);
@@ -721,7 +739,7 @@
// 3. apply zen off - verify ringer remains normal
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
- mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF;
+ mZenModeHelper.mZenMode = ZEN_MODE_OFF;
mZenModeHelper.applyZenToRingerMode();
verify(mAudioManager, atLeastOnce()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL,
mZenModeHelper.TAG);
@@ -746,7 +764,7 @@
// 3. apply zen-off - verify ringer is still silent
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
- mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF;
+ mZenModeHelper.mZenMode = ZEN_MODE_OFF;
mZenModeHelper.applyZenToRingerMode();
verify(mAudioManager, atLeastOnce()).setRingerModeInternal(AudioManager.RINGER_MODE_SILENT,
mZenModeHelper.TAG);
@@ -781,7 +799,7 @@
// 4. apply zen off - verify ringer still silenced
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
- mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF;
+ mZenModeHelper.mZenMode = ZEN_MODE_OFF;
mZenModeHelper.applyZenToRingerMode();
verify(mAudioManager, atLeastOnce()).setRingerModeInternal(AudioManager.RINGER_MODE_SILENT,
mZenModeHelper.TAG);
@@ -795,7 +813,7 @@
// apply zen off multiple times - verify ringer is not set to normal
when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
- mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF;
+ mZenModeHelper.mZenMode = ZEN_MODE_OFF;
mZenModeHelper.mConfig = null; // will evaluate config to zen mode off
for (int i = 0; i < 3; i++) {
// if zen doesn't change, zen should not reapply itself to the ringer
@@ -809,7 +827,7 @@
public void testSilentRingerSavedOnZenOff_startsZenOn() {
AudioManagerInternal mAudioManager = mock(AudioManagerInternal.class);
mZenModeHelper.mAudioManager = mAudioManager;
- mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF;
+ mZenModeHelper.mZenMode = ZEN_MODE_OFF;
mZenModeHelper.mConfig = new ZenModeConfig();
// previously set silent ringer
@@ -836,7 +854,7 @@
public void testVibrateRingerSavedOnZenOff_startsZenOn() {
AudioManagerInternal mAudioManager = mock(AudioManagerInternal.class);
mZenModeHelper.mAudioManager = mAudioManager;
- mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF;
+ mZenModeHelper.mZenMode = ZEN_MODE_OFF;
mZenModeHelper.mConfig = new ZenModeConfig();
// previously set silent ringer
@@ -1209,7 +1227,7 @@
.allowMedia(false)
.allowRepeatCallers(false)
.allowCalls(ZenPolicy.PEOPLE_TYPE_NONE)
- .allowMessages(ZenPolicy.PEOPLE_TYPE_CONTACTS)
+ .allowMessages(PEOPLE_TYPE_CONTACTS)
.allowEvents(true)
.allowReminders(false)
.build();
@@ -2023,10 +2041,10 @@
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode);
// and also that it works to turn it back off again
- mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "",
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "",
Process.SYSTEM_UID, true);
- assertEquals(Global.ZEN_MODE_OFF, mZenModeHelper.mZenMode);
+ assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode);
}
@Test
@@ -2041,7 +2059,7 @@
// Now turn zen mode off, but via a different package UID -- this should get registered as
// "not an action by the user" because some other app is changing zen mode
- mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "", CUSTOM_PKG_UID,
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "", CUSTOM_PKG_UID,
false);
// In total, this should be 2 loggable changes
@@ -2060,7 +2078,7 @@
// - resulting DNDPolicyProto the same as the values in setupZenConfig()
assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(),
mZenModeEventLogger.getEventId(0));
- assertEquals(Global.ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0));
+ assertEquals(ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0));
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getNewZenMode(0));
assertEquals(DNDProtoEnums.MANUAL_RULE, mZenModeEventLogger.getChangedRuleType(0));
assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
@@ -2080,7 +2098,7 @@
assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_OFF.getId(),
mZenModeEventLogger.getEventId(1));
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getPrevZenMode(1));
- assertEquals(Global.ZEN_MODE_OFF, mZenModeEventLogger.getNewZenMode(1));
+ assertEquals(ZEN_MODE_OFF, mZenModeEventLogger.getNewZenMode(1));
assertEquals(DNDProtoEnums.MANUAL_RULE, mZenModeEventLogger.getChangedRuleType(1));
assertEquals(0, mZenModeEventLogger.getNumRulesActive(1));
assertFalse(mZenModeEventLogger.getIsUserAction(1));
@@ -2144,7 +2162,7 @@
// - zen policy is the same as the set-up zen config
assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(),
mZenModeEventLogger.getEventId(0));
- assertEquals(Global.ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0));
+ assertEquals(ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0));
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getNewZenMode(0));
assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(0));
assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
@@ -2157,7 +2175,7 @@
assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_OFF.getId(),
mZenModeEventLogger.getEventId(1));
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getPrevZenMode(1));
- assertEquals(Global.ZEN_MODE_OFF, mZenModeEventLogger.getNewZenMode(1));
+ assertEquals(ZEN_MODE_OFF, mZenModeEventLogger.getNewZenMode(1));
assertEquals(DNDProtoEnums.AUTOMATIC_RULE, mZenModeEventLogger.getChangedRuleType(1));
assertEquals(0, mZenModeEventLogger.getNumRulesActive(1));
assertTrue(mZenModeEventLogger.getIsUserAction(1));
@@ -2201,7 +2219,7 @@
// Turn zen mode off; we want to make sure policy changes do not get logged when zen mode
// is off.
- mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "",
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "",
Process.SYSTEM_UID, true);
// Change the policy again
@@ -2305,7 +2323,7 @@
// what the event should reflect. At this time, the policy is the same as initial setup.
assertEquals(ZenModeEventLogger.ZenStateChangedEvent.DND_TURNED_ON.getId(),
mZenModeEventLogger.getEventId(0));
- assertEquals(Global.ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0));
+ assertEquals(ZEN_MODE_OFF, mZenModeEventLogger.getPrevZenMode(0));
assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeEventLogger.getNewZenMode(0));
assertEquals(1, mZenModeEventLogger.getNumRulesActive(0));
assertFalse(mZenModeEventLogger.getIsUserAction(0));
@@ -2355,7 +2373,7 @@
mZenModeHelper.evaluateZenModeLocked("test", true);
// Check that the change actually took: zen mode should be off now
- assertEquals(Global.ZEN_MODE_OFF, mZenModeHelper.mZenMode);
+ assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode);
// but still, nothing should've been logged
assertEquals(0, mZenModeEventLogger.numLoggedChanges());
@@ -2483,7 +2501,7 @@
true);
// Turn off manual mode, call from a package: don't reset UID even though enabler is set
- mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null,
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null,
CUSTOM_PKG_NAME, "", 12345, false);
// And likewise when turning it back on again
@@ -2660,8 +2678,11 @@
}
@Test
- public void testCreateAutomaticZenRule_allFields() {
+ public void zenRuleToAutomaticZenRule_allFields() {
mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+ when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(
+ new String[] {OWNER.getPackageName()});
+
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
rule.configurationActivity = CONFIG_ACTIVITY;
rule.component = OWNER;
@@ -2682,7 +2703,8 @@
rule.iconResId = ICON_RES_ID;
rule.triggerDescription = TRIGGER_DESC;
- AutomaticZenRule actual = mZenModeHelper.createAutomaticZenRule(rule);
+ mZenModeHelper.mConfig.automaticRules.put(rule.id, rule);
+ AutomaticZenRule actual = mZenModeHelper.getAutomaticZenRule(rule.id);
assertEquals(NAME, actual.getName());
assertEquals(OWNER, actual.getOwner());
@@ -2915,8 +2937,253 @@
assertEquals(false, mZenModeHelper.mConfig.automaticRules.get(createdId).snoozing);
}
+ @Test
+ public void applyGlobalZenModeAsImplicitZenRule_createsImplicitRuleAndActivatesIt() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mZenModeHelper.mConfig.automaticRules.clear();
+
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.values())
+ .comparingElementsUsing(IGNORE_TIMESTAMPS)
+ .containsExactly(
+ expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ null, true));
+ }
+
+ @Test
+ public void applyGlobalZenModeAsImplicitZenRule_updatesImplicitRuleAndActivatesIt() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mZenModeHelper.mConfig.automaticRules.clear();
+
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, "test", "test", 0, true);
+ assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
+
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
+ ZEN_MODE_ALARMS);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.values())
+ .comparingElementsUsing(IGNORE_TIMESTAMPS)
+ .containsExactly(
+ expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_ALARMS, null, true));
+ }
+
+ @Test
+ public void applyGlobalZenModeAsImplicitZenRule_modeOff_deactivatesImplicitRule() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mZenModeHelper.mConfig.automaticRules.clear();
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).condition.state)
+ .isEqualTo(STATE_TRUE);
+
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
+ ZEN_MODE_OFF);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).condition.state)
+ .isEqualTo(STATE_FALSE);
+ }
+
+ @Test
+ public void applyGlobalZenModeAsImplicitZenRule_modeOffButNoPreviousRule_ignored() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mZenModeHelper.mConfig.automaticRules.clear();
+
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
+ ZEN_MODE_OFF);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty();
+ }
+
+ @Test
+ public void applyGlobalZenModeAsImplicitZenRule_update_unsnoozesRule() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mZenModeHelper.mConfig.automaticRules.clear();
+
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1);
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isFalse();
+
+ mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, "test", "test", 0, true);
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isTrue();
+
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
+ ZEN_MODE_ALARMS);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isFalse();
+ assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).condition.state)
+ .isEqualTo(STATE_TRUE);
+ }
+
+ @Test
+ public void applyGlobalZenModeAsImplicitZenRule_flagOff_ignored() {
+ mSetFlagsRule.disableFlags(android.app.Flags.FLAG_MODES_API);
+ mZenModeHelper.mConfig.automaticRules.clear();
+
+ withoutWtfCrash(
+ () -> mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID,
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS));
+
+ assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty();
+ }
+
+ @Test
+ public void applyGlobalPolicyAsImplicitZenRule_createsImplicitRule() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mZenModeHelper.mConfig.automaticRules.clear();
+
+ Policy policy = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
+ PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
+ Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy);
+
+ ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
+ .disallowAllSounds()
+ .allowCalls(PEOPLE_TYPE_CONTACTS)
+ .allowConversations(CONVERSATION_SENDERS_IMPORTANT)
+ .hideAllVisualEffects()
+ .build();
+ assertThat(mZenModeHelper.mConfig.automaticRules.values())
+ .comparingElementsUsing(IGNORE_TIMESTAMPS)
+ .containsExactly(
+ expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ expectedZenPolicy, /* conditionActive= */ null));
+ }
+
+ @Test
+ public void applyGlobalPolicyAsImplicitZenRule_updatesImplicitRule() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ mZenModeHelper.mConfig.automaticRules.clear();
+
+ Policy original = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
+ PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
+ Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
+ original);
+
+ // Change priorityCallSenders: contacts -> starred.
+ Policy updated = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
+ PRIORITY_SENDERS_STARRED, PRIORITY_SENDERS_STARRED,
+ Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated);
+
+ ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
+ .disallowAllSounds()
+ .allowCalls(PEOPLE_TYPE_STARRED)
+ .allowConversations(CONVERSATION_SENDERS_IMPORTANT)
+ .hideAllVisualEffects()
+ .build();
+ assertThat(mZenModeHelper.mConfig.automaticRules.values())
+ .comparingElementsUsing(IGNORE_TIMESTAMPS)
+ .containsExactly(
+ expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ expectedZenPolicy, /* conditionActive= */ null));
+ }
+
+ @Test
+ public void applyGlobalPolicyAsImplicitZenRule_flagOff_ignored() {
+ mSetFlagsRule.disableFlags(android.app.Flags.FLAG_MODES_API);
+ mZenModeHelper.mConfig.automaticRules.clear();
+
+ withoutWtfCrash(
+ () -> mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME,
+ CUSTOM_PKG_UID, new Policy(0, 0, 0)));
+
+ assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty();
+ }
+
+ @Test
+ public void getNotificationPolicyFromImplicitZenRule_returnsSetPolicy() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+ Policy writtenPolicy = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
+ PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
+ Policy.getAllSuppressedVisualEffects(), STATE_FALSE,
+ CONVERSATION_SENDERS_IMPORTANT);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
+ writtenPolicy);
+
+ Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
+ CUSTOM_PKG_NAME);
+
+ assertThat(readPolicy).isEqualTo(writtenPolicy);
+ }
+
+ @Test
+ public void getNotificationPolicyFromImplicitZenRule_ruleWithoutPolicy_returnsGlobalPolicy() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
+ ZEN_MODE_ALARMS);
+ mZenModeHelper.mConfig.allowCalls = true;
+ mZenModeHelper.mConfig.allowConversations = false;
+
+ Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
+ CUSTOM_PKG_NAME);
+
+ assertThat(readPolicy).isNotNull();
+ assertThat(readPolicy.allowCalls()).isTrue();
+ assertThat(readPolicy.allowConversations()).isFalse();
+ }
+
+ @Test
+ public void getNotificationPolicyFromImplicitZenRule_noImplicitRule_returnsGlobalPolicy() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+ mZenModeHelper.mConfig.allowCalls = true;
+ mZenModeHelper.mConfig.allowConversations = false;
+
+ Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
+ CUSTOM_PKG_NAME);
+
+ assertThat(readPolicy).isNotNull();
+ assertThat(readPolicy.allowCalls()).isTrue();
+ assertThat(readPolicy.allowConversations()).isFalse();
+ }
+
+ private static final Correspondence<ZenRule, ZenRule> IGNORE_TIMESTAMPS =
+ Correspondence.transforming(zr -> {
+ Parcel p = Parcel.obtain();
+ try {
+ zr.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ ZenRule copy = new ZenRule(p);
+ copy.creationTime = 0;
+ return copy;
+ } finally {
+ p.recycle();
+ }
+ },
+ "Ignoring timestamps");
+
+ private ZenRule expectedImplicitRule(String ownerPkg, int zenMode, ZenPolicy policy,
+ @Nullable Boolean conditionActive) {
+ ZenRule rule = new ZenModeConfig.ZenRule();
+ rule.id = "implicit_" + ownerPkg;
+ rule.conditionId = Uri.parse("condition://android/implicit/" + ownerPkg);
+ if (conditionActive != null) {
+ rule.condition = conditionActive
+ ? new Condition(rule.conditionId,
+ mContext.getString(R.string.zen_mode_implicit_activated), STATE_TRUE)
+ : new Condition(rule.conditionId,
+ mContext.getString(R.string.zen_mode_implicit_deactivated),
+ STATE_FALSE);
+ }
+ rule.zenMode = zenMode;
+ rule.zenPolicy = policy;
+ rule.pkg = ownerPkg;
+ rule.name = CUSTOM_APP_LABEL;
+ rule.enabled = true;
+ return rule;
+ }
+
private void setupZenConfig() {
- mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF;
+ mZenModeHelper.mZenMode = ZEN_MODE_OFF;
mZenModeHelper.mConfig.allowAlarms = false;
mZenModeHelper.mConfig.allowMedia = false;
mZenModeHelper.mConfig.allowSystem = false;
@@ -2965,6 +3232,15 @@
assertEquals(STATE_ALLOW, dndProto.getNotificationList().getNumber());
}
+ private static void withoutWtfCrash(Runnable test) {
+ Log.TerribleFailureHandler oldHandler = Log.setWtfHandler((tag, what, system) -> {});
+ try {
+ test.run();
+ } finally {
+ Log.setWtfHandler(oldHandler);
+ }
+ }
+
/**
* Wrapper to use TypedXmlPullParser as XmlResourceParser for Resources.getXml()
*/