Merge "Don't expect an error on log to proto" into main
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
index f20b170..3577fcd 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ClientSocketPerfTest.java
@@ -194,7 +194,7 @@
/**
* Simple benchmark for the amount of time to send a given number of messages
*/
- @Test
+ // @Test Temporarily disabled
@Parameters(method = "getParams")
public void time(Config config) throws Exception {
reset();
diff --git a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
index af3c405..ac57100 100644
--- a/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
+++ b/apct-tests/perftests/core/src/android/conscrypt/conscrypt/ServerSocketPerfTest.java
@@ -198,7 +198,7 @@
executor.awaitTermination(5, TimeUnit.SECONDS);
}
- @Test
+ // @Test Temporarily disabled
@Parameters(method = "getParams")
public void throughput(Config config) throws Exception {
setup(config);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 7b3b207..1563994 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -982,7 +982,6 @@
mShellBackAnimationRegistry.resetDefaultCrossActivity();
cancelLatencyTracking();
mReceivedNullNavigationInfo = false;
- mBackTransitionHandler.mLastTrigger = triggerBack;
if (mBackNavigationInfo != null) {
mPreviousNavigationType = mBackNavigationInfo.getType();
mBackNavigationInfo.onBackNavigationFinished(triggerBack);
@@ -1103,7 +1102,6 @@
endLatencyTracking();
if (!validateAnimationTargets(apps)) {
Log.e(TAG, "Invalid animation targets!");
- mBackTransitionHandler.consumeQueuedTransitionIfNeeded();
return;
}
mBackAnimationFinishedCallback = finishedCallback;
@@ -1113,7 +1111,6 @@
return;
}
kickStartAnimation();
- mBackTransitionHandler.consumeQueuedTransitionIfNeeded();
});
}
@@ -1121,7 +1118,6 @@
public void onAnimationCancelled() {
mShellExecutor.execute(
() -> {
- mBackTransitionHandler.consumeQueuedTransitionIfNeeded();
if (!mShellBackAnimationRegistry.cancel(
mBackNavigationInfo != null
? mBackNavigationInfo.getType()
@@ -1160,8 +1156,6 @@
boolean mCloseTransitionRequested;
SurfaceControl.Transaction mFinishOpenTransaction;
Transitions.TransitionFinishCallback mFinishOpenTransitionCallback;
- QueuedTransition mQueuedTransition = null;
- boolean mLastTrigger;
// The Transition to make behindActivity become visible
IBinder mPrepareOpenTransition;
// The Transition to make behindActivity become invisible, if prepare open exist and
@@ -1178,13 +1172,6 @@
}
}
- void consumeQueuedTransitionIfNeeded() {
- if (mQueuedTransition != null) {
- mQueuedTransition.consume();
- mQueuedTransition = null;
- }
- }
-
private void applyFinishOpenTransition() {
mOpenTransitionInfo = null;
mPrepareOpenTransition = null;
@@ -1215,7 +1202,9 @@
@NonNull SurfaceControl.Transaction st,
@NonNull SurfaceControl.Transaction ft,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION) {
+ final boolean isPrepareTransition =
+ info.getType() == WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
+ if (isPrepareTransition) {
kickStartAnimation();
}
// Both mShellExecutor and Transitions#mMainExecutor are ShellMainThread, so we don't
@@ -1240,21 +1229,14 @@
}
if (mApps == null || mApps.length == 0) {
- if (mBackNavigationInfo != null && mShellBackAnimationRegistry
- .isWaitingAnimation(mBackNavigationInfo.getType())) {
- // Waiting for animation? Queue update to wait for animation start.
- consumeQueuedTransitionIfNeeded();
- mQueuedTransition = new QueuedTransition(info, st, ft, finishCallback);
- return true;
- } else if (mLastTrigger) {
- // animation was done, consume directly
+ if (mCloseTransitionRequested) {
+ // animation never start, consume directly
applyAndFinish(st, ft, finishCallback);
return true;
- } else {
- // animation was cancelled but transition haven't happen, we must handle it
- if (mClosePrepareTransition == null && mCurrentTracker.isFinished()) {
- createClosePrepareTransition();
- }
+ } else if (mClosePrepareTransition == null && isPrepareTransition) {
+ // Gesture animation was cancelled before prepare transition ready, create the
+ // the close prepare transition
+ createClosePrepareTransition();
}
}
@@ -1413,9 +1395,6 @@
if (mPrepareOpenTransition != null) {
applyFinishOpenTransition();
}
- if (mQueuedTransition != null) {
- consumeQueuedTransitionIfNeeded();
- }
return;
}
// Handle the commit transition if this handler is running the open transition.
@@ -1423,11 +1402,9 @@
t.apply();
if (mCloseTransitionRequested) {
if (mApps == null || mApps.length == 0) {
- if (mQueuedTransition == null) {
- // animation was done
- applyFinishOpenTransition();
- mCloseTransitionRequested = false;
- } // let queued transition finish.
+ // animation was done
+ applyFinishOpenTransition();
+ mCloseTransitionRequested = false;
} else {
// we are animating, wait until animation finish
mOnAnimationFinishCallback = () -> {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index ff09084..c4173ed 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -460,7 +460,7 @@
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
+ if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE) {
event.startTracking();
return true;
}
@@ -479,7 +479,7 @@
return true;
}
- if (keyCode == KeyEvent.KEYCODE_BACK
+ if ((keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_ESCAPE)
&& event.isTracking() && !event.isCanceled()) {
if (mPrintPreviewController != null && mPrintPreviewController.isOptionsOpened()
&& !hasErrors()) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingContract.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingContract.kt
new file mode 100644
index 0000000..65adec4
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingContract.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 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.bluetooth.devicesettings
+
+/** The contract between the device settings provider services and Settings. */
+object DeviceSettingContract {
+ const val INVISIBLE_PROFILES = "INVISIBLE_PROFILES"
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
index 457d6a3..769b6e6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
@@ -22,6 +22,7 @@
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.devicesettings.ActionSwitchPreference
import com.android.settingslib.bluetooth.devicesettings.DeviceSetting
+import com.android.settingslib.bluetooth.devicesettings.DeviceSettingContract
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingItem
import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig
@@ -30,6 +31,9 @@
import com.android.settingslib.bluetooth.devicesettings.MultiTogglePreference
import com.android.settingslib.bluetooth.devicesettings.ToggleInfo
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel.AppProvidedItem
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel.BuiltinItem.BluetoothProfilesItem
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
@@ -103,9 +107,18 @@
private fun DeviceSettingItem.toModel(): DeviceSettingConfigItemModel {
return if (!TextUtils.isEmpty(preferenceKey)) {
- DeviceSettingConfigItemModel.BuiltinItem(settingId, preferenceKey!!)
+ if (settingId == DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES) {
+ BluetoothProfilesItem(
+ settingId,
+ preferenceKey!!,
+ extras.getStringArrayList(DeviceSettingContract.INVISIBLE_PROFILES)
+ ?: emptyList()
+ )
+ } else {
+ CommonBuiltinItem(settingId, preferenceKey!!)
+ }
} else {
- DeviceSettingConfigItemModel.AppProvidedItem(settingId)
+ AppProvidedItem(settingId)
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
index 33beb06..7eae5b2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingServiceConnection.kt
@@ -23,6 +23,7 @@
import android.content.ServiceConnection
import android.os.IBinder
import android.os.IInterface
+import android.text.TextUtils
import android.util.Log
import com.android.settingslib.bluetooth.BluetoothUtils
import com.android.settingslib.bluetooth.CachedBluetoothDevice
@@ -84,6 +85,10 @@
}
setAction(intentAction)
}
+
+ fun isValid(): Boolean {
+ return !TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(intentAction)
+ }
}
private var isServiceEnabled =
@@ -96,7 +101,8 @@
} else if (allStatus.all { it is ServiceConnectionStatus.Connected }) {
allStatus
.filterIsInstance<
- ServiceConnectionStatus.Connected<IDeviceSettingsProviderService>
+ ServiceConnectionStatus.Connected<
+ IDeviceSettingsProviderService>
>()
.all { it.service.serviceStatus?.enabled == true }
} else {
@@ -215,6 +221,7 @@
)
}
}
+ ?.filter { it.isValid() }
?.distinct()
?.associateBy(
{ it },
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
index c1ac763..08fb3fb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
@@ -36,10 +36,23 @@
@DeviceSettingId val settingId: Int
/** A built-in item in Settings. */
- data class BuiltinItem(
- @DeviceSettingId override val settingId: Int,
- val preferenceKey: String?
- ) : DeviceSettingConfigItemModel
+ sealed interface BuiltinItem : DeviceSettingConfigItemModel {
+ @DeviceSettingId override val settingId: Int
+ val preferenceKey: String
+
+ /** A general built-in item in Settings. */
+ data class CommonBuiltinItem(
+ @DeviceSettingId override val settingId: Int,
+ override val preferenceKey: String,
+ ) : BuiltinItem
+
+ /** A bluetooth profiles in Settings. */
+ data class BluetoothProfilesItem(
+ @DeviceSettingId override val settingId: Int,
+ override val preferenceKey: String,
+ val invisibleProfiles: List<String>,
+ ) : BuiltinItem
+ }
/** A remote item provided by other apps. */
data class AppProvidedItem(@DeviceSettingId override val settingId: Int) :
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
index ce155b5..81b5634 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
@@ -91,7 +91,9 @@
`when`(cachedDevice.address).thenReturn(BLUETOOTH_ADDRESS)
`when`(
bluetoothDevice.getMetadata(
- DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS
+ )
+ )
.thenReturn(BLUETOOTH_DEVICE_METADATA.toByteArray())
`when`(configService.queryLocalInterface(anyString())).thenReturn(configService)
@@ -114,7 +116,8 @@
connection.onServiceConnected(
ComponentName(
SETTING_PROVIDER_SERVICE_PACKAGE_NAME_1,
- SETTING_PROVIDER_SERVICE_CLASS_NAME_1),
+ SETTING_PROVIDER_SERVICE_CLASS_NAME_1,
+ ),
settingProviderService1,
)
SETTING_PROVIDER_SERVICE_INTENT_ACTION_2 ->
@@ -146,16 +149,24 @@
fun getDeviceSettingsConfig_withMetadata_success() {
testScope.runTest {
`when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
- `when`(settingProviderService1.serviceStatus).thenReturn(
- DeviceSettingsProviderServiceStatus(true)
- )
- `when`(settingProviderService2.serviceStatus).thenReturn(
- DeviceSettingsProviderServiceStatus(true)
- )
+ `when`(settingProviderService1.serviceStatus)
+ .thenReturn(DeviceSettingsProviderServiceStatus(true))
+ `when`(settingProviderService2.serviceStatus)
+ .thenReturn(DeviceSettingsProviderServiceStatus(true))
val config = underTest.getDeviceSettingsConfig(cachedDevice)
assertConfig(config!!, DEVICE_SETTING_CONFIG)
+ assertThat(config.mainItems[0])
+ .isInstanceOf(DeviceSettingConfigItemModel.AppProvidedItem::class.java)
+ assertThat(config.mainItems[1])
+ .isInstanceOf(
+ DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem::class.java
+ )
+ assertThat(config.mainItems[2])
+ .isInstanceOf(
+ DeviceSettingConfigItemModel.BuiltinItem.BluetoothProfilesItem::class.java
+ )
}
}
@@ -163,16 +174,16 @@
fun getDeviceSettingsConfig_noMetadata_returnNull() {
testScope.runTest {
`when`(
- bluetoothDevice.getMetadata(
- DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS))
+ bluetoothDevice.getMetadata(
+ DeviceSettingServiceConnection.METADATA_FAST_PAIR_CUSTOMIZED_FIELDS
+ )
+ )
.thenReturn("".toByteArray())
`when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
- `when`(settingProviderService1.serviceStatus).thenReturn(
- DeviceSettingsProviderServiceStatus(true)
- )
- `when`(settingProviderService2.serviceStatus).thenReturn(
- DeviceSettingsProviderServiceStatus(true)
- )
+ `when`(settingProviderService1.serviceStatus)
+ .thenReturn(DeviceSettingsProviderServiceStatus(true))
+ `when`(settingProviderService2.serviceStatus)
+ .thenReturn(DeviceSettingsProviderServiceStatus(true))
val config = underTest.getDeviceSettingsConfig(cachedDevice)
@@ -184,12 +195,10 @@
fun getDeviceSettingsConfig_providerServiceNotEnabled_returnNull() {
testScope.runTest {
`when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
- `when`(settingProviderService1.serviceStatus).thenReturn(
- DeviceSettingsProviderServiceStatus(false)
- )
- `when`(settingProviderService2.serviceStatus).thenReturn(
- DeviceSettingsProviderServiceStatus(true)
- )
+ `when`(settingProviderService1.serviceStatus)
+ .thenReturn(DeviceSettingsProviderServiceStatus(false))
+ `when`(settingProviderService2.serviceStatus)
+ .thenReturn(DeviceSettingsProviderServiceStatus(true))
val config = underTest.getDeviceSettingsConfig(cachedDevice)
@@ -219,12 +228,10 @@
.getArgument<IDeviceSettingsListener>(1)
.onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
}
- `when`(settingProviderService1.serviceStatus).thenReturn(
- DeviceSettingsProviderServiceStatus(true)
- )
- `when`(settingProviderService2.serviceStatus).thenReturn(
- DeviceSettingsProviderServiceStatus(true)
- )
+ `when`(settingProviderService1.serviceStatus)
+ .thenReturn(DeviceSettingsProviderServiceStatus(true))
+ `when`(settingProviderService2.serviceStatus)
+ .thenReturn(DeviceSettingsProviderServiceStatus(true))
var setting: DeviceSettingModel? = null
underTest
@@ -247,12 +254,10 @@
.getArgument<IDeviceSettingsListener>(1)
.onDeviceSettingsChanged(listOf(DEVICE_SETTING_2))
}
- `when`(settingProviderService1.serviceStatus).thenReturn(
- DeviceSettingsProviderServiceStatus(true)
- )
- `when`(settingProviderService2.serviceStatus).thenReturn(
- DeviceSettingsProviderServiceStatus(true)
- )
+ `when`(settingProviderService1.serviceStatus)
+ .thenReturn(DeviceSettingsProviderServiceStatus(true))
+ `when`(settingProviderService2.serviceStatus)
+ .thenReturn(DeviceSettingsProviderServiceStatus(true))
var setting: DeviceSettingModel? = null
underTest
@@ -270,17 +275,15 @@
testScope.runTest {
`when`(configService.getDeviceSettingsConfig(any())).thenReturn(DEVICE_SETTING_CONFIG)
`when`(settingProviderService2.registerDeviceSettingsListener(any(), any())).then {
- input ->
+ input ->
input
.getArgument<IDeviceSettingsListener>(1)
.onDeviceSettingsChanged(listOf(DEVICE_SETTING_HELP))
}
- `when`(settingProviderService1.serviceStatus).thenReturn(
- DeviceSettingsProviderServiceStatus(true)
- )
- `when`(settingProviderService2.serviceStatus).thenReturn(
- DeviceSettingsProviderServiceStatus(true)
- )
+ `when`(settingProviderService1.serviceStatus)
+ .thenReturn(DeviceSettingsProviderServiceStatus(true))
+ `when`(settingProviderService2.serviceStatus)
+ .thenReturn(DeviceSettingsProviderServiceStatus(true))
var setting: DeviceSettingModel? = null
underTest
@@ -324,12 +327,10 @@
.getArgument<IDeviceSettingsListener>(1)
.onDeviceSettingsChanged(listOf(DEVICE_SETTING_1))
}
- `when`(settingProviderService1.serviceStatus).thenReturn(
- DeviceSettingsProviderServiceStatus(true)
- )
- `when`(settingProviderService2.serviceStatus).thenReturn(
- DeviceSettingsProviderServiceStatus(true)
- )
+ `when`(settingProviderService1.serviceStatus)
+ .thenReturn(DeviceSettingsProviderServiceStatus(true))
+ `when`(settingProviderService2.serviceStatus)
+ .thenReturn(DeviceSettingsProviderServiceStatus(true))
var setting: DeviceSettingModel? = null
underTest
@@ -347,8 +348,10 @@
DeviceSettingState.Builder()
.setSettingId(DeviceSettingId.DEVICE_SETTING_ID_HEADER)
.setPreferenceState(
- ActionSwitchPreferenceState.Builder().setChecked(false).build())
- .build())
+ ActionSwitchPreferenceState.Builder().setChecked(false).build()
+ )
+ .build(),
+ )
}
}
@@ -362,12 +365,10 @@
.getArgument<IDeviceSettingsListener>(1)
.onDeviceSettingsChanged(listOf(DEVICE_SETTING_2))
}
- `when`(settingProviderService1.serviceStatus).thenReturn(
- DeviceSettingsProviderServiceStatus(true)
- )
- `when`(settingProviderService2.serviceStatus).thenReturn(
- DeviceSettingsProviderServiceStatus(true)
- )
+ `when`(settingProviderService1.serviceStatus)
+ .thenReturn(DeviceSettingsProviderServiceStatus(true))
+ `when`(settingProviderService2.serviceStatus)
+ .thenReturn(DeviceSettingsProviderServiceStatus(true))
var setting: DeviceSettingModel? = null
underTest
@@ -385,8 +386,10 @@
DeviceSettingState.Builder()
.setSettingId(DeviceSettingId.DEVICE_SETTING_ID_ANC)
.setPreferenceState(
- MultiTogglePreferenceState.Builder().setState(2).build())
- .build())
+ MultiTogglePreferenceState.Builder().setState(2).build()
+ )
+ .build(),
+ )
}
}
@@ -437,7 +440,7 @@
private fun assertConfig(
actual: DeviceSettingConfigModel,
- serviceResponse: DeviceSettingsConfig
+ serviceResponse: DeviceSettingsConfig,
) {
assertThat(actual.mainItems.size).isEqualTo(serviceResponse.mainContentItems.size)
for (i in 0..<actual.mainItems.size) {
@@ -451,7 +454,7 @@
private fun assertConfigItem(
actual: DeviceSettingConfigItemModel,
- serviceResponse: DeviceSettingItem
+ serviceResponse: DeviceSettingItem,
) {
assertThat(actual.settingId).isEqualTo(serviceResponse.settingId)
}
@@ -485,24 +488,43 @@
"</DEVICE_SETTINGS_CONFIG_ACTION>"
val DEVICE_INFO = DeviceInfo.Builder().setBluetoothAddress(BLUETOOTH_ADDRESS).build()
const val DEVICE_SETTING_ID_HELP = 12345
- val DEVICE_SETTING_ITEM_1 =
+ val DEVICE_SETTING_APP_PROVIDED_ITEM_1 =
DeviceSettingItem(
DeviceSettingId.DEVICE_SETTING_ID_HEADER,
SETTING_PROVIDER_SERVICE_PACKAGE_NAME_1,
SETTING_PROVIDER_SERVICE_CLASS_NAME_1,
- SETTING_PROVIDER_SERVICE_INTENT_ACTION_1)
- val DEVICE_SETTING_ITEM_2 =
+ SETTING_PROVIDER_SERVICE_INTENT_ACTION_1,
+ )
+ val DEVICE_SETTING_APP_PROVIDED_ITEM_2 =
DeviceSettingItem(
DeviceSettingId.DEVICE_SETTING_ID_ANC,
SETTING_PROVIDER_SERVICE_PACKAGE_NAME_2,
SETTING_PROVIDER_SERVICE_CLASS_NAME_2,
- SETTING_PROVIDER_SERVICE_INTENT_ACTION_2)
+ SETTING_PROVIDER_SERVICE_INTENT_ACTION_2,
+ )
+ val DEVICE_SETTING_BUILT_IN_ITEM =
+ DeviceSettingItem(
+ DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_AUDIO_DEVICE_TYPE_GROUP,
+ "",
+ "",
+ "",
+ "device_type",
+ )
+ val DEVICE_SETTING_BUILT_IN_BT_PROFILES_ITEM =
+ DeviceSettingItem(
+ DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES,
+ "",
+ "",
+ "",
+ "bluetooth_profiles",
+ )
val DEVICE_SETTING_HELP_ITEM =
DeviceSettingItem(
DEVICE_SETTING_ID_HELP,
SETTING_PROVIDER_SERVICE_PACKAGE_NAME_2,
SETTING_PROVIDER_SERVICE_CLASS_NAME_2,
- SETTING_PROVIDER_SERVICE_INTENT_ACTION_2)
+ SETTING_PROVIDER_SERVICE_INTENT_ACTION_2,
+ )
val DEVICE_SETTING_1 =
DeviceSetting.Builder()
.setSettingId(DeviceSettingId.DEVICE_SETTING_ID_HEADER)
@@ -511,7 +533,8 @@
.setTitle("title1")
.setHasSwitch(true)
.setAllowedChangingState(true)
- .build())
+ .build()
+ )
.build()
val DEVICE_SETTING_2 =
DeviceSetting.Builder()
@@ -524,22 +547,30 @@
ToggleInfo.Builder()
.setLabel("label1")
.setIcon(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
- .build())
+ .build()
+ )
.addToggleInfo(
ToggleInfo.Builder()
.setLabel("label2")
.setIcon(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
- .build())
- .build())
+ .build()
+ )
+ .build()
+ )
.build()
- val DEVICE_SETTING_HELP = DeviceSetting.Builder()
- .setSettingId(DEVICE_SETTING_ID_HELP)
- .setPreference(DeviceSettingHelpPreference.Builder().setIntent(Intent()).build())
- .build()
+ val DEVICE_SETTING_HELP =
+ DeviceSetting.Builder()
+ .setSettingId(DEVICE_SETTING_ID_HELP)
+ .setPreference(DeviceSettingHelpPreference.Builder().setIntent(Intent()).build())
+ .build()
val DEVICE_SETTING_CONFIG =
DeviceSettingsConfig(
- listOf(DEVICE_SETTING_ITEM_1),
- listOf(DEVICE_SETTING_ITEM_2),
+ listOf(
+ DEVICE_SETTING_APP_PROVIDED_ITEM_1,
+ DEVICE_SETTING_BUILT_IN_ITEM,
+ DEVICE_SETTING_BUILT_IN_BT_PROFILES_ITEM,
+ ),
+ listOf(DEVICE_SETTING_APP_PROVIDED_ITEM_2),
DEVICE_SETTING_HELP_ITEM,
)
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 892f778..7c89592 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -288,6 +288,16 @@
}
flag {
+ name: "qs_quick_rebind_active_tiles"
+ namespace: "systemui"
+ description: "Rebind active custom tiles quickly."
+ bug: "362526228"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "coroutine_tracing"
namespace: "systemui"
description: "Adds thread-local data to System UI's global coroutine scopes to "
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/CaptioningRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/CaptioningRepositoryTest.kt
index dd85d9b..fc57757 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/CaptioningRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/CaptioningRepositoryTest.kt
@@ -20,11 +20,15 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.user.utils.FakeUserScopedService
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -39,10 +43,11 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@Suppress("UnspecifiedRegisterReceiverFlag")
@RunWith(AndroidJUnit4::class)
class CaptioningRepositoryTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
@Captor
private lateinit var listenerCaptor: ArgumentCaptor<CaptioningManager.CaptioningChangeListener>
@@ -50,34 +55,33 @@
private lateinit var underTest: CaptioningRepository
- private val testScope = TestScope()
-
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
underTest =
- CaptioningRepositoryImpl(
- captioningManager,
- testScope.testScheduler,
- testScope.backgroundScope
- )
+ with(kosmos) {
+ CaptioningRepositoryImpl(
+ FakeUserScopedService(captioningManager),
+ userRepository,
+ testScope.testScheduler,
+ applicationCoroutineScope,
+ )
+ }
}
@Test
fun isSystemAudioCaptioningEnabled_change_repositoryEmits() {
- testScope.runTest {
- `when`(captioningManager.isEnabled).thenReturn(false)
- val isSystemAudioCaptioningEnabled = mutableListOf<Boolean>()
- underTest.isSystemAudioCaptioningEnabled
- .onEach { isSystemAudioCaptioningEnabled.add(it) }
- .launchIn(backgroundScope)
+ kosmos.testScope.runTest {
+ `when`(captioningManager.isSystemAudioCaptioningEnabled).thenReturn(false)
+ val models by collectValues(underTest.captioningModel.filterNotNull())
runCurrent()
+ `when`(captioningManager.isSystemAudioCaptioningEnabled).thenReturn(true)
triggerOnSystemAudioCaptioningChange()
runCurrent()
- assertThat(isSystemAudioCaptioningEnabled)
+ assertThat(models.map { it.isSystemAudioCaptioningEnabled })
.containsExactlyElementsIn(listOf(false, true))
.inOrder()
}
@@ -85,18 +89,16 @@
@Test
fun isSystemAudioCaptioningUiEnabled_change_repositoryEmits() {
- testScope.runTest {
- `when`(captioningManager.isSystemAudioCaptioningUiEnabled).thenReturn(false)
- val isSystemAudioCaptioningUiEnabled = mutableListOf<Boolean>()
- underTest.isSystemAudioCaptioningUiEnabled
- .onEach { isSystemAudioCaptioningUiEnabled.add(it) }
- .launchIn(backgroundScope)
+ kosmos.testScope.runTest {
+ `when`(captioningManager.isEnabled).thenReturn(false)
+ val models by collectValues(underTest.captioningModel.filterNotNull())
runCurrent()
+ `when`(captioningManager.isSystemAudioCaptioningUiEnabled).thenReturn(true)
triggerSystemAudioCaptioningUiChange()
runCurrent()
- assertThat(isSystemAudioCaptioningUiEnabled)
+ assertThat(models.map { it.isSystemAudioCaptioningUiEnabled })
.containsExactlyElementsIn(listOf(false, true))
.inOrder()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
index 3b0057d..e531e65 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalDreamStartableTest.kt
@@ -22,6 +22,7 @@
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -73,6 +74,7 @@
keyguardInteractor = kosmos.keyguardInteractor,
keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
dreamManager = dreamManager,
+ communalSceneInteractor = kosmos.communalSceneInteractor,
bgScope = kosmos.applicationCoroutineScope,
)
.apply { start() }
@@ -158,6 +160,36 @@
}
}
+ @Test
+ fun shouldNotStartDreamWhenLaunchingWidget() =
+ testScope.runTest {
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setDreaming(false)
+ powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ kosmos.communalSceneInteractor.setIsLaunchingWidget(true)
+ whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
+ runCurrent()
+
+ transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)
+
+ verify(dreamManager, never()).startDream()
+ }
+
+ @Test
+ fun shouldNotStartDreamWhenOccluded() =
+ testScope.runTest {
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setDreaming(false)
+ powerRepository.setScreenPowerState(ScreenPowerState.SCREEN_ON)
+ keyguardRepository.setKeyguardOccluded(true)
+ whenever(dreamManager.canStartDreaming(/* isScreenOn= */ true)).thenReturn(true)
+ runCurrent()
+
+ transition(from = KeyguardState.DREAMING, to = KeyguardState.GLANCEABLE_HUB)
+
+ verify(dreamManager, never()).startDream()
+ }
+
private suspend fun TestScope.transition(from: KeyguardState, to: KeyguardState) {
kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
from = from,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index 840aa92..26e1a4d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -26,6 +26,7 @@
import com.android.settingslib.notification.data.repository.updateNotificationPolicy
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.andSceneContainer
@@ -36,6 +37,7 @@
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.statusbar.data.repository.fakeRemoteInputRepository
import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRowRepository
@@ -51,6 +53,7 @@
import com.android.systemui.util.ui.value
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -153,7 +156,7 @@
fun shouldShowEmptyShadeView_trueWhenNoNotifs() =
testScope.runTest {
val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView)
- val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView)
+ val shouldIncludeFooterView by collectFooterViewVisibility()
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
@@ -196,7 +199,7 @@
fun shouldShowEmptyShadeView_trueWhenQsExpandedInSplitShade() =
testScope.runTest {
val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView)
- val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView)
+ val shouldIncludeFooterView by collectFooterViewVisibility()
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
@@ -217,7 +220,7 @@
fun shouldShowEmptyShadeView_trueWhenLockedShade() =
testScope.runTest {
val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView)
- val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView)
+ val shouldIncludeFooterView by collectFooterViewVisibility()
// WHEN has no notifs
activeNotificationListRepository.setActiveNotifs(count = 0)
@@ -315,7 +318,7 @@
@Test
fun shouldIncludeFooterView_trueWhenShade() =
testScope.runTest {
- val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView)
+ val shouldIncludeFooterView by collectFooterViewVisibility()
val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has notifs
@@ -333,7 +336,7 @@
@Test
fun shouldIncludeFooterView_trueWhenLockedShade() =
testScope.runTest {
- val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView)
+ val shouldIncludeFooterView by collectFooterViewVisibility()
val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has notifs
@@ -351,7 +354,7 @@
@Test
fun shouldIncludeFooterView_falseWhenKeyguard() =
testScope.runTest {
- val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView)
+ val shouldInclude by collectFooterViewVisibility()
// WHEN has notifs
activeNotificationListRepository.setActiveNotifs(count = 2)
@@ -366,7 +369,7 @@
@Test
fun shouldIncludeFooterView_falseWhenUserNotSetUp() =
testScope.runTest {
- val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView)
+ val shouldInclude by collectFooterViewVisibility()
// WHEN has notifs
activeNotificationListRepository.setActiveNotifs(count = 2)
@@ -384,7 +387,7 @@
@Test
fun shouldIncludeFooterView_falseWhenStartingToSleep() =
testScope.runTest {
- val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView)
+ val shouldInclude by collectFooterViewVisibility()
// WHEN has notifs
activeNotificationListRepository.setActiveNotifs(count = 2)
@@ -402,7 +405,7 @@
@Test
fun shouldIncludeFooterView_falseWhenQsExpandedDefault() =
testScope.runTest {
- val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView)
+ val shouldInclude by collectFooterViewVisibility()
// WHEN has notifs
activeNotificationListRepository.setActiveNotifs(count = 2)
@@ -421,7 +424,7 @@
@Test
fun shouldIncludeFooterView_trueWhenQsExpandedSplitShade() =
testScope.runTest {
- val shouldIncludeFooterView by collectLastValue(underTest.shouldIncludeFooterView)
+ val shouldIncludeFooterView by collectFooterViewVisibility()
val shouldShowEmptyShadeView by collectLastValue(underTest.shouldShowEmptyShadeView)
// WHEN has notifs
@@ -444,7 +447,7 @@
@Test
fun shouldIncludeFooterView_falseWhenRemoteInputActive() =
testScope.runTest {
- val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView)
+ val shouldInclude by collectFooterViewVisibility()
// WHEN has notifs
activeNotificationListRepository.setActiveNotifs(count = 2)
@@ -462,7 +465,7 @@
@Test
fun shouldIncludeFooterView_animatesWhenShade() =
testScope.runTest {
- val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView)
+ val shouldInclude by collectFooterViewVisibility()
// WHEN has notifs
activeNotificationListRepository.setActiveNotifs(count = 2)
@@ -478,7 +481,7 @@
@Test
fun shouldIncludeFooterView_notAnimatingOnKeyguard() =
testScope.runTest {
- val shouldInclude by collectLastValue(underTest.shouldIncludeFooterView)
+ val shouldInclude by collectFooterViewVisibility()
// WHEN has notifs
activeNotificationListRepository.setActiveNotifs(count = 2)
@@ -492,6 +495,22 @@
}
@Test
+ @EnableSceneContainer
+ fun shouldShowFooterView_falseWhenShadeIsClosed() =
+ testScope.runTest {
+ val shouldShow by collectLastValue(underTest.shouldShowFooterView)
+
+ // WHEN shade is closed
+ fakeKeyguardRepository.setStatusBarState(StatusBarState.SHADE)
+ shadeTestUtil.setShadeExpansion(0f)
+ runCurrent()
+
+ // THEN footer is hidden
+ assertThat(shouldShow?.value).isFalse()
+ }
+
+ @Test
+ @DisableSceneContainer
fun shouldHideFooterView_trueWhenShadeIsClosed() =
testScope.runTest {
val shouldHide by collectLastValue(underTest.shouldHideFooterView)
@@ -506,6 +525,7 @@
}
@Test
+ @DisableSceneContainer
fun shouldHideFooterView_falseWhenShadeIsOpen() =
testScope.runTest {
val shouldHide by collectLastValue(underTest.shouldHideFooterView)
@@ -520,6 +540,7 @@
}
@Test
+ @DisableSceneContainer
fun shouldHideFooterView_falseWhenQSPartiallyOpen() =
testScope.runTest {
val shouldHide by collectLastValue(underTest.shouldHideFooterView)
@@ -642,4 +663,10 @@
assertThat(animationsEnabled).isTrue()
}
+
+ private fun TestScope.collectFooterViewVisibility() =
+ collectLastValue(
+ if (SceneContainerFlag.isEnabled) underTest.shouldShowFooterView
+ else underTest.shouldIncludeFooterView
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/model/CaptioningModel.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/CaptioningModel.kt
new file mode 100644
index 0000000..4eb2274
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/model/CaptioningModel.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.model
+
+data class CaptioningModel(
+ val isSystemAudioCaptioningUiEnabled: Boolean,
+ val isSystemAudioCaptioningEnabled: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt
index bf749d4..5414b62 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt
@@ -16,98 +16,90 @@
package com.android.systemui.accessibility.data.repository
+import android.annotation.SuppressLint
import android.view.accessibility.CaptioningManager
+import com.android.systemui.accessibility.data.model.CaptioningModel
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.user.utils.UserScopedService
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.ProducerScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
interface CaptioningRepository {
- /** The system audio caption enabled state. */
- val isSystemAudioCaptioningEnabled: StateFlow<Boolean>
+ /** Current state of Live Captions. */
+ val captioningModel: StateFlow<CaptioningModel?>
- /** The system audio caption UI enabled state. */
- val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean>
-
- /** Sets [isSystemAudioCaptioningEnabled]. */
+ /** Sets [CaptioningModel.isSystemAudioCaptioningEnabled]. */
suspend fun setIsSystemAudioCaptioningEnabled(isEnabled: Boolean)
}
-class CaptioningRepositoryImpl(
- private val captioningManager: CaptioningManager,
- private val backgroundCoroutineContext: CoroutineContext,
- coroutineScope: CoroutineScope,
+@OptIn(ExperimentalCoroutinesApi::class)
+class CaptioningRepositoryImpl
+@Inject
+constructor(
+ private val userScopedCaptioningManagerProvider: UserScopedService<CaptioningManager>,
+ userRepository: UserRepository,
+ @Background private val backgroundCoroutineContext: CoroutineContext,
+ @Application coroutineScope: CoroutineScope,
) : CaptioningRepository {
- private val captioningChanges: SharedFlow<CaptioningChange> =
- callbackFlow {
- val listener = CaptioningChangeProducingListener(this)
- captioningManager.addCaptioningChangeListener(listener)
- awaitClose { captioningManager.removeCaptioningChangeListener(listener) }
- }
- .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 0)
+ @SuppressLint("NonInjectedService") // this uses user-aware context
+ private val captioningManager: StateFlow<CaptioningManager?> =
+ userRepository.selectedUser
+ .map { userScopedCaptioningManagerProvider.forUser(it.userInfo.userHandle) }
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
- override val isSystemAudioCaptioningEnabled: StateFlow<Boolean> =
- captioningChanges
- .filterIsInstance(CaptioningChange.IsSystemAudioCaptioningEnabled::class)
- .map { it.isEnabled }
- .onStart { emit(captioningManager.isSystemAudioCaptioningEnabled) }
- .stateIn(
- coroutineScope,
- SharingStarted.WhileSubscribed(),
- captioningManager.isSystemAudioCaptioningEnabled,
- )
-
- override val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean> =
- captioningChanges
- .filterIsInstance(CaptioningChange.IsSystemUICaptioningEnabled::class)
- .map { it.isEnabled }
- .onStart { emit(captioningManager.isSystemAudioCaptioningUiEnabled) }
- .stateIn(
- coroutineScope,
- SharingStarted.WhileSubscribed(),
- captioningManager.isSystemAudioCaptioningUiEnabled,
- )
+ override val captioningModel: StateFlow<CaptioningModel?> =
+ captioningManager
+ .filterNotNull()
+ .flatMapLatest { it.captioningModel() }
+ .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
override suspend fun setIsSystemAudioCaptioningEnabled(isEnabled: Boolean) {
withContext(backgroundCoroutineContext) {
- captioningManager.isSystemAudioCaptioningEnabled = isEnabled
+ captioningManager.value?.isSystemAudioCaptioningEnabled = isEnabled
}
}
- private sealed interface CaptioningChange {
+ private fun CaptioningManager.captioningModel(): Flow<CaptioningModel> {
+ return conflatedCallbackFlow {
+ val listener =
+ object : CaptioningManager.CaptioningChangeListener() {
- data class IsSystemAudioCaptioningEnabled(val isEnabled: Boolean) : CaptioningChange
+ override fun onSystemAudioCaptioningChanged(enabled: Boolean) {
+ trySend(Unit)
+ }
- data class IsSystemUICaptioningEnabled(val isEnabled: Boolean) : CaptioningChange
- }
-
- private class CaptioningChangeProducingListener(
- private val scope: ProducerScope<CaptioningChange>
- ) : CaptioningManager.CaptioningChangeListener() {
-
- override fun onSystemAudioCaptioningChanged(enabled: Boolean) {
- emitChange(CaptioningChange.IsSystemAudioCaptioningEnabled(enabled))
- }
-
- override fun onSystemAudioCaptioningUiChanged(enabled: Boolean) {
- emitChange(CaptioningChange.IsSystemUICaptioningEnabled(enabled))
- }
-
- private fun emitChange(change: CaptioningChange) {
- scope.launch { scope.send(change) }
- }
+ override fun onSystemAudioCaptioningUiChanged(enabled: Boolean) {
+ trySend(Unit)
+ }
+ }
+ addCaptioningChangeListener(listener)
+ awaitClose { removeCaptioningChangeListener(listener) }
+ }
+ .onStart { emit(Unit) }
+ .map {
+ CaptioningModel(
+ isSystemAudioCaptioningEnabled = isSystemAudioCaptioningEnabled,
+ isSystemAudioCaptioningUiEnabled = isSystemAudioCaptioningUiEnabled,
+ )
+ }
+ .flowOn(backgroundCoroutineContext)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/CaptioningInteractor.kt b/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/CaptioningInteractor.kt
index 1d493c6..840edf4 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/CaptioningInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/domain/interactor/CaptioningInteractor.kt
@@ -17,16 +17,22 @@
package com.android.systemui.accessibility.domain.interactor
import com.android.systemui.accessibility.data.repository.CaptioningRepository
-import kotlinx.coroutines.flow.StateFlow
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
-class CaptioningInteractor(private val repository: CaptioningRepository) {
+@SysUISingleton
+class CaptioningInteractor @Inject constructor(private val repository: CaptioningRepository) {
- val isSystemAudioCaptioningEnabled: StateFlow<Boolean>
- get() = repository.isSystemAudioCaptioningEnabled
+ val isSystemAudioCaptioningEnabled: Flow<Boolean> =
+ repository.captioningModel.filterNotNull().map { it.isSystemAudioCaptioningEnabled }
- val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean>
- get() = repository.isSystemAudioCaptioningUiEnabled
+ val isSystemAudioCaptioningUiEnabled: Flow<Boolean> =
+ repository.captioningModel.filterNotNull().map { it.isSystemAudioCaptioningUiEnabled }
- suspend fun setIsSystemAudioCaptioningEnabled(enabled: Boolean) =
+ suspend fun setIsSystemAudioCaptioningEnabled(enabled: Boolean) {
repository.setIsSystemAudioCaptioningEnabled(enabled)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
index c69cea4..04393fe 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
@@ -21,6 +21,7 @@
import com.android.systemui.CoreStartable
import com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming
import com.android.systemui.Flags.restartDreamOnUnocclude
+import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -55,6 +56,7 @@
private val keyguardInteractor: KeyguardInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val dreamManager: DreamManager,
+ private val communalSceneInteractor: CommunalSceneInteractor,
@Background private val bgScope: CoroutineScope,
) : CoreStartable {
/** Flow that emits when the dream should be started underneath the glanceable hub. */
@@ -66,6 +68,8 @@
not(keyguardInteractor.isDreaming),
// TODO(b/362830856): Remove this workaround.
keyguardInteractor.isKeyguardShowing,
+ not(communalSceneInteractor.isLaunchingWidget),
+ not(keyguardInteractor.isKeyguardOccluded),
)
.filter { it }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 21a704d..8818c3a 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -202,6 +202,13 @@
return context.getSystemService(CaptioningManager.class);
}
+ @Provides
+ @Singleton
+ static UserScopedService<CaptioningManager> provideUserScopedCaptioningManager(
+ Context context) {
+ return new UserScopedServiceImpl<>(context, CaptioningManager.class);
+ }
+
/** */
@Provides
@Singleton
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index cbcf68c..2f843ac 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -50,10 +50,12 @@
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
+import com.android.systemui.Flags;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.time.SystemClock;
import dagger.assisted.Assisted;
import dagger.assisted.AssistedFactory;
@@ -95,6 +97,7 @@
// Bind retry control.
private static final int MAX_BIND_RETRIES = 5;
private static final long DEFAULT_BIND_RETRY_DELAY = 5 * DateUtils.SECOND_IN_MILLIS;
+ private static final long ACTIVE_TILE_BIND_RETRY_DELAY = 1 * DateUtils.SECOND_IN_MILLIS;
private static final long LOW_MEMORY_BIND_RETRY_DELAY = 20 * DateUtils.SECOND_IN_MILLIS;
private static final long TILE_SERVICE_ONCLICK_ALLOW_LIST_DEFAULT_DURATION_MS = 15_000;
private static final String PROPERTY_TILE_SERVICE_ONCLICK_ALLOW_LIST_DURATION =
@@ -107,6 +110,7 @@
private final Intent mIntent;
private final UserHandle mUser;
private final DelayableExecutor mExecutor;
+ private final SystemClock mSystemClock;
private final IBinder mToken = new Binder();
private final PackageManagerAdapter mPackageManagerAdapter;
private final BroadcastDispatcher mBroadcastDispatcher;
@@ -120,7 +124,6 @@
private IBinder mClickBinder;
private int mBindTryCount;
- private long mBindRetryDelay = DEFAULT_BIND_RETRY_DELAY;
private AtomicBoolean isDeathRebindScheduled = new AtomicBoolean(false);
private AtomicBoolean mBound = new AtomicBoolean(false);
private AtomicBoolean mPackageReceiverRegistered = new AtomicBoolean(false);
@@ -138,7 +141,8 @@
TileLifecycleManager(@Main Handler handler, Context context, IQSService service,
PackageManagerAdapter packageManagerAdapter, BroadcastDispatcher broadcastDispatcher,
@Assisted Intent intent, @Assisted UserHandle user, ActivityManager activityManager,
- IDeviceIdleController deviceIdleController, @Background DelayableExecutor executor) {
+ IDeviceIdleController deviceIdleController, @Background DelayableExecutor executor,
+ SystemClock systemClock) {
mContext = context;
mHandler = handler;
mIntent = intent;
@@ -146,6 +150,7 @@
mIntent.putExtra(TileService.EXTRA_TOKEN, mToken);
mUser = user;
mExecutor = executor;
+ mSystemClock = systemClock;
mPackageManagerAdapter = packageManagerAdapter;
mBroadcastDispatcher = broadcastDispatcher;
mActivityManager = activityManager;
@@ -436,25 +441,31 @@
// If mBound is true (meaning that we should be bound), then reschedule binding for
// later.
if (mBound.get() && checkComponentState()) {
- if (isDeathRebindScheduled.compareAndSet(false, true)) {
+ if (isDeathRebindScheduled.compareAndSet(false, true)) { // if already not scheduled
+
+
mExecutor.executeDelayed(() -> {
// Only rebind if we are supposed to, but remove the scheduling anyway.
if (mBound.get()) {
setBindService(true);
}
- isDeathRebindScheduled.set(false);
+ isDeathRebindScheduled.set(false); // allow scheduling again
}, getRebindDelay());
}
}
});
}
+ private long mLastRebind = 0;
/**
* @return the delay to automatically rebind after a service died. It provides a longer delay if
* the device is a low memory state because the service is likely to get killed again by the
* system. In this case we want to rebind later and not to cause a loop of a frequent rebinds.
+ * It also provides a longer delay if called quickly (a few seconds) after a first call.
*/
private long getRebindDelay() {
+ final long now = mSystemClock.currentTimeMillis();
+
final ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
mActivityManager.getMemoryInfo(info);
@@ -462,7 +473,20 @@
if (info.lowMemory) {
delay = LOW_MEMORY_BIND_RETRY_DELAY;
} else {
- delay = mBindRetryDelay;
+ if (Flags.qsQuickRebindActiveTiles()) {
+ final long elapsedTimeSinceLastRebind = now - mLastRebind;
+ final boolean justAttemptedRebind =
+ elapsedTimeSinceLastRebind < DEFAULT_BIND_RETRY_DELAY;
+ if (isActiveTile() && !justAttemptedRebind) {
+ delay = ACTIVE_TILE_BIND_RETRY_DELAY;
+ } else {
+ delay = DEFAULT_BIND_RETRY_DELAY;
+ }
+ } else {
+ delay = DEFAULT_BIND_RETRY_DELAY;
+ }
+
+ mLastRebind = now;
}
if (mDebug) Log.i(TAG, "Rebinding with a delay=" + delay + " - " + getComponent());
return delay;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index d10471d..c5fa8cf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -44,7 +44,7 @@
/**
* Manages the priority which lets {@link TileServices} make decisions about which tiles
* to bind. Also holds on to and manages the {@link TileLifecycleManager}, informing it
- * of when it is allowed to bind based on decisions frome the {@link TileServices}.
+ * of when it is allowed to bind based on decisions from the {@link TileServices}.
*/
public class TileServiceManager {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt
index 37ac7c4..38cab82 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionLogger.kt
@@ -108,7 +108,7 @@
TAG,
INFO,
{ bool1 = isEnabled },
- { "Cooldown enabled: $isEnabled" }
+ { "Cooldown enabled: $bool1" }
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
index 580431a..969ff1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SectionHeaderView.java
@@ -68,6 +68,7 @@
if (mLabelTextId != null) {
mLabelView.setText(mLabelTextId);
}
+ mLabelView.setAccessibilityHeading(true);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index ef1bcfc..cccac4b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -682,7 +682,10 @@
// doesn't get updated quickly enough and can cause the footer to flash when
// closing the shade. As such, we temporarily also check the ambientState directly.
if (((FooterView) view).shouldBeHidden() || !ambientState.isShadeExpanded()) {
- viewState.hidden = true;
+ // Note: This is no longer necessary in flexiglass.
+ if (!SceneContainerFlag.isEnabled()) {
+ viewState.hidden = true;
+ }
} else {
final float footerEnd = algorithmState.mCurrentExpandedYPosition
+ view.getIntrinsicHeight();
@@ -691,7 +694,6 @@
noSpaceForFooter || (ambientState.isClearAllInProgress()
&& !hasNonClearableNotifs(algorithmState));
}
-
} else {
final boolean shadeClosed = !ambientState.isShadeExpanded();
final boolean isShelfShowing = algorithmState.firstViewInShelf != null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index d770b20..dc9615c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -188,15 +188,26 @@
.startHistoryIntent(view, /* showHistory= */ true)
},
)
- launch {
- viewModel.shouldIncludeFooterView.collect { animatedVisibility ->
- footerView.setVisible(
- /* visible = */ animatedVisibility.value,
- /* animate = */ animatedVisibility.isAnimating,
- )
+ if (SceneContainerFlag.isEnabled) {
+ launch {
+ viewModel.shouldShowFooterView.collect { animatedVisibility ->
+ footerView.setVisible(
+ /* visible = */ animatedVisibility.value,
+ /* animate = */ animatedVisibility.isAnimating,
+ )
+ }
}
+ } else {
+ launch {
+ viewModel.shouldIncludeFooterView.collect { animatedVisibility ->
+ footerView.setVisible(
+ /* visible = */ animatedVisibility.value,
+ /* animate = */ animatedVisibility.isAnimating,
+ )
+ }
+ }
+ launch { viewModel.shouldHideFooterView.collect { footerView.setShouldBeHidden(it) } }
}
- launch { viewModel.shouldHideFooterView.collect { footerView.setShouldBeHidden(it) } }
disposableHandle.awaitCancellationThenDispose()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index e55492e6..4e2a46d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -32,6 +32,7 @@
import com.android.systemui.statusbar.policy.domain.interactor.UserSetupInteractor
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import com.android.systemui.util.kotlin.FlowDumperImpl
+import com.android.systemui.util.kotlin.combine
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.ui.AnimatableEvent
import com.android.systemui.util.ui.AnimatedValue
@@ -120,6 +121,7 @@
* This essentially corresponds to having the view set to INVISIBLE.
*/
val shouldHideFooterView: Flow<Boolean> by lazy {
+ SceneContainerFlag.assertInLegacyMode()
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(false)
} else {
@@ -143,6 +145,7 @@
* be hidden by another condition (see [shouldHideFooterView] above).
*/
val shouldIncludeFooterView: Flow<AnimatedValue<Boolean>> by lazy {
+ SceneContainerFlag.assertInLegacyMode()
if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
flowOf(AnimatedValue.NotAnimating(false))
} else {
@@ -207,6 +210,76 @@
}
}
+ // This flow replaces shouldHideFooterView+shouldIncludeFooterView in flexiglass.
+ val shouldShowFooterView: Flow<AnimatedValue<Boolean>> by lazy {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
+ flowOf(AnimatedValue.NotAnimating(false))
+ } else {
+ combine(
+ activeNotificationsInteractor.areAnyNotificationsPresent,
+ userSetupInteractor.isUserSetUp,
+ notificationStackInteractor.isShowingOnLockscreen,
+ shadeInteractor.isQsFullscreen,
+ remoteInputInteractor.isRemoteInputActive,
+ shadeInteractor.shadeExpansion.map { it < 0.5f }.distinctUntilChanged(),
+ ) {
+ hasNotifications,
+ isUserSetUp,
+ isShowingOnLockscreen,
+ qsFullScreen,
+ isRemoteInputActive,
+ shadeLessThanHalfwayExpanded ->
+ when {
+ !hasNotifications -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
+ // Hide the footer until the user setup is complete, to prevent access
+ // to settings (b/193149550).
+ !isUserSetUp -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
+ // Do not show the footer if the lockscreen is visible (incl. AOD),
+ // except if the shade is opened on top. See also b/219680200.
+ // Do not animate, as that makes the footer appear briefly when
+ // transitioning between the shade and keyguard.
+ isShowingOnLockscreen -> VisibilityChange.DISAPPEAR_WITHOUT_ANIMATION
+ // Do not show the footer if quick settings are fully expanded (except
+ // for the foldable split shade view). See b/201427195 && b/222699879.
+ qsFullScreen -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
+ // Hide the footer if remote input is active (i.e. user is replying to a
+ // notification). See b/75984847.
+ isRemoteInputActive -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
+ // If the shade is not expanded enough, the footer shouldn't be visible.
+ shadeLessThanHalfwayExpanded -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
+ else -> VisibilityChange.APPEAR_WITH_ANIMATION
+ }
+ }
+ .distinctUntilChanged(
+ // Equivalent unless visibility changes
+ areEquivalent = { a: VisibilityChange, b: VisibilityChange ->
+ a.visible == b.visible
+ }
+ )
+ // Should we animate the visibility change?
+ .sample(
+ // TODO(b/322167853): This check is currently duplicated in FooterViewModel,
+ // but instead it should be a field in ShadeAnimationInteractor.
+ combine(
+ shadeInteractor.isShadeFullyExpanded,
+ shadeInteractor.isShadeTouchable,
+ ::Pair
+ )
+ .onStart { emit(Pair(false, false)) }
+ ) { visibilityChange, (isShadeFullyExpanded, animationsEnabled) ->
+ // Animate if the shade is interactive, but NOT on the lockscreen. Having
+ // animations enabled while on the lockscreen makes the footer appear briefly
+ // when transitioning between the shade and keyguard.
+ val shouldAnimate =
+ isShadeFullyExpanded && animationsEnabled && visibilityChange.canAnimate
+ AnimatableEvent(visibilityChange.visible, shouldAnimate)
+ }
+ .toAnimatedValueFlow()
+ .dumpWhileCollecting("shouldShowFooterView")
+ .flowOn(bgDispatcher)
+ }
+ }
+
enum class VisibilityChange(val visible: Boolean, val canAnimate: Boolean) {
DISAPPEAR_WITHOUT_ANIMATION(visible = false, canAnimate = false),
DISAPPEAR_WITH_ANIMATION(visible = false, canAnimate = true),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index dd4b000..f3b9371 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -182,6 +182,7 @@
private boolean mBouncerShowingOverDream;
private int mAttemptsToShowBouncer = 0;
private DelayableExecutor mExecutor;
+ private boolean mIsSleeping = false;
private final PrimaryBouncerExpansionCallback mExpansionCallback =
new PrimaryBouncerExpansionCallback() {
@@ -713,7 +714,11 @@
* {@link #needsFullscreenBouncer()}.
*/
protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing, boolean isFalsingReset) {
- if (needsFullscreenBouncer() && !mDozing) {
+ boolean showBouncer = needsFullscreenBouncer() && !mDozing;
+ if (Flags.simPinRaceConditionOnRestart()) {
+ showBouncer = showBouncer && !mIsSleeping;
+ }
+ if (showBouncer) {
// The keyguard might be showing (already). So we need to hide it.
if (!primaryBouncerIsShowing()) {
if (SceneContainerFlag.isEnabled()) {
@@ -1041,6 +1046,7 @@
@Override
public void onStartedWakingUp() {
+ mIsSleeping = false;
setRootViewAnimationDisabled(false);
NavigationBarView navBarView = mCentralSurfaces.getNavigationBarView();
if (navBarView != null) {
@@ -1054,6 +1060,7 @@
@Override
public void onStartedGoingToSleep() {
+ mIsSleeping = true;
setRootViewAnimationDisabled(true);
NavigationBarView navBarView = mCentralSurfaces.getNavigationBarView();
if (navBarView != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/CaptioningModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/CaptioningModule.kt
index 9715772..28a43df 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/CaptioningModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/CaptioningModule.kt
@@ -16,35 +16,16 @@
package com.android.systemui.volume.dagger
-import android.view.accessibility.CaptioningManager
import com.android.systemui.accessibility.data.repository.CaptioningRepository
import com.android.systemui.accessibility.data.repository.CaptioningRepositoryImpl
-import com.android.systemui.accessibility.domain.interactor.CaptioningInteractor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
+import dagger.Binds
import dagger.Module
-import dagger.Provides
-import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.CoroutineScope
@Module
interface CaptioningModule {
- companion object {
-
- @Provides
- @SysUISingleton
- fun provideCaptioningRepository(
- captioningManager: CaptioningManager,
- @Background coroutineContext: CoroutineContext,
- @Application coroutineScope: CoroutineScope,
- ): CaptioningRepository =
- CaptioningRepositoryImpl(captioningManager, coroutineContext, coroutineScope)
-
- @Provides
- @SysUISingleton
- fun provideCaptioningInteractor(repository: CaptioningRepository): CaptioningInteractor =
- CaptioningInteractor(repository)
- }
+ @Binds
+ @SysUISingleton
+ fun bindCaptioningRepository(impl: CaptioningRepositoryImpl): CaptioningRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt
index 52f2ce6..2e5e389 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/captioning/domain/CaptioningAvailabilityCriteria.kt
@@ -26,7 +26,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.stateIn
@VolumePanelScope
class CaptioningAvailabilityCriteria
@@ -45,7 +45,7 @@
else VolumePanelUiEvent.VOLUME_PANEL_LIVE_CAPTION_TOGGLE_GONE
)
}
- .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1)
+ .stateIn(scope, SharingStarted.WhileSubscribed(), false)
override fun isAvailable(): Flow<Boolean> = availability
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
index 02a5c46..22946c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
@@ -69,6 +69,8 @@
import com.android.systemui.scene.ui.composable.Scene
import com.android.systemui.scene.ui.composable.SceneContainer
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.testKosmos
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.Flow
@@ -131,6 +133,8 @@
sceneInteractor = kosmos.sceneInteractor,
falsingInteractor = kosmos.falsingInteractor,
powerInteractor = kosmos.powerInteractor,
+ shadeInteractor = kosmos.shadeInteractor,
+ splitEdgeDetector = kosmos.splitEdgeDetector,
logger = kosmos.sceneLogger,
motionEventHandlerReceiver = {},
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index c1cf91d..bc0ec2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -22,6 +22,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX;
+import static com.android.systemui.Flags.FLAG_QS_QUICK_REBIND_ACTIVE_TILES;
import static com.google.common.truth.Truth.assertThat;
@@ -75,6 +76,8 @@
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
+import com.google.common.truth.Truth;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -95,7 +98,8 @@
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
- return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX);
+ return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX,
+ FLAG_QS_QUICK_REBIND_ACTIVE_TILES);
}
private final PackageManagerAdapter mMockPackageManagerAdapter =
@@ -154,7 +158,8 @@
mUser,
mActivityManager,
mDeviceIdleController,
- mExecutor);
+ mExecutor,
+ mClock);
}
@After
@@ -169,12 +174,12 @@
mStateManager.handleDestroy();
}
- private void setPackageEnabled(boolean enabled) throws Exception {
+ private void setPackageEnabledAndActive(boolean enabled, boolean active) throws Exception {
ServiceInfo defaultServiceInfo = null;
if (enabled) {
defaultServiceInfo = new ServiceInfo();
defaultServiceInfo.metaData = new Bundle();
- defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_ACTIVE_TILE, true);
+ defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_ACTIVE_TILE, active);
defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_TOGGLEABLE_TILE, true);
}
when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt(), anyInt()))
@@ -186,6 +191,10 @@
.thenReturn(defaultPackageInfo);
}
+ private void setPackageEnabled(boolean enabled) throws Exception {
+ setPackageEnabledAndActive(enabled, true);
+ }
+
private void setPackageInstalledForUser(
boolean installed,
boolean active,
@@ -396,13 +405,40 @@
}
@Test
- public void testKillProcess() throws Exception {
+ public void testKillProcessWhenTileServiceIsNotActive() throws Exception {
+ setPackageEnabledAndActive(true, false);
mStateManager.onStartListening();
mStateManager.executeSetBindService(true);
mExecutor.runAllReady();
+ verifyBind(1);
+ verify(mMockTileService, times(1)).onStartListening();
+
mStateManager.onBindingDied(mTileServiceComponentName);
mExecutor.runAllReady();
- mClock.advanceTime(5000);
+ mClock.advanceTime(1000);
+ mExecutor.runAllReady();
+
+ // still 4 seconds left because non active tile service rebind time is 5 seconds
+ Truth.assertThat(mContext.isBound(mTileServiceComponentName)).isFalse();
+
+ mClock.advanceTime(4000); // 5 seconds delay for nonActive service rebinding
+ mExecutor.runAllReady();
+ verifyBind(2);
+ verify(mMockTileService, times(2)).onStartListening();
+ }
+
+ @EnableFlags(FLAG_QS_QUICK_REBIND_ACTIVE_TILES)
+ @Test
+ public void testKillProcessWhenTileServiceIsActive_withRebindFlagOn() throws Exception {
+ mStateManager.onStartListening();
+ mStateManager.executeSetBindService(true);
+ mExecutor.runAllReady();
+ verifyBind(1);
+ verify(mMockTileService, times(1)).onStartListening();
+
+ mStateManager.onBindingDied(mTileServiceComponentName);
+ mExecutor.runAllReady();
+ mClock.advanceTime(1000);
mExecutor.runAllReady();
// Two calls: one for the first bind, one for the restart.
@@ -410,6 +446,86 @@
verify(mMockTileService, times(2)).onStartListening();
}
+ @DisableFlags(FLAG_QS_QUICK_REBIND_ACTIVE_TILES)
+ @Test
+ public void testKillProcessWhenTileServiceIsActive_withRebindFlagOff() throws Exception {
+ mStateManager.onStartListening();
+ mStateManager.executeSetBindService(true);
+ mExecutor.runAllReady();
+ verifyBind(1);
+ verify(mMockTileService, times(1)).onStartListening();
+
+ mStateManager.onBindingDied(mTileServiceComponentName);
+ mExecutor.runAllReady();
+ mClock.advanceTime(1000);
+ mExecutor.runAllReady();
+ verifyBind(0); // the rebind happens after 4 more seconds
+
+ mClock.advanceTime(4000);
+ mExecutor.runAllReady();
+ verifyBind(1);
+ }
+
+ @EnableFlags(FLAG_QS_QUICK_REBIND_ACTIVE_TILES)
+ @Test
+ public void testKillProcessWhenTileServiceIsActiveTwice_withRebindFlagOn_delaysSecondRebind()
+ throws Exception {
+ mStateManager.onStartListening();
+ mStateManager.executeSetBindService(true);
+ mExecutor.runAllReady();
+ verifyBind(1);
+ verify(mMockTileService, times(1)).onStartListening();
+
+ mStateManager.onBindingDied(mTileServiceComponentName);
+ mExecutor.runAllReady();
+ mClock.advanceTime(1000);
+ mExecutor.runAllReady();
+
+ // Two calls: one for the first bind, one for the restart.
+ verifyBind(2);
+ verify(mMockTileService, times(2)).onStartListening();
+
+ mStateManager.onBindingDied(mTileServiceComponentName);
+ mExecutor.runAllReady();
+ mClock.advanceTime(1000);
+ mExecutor.runAllReady();
+ // because active tile will take 5 seconds to bind the second time, not 1
+ verifyBind(0);
+
+ mClock.advanceTime(4000);
+ mExecutor.runAllReady();
+ verifyBind(1);
+ }
+
+ @DisableFlags(FLAG_QS_QUICK_REBIND_ACTIVE_TILES)
+ @Test
+ public void testKillProcessWhenTileServiceIsActiveTwice_withRebindFlagOff_rebindsFromFirstKill()
+ throws Exception {
+ mStateManager.onStartListening();
+ mStateManager.executeSetBindService(true);
+ mExecutor.runAllReady();
+ verifyBind(1);
+ verify(mMockTileService, times(1)).onStartListening();
+
+ mStateManager.onBindingDied(mTileServiceComponentName); // rebind scheduled for 5 seconds
+ mExecutor.runAllReady();
+ mClock.advanceTime(1000);
+ mExecutor.runAllReady();
+
+ verifyBind(0); // it would bind in 4 more seconds
+
+ mStateManager.onBindingDied(mTileServiceComponentName); // this does not affect the rebind
+ mExecutor.runAllReady();
+ mClock.advanceTime(1000);
+ mExecutor.runAllReady();
+
+ verifyBind(0); // only 2 seconds passed from first kill
+
+ mClock.advanceTime(3000);
+ mExecutor.runAllReady();
+ verifyBind(1); // the rebind scheduled 5 seconds from the first kill should now happen
+ }
+
@Test
public void testKillProcessLowMemory() throws Exception {
doAnswer(invocation -> {
@@ -510,7 +626,8 @@
mUser,
mActivityManager,
mDeviceIdleController,
- mExecutor);
+ mExecutor,
+ mClock);
manager.executeSetBindService(true);
mExecutor.runAllReady();
@@ -533,7 +650,8 @@
mUser,
mActivityManager,
mDeviceIdleController,
- mExecutor);
+ mExecutor,
+ mClock);
manager.executeSetBindService(true);
mExecutor.runAllReady();
@@ -556,7 +674,8 @@
mUser,
mActivityManager,
mDeviceIdleController,
- mExecutor);
+ mExecutor,
+ mClock);
manager.executeSetBindService(true);
mExecutor.runAllReady();
@@ -581,7 +700,8 @@
mUser,
mActivityManager,
mDeviceIdleController,
- mExecutor);
+ mExecutor,
+ mClock);
manager.executeSetBindService(true);
mExecutor.runAllReady();
@@ -607,7 +727,8 @@
mUser,
mActivityManager,
mDeviceIdleController,
- mExecutor);
+ mExecutor,
+ mClock);
assertThat(manager.isActiveTile()).isTrue();
}
@@ -626,7 +747,8 @@
mUser,
mActivityManager,
mDeviceIdleController,
- mExecutor);
+ mExecutor,
+ mClock);
assertThat(manager.isActiveTile()).isTrue();
}
@@ -644,7 +766,8 @@
mUser,
mActivityManager,
mDeviceIdleController,
- mExecutor);
+ mExecutor,
+ mClock);
assertThat(manager.isToggleableTile()).isTrue();
}
@@ -663,7 +786,8 @@
mUser,
mActivityManager,
mDeviceIdleController,
- mExecutor);
+ mExecutor,
+ mClock);
assertThat(manager.isToggleableTile()).isTrue();
}
@@ -682,7 +806,8 @@
mUser,
mActivityManager,
mDeviceIdleController,
- mExecutor);
+ mExecutor,
+ mClock);
assertThat(manager.isToggleableTile()).isFalse();
assertThat(manager.isActiveTile()).isFalse();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 01a3d36..1d74331 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -1112,9 +1112,11 @@
public void testShowBouncerOrKeyguard_showsKeyguardIfShowBouncerReturnsFalse() {
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.SimPin);
+ // Returning false means unable to show the bouncer
when(mPrimaryBouncerInteractor.show(true)).thenReturn(false);
when(mKeyguardTransitionInteractor.getTransitionState().getValue().getTo())
.thenReturn(KeyguardState.LOCKSCREEN);
+ mStatusBarKeyguardViewManager.onStartedWakingUp();
reset(mCentralSurfaces);
// Advance past reattempts
@@ -1127,6 +1129,23 @@
@Test
@DisableSceneContainer
+ @EnableFlags(Flags.FLAG_SIM_PIN_RACE_CONDITION_ON_RESTART)
+ public void testShowBouncerOrKeyguard_showsKeyguardIfSleeping() {
+ when(mKeyguardTransitionInteractor.getTransitionState().getValue().getTo())
+ .thenReturn(KeyguardState.LOCKSCREEN);
+ mStatusBarKeyguardViewManager.onStartedGoingToSleep();
+
+ reset(mCentralSurfaces);
+ reset(mPrimaryBouncerInteractor);
+ mStatusBarKeyguardViewManager.showBouncerOrKeyguard(
+ /* hideBouncerWhenShowing= */true, false);
+ verify(mCentralSurfaces).showKeyguard();
+ verify(mPrimaryBouncerInteractor).hide();
+ }
+
+
+ @Test
+ @DisableSceneContainer
public void testShowBouncerOrKeyguard_needsFullScreen_bouncerAlreadyShowing() {
boolean isFalsingReset = false;
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeCaptioningRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeCaptioningRepository.kt
index 2a0e764..a639463 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeCaptioningRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeCaptioningRepository.kt
@@ -16,25 +16,31 @@
package com.android.systemui.accessibility.data.repository
+import com.android.systemui.accessibility.data.model.CaptioningModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class FakeCaptioningRepository : CaptioningRepository {
- private val mutableIsSystemAudioCaptioningEnabled = MutableStateFlow(false)
- override val isSystemAudioCaptioningEnabled: StateFlow<Boolean>
- get() = mutableIsSystemAudioCaptioningEnabled.asStateFlow()
-
- private val mutableIsSystemAudioCaptioningUiEnabled = MutableStateFlow(false)
- override val isSystemAudioCaptioningUiEnabled: StateFlow<Boolean>
- get() = mutableIsSystemAudioCaptioningUiEnabled.asStateFlow()
+ private val mutableCaptioningModel = MutableStateFlow<CaptioningModel?>(null)
+ override val captioningModel: StateFlow<CaptioningModel?> = mutableCaptioningModel.asStateFlow()
override suspend fun setIsSystemAudioCaptioningEnabled(isEnabled: Boolean) {
- mutableIsSystemAudioCaptioningEnabled.value = isEnabled
+ mutableCaptioningModel.value =
+ CaptioningModel(
+ isSystemAudioCaptioningEnabled = isEnabled,
+ isSystemAudioCaptioningUiEnabled =
+ mutableCaptioningModel.value?.isSystemAudioCaptioningUiEnabled == true,
+ )
}
- fun setIsSystemAudioCaptioningUiEnabled(isSystemAudioCaptioningUiEnabled: Boolean) {
- mutableIsSystemAudioCaptioningUiEnabled.value = isSystemAudioCaptioningUiEnabled
+ fun setIsSystemAudioCaptioningUiEnabled(isEnabled: Boolean) {
+ mutableCaptioningModel.value =
+ CaptioningModel(
+ isSystemAudioCaptioningEnabled =
+ mutableCaptioningModel.value?.isSystemAudioCaptioningEnabled == true,
+ isSystemAudioCaptioningUiEnabled = isEnabled,
+ )
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt
index a0fc76b..4978558 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt
@@ -24,6 +24,7 @@
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.fakeSystemClock
val Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by
Kosmos.Fixture {
@@ -39,6 +40,7 @@
activityManager,
mock(),
fakeExecutor,
+ fakeSystemClock,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/utils/FakeUserScopedService.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/utils/FakeUserScopedService.kt
new file mode 100644
index 0000000..78763f9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/utils/FakeUserScopedService.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.user.utils
+
+import android.os.UserHandle
+
+class FakeUserScopedService<T>(private val defaultImplementation: T) : UserScopedService<T> {
+
+ private val implementations = mutableMapOf<UserHandle, T>()
+
+ fun addImplementation(user: UserHandle, implementation: T) {
+ implementations[user] = implementation
+ }
+
+ fun removeImplementation(user: UserHandle): T? = implementations.remove(user)
+
+ override fun forUser(user: UserHandle): T =
+ implementations.getOrDefault(user, defaultImplementation)
+}
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 333fe4c..eebe5e9f 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -94,6 +94,9 @@
libs: [
"ravenwood-runtime-common-ravenwood",
],
+ static_libs: [
+ "framework-annotations-lib", // should it be "libs" instead?
+ ],
visibility: ["//visibility:private"],
}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodAfterClassFailureTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodAfterClassFailureTest.java
new file mode 100644
index 0000000..f9794ad
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodAfterClassFailureTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 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.ravenwoodtest.bivalenttest.listenertests;
+
+import static android.platform.test.ravenwood.RavenwoodRule.isOnRavenwood;
+
+import org.junit.AfterClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+/**
+ * Test that throws from @AfterClass.
+ *
+ * Tradefed would ignore it, so instead RavenwoodAwareTestRunner would detect it and kill
+ * the self (test) process.
+ *
+ * Unfortunately, this behavior can't easily be tested from within this class, so for now
+ * it's only used for a manual test, which you can run by removing the @Ignore.
+ *
+ * TODO(b/364948126) Improve the tests and automate it.
+ */
+@Ignore
+@RunWith(ParameterizedAndroidJunit4.class)
+public class RavenwoodAfterClassFailureTest {
+ public RavenwoodAfterClassFailureTest(String param) {
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ if (!isOnRavenwood()) return; // Don't do anything on real device.
+
+ throw new RuntimeException("FAILURE");
+ }
+
+ @Parameters
+ public static List<String> getParams() {
+ var params = new ArrayList<String>();
+ params.add("foo");
+ params.add("bar");
+ return params;
+ }
+
+ @Test
+ public void test1() {
+ }
+
+ @Test
+ public void test2() {
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodBeforeClassAssumptionFailureTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodBeforeClassAssumptionFailureTest.java
new file mode 100644
index 0000000..61fb068
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodBeforeClassAssumptionFailureTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 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.ravenwoodtest.bivalenttest.listenertests;
+
+import static android.platform.test.ravenwood.RavenwoodRule.isOnRavenwood;
+
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+/**
+ * Test that fails in assumption in @BeforeClass.
+ *
+ * This is only used for manual tests. Make sure `atest` shows 4 test results with
+ * "ASSUMPTION_FAILED".
+ *
+ * TODO(b/364948126) Improve the tests and automate it.
+ */
+@RunWith(ParameterizedAndroidJunit4.class)
+public class RavenwoodBeforeClassAssumptionFailureTest {
+ public RavenwoodBeforeClassAssumptionFailureTest(String param) {
+ }
+
+ @BeforeClass
+ public static void beforeClass() {
+ if (!isOnRavenwood()) return; // Don't do anything on real device.
+
+ Assume.assumeTrue(false);
+ }
+
+ @Parameters
+ public static List<String> getParams() {
+ var params = new ArrayList<String>();
+ params.add("foo");
+ params.add("bar");
+ return params;
+ }
+
+ @Test
+ public void test1() {
+ }
+
+ @Test
+ public void test2() {
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodBeforeClassFailureTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodBeforeClassFailureTest.java
new file mode 100644
index 0000000..626ce81
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodBeforeClassFailureTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 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.ravenwoodtest.bivalenttest.listenertests;
+
+import static android.platform.test.ravenwood.RavenwoodRule.isOnRavenwood;
+
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+/**
+ * Test that fails throws from @BeforeClass.
+ *
+ * This is only used for manual tests. Make sure `atest` shows 4 test results with
+ * a "FAILURE" runtime exception.
+ *
+ * In order to run the test, you'll need to remove the @Ignore.
+ *
+ * TODO(b/364948126) Improve the tests and automate it.
+ */
+@Ignore
+@RunWith(ParameterizedAndroidJunit4.class)
+public class RavenwoodBeforeClassFailureTest {
+ public static final String TAG = "RavenwoodBeforeClassFailureTest";
+
+ public RavenwoodBeforeClassFailureTest(String param) {
+ }
+
+ @BeforeClass
+ public static void beforeClass() {
+ if (!isOnRavenwood()) return; // Don't do anything on real device.
+
+ throw new RuntimeException("FAILURE");
+ }
+
+ @Parameters
+ public static List<String> getParams() {
+ var params = new ArrayList<String>();
+ params.add("foo");
+ params.add("bar");
+ return params;
+ }
+
+ @Test
+ public void test1() {
+ }
+
+ @Test
+ public void test2() {
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodClassRuleAssumptionFailureTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodClassRuleAssumptionFailureTest.java
new file mode 100644
index 0000000..dc949c4
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodClassRuleAssumptionFailureTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 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.ravenwoodtest.bivalenttest.listenertests;
+
+import static android.platform.test.ravenwood.RavenwoodRule.isOnRavenwood;
+
+import static org.junit.Assume.assumeTrue;
+
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+/**
+ * Test that fails in assumption from a class rule.
+ *
+ * This is only used for manual tests. Make sure `atest` shows 4 test results with
+ * "ASSUMPTION_FAILED".
+ *
+ * TODO(b/364948126) Improve the tests and automate it.
+ */
+@RunWith(ParameterizedAndroidJunit4.class)
+public class RavenwoodClassRuleAssumptionFailureTest {
+ public static final String TAG = "RavenwoodClassRuleFailureTest";
+
+ @ClassRule
+ public static final TestRule sClassRule = new TestRule() {
+ @Override
+ public Statement apply(Statement base, Description description) {
+ if (!isOnRavenwood()) {
+ return base; // Just run the test as-is on a real device.
+ }
+
+ assumeTrue(false);
+ return null; // unreachable
+ }
+ };
+
+ public RavenwoodClassRuleAssumptionFailureTest(String param) {
+ }
+
+ @Parameters
+ public static List<String> getParams() {
+ var params = new ArrayList<String>();
+ params.add("foo");
+ params.add("bar");
+ return params;
+ }
+
+ @Test
+ public void test1() {
+ }
+
+ @Test
+ public void test2() {
+ }
+}
diff --git a/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodClassRuleFailureTest.java b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodClassRuleFailureTest.java
new file mode 100644
index 0000000..9996bec
--- /dev/null
+++ b/ravenwood/bivalenttest/test/com/android/ravenwoodtest/bivalenttest/listenertests/RavenwoodClassRuleFailureTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 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.ravenwoodtest.bivalenttest.listenertests;
+
+import static android.platform.test.ravenwood.RavenwoodRule.isOnRavenwood;
+
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
+/**
+ * Test that fails throws from a class rule.
+ *
+ * This is only used for manual tests. Make sure `atest` shows 4 test results with
+ * a "FAILURE" runtime exception.
+ *
+ * In order to run the test, you'll need to remove the @Ignore.
+ *
+ * TODO(b/364948126) Improve the tests and automate it.
+ */
+@Ignore
+@RunWith(ParameterizedAndroidJunit4.class)
+public class RavenwoodClassRuleFailureTest {
+ public static final String TAG = "RavenwoodClassRuleFailureTest";
+
+ @ClassRule
+ public static final TestRule sClassRule = new TestRule() {
+ @Override
+ public Statement apply(Statement base, Description description) {
+ if (!isOnRavenwood()) {
+ return base; // Just run the test as-is on a real device.
+ }
+
+ throw new RuntimeException("FAILURE");
+ }
+ };
+
+ public RavenwoodClassRuleFailureTest(String param) {
+ }
+
+ @Parameters
+ public static List<String> getParams() {
+ var params = new ArrayList<String>();
+ params.add("foo");
+ params.add("bar");
+ return params;
+ }
+
+ @Test
+ public void test1() {
+ }
+
+ @Test
+ public void test2() {
+ }
+}
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
index 1d182da..6d21e440 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunnerHook.java
@@ -34,6 +34,8 @@
import org.junit.runner.Runner;
import org.junit.runners.model.TestClass;
+import java.util.Stack;
+
/**
* Provide hook points created by {@link RavenwoodAwareTestRunner}.
*/
@@ -44,6 +46,11 @@
}
private static RavenwoodTestStats sStats; // lazy initialization.
+
+ // Keep track of the current class description.
+
+ // Test classes can be nested because of "Suite", so we need a stack to keep track.
+ private static final Stack<Description> sClassDescriptions = new Stack<>();
private static Description sCurrentClassDescription;
private static RavenwoodTestStats getStats() {
@@ -108,14 +115,15 @@
Scope scope, Order order) {
Log.v(TAG, "onBefore: description=" + description + ", " + scope + ", " + order);
- if (scope == Scope.Class && order == Order.First) {
+ if (scope == Scope.Class && order == Order.Outer) {
// Keep track of the current class.
sCurrentClassDescription = description;
+ sClassDescriptions.push(description);
}
// Class-level annotations are checked by the runner already, so we only check
// method-level annotations here.
- if (scope == Scope.Instance && order == Order.First) {
+ if (scope == Scope.Instance && order == Order.Outer) {
if (!RavenwoodEnablementChecker.shouldEnableOnRavenwood(
description, true)) {
getStats().onTestFinished(sCurrentClassDescription, description, Result.Skipped);
@@ -134,17 +142,20 @@
Scope scope, Order order, Throwable th) {
Log.v(TAG, "onAfter: description=" + description + ", " + scope + ", " + order + ", " + th);
- if (scope == Scope.Instance && order == Order.First) {
+ if (scope == Scope.Instance && order == Order.Outer) {
getStats().onTestFinished(sCurrentClassDescription, description,
th == null ? Result.Passed : Result.Failed);
- } else if (scope == Scope.Class && order == Order.Last) {
+ } else if (scope == Scope.Class && order == Order.Outer) {
getStats().onClassFinished(sCurrentClassDescription);
+ sClassDescriptions.pop();
+ sCurrentClassDescription =
+ sClassDescriptions.size() == 0 ? null : sClassDescriptions.peek();
}
// If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error.
if (RavenwoodRule.private$ravenwood().isRunningDisabledTests()
- && scope == Scope.Instance && order == Order.First) {
+ && scope == Scope.Instance && order == Order.Outer) {
boolean isTestEnabled = RavenwoodEnablementChecker.shouldEnableOnRavenwood(
description, false);
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
index 631f68f..3ffabef 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
@@ -127,7 +127,11 @@
int passed = 0;
int skipped = 0;
int failed = 0;
- for (var e : mStats.get(classDescription).values()) {
+ var stats = mStats.get(classDescription);
+ if (stats == null) {
+ return;
+ }
+ for (var e : stats.values()) {
switch (e) {
case Passed: passed++; break;
case Skipped: skipped++; break;
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index bfde9cb..dffb263 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -15,20 +15,25 @@
*/
package android.platform.test.ravenwood;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
import static com.android.ravenwood.common.RavenwoodCommonUtils.ensureIsPublicVoidMethod;
import static com.android.ravenwood.common.RavenwoodCommonUtils.isOnRavenwood;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.util.Log;
import com.android.ravenwood.common.SneakyThrow;
import org.junit.Assume;
+import org.junit.AssumptionViolatedException;
import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
+import org.junit.runner.Result;
import org.junit.runner.Runner;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.Filterable;
@@ -39,8 +44,11 @@
import org.junit.runner.manipulation.Sortable;
import org.junit.runner.manipulation.Sorter;
import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
import org.junit.runner.notification.RunNotifier;
+import org.junit.runner.notification.StoppedByUserException;
import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.MultipleFailureException;
import org.junit.runners.model.RunnerBuilder;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestClass;
@@ -51,6 +59,8 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Stack;
/**
* A test runner used for Ravenwood.
@@ -61,7 +71,7 @@
* the inner runner gets a chance to run. This can be used to initialize stuff used by the
* inner runner.
* - Add hook points, which are handed by RavenwoodAwareTestRunnerHook, with help from
- * the four test rules such as {@link #sImplicitClassMinRule}, which are also injected by
+ * the four test rules such as {@link #sImplicitClassOuterRule}, which are also injected by
* the ravenizer tool.
*
* We use this runner to:
@@ -102,28 +112,50 @@
/** Order of a hook. */
public enum Order {
- First,
- Last,
+ Outer,
+ Inner,
}
// The following four rule instances will be injected to tests by the Ravenizer tool.
+ private static class RavenwoodClassOuterRule implements TestRule {
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return getCurrentRunner().updateStatement(base, description, Scope.Class, Order.Outer);
+ }
+ }
- public static final TestRule sImplicitClassMinRule = (base, description) ->
- getCurrentRunner().updateStatement(base, description, Scope.Class, Order.First);
+ private static class RavenwoodClassInnerRule implements TestRule {
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return getCurrentRunner().updateStatement(base, description, Scope.Class, Order.Inner);
+ }
+ }
- public static final TestRule sImplicitClassMaxRule = (base, description) ->
- getCurrentRunner().updateStatement(base, description, Scope.Class, Order.Last);
+ private static class RavenwoodInstanceOuterRule implements TestRule {
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return getCurrentRunner().updateStatement(
+ base, description, Scope.Instance, Order.Outer);
+ }
+ }
- public static final TestRule sImplicitInstMinRule = (base, description) ->
- getCurrentRunner().updateStatement(base, description, Scope.Instance, Order.First);
+ private static class RavenwoodInstanceInnerRule implements TestRule {
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return getCurrentRunner().updateStatement(
+ base, description, Scope.Instance, Order.Inner);
+ }
+ }
- public static final TestRule sImplicitInstMaxRule = (base, description) ->
- getCurrentRunner().updateStatement(base, description, Scope.Instance, Order.Last);
+ public static final TestRule sImplicitClassOuterRule = new RavenwoodClassOuterRule();
+ public static final TestRule sImplicitClassInnerRule = new RavenwoodClassInnerRule();
+ public static final TestRule sImplicitInstOuterRule = new RavenwoodInstanceOuterRule();
+ public static final TestRule sImplicitInstInnerRule = new RavenwoodInstanceOuterRule();
- public static final String IMPLICIT_CLASS_MIN_RULE_NAME = "sImplicitClassMinRule";
- public static final String IMPLICIT_CLASS_MAX_RULE_NAME = "sImplicitClassMaxRule";
- public static final String IMPLICIT_INST_MIN_RULE_NAME = "sImplicitInstMinRule";
- public static final String IMPLICIT_INST_MAX_RULE_NAME = "sImplicitInstMaxRule";
+ public static final String IMPLICIT_CLASS_OUTER_RULE_NAME = "sImplicitClassOuterRule";
+ public static final String IMPLICIT_CLASS_INNER_RULE_NAME = "sImplicitClassInnerRule";
+ public static final String IMPLICIT_INST_OUTER_RULE_NAME = "sImplicitInstOuterRule";
+ public static final String IMPLICIT_INST_INNER_RULE_NAME = "sImplicitInstInnerRule";
/** Keeps track of the runner on the current thread. */
private static final ThreadLocal<RavenwoodAwareTestRunner> sCurrentRunner = new ThreadLocal<>();
@@ -157,6 +189,8 @@
try {
mTestClass = new TestClass(testClass);
+ Log.v(TAG, "RavenwoodAwareTestRunner starting for " + testClass.getCanonicalName());
+
onRunnerInitializing();
/*
@@ -261,20 +295,27 @@
}
@Override
- public void run(RunNotifier notifier) {
+ public void run(RunNotifier realNotifier) {
+ final RunNotifier notifier = new RavenwoodRunNotifier(realNotifier);
+
if (mRealRunner instanceof ClassSkippingTestRunner) {
mRealRunner.run(notifier);
RavenwoodAwareTestRunnerHook.onClassSkipped(getDescription());
return;
}
+ Log.v(TAG, "Starting " + mTestClass.getJavaClass().getCanonicalName());
+ if (RAVENWOOD_VERBOSE_LOGGING) {
+ dumpDescription(getDescription());
+ }
+
if (maybeReportExceptionFromConstructor(notifier)) {
return;
}
sCurrentRunner.set(this);
try {
- runWithHooks(getDescription(), Scope.Runner, Order.First,
+ runWithHooks(getDescription(), Scope.Runner, Order.Outer,
() -> mRealRunner.run(notifier));
} finally {
sCurrentRunner.remove();
@@ -399,4 +440,217 @@
}
}
}
+
+ private void dumpDescription(Description desc) {
+ dumpDescription(desc, "[TestDescription]=", " ");
+ }
+
+ private void dumpDescription(Description desc, String header, String indent) {
+ Log.v(TAG, indent + header + desc);
+
+ var children = desc.getChildren();
+ var childrenIndent = " " + indent;
+ for (int i = 0; i < children.size(); i++) {
+ dumpDescription(children.get(i), "#" + i + ": ", childrenIndent);
+ }
+ }
+
+ /**
+ * A run notifier that wraps another notifier and provides the following features:
+ * - Handle a failure that happened before testStarted and testEnded (typically that means
+ * it's from @BeforeClass or @AfterClass, or a @ClassRule) and deliver it as if
+ * individual tests in the class reported it. This is for b/364395552.
+ *
+ * - Logging.
+ */
+ private class RavenwoodRunNotifier extends RunNotifier {
+ private final RunNotifier mRealNotifier;
+
+ private final Stack<Description> mSuiteStack = new Stack<>();
+ private Description mCurrentSuite = null;
+ private final ArrayList<Throwable> mOutOfTestFailures = new ArrayList<>();
+
+ private boolean mBeforeTest = true;
+ private boolean mAfterTest = false;
+
+ private RavenwoodRunNotifier(RunNotifier realNotifier) {
+ mRealNotifier = realNotifier;
+ }
+
+ private boolean isInTest() {
+ return !mBeforeTest && !mAfterTest;
+ }
+
+ @Override
+ public void addListener(RunListener listener) {
+ mRealNotifier.addListener(listener);
+ }
+
+ @Override
+ public void removeListener(RunListener listener) {
+ mRealNotifier.removeListener(listener);
+ }
+
+ @Override
+ public void addFirstListener(RunListener listener) {
+ mRealNotifier.addFirstListener(listener);
+ }
+
+ @Override
+ public void fireTestRunStarted(Description description) {
+ Log.i(TAG, "testRunStarted: " + description);
+ mRealNotifier.fireTestRunStarted(description);
+ }
+
+ @Override
+ public void fireTestRunFinished(Result result) {
+ Log.i(TAG, "testRunFinished: "
+ + result.getRunCount() + ","
+ + result.getFailureCount() + ","
+ + result.getAssumptionFailureCount() + ","
+ + result.getIgnoreCount());
+ mRealNotifier.fireTestRunFinished(result);
+ }
+
+ @Override
+ public void fireTestSuiteStarted(Description description) {
+ Log.i(TAG, "testSuiteStarted: " + description);
+ mRealNotifier.fireTestSuiteStarted(description);
+
+ mBeforeTest = true;
+ mAfterTest = false;
+
+ // Keep track of the current suite, needed if the outer test is a Suite,
+ // in which case its children are test classes. (not test methods)
+ mCurrentSuite = description;
+ mSuiteStack.push(description);
+
+ mOutOfTestFailures.clear();
+ }
+
+ @Override
+ public void fireTestSuiteFinished(Description description) {
+ Log.i(TAG, "testSuiteFinished: " + description);
+ mRealNotifier.fireTestSuiteFinished(description);
+
+ maybeHandleOutOfTestFailures();
+
+ mBeforeTest = true;
+ mAfterTest = false;
+
+ // Restore the upper suite.
+ mSuiteStack.pop();
+ mCurrentSuite = mSuiteStack.size() == 0 ? null : mSuiteStack.peek();
+ }
+
+ @Override
+ public void fireTestStarted(Description description) throws StoppedByUserException {
+ Log.i(TAG, "testStarted: " + description);
+ mRealNotifier.fireTestStarted(description);
+
+ mAfterTest = false;
+ mBeforeTest = false;
+ }
+
+ @Override
+ public void fireTestFailure(Failure failure) {
+ Log.i(TAG, "testFailure: " + failure);
+
+ if (isInTest()) {
+ mRealNotifier.fireTestFailure(failure);
+ } else {
+ mOutOfTestFailures.add(failure.getException());
+ }
+ }
+
+ @Override
+ public void fireTestAssumptionFailed(Failure failure) {
+ Log.i(TAG, "testAssumptionFailed: " + failure);
+
+ if (isInTest()) {
+ mRealNotifier.fireTestAssumptionFailed(failure);
+ } else {
+ mOutOfTestFailures.add(failure.getException());
+ }
+ }
+
+ @Override
+ public void fireTestIgnored(Description description) {
+ Log.i(TAG, "testIgnored: " + description);
+ mRealNotifier.fireTestIgnored(description);
+ }
+
+ @Override
+ public void fireTestFinished(Description description) {
+ Log.i(TAG, "testFinished: " + description);
+ mRealNotifier.fireTestFinished(description);
+
+ mAfterTest = true;
+ }
+
+ @Override
+ public void pleaseStop() {
+ Log.w(TAG, "pleaseStop:");
+ mRealNotifier.pleaseStop();
+ }
+
+ /**
+ * At the end of each Suite, we handle failures happened out of test methods.
+ * (typically in @BeforeClass or @AfterClasses)
+ *
+ * This is to work around b/364395552.
+ */
+ private boolean maybeHandleOutOfTestFailures() {
+ if (mOutOfTestFailures.size() == 0) {
+ return false;
+ }
+ Throwable th;
+ if (mOutOfTestFailures.size() == 1) {
+ th = mOutOfTestFailures.get(0);
+ } else {
+ th = new MultipleFailureException(mOutOfTestFailures);
+ }
+ if (mBeforeTest) {
+ reportBeforeTestFailure(mCurrentSuite, th);
+ return true;
+ }
+ if (mAfterTest) {
+ // Unfortunately, there's no good way to report it, so kill the own process.
+ onCriticalError(
+ "Failures detected in @AfterClass, which would be swalloed by tradefed",
+ th);
+ return true; // unreachable
+ }
+ return false;
+ }
+
+ private void reportBeforeTestFailure(Description suiteDesc, Throwable th) {
+ // If a failure happens befere running any tests, we'll need to pretend
+ // as if each test in the suite reported the failure, to work around b/364395552.
+ for (var child : suiteDesc.getChildren()) {
+ if (child.isSuite()) {
+ // If the chiil is still a "parent" -- a test class or a test suite
+ // -- propagate to its children.
+ mRealNotifier.fireTestSuiteStarted(child);
+ reportBeforeTestFailure(child, th);
+ mRealNotifier.fireTestSuiteFinished(child);
+ } else {
+ mRealNotifier.fireTestStarted(child);
+ Failure f = new Failure(child, th);
+ if (th instanceof AssumptionViolatedException) {
+ mRealNotifier.fireTestAssumptionFailed(f);
+ } else {
+ mRealNotifier.fireTestFailure(f);
+ }
+ mRealNotifier.fireTestFinished(child);
+ }
+ }
+ }
+ }
+
+ private void onCriticalError(@NonNull String message, @Nullable Throwable th) {
+ Log.e(TAG, "Critical error! Ravenwood cannot continue. Killing self process: "
+ + message, th);
+ System.exit(1);
+ }
}
diff --git a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
index 7b5bc5a..875ce71 100644
--- a/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
+++ b/ravenwood/runtime-common-src/com/android/ravenwood/common/RavenwoodCommonUtils.java
@@ -15,12 +15,17 @@
*/
package com.android.ravenwood.common;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
import com.android.ravenwood.common.divergence.RavenwoodDivergence;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
@@ -33,6 +38,14 @@
private static final Object sLock = new Object();
+ /**
+ * If set to "1", we enable the verbose logging.
+ *
+ * (See also InitLogging() in http://ac/system/libbase/logging.cpp)
+ */
+ public static final boolean RAVENWOOD_VERBOSE_LOGGING = "1".equals(System.getenv(
+ "RAVENWOOD_VERBOSE"));
+
/** Name of `libravenwood_runtime` */
private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime";
@@ -265,4 +278,12 @@
method.getDeclaringClass().getName(), method.getName(),
(isStatic ? "static " : "")));
}
+
+ @NonNull
+ public static String getStackTraceString(@Nullable Throwable th) {
+ StringWriter stringWriter = new StringWriter();
+ PrintWriter writer = new PrintWriter(stringWriter);
+ th.printStackTrace(writer);
+ return stringWriter.toString();
+ }
}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
index c519204..01e19a7 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/platform/test/ravenwood/runtimehelper/ClassLoadHook.java
@@ -15,6 +15,8 @@
*/
package com.android.platform.test.ravenwood.runtimehelper;
+import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
+
import android.system.ErrnoException;
import android.system.Os;
@@ -40,14 +42,6 @@
private static final boolean SKIP_LOADING_LIBANDROID = "1".equals(System.getenv(
"RAVENWOOD_SKIP_LOADING_LIBANDROID"));
- /**
- * If set to 1, and if $ANDROID_LOG_TAGS isn't set, we enable the verbose logging.
- *
- * (See also InitLogging() in http://ac/system/libbase/logging.cpp)
- */
- private static final boolean RAVENWOOD_VERBOSE_LOGGING = "1".equals(System.getenv(
- "RAVENWOOD_VERBOSE"));
-
public static final String CORE_NATIVE_CLASSES = "core_native_classes";
public static final String ICU_DATA_PATH = "icu.data.path";
public static final String KEYBOARD_PATHS = "keyboard_paths";
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
index eaef2cf..bd9d96d 100644
--- a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/RunnerRewritingAdapter.kt
@@ -302,7 +302,7 @@
override fun visitCode() {
visitFieldInsn(Opcodes.GETSTATIC,
ravenwoodTestRunnerType.internlName,
- RavenwoodAwareTestRunner.IMPLICIT_CLASS_MIN_RULE_NAME,
+ RavenwoodAwareTestRunner.IMPLICIT_CLASS_OUTER_RULE_NAME,
testRuleType.desc
)
visitFieldInsn(Opcodes.PUTSTATIC,
@@ -313,7 +313,7 @@
visitFieldInsn(Opcodes.GETSTATIC,
ravenwoodTestRunnerType.internlName,
- RavenwoodAwareTestRunner.IMPLICIT_CLASS_MAX_RULE_NAME,
+ RavenwoodAwareTestRunner.IMPLICIT_CLASS_INNER_RULE_NAME,
testRuleType.desc
)
visitFieldInsn(Opcodes.PUTSTATIC,
@@ -361,7 +361,7 @@
visitVarInsn(ALOAD, 0)
visitFieldInsn(Opcodes.GETSTATIC,
ravenwoodTestRunnerType.internlName,
- RavenwoodAwareTestRunner.IMPLICIT_INST_MIN_RULE_NAME,
+ RavenwoodAwareTestRunner.IMPLICIT_INST_OUTER_RULE_NAME,
testRuleType.desc
)
visitFieldInsn(Opcodes.PUTFIELD,
@@ -373,7 +373,7 @@
visitVarInsn(ALOAD, 0)
visitFieldInsn(Opcodes.GETSTATIC,
ravenwoodTestRunnerType.internlName,
- RavenwoodAwareTestRunner.IMPLICIT_INST_MAX_RULE_NAME,
+ RavenwoodAwareTestRunner.IMPLICIT_INST_INNER_RULE_NAME,
testRuleType.desc
)
visitFieldInsn(Opcodes.PUTFIELD,
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
index 03dd5dd..0947238 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
@@ -22,6 +22,9 @@
import android.app.appsearch.AppSearchManager.SearchContext;
import android.app.appsearch.AppSearchResult;
import android.app.appsearch.AppSearchSession;
+import android.app.appsearch.BatchResultCallback;
+import android.app.appsearch.GenericDocument;
+import android.app.appsearch.GetByDocumentIdRequest;
import android.app.appsearch.GetSchemaResponse;
import android.app.appsearch.PutDocumentsRequest;
import android.app.appsearch.SearchResult;
@@ -189,4 +192,58 @@
});
}
}
+
+ /** A future API to retrieve a document by its id from the local AppSearch session. */
+ public AndroidFuture<GenericDocument> getByDocumentId(
+ @NonNull String documentId, @NonNull String namespace) {
+ Objects.requireNonNull(documentId);
+ Objects.requireNonNull(namespace);
+
+ GetByDocumentIdRequest request =
+ new GetByDocumentIdRequest.Builder(namespace)
+ .addIds(documentId)
+ .build();
+ return getSessionAsync()
+ .thenCompose(
+ session -> {
+ AndroidFuture<AppSearchBatchResult<String, GenericDocument>>
+ batchResultFuture = new AndroidFuture<>();
+ session.getByDocumentId(
+ request,
+ mExecutor,
+ new BatchResultCallbackAdapter<>(batchResultFuture));
+
+ return batchResultFuture.thenApply(
+ batchResult ->
+ getGenericDocumentFromBatchResult(
+ batchResult, documentId));
+ });
+ }
+
+ private static GenericDocument getGenericDocumentFromBatchResult(
+ AppSearchBatchResult<String, GenericDocument> result, String documentId) {
+ if (result.isSuccess()) {
+ return result.getSuccesses().get(documentId);
+ }
+ throw new IllegalArgumentException("No document in the result for id: " + documentId);
+ }
+
+ private static final class BatchResultCallbackAdapter<K, V>
+ implements BatchResultCallback<K, V> {
+ private final AndroidFuture<AppSearchBatchResult<K, V>> mFuture;
+
+ BatchResultCallbackAdapter(AndroidFuture<AppSearchBatchResult<K, V>> future) {
+ mFuture = future;
+ }
+
+ @Override
+ public void onResult(@NonNull AppSearchBatchResult<K, V> result) {
+ mFuture.complete(result);
+ }
+
+ @Override
+ public void onSystemError(Throwable t) {
+ mFuture.completeExceptionally(t);
+ }
+ }
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
new file mode 100644
index 0000000..be5770b
--- /dev/null
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 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.appfunctions;
+
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchSpec;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+
+/**
+ * This class implements helper methods for synchronously interacting with AppSearch while
+ * synchronizing AppFunction runtime and static metadata.
+ */
+public class MetadataSyncAdapter {
+ private final FutureAppSearchSession mFutureAppSearchSession;
+ private final Executor mSyncExecutor;
+
+ public MetadataSyncAdapter(
+ @NonNull Executor syncExecutor,
+ @NonNull FutureAppSearchSession futureAppSearchSession) {
+ mSyncExecutor = Objects.requireNonNull(syncExecutor);
+ mFutureAppSearchSession = Objects.requireNonNull(futureAppSearchSession);
+ }
+
+ /**
+ * This method returns a map of package names to a set of function ids that are in the static
+ * metadata but not in the runtime metadata.
+ *
+ * @param staticPackageToFunctionMap A map of package names to a set of function ids from the
+ * static metadata.
+ * @param runtimePackageToFunctionMap A map of package names to a set of function ids from the
+ * runtime metadata.
+ * @return A map of package names to a set of function ids that are in the static metadata but
+ * not in the runtime metadata.
+ */
+ @NonNull
+ @VisibleForTesting
+ static ArrayMap<String, ArraySet<String>> getAddedFunctionsDiffMap(
+ ArrayMap<String, ArraySet<String>> staticPackageToFunctionMap,
+ ArrayMap<String, ArraySet<String>> runtimePackageToFunctionMap) {
+ return getFunctionsDiffMap(staticPackageToFunctionMap, runtimePackageToFunctionMap);
+ }
+
+ /**
+ * This method returns a map of package names to a set of function ids that are in the runtime
+ * metadata but not in the static metadata.
+ *
+ * @param staticPackageToFunctionMap A map of package names to a set of function ids from the
+ * static metadata.
+ * @param runtimePackageToFunctionMap A map of package names to a set of function ids from the
+ * runtime metadata.
+ * @return A map of package names to a set of function ids that are in the runtime metadata but
+ * not in the static metadata.
+ */
+ @NonNull
+ @VisibleForTesting
+ static ArrayMap<String, ArraySet<String>> getRemovedFunctionsDiffMap(
+ ArrayMap<String, ArraySet<String>> staticPackageToFunctionMap,
+ ArrayMap<String, ArraySet<String>> runtimePackageToFunctionMap) {
+ return getFunctionsDiffMap(runtimePackageToFunctionMap, staticPackageToFunctionMap);
+ }
+
+ @NonNull
+ private static ArrayMap<String, ArraySet<String>> getFunctionsDiffMap(
+ ArrayMap<String, ArraySet<String>> packageToFunctionMapA,
+ ArrayMap<String, ArraySet<String>> packageToFunctionMapB) {
+ ArrayMap<String, ArraySet<String>> diffMap = new ArrayMap<>();
+ for (String packageName : packageToFunctionMapA.keySet()) {
+ if (!packageToFunctionMapB.containsKey(packageName)) {
+ diffMap.put(packageName, packageToFunctionMapA.get(packageName));
+ continue;
+ }
+ ArraySet<String> diffFunctions = new ArraySet<>();
+ for (String functionId :
+ Objects.requireNonNull(packageToFunctionMapA.get(packageName))) {
+ if (!Objects.requireNonNull(packageToFunctionMapB.get(packageName))
+ .contains(functionId)) {
+ diffFunctions.add(functionId);
+ }
+ }
+ if (!diffFunctions.isEmpty()) {
+ diffMap.put(packageName, diffFunctions);
+ }
+ }
+ return diffMap;
+ }
+
+ /**
+ * This method returns a map of package names to a set of function ids.
+ *
+ * @param queryExpression The query expression to use when searching for AppFunction metadata.
+ * @param metadataSearchSpec The search spec to use when searching for AppFunction metadata.
+ * @return A map of package names to a set of function ids.
+ * @throws ExecutionException If the future search results fail to execute.
+ * @throws InterruptedException If the future search results are interrupted.
+ */
+ @NonNull
+ @VisibleForTesting
+ @WorkerThread
+ ArrayMap<String, ArraySet<String>> getPackageToFunctionIdMap(
+ @NonNull String queryExpression,
+ @NonNull SearchSpec metadataSearchSpec,
+ @NonNull String propertyFunctionId,
+ @NonNull String propertyPackageName)
+ throws ExecutionException, InterruptedException {
+ ArrayMap<String, ArraySet<String>> packageToFunctionIds = new ArrayMap<>();
+ FutureSearchResults futureSearchResults =
+ mFutureAppSearchSession.search(queryExpression, metadataSearchSpec).get();
+ List<SearchResult> searchResultsList = futureSearchResults.getNextPage().get();
+ // TODO(b/357551503): This could be expensive if we have more functions
+ while (!searchResultsList.isEmpty()) {
+ for (SearchResult searchResult : searchResultsList) {
+ String packageName =
+ searchResult.getGenericDocument().getPropertyString(propertyPackageName);
+ String functionId =
+ searchResult.getGenericDocument().getPropertyString(propertyFunctionId);
+ packageToFunctionIds
+ .computeIfAbsent(packageName, k -> new ArraySet<>())
+ .add(functionId);
+ }
+ searchResultsList = futureSearchResults.getNextPage().get();
+ }
+ return packageToFunctionIds;
+ }
+}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 2937307..99c3eca 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -139,6 +139,7 @@
static final String[] sDeviceConfigAconfigScopes = new String[] {
"accessibility",
"android_core_networking",
+ "android_health_services",
"android_sdk",
"android_stylus",
"aoc",
@@ -235,7 +236,6 @@
"wear_connectivity",
"wear_esim_carriers",
"wear_frameworks",
- "wear_health_services",
"wear_media",
"wear_offload",
"wear_security",
diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
index bad959a..925ba17 100644
--- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -22,6 +22,7 @@
import static com.android.server.notification.ZenLog.traceApplyDeviceEffect;
import static com.android.server.notification.ZenLog.traceScheduleApplyDeviceEffect;
+import android.app.KeyguardManager;
import android.app.UiModeManager;
import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
@@ -53,6 +54,7 @@
private final Context mContext;
private final ColorDisplayManager mColorDisplayManager;
+ private final KeyguardManager mKeyguardManager;
private final PowerManager mPowerManager;
private final UiModeManager mUiModeManager;
private final WallpaperManager mWallpaperManager;
@@ -67,6 +69,7 @@
DefaultDeviceEffectsApplier(Context context) {
mContext = context;
mColorDisplayManager = context.getSystemService(ColorDisplayManager.class);
+ mKeyguardManager = context.getSystemService(KeyguardManager.class);
mPowerManager = context.getSystemService(PowerManager.class);
mUiModeManager = context.getSystemService(UiModeManager.class);
WallpaperManager wallpaperManager = context.getSystemService(WallpaperManager.class);
@@ -133,12 +136,14 @@
// Changing the theme can be disruptive for the user (Activities are likely recreated, may
// lose some state). Therefore we only apply the change immediately if the rule was
- // activated manually, or we are initializing, or the screen is currently off/dreaming.
+ // activated manually, or we are initializing, or the screen is currently off/dreaming,
+ // or if the device is locked.
if (origin == ZenModeConfig.ORIGIN_INIT
|| origin == ZenModeConfig.ORIGIN_INIT_USER
|| origin == ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI
|| origin == ZenModeConfig.ORIGIN_USER_IN_APP
- || !mPowerManager.isInteractive()) {
+ || !mPowerManager.isInteractive()
+ || (android.app.Flags.modesUi() && mKeyguardManager.isKeyguardLocked())) {
unregisterScreenOffReceiver();
updateNightModeImmediately(useNightMode);
} else {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 2c3f6ea..2856eb4 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1897,10 +1897,16 @@
}
if (!oldSharedUid.equals(newSharedUid)) {
- throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED,
- "Package " + parsedPackage.getPackageName()
- + " shared user changed from "
- + oldSharedUid + " to " + newSharedUid);
+ if (!(oldSharedUid.equals("<nothing>") && ps.getPkg() == null
+ && ps.isArchivedOnAnyUser(allUsers))) {
+ // Only allow changing sharedUserId if unarchiving
+ // TODO(b/361558423): remove this check after pre-archiving installs
+ // accept a sharedUserId param in the API
+ throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED,
+ "Package " + parsedPackage.getPackageName()
+ + " shared user changed from "
+ + oldSharedUid + " to " + newSharedUid);
+ }
}
// APK should not re-join shared UID
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 9fb9e71..9428de7 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -925,6 +925,18 @@
return PackageArchiver.isArchived(readUserState(userId));
}
+ /**
+ * @return if the package is archived in any of the users
+ */
+ boolean isArchivedOnAnyUser(int[] userIds) {
+ for (int user : userIds) {
+ if (isArchived(user)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
int getInstallReason(int userId) {
return readUserState(userId).getInstallReason();
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index f53dda6..4dcc6e1 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3169,7 +3169,8 @@
final WallpaperDestinationChangeHandler
liveSync = new WallpaperDestinationChangeHandler(
newWallpaper);
- boolean same = changingToSame(name, newWallpaper);
+ boolean same = changingToSame(name, newWallpaper.connection,
+ newWallpaper.wallpaperComponent);
/*
* If we have a shared system+lock wallpaper, and we reapply the same wallpaper
@@ -3257,14 +3258,15 @@
return name == null || name.equals(mDefaultWallpaperComponent);
}
- private boolean changingToSame(ComponentName componentName, WallpaperData wallpaper) {
- if (wallpaper.connection != null) {
- final ComponentName wallpaperName = wallpaper.wallpaperComponent;
- if (isDefaultComponent(componentName) && isDefaultComponent(wallpaperName)) {
+ private boolean changingToSame(ComponentName newComponentName,
+ WallpaperConnection currentConnection, ComponentName currentComponentName) {
+ if (currentConnection != null) {
+ if (isDefaultComponent(newComponentName) && isDefaultComponent(currentComponentName)) {
if (DEBUG) Slog.v(TAG, "changingToSame: still using default");
// Still using default wallpaper.
return true;
- } else if (wallpaperName != null && wallpaperName.equals(componentName)) {
+ } else if (currentComponentName != null && currentComponentName.equals(
+ newComponentName)) {
// Changing to same wallpaper.
if (DEBUG) Slog.v(TAG, "same wallpaper");
return true;
@@ -3279,7 +3281,8 @@
Slog.v(TAG, "bindWallpaperComponentLocked: componentName=" + componentName);
}
// Has the component changed?
- if (!force && changingToSame(componentName, wallpaper)) {
+ if (!force && changingToSame(componentName, wallpaper.connection,
+ wallpaper.wallpaperComponent)) {
try {
if (DEBUG_LIVE) {
Slog.v(TAG, "Changing to the same component, ignoring");
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt
index a323799..a0f1a55 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt
@@ -16,6 +16,7 @@
package com.android.server.appfunctions
import android.app.appfunctions.AppFunctionRuntimeMetadata
+import android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_NAMESPACE
import android.app.appfunctions.AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema
import android.app.appfunctions.AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema
import android.app.appsearch.AppSearchManager
@@ -123,6 +124,38 @@
}
}
+ @Test
+ fun getByDocumentId() {
+ val searchContext = AppSearchManager.SearchContext.Builder(TEST_DB).build()
+ FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use { session ->
+ val setSchemaRequest =
+ SetSchemaRequest.Builder()
+ .addSchemas(
+ createParentAppFunctionRuntimeSchema(),
+ createAppFunctionRuntimeSchema(TEST_PACKAGE_NAME)
+ )
+ .build()
+ val schema = session.setSchema(setSchemaRequest)
+ val appFunctionRuntimeMetadata =
+ AppFunctionRuntimeMetadata.Builder(TEST_PACKAGE_NAME, TEST_FUNCTION_ID, "").build()
+ val putDocumentsRequest: PutDocumentsRequest =
+ PutDocumentsRequest.Builder()
+ .addGenericDocuments(appFunctionRuntimeMetadata)
+ .build()
+ val putResult = session.put(putDocumentsRequest)
+
+ val genricDocument = session
+ .getByDocumentId(
+ /* documentId= */ "${TEST_PACKAGE_NAME}/${TEST_FUNCTION_ID}",
+ APP_FUNCTION_RUNTIME_NAMESPACE
+ )
+ .get()
+
+ val foundAppFunctionRuntimeMetadata = AppFunctionRuntimeMetadata(genricDocument)
+ assertThat(foundAppFunctionRuntimeMetadata.functionId).isEqualTo(TEST_FUNCTION_ID)
+ }
+ }
+
private companion object {
const val TEST_DB: String = "test_db"
const val TEST_PACKAGE_NAME: String = "test_pkg"
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureGlobalSearchSessionTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureGlobalSearchSessionTest.kt
index 8817a66..1fa55c7 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureGlobalSearchSessionTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureGlobalSearchSessionTest.kt
@@ -18,7 +18,6 @@
import android.app.appfunctions.AppFunctionRuntimeMetadata
import android.app.appfunctions.AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema
import android.app.appfunctions.AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema
-import android.app.appfunctions.AppFunctionStaticMetadataHelper
import android.app.appsearch.AppSearchManager
import android.app.appsearch.AppSearchManager.SearchContext
import android.app.appsearch.PutDocumentsRequest
@@ -55,10 +54,6 @@
@Test
fun registerDocumentChangeObserverCallback() {
- val baseObserverSpec: ObserverSpec =
- ObserverSpec.Builder()
- .addFilterSchemas(AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE)
- .build()
val packageObserverSpec: ObserverSpec =
ObserverSpec.Builder()
.addFilterSchemas(
@@ -76,15 +71,6 @@
}
val futureGlobalSearchSession = FutureGlobalSearchSession(appSearchManager, testExecutor)
- val registerBaseObserver: Void? =
- futureGlobalSearchSession
- .registerObserverCallbackAsync(
- TEST_TARGET_PKG_NAME,
- baseObserverSpec,
- testExecutor,
- observer,
- )
- .get()
val registerPackageObserver: Void? =
futureGlobalSearchSession
.registerObserverCallbackAsync(
@@ -94,8 +80,6 @@
observer,
)
.get()
-
- assertThat(registerBaseObserver).isNull()
assertThat(registerPackageObserver).isNull()
// Trigger document change
val searchContext = SearchContext.Builder(TEST_DB).build()
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
new file mode 100644
index 0000000..1061da2
--- /dev/null
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
@@ -0,0 +1,296 @@
+/*
+ * 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.appfunctions
+
+import android.app.appfunctions.AppFunctionRuntimeMetadata
+import android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID
+import android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME
+import android.app.appsearch.AppSearchManager
+import android.app.appsearch.AppSearchManager.SearchContext
+import android.app.appsearch.PutDocumentsRequest
+import android.app.appsearch.SearchSpec
+import android.app.appsearch.SetSchemaRequest
+import android.util.ArrayMap
+import android.util.ArraySet
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class MetadataSyncAdapterTest {
+ private val context = InstrumentationRegistry.getInstrumentation().targetContext
+ private val appSearchManager = context.getSystemService(AppSearchManager::class.java)
+ private val testExecutor = MoreExecutors.directExecutor()
+
+ @Before
+ @After
+ fun clearData() {
+ val searchContext = SearchContext.Builder(TEST_DB).build()
+ FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use {
+ val setSchemaRequest = SetSchemaRequest.Builder().setForceOverride(true).build()
+ it.setSchema(setSchemaRequest)
+ }
+ }
+
+ @Test
+ fun getPackageToFunctionIdMap() {
+ val searchContext: SearchContext = SearchContext.Builder(TEST_DB).build()
+ val functionRuntimeMetadata =
+ AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId", "").build()
+ val setSchemaRequest =
+ SetSchemaRequest.Builder()
+ .addSchemas(AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema())
+ .addSchemas(
+ AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema(TEST_TARGET_PKG_NAME)
+ )
+ .build()
+ val putDocumentsRequest: PutDocumentsRequest =
+ PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
+ FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use {
+ val setSchemaResponse = it.setSchema(setSchemaRequest).get()
+ assertThat(setSchemaResponse).isNotNull()
+ val appSearchBatchResult = it.put(putDocumentsRequest).get()
+ assertThat(appSearchBatchResult.isSuccess).isTrue()
+ }
+
+ val metadataSyncAdapter =
+ MetadataSyncAdapter(
+ testExecutor,
+ FutureAppSearchSession(appSearchManager, testExecutor, searchContext),
+ )
+ val searchSpec: SearchSpec =
+ SearchSpec.Builder()
+ .addFilterSchemas(
+ AppFunctionRuntimeMetadata.RUNTIME_SCHEMA_TYPE,
+ AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema(TEST_TARGET_PKG_NAME)
+ .schemaType,
+ )
+ .build()
+ val packageToFunctionIdMap =
+ metadataSyncAdapter.getPackageToFunctionIdMap(
+ "",
+ searchSpec,
+ PROPERTY_FUNCTION_ID,
+ PROPERTY_PACKAGE_NAME,
+ )
+
+ assertThat(packageToFunctionIdMap).isNotNull()
+ assertThat(packageToFunctionIdMap[TEST_TARGET_PKG_NAME]).containsExactly("testFunctionId")
+ }
+
+ @Test
+ fun getPackageToFunctionIdMap_multipleDocuments() {
+ val searchContext: SearchContext = SearchContext.Builder(TEST_DB).build()
+ val functionRuntimeMetadata =
+ AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId", "").build()
+ val functionRuntimeMetadata1 =
+ AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId1", "").build()
+ val functionRuntimeMetadata2 =
+ AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId2", "").build()
+ val functionRuntimeMetadata3 =
+ AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, "testFunctionId3", "").build()
+ val setSchemaRequest =
+ SetSchemaRequest.Builder()
+ .addSchemas(AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema())
+ .addSchemas(
+ AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema(TEST_TARGET_PKG_NAME)
+ )
+ .build()
+ val putDocumentsRequest: PutDocumentsRequest =
+ PutDocumentsRequest.Builder()
+ .addGenericDocuments(
+ functionRuntimeMetadata,
+ functionRuntimeMetadata1,
+ functionRuntimeMetadata2,
+ functionRuntimeMetadata3,
+ )
+ .build()
+ FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use {
+ val setSchemaResponse = it.setSchema(setSchemaRequest).get()
+ assertThat(setSchemaResponse).isNotNull()
+ val appSearchBatchResult = it.put(putDocumentsRequest).get()
+ assertThat(appSearchBatchResult.isSuccess).isTrue()
+ }
+
+ val metadataSyncAdapter =
+ MetadataSyncAdapter(
+ testExecutor,
+ FutureAppSearchSession(appSearchManager, testExecutor, searchContext),
+ )
+ val searchSpec: SearchSpec =
+ SearchSpec.Builder()
+ .setResultCountPerPage(1)
+ .addFilterSchemas(
+ AppFunctionRuntimeMetadata.RUNTIME_SCHEMA_TYPE,
+ AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema(TEST_TARGET_PKG_NAME)
+ .schemaType,
+ )
+ .build()
+ val packageToFunctionIdMap =
+ metadataSyncAdapter.getPackageToFunctionIdMap(
+ "",
+ searchSpec,
+ PROPERTY_FUNCTION_ID,
+ PROPERTY_PACKAGE_NAME,
+ )
+
+ assertThat(packageToFunctionIdMap).isNotNull()
+ assertThat(packageToFunctionIdMap[TEST_TARGET_PKG_NAME])
+ .containsExactly(
+ "testFunctionId",
+ "testFunctionId1",
+ "testFunctionId2",
+ "testFunctionId3",
+ )
+ }
+
+ @Test
+ fun getAddedFunctionsDiffMap_noDiff() {
+ val staticPackageToFunctionMap: ArrayMap<String, ArraySet<String>> = ArrayMap()
+ staticPackageToFunctionMap.putAll(
+ mapOf(TEST_TARGET_PKG_NAME to ArraySet(setOf("testFunction1")))
+ )
+ val runtimePackageToFunctionMap: ArrayMap<String, ArraySet<String>> =
+ ArrayMap(staticPackageToFunctionMap)
+
+ val addedFunctionsDiffMap =
+ MetadataSyncAdapter.getAddedFunctionsDiffMap(
+ staticPackageToFunctionMap,
+ runtimePackageToFunctionMap,
+ )
+
+ assertThat(addedFunctionsDiffMap.isEmpty()).isEqualTo(true)
+ }
+
+ @Test
+ fun getAddedFunctionsDiffMap_addedFunction() {
+ val staticPackageToFunctionMap: ArrayMap<String, ArraySet<String>> = ArrayMap()
+ staticPackageToFunctionMap.putAll(
+ mapOf(TEST_TARGET_PKG_NAME to ArraySet(setOf("testFunction1", "testFunction2")))
+ )
+ val runtimePackageToFunctionMap: ArrayMap<String, ArraySet<String>> = ArrayMap()
+ runtimePackageToFunctionMap.putAll(
+ mapOf(TEST_TARGET_PKG_NAME to ArraySet(setOf("testFunction1")))
+ )
+
+ val addedFunctionsDiffMap =
+ MetadataSyncAdapter.getAddedFunctionsDiffMap(
+ staticPackageToFunctionMap,
+ runtimePackageToFunctionMap,
+ )
+
+ assertThat(addedFunctionsDiffMap.size).isEqualTo(1)
+ assertThat(addedFunctionsDiffMap[TEST_TARGET_PKG_NAME]).containsExactly("testFunction2")
+ }
+
+ @Test
+ fun getAddedFunctionsDiffMap_addedFunctionNewPackage() {
+ val staticPackageToFunctionMap: ArrayMap<String, ArraySet<String>> = ArrayMap()
+ staticPackageToFunctionMap.putAll(
+ mapOf(TEST_TARGET_PKG_NAME to ArraySet(setOf("testFunction1")))
+ )
+ val runtimePackageToFunctionMap: ArrayMap<String, ArraySet<String>> = ArrayMap()
+
+ val addedFunctionsDiffMap =
+ MetadataSyncAdapter.getAddedFunctionsDiffMap(
+ staticPackageToFunctionMap,
+ runtimePackageToFunctionMap,
+ )
+
+ assertThat(addedFunctionsDiffMap.size).isEqualTo(1)
+ assertThat(addedFunctionsDiffMap[TEST_TARGET_PKG_NAME]).containsExactly("testFunction1")
+ }
+
+ @Test
+ fun getAddedFunctionsDiffMap_removedFunction() {
+ val staticPackageToFunctionMap: ArrayMap<String, ArraySet<String>> = ArrayMap()
+ val runtimePackageToFunctionMap: ArrayMap<String, ArraySet<String>> = ArrayMap()
+ runtimePackageToFunctionMap.putAll(
+ mapOf(TEST_TARGET_PKG_NAME to ArraySet(setOf("testFunction1")))
+ )
+
+ val addedFunctionsDiffMap =
+ MetadataSyncAdapter.getAddedFunctionsDiffMap(
+ staticPackageToFunctionMap,
+ runtimePackageToFunctionMap,
+ )
+
+ assertThat(addedFunctionsDiffMap.isEmpty()).isEqualTo(true)
+ }
+
+ @Test
+ fun getRemovedFunctionsDiffMap_noDiff() {
+ val staticPackageToFunctionMap: ArrayMap<String, ArraySet<String>> = ArrayMap()
+ staticPackageToFunctionMap.putAll(
+ mapOf(TEST_TARGET_PKG_NAME to ArraySet(setOf("testFunction1")))
+ )
+ val runtimePackageToFunctionMap: ArrayMap<String, ArraySet<String>> =
+ ArrayMap(staticPackageToFunctionMap)
+
+ val removedFunctionsDiffMap =
+ MetadataSyncAdapter.getRemovedFunctionsDiffMap(
+ staticPackageToFunctionMap,
+ runtimePackageToFunctionMap,
+ )
+
+ assertThat(removedFunctionsDiffMap.isEmpty()).isEqualTo(true)
+ }
+
+ @Test
+ fun getRemovedFunctionsDiffMap_removedFunction() {
+ val staticPackageToFunctionMap: ArrayMap<String, ArraySet<String>> = ArrayMap()
+ val runtimePackageToFunctionMap: ArrayMap<String, ArraySet<String>> = ArrayMap()
+ runtimePackageToFunctionMap.putAll(
+ mapOf(TEST_TARGET_PKG_NAME to ArraySet(setOf("testFunction1")))
+ )
+
+ val removedFunctionsDiffMap =
+ MetadataSyncAdapter.getRemovedFunctionsDiffMap(
+ staticPackageToFunctionMap,
+ runtimePackageToFunctionMap,
+ )
+
+ assertThat(removedFunctionsDiffMap.size).isEqualTo(1)
+ assertThat(removedFunctionsDiffMap[TEST_TARGET_PKG_NAME]).containsExactly("testFunction1")
+ }
+
+ @Test
+ fun getRemovedFunctionsDiffMap_addedFunction() {
+ val staticPackageToFunctionMap: ArrayMap<String, ArraySet<String>> = ArrayMap()
+ staticPackageToFunctionMap.putAll(
+ mapOf(TEST_TARGET_PKG_NAME to ArraySet(setOf("testFunction1")))
+ )
+ val runtimePackageToFunctionMap: ArrayMap<String, ArraySet<String>> = ArrayMap()
+
+ val removedFunctionsDiffMap =
+ MetadataSyncAdapter.getRemovedFunctionsDiffMap(
+ staticPackageToFunctionMap,
+ runtimePackageToFunctionMap,
+ )
+
+ assertThat(removedFunctionsDiffMap.isEmpty()).isEqualTo(true)
+ }
+
+ private companion object {
+ const val TEST_DB: String = "test_db"
+ const val TEST_TARGET_PKG_NAME = "com.android.frameworks.appfunctionstests"
+ }
+}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
index 4a19973..1890879 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -39,6 +39,7 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.app.KeyguardManager;
import android.app.UiModeManager;
import android.app.WallpaperManager;
import android.content.BroadcastReceiver;
@@ -78,6 +79,7 @@
private DefaultDeviceEffectsApplier mApplier;
@Mock PowerManager mPowerManager;
@Mock ColorDisplayManager mColorDisplayManager;
+ @Mock KeyguardManager mKeyguardManager;
@Mock UiModeManager mUiModeManager;
@Mock WallpaperManager mWallpaperManager;
@@ -87,6 +89,7 @@
mContext = spy(new TestableContext(InstrumentationRegistry.getContext(), null));
mContext.addMockSystemService(PowerManager.class, mPowerManager);
mContext.addMockSystemService(ColorDisplayManager.class, mColorDisplayManager);
+ mContext.addMockSystemService(KeyguardManager.class, mKeyguardManager);
mContext.addMockSystemService(UiModeManager.class, mUiModeManager);
mContext.addMockSystemService(WallpaperManager.class, mWallpaperManager);
when(mWallpaperManager.isWallpaperSupported()).thenReturn(true);
@@ -311,6 +314,22 @@
}
@Test
+ @EnableFlags({android.app.Flags.FLAG_MODES_API, android.app.Flags.FLAG_MODES_UI})
+ public void apply_nightModeWithScreenOnAndKeyguardShowing_appliedImmediately(
+ @TestParameter ZenChangeOrigin origin) {
+
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mKeyguardManager.isKeyguardLocked()).thenReturn(true);
+
+ mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(),
+ origin.value());
+
+ // Effect was applied, and no broadcast receiver was registered.
+ verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
+ verify(mContext, never()).registerReceiver(any(), any(), anyInt());
+ }
+
+ @Test
@TestParameters({"{origin: ORIGIN_USER_IN_SYSTEMUI}", "{origin: ORIGIN_USER_IN_APP}",
"{origin: ORIGIN_INIT}", "{origin: ORIGIN_INIT_USER}"})
public void apply_nightModeWithScreenOn_appliedImmediatelyBasedOnOrigin(
diff --git a/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt b/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
index 24d203f..f5af99e 100644
--- a/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
+++ b/tools/lint/common/src/main/java/com/google/android/lint/aidl/EnforcePermissionUtils.kt
@@ -24,20 +24,31 @@
import org.jetbrains.uast.UMethod
/**
- * Given a UMethod, determine if this method is the entrypoint to an interface
- * generated by AIDL, returning the interface name if so, otherwise returning
- * null
+ * Given a UMethod, determine if this method is the entrypoint to an interface generated by AIDL,
+ * returning the interface name if so, otherwise returning null.
*/
fun getContainingAidlInterface(context: JavaContext, node: UMethod): String? {
+ return containingAidlInterfacePsiClass(context, node)?.name
+}
+
+/**
+ * Given a UMethod, determine if this method is the entrypoint to an interface generated by AIDL,
+ * returning the fully qualified interface name if so, otherwise returning null.
+ */
+fun getContainingAidlInterfaceQualified(context: JavaContext, node: UMethod): String? {
+ return containingAidlInterfacePsiClass(context, node)?.qualifiedName
+}
+
+private fun containingAidlInterfacePsiClass(context: JavaContext, node: UMethod): PsiClass? {
val containingStub = containingStub(context, node) ?: return null
val superMethod = node.findSuperMethods(containingStub)
if (superMethod.isEmpty()) return null
- return containingStub.containingClass?.name
+ return containingStub.containingClass
}
-/* Returns the containing Stub class if any. This is not sufficient to infer
- * that the method itself extends an AIDL generated method. See
- * getContainingAidlInterface for that purpose.
+/**
+ * Returns the containing Stub class if any. This is not sufficient to infer that the method itself
+ * extends an AIDL generated method. See getContainingAidlInterface for that purpose.
*/
fun containingStub(context: JavaContext, node: UMethod?): PsiClass? {
var superClass = node?.containingClass?.superClass
@@ -48,7 +59,7 @@
return null
}
-private fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean {
+fun isStub(context: JavaContext, psiClass: PsiClass?): Boolean {
if (psiClass == null) return false
if (psiClass.name != "Stub") return false
if (!context.evaluator.isStatic(psiClass)) return false
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfaces.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfaces.kt
new file mode 100644
index 0000000..8777712
--- /dev/null
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfaces.kt
@@ -0,0 +1,774 @@
+/*
+ * Copyright (C) 2024 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.google.android.lint.aidl
+
+/**
+ * The exemptAidlInterfaces set was generated by running ExemptAidlInterfacesGenerator on the
+ * entire source tree. To reproduce the results, run generate-exempt-aidl-interfaces.sh
+ * located in tools/lint/utils.
+ *
+ * TODO: b/363248121 - Use the exemptAidlInterfaces set inside PermissionAnnotationDetector when it
+ * gets migrated to a global lint check.
+ */
+val exemptAidlInterfaces = setOf(
+ "android.accessibilityservice.IAccessibilityServiceConnection",
+ "android.accessibilityservice.IBrailleDisplayConnection",
+ "android.accounts.IAccountAuthenticatorResponse",
+ "android.accounts.IAccountManager",
+ "android.accounts.IAccountManagerResponse",
+ "android.adservices.adid.IAdIdProviderService",
+ "android.adservices.adid.IAdIdService",
+ "android.adservices.adid.IGetAdIdCallback",
+ "android.adservices.adid.IGetAdIdProviderCallback",
+ "android.adservices.adselection.AdSelectionCallback",
+ "android.adservices.adselection.AdSelectionOverrideCallback",
+ "android.adservices.adselection.AdSelectionService",
+ "android.adservices.adselection.GetAdSelectionDataCallback",
+ "android.adservices.adselection.PersistAdSelectionResultCallback",
+ "android.adservices.adselection.ReportImpressionCallback",
+ "android.adservices.adselection.ReportInteractionCallback",
+ "android.adservices.adselection.SetAppInstallAdvertisersCallback",
+ "android.adservices.adselection.UpdateAdCounterHistogramCallback",
+ "android.adservices.appsetid.IAppSetIdProviderService",
+ "android.adservices.appsetid.IAppSetIdService",
+ "android.adservices.appsetid.IGetAppSetIdCallback",
+ "android.adservices.appsetid.IGetAppSetIdProviderCallback",
+ "android.adservices.cobalt.IAdServicesCobaltUploadService",
+ "android.adservices.common.IAdServicesCommonCallback",
+ "android.adservices.common.IAdServicesCommonService",
+ "android.adservices.common.IAdServicesCommonStatesCallback",
+ "android.adservices.common.IEnableAdServicesCallback",
+ "android.adservices.common.IUpdateAdIdCallback",
+ "android.adservices.customaudience.CustomAudienceOverrideCallback",
+ "android.adservices.customaudience.FetchAndJoinCustomAudienceCallback",
+ "android.adservices.customaudience.ICustomAudienceCallback",
+ "android.adservices.customaudience.ICustomAudienceService",
+ "android.adservices.customaudience.ScheduleCustomAudienceUpdateCallback",
+ "android.adservices.extdata.IAdServicesExtDataStorageService",
+ "android.adservices.extdata.IGetAdServicesExtDataCallback",
+ "android.adservices.measurement.IMeasurementApiStatusCallback",
+ "android.adservices.measurement.IMeasurementCallback",
+ "android.adservices.measurement.IMeasurementService",
+ "android.adservices.ondevicepersonalization.aidl.IDataAccessService",
+ "android.adservices.ondevicepersonalization.aidl.IDataAccessServiceCallback",
+ "android.adservices.ondevicepersonalization.aidl.IExecuteCallback",
+ "android.adservices.ondevicepersonalization.aidl.IFederatedComputeCallback",
+ "android.adservices.ondevicepersonalization.aidl.IFederatedComputeService",
+ "android.adservices.ondevicepersonalization.aidl.IIsolatedModelService",
+ "android.adservices.ondevicepersonalization.aidl.IIsolatedModelServiceCallback",
+ "android.adservices.ondevicepersonalization.aidl.IIsolatedService",
+ "android.adservices.ondevicepersonalization.aidl.IIsolatedServiceCallback",
+ "android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationConfigService",
+ "android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationConfigServiceCallback",
+ "android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationDebugService",
+ "android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationManagingService",
+ "android.adservices.ondevicepersonalization.aidl.IRegisterMeasurementEventCallback",
+ "android.adservices.ondevicepersonalization.aidl.IRequestSurfacePackageCallback",
+ "android.adservices.shell.IShellCommand",
+ "android.adservices.shell.IShellCommandCallback",
+ "android.adservices.signals.IProtectedSignalsService",
+ "android.adservices.signals.UpdateSignalsCallback",
+ "android.adservices.topics.IGetTopicsCallback",
+ "android.adservices.topics.ITopicsService",
+ "android.app.admin.IDevicePolicyManager",
+ "android.app.adservices.IAdServicesManager",
+ "android.app.ambientcontext.IAmbientContextManager",
+ "android.app.ambientcontext.IAmbientContextObserver",
+ "android.app.appsearch.aidl.IAppFunctionService",
+ "android.app.appsearch.aidl.IAppSearchBatchResultCallback",
+ "android.app.appsearch.aidl.IAppSearchManager",
+ "android.app.appsearch.aidl.IAppSearchObserverProxy",
+ "android.app.appsearch.aidl.IAppSearchResultCallback",
+ "android.app.backup.IBackupCallback",
+ "android.app.backup.IBackupManager",
+ "android.app.backup.IRestoreSession",
+ "android.app.blob.IBlobCommitCallback",
+ "android.app.blob.IBlobStoreManager",
+ "android.app.blob.IBlobStoreSession",
+ "android.app.contentsuggestions.IContentSuggestionsManager",
+ "android.app.contextualsearch.IContextualSearchManager",
+ "android.app.ecm.IEnhancedConfirmationManager",
+ "android.apphibernation.IAppHibernationService",
+ "android.app.IActivityClientController",
+ "android.app.IActivityController",
+ "android.app.IActivityTaskManager",
+ "android.app.IAlarmCompleteListener",
+ "android.app.IAlarmListener",
+ "android.app.IAlarmManager",
+ "android.app.IApplicationThread",
+ "android.app.IAppTask",
+ "android.app.IAppTraceRetriever",
+ "android.app.IAssistDataReceiver",
+ "android.app.IForegroundServiceObserver",
+ "android.app.IGameManagerService",
+ "android.app.IGrammaticalInflectionManager",
+ "android.app.ILocaleManager",
+ "android.app.INotificationManager",
+ "android.app.IParcelFileDescriptorRetriever",
+ "android.app.IProcessObserver",
+ "android.app.ISearchManager",
+ "android.app.IStopUserCallback",
+ "android.app.ITaskStackListener",
+ "android.app.IUiModeManager",
+ "android.app.IUriGrantsManager",
+ "android.app.IUserSwitchObserver",
+ "android.app.IWallpaperManager",
+ "android.app.job.IJobCallback",
+ "android.app.job.IJobScheduler",
+ "android.app.job.IJobService",
+ "android.app.ondeviceintelligence.IDownloadCallback",
+ "android.app.ondeviceintelligence.IFeatureCallback",
+ "android.app.ondeviceintelligence.IFeatureDetailsCallback",
+ "android.app.ondeviceintelligence.IListFeaturesCallback",
+ "android.app.ondeviceintelligence.IOnDeviceIntelligenceManager",
+ "android.app.ondeviceintelligence.IProcessingSignal",
+ "android.app.ondeviceintelligence.IResponseCallback",
+ "android.app.ondeviceintelligence.IStreamingResponseCallback",
+ "android.app.ondeviceintelligence.ITokenInfoCallback",
+ "android.app.people.IPeopleManager",
+ "android.app.pinner.IPinnerService",
+ "android.app.prediction.IPredictionManager",
+ "android.app.role.IOnRoleHoldersChangedListener",
+ "android.app.role.IRoleController",
+ "android.app.role.IRoleManager",
+ "android.app.sdksandbox.ILoadSdkCallback",
+ "android.app.sdksandbox.IRequestSurfacePackageCallback",
+ "android.app.sdksandbox.ISdkSandboxManager",
+ "android.app.sdksandbox.ISdkSandboxProcessDeathCallback",
+ "android.app.sdksandbox.ISdkToServiceCallback",
+ "android.app.sdksandbox.ISharedPreferencesSyncCallback",
+ "android.app.sdksandbox.IUnloadSdkCallback",
+ "android.app.sdksandbox.testutils.testscenario.ISdkSandboxTestExecutor",
+ "android.app.search.ISearchUiManager",
+ "android.app.slice.ISliceManager",
+ "android.app.smartspace.ISmartspaceManager",
+ "android.app.timedetector.ITimeDetectorService",
+ "android.app.timezonedetector.ITimeZoneDetectorService",
+ "android.app.trust.ITrustManager",
+ "android.app.usage.IStorageStatsManager",
+ "android.app.usage.IUsageStatsManager",
+ "android.app.wallpapereffectsgeneration.IWallpaperEffectsGenerationManager",
+ "android.app.wearable.IWearableSensingCallback",
+ "android.app.wearable.IWearableSensingManager",
+ "android.bluetooth.IBluetooth",
+ "android.bluetooth.IBluetoothA2dp",
+ "android.bluetooth.IBluetoothA2dpSink",
+ "android.bluetooth.IBluetoothActivityEnergyInfoListener",
+ "android.bluetooth.IBluetoothAvrcpController",
+ "android.bluetooth.IBluetoothCallback",
+ "android.bluetooth.IBluetoothConnectionCallback",
+ "android.bluetooth.IBluetoothCsipSetCoordinator",
+ "android.bluetooth.IBluetoothCsipSetCoordinatorLockCallback",
+ "android.bluetooth.IBluetoothGatt",
+ "android.bluetooth.IBluetoothGattCallback",
+ "android.bluetooth.IBluetoothGattServerCallback",
+ "android.bluetooth.IBluetoothHapClient",
+ "android.bluetooth.IBluetoothHapClientCallback",
+ "android.bluetooth.IBluetoothHeadset",
+ "android.bluetooth.IBluetoothHeadsetClient",
+ "android.bluetooth.IBluetoothHearingAid",
+ "android.bluetooth.IBluetoothHidDevice",
+ "android.bluetooth.IBluetoothHidDeviceCallback",
+ "android.bluetooth.IBluetoothHidHost",
+ "android.bluetooth.IBluetoothLeAudio",
+ "android.bluetooth.IBluetoothLeAudioCallback",
+ "android.bluetooth.IBluetoothLeBroadcastAssistant",
+ "android.bluetooth.IBluetoothLeBroadcastAssistantCallback",
+ "android.bluetooth.IBluetoothLeBroadcastCallback",
+ "android.bluetooth.IBluetoothLeCallControl",
+ "android.bluetooth.IBluetoothLeCallControlCallback",
+ "android.bluetooth.IBluetoothManager",
+ "android.bluetooth.IBluetoothManagerCallback",
+ "android.bluetooth.IBluetoothMap",
+ "android.bluetooth.IBluetoothMapClient",
+ "android.bluetooth.IBluetoothMcpServiceManager",
+ "android.bluetooth.IBluetoothMetadataListener",
+ "android.bluetooth.IBluetoothOobDataCallback",
+ "android.bluetooth.IBluetoothPan",
+ "android.bluetooth.IBluetoothPanCallback",
+ "android.bluetooth.IBluetoothPbap",
+ "android.bluetooth.IBluetoothPbapClient",
+ "android.bluetooth.IBluetoothPreferredAudioProfilesCallback",
+ "android.bluetooth.IBluetoothQualityReportReadyCallback",
+ "android.bluetooth.IBluetoothSap",
+ "android.bluetooth.IBluetoothScan",
+ "android.bluetooth.IBluetoothSocketManager",
+ "android.bluetooth.IBluetoothVolumeControl",
+ "android.bluetooth.IBluetoothVolumeControlCallback",
+ "android.bluetooth.le.IAdvertisingSetCallback",
+ "android.bluetooth.le.IDistanceMeasurementCallback",
+ "android.bluetooth.le.IPeriodicAdvertisingCallback",
+ "android.bluetooth.le.IScannerCallback",
+ "android.companion.ICompanionDeviceManager",
+ "android.companion.IOnMessageReceivedListener",
+ "android.companion.IOnTransportsChangedListener",
+ "android.companion.virtualcamera.IVirtualCameraCallback",
+ "android.companion.virtual.IVirtualDevice",
+ "android.companion.virtual.IVirtualDeviceManager",
+ "android.companion.virtualnative.IVirtualDeviceManagerNative",
+ "android.content.IClipboard",
+ "android.content.IContentService",
+ "android.content.IIntentReceiver",
+ "android.content.IIntentSender",
+ "android.content.integrity.IAppIntegrityManager",
+ "android.content.IRestrictionsManager",
+ "android.content.ISyncAdapterUnsyncableAccountCallback",
+ "android.content.ISyncContext",
+ "android.content.om.IOverlayManager",
+ "android.content.pm.dex.IArtManager",
+ "android.content.pm.dex.ISnapshotRuntimeProfileCallback",
+ "android.content.pm.IBackgroundInstallControlService",
+ "android.content.pm.ICrossProfileApps",
+ "android.content.pm.IDataLoaderManager",
+ "android.content.pm.IDataLoaderStatusListener",
+ "android.content.pm.ILauncherApps",
+ "android.content.pm.IOnChecksumsReadyListener",
+ "android.content.pm.IOtaDexopt",
+ "android.content.pm.IPackageDataObserver",
+ "android.content.pm.IPackageDeleteObserver",
+ "android.content.pm.IPackageInstaller",
+ "android.content.pm.IPackageInstallerSession",
+ "android.content.pm.IPackageInstallerSessionFileSystemConnector",
+ "android.content.pm.IPackageInstallObserver2",
+ "android.content.pm.IPackageLoadingProgressCallback",
+ "android.content.pm.IPackageManager",
+ "android.content.pm.IPackageManagerNative",
+ "android.content.pm.IPackageMoveObserver",
+ "android.content.pm.IPinItemRequest",
+ "android.content.pm.IShortcutService",
+ "android.content.pm.IStagedApexObserver",
+ "android.content.pm.verify.domain.IDomainVerificationManager",
+ "android.content.res.IResourcesManager",
+ "android.content.rollback.IRollbackManager",
+ "android.credentials.ICredentialManager",
+ "android.debug.IAdbTransport",
+ "android.devicelock.IDeviceLockService",
+ "android.devicelock.IGetDeviceIdCallback",
+ "android.devicelock.IGetKioskAppsCallback",
+ "android.devicelock.IIsDeviceLockedCallback",
+ "android.devicelock.IVoidResultCallback",
+ "android.federatedcompute.aidl.IExampleStoreCallback",
+ "android.federatedcompute.aidl.IExampleStoreIterator",
+ "android.federatedcompute.aidl.IExampleStoreIteratorCallback",
+ "android.federatedcompute.aidl.IExampleStoreService",
+ "android.federatedcompute.aidl.IFederatedComputeCallback",
+ "android.federatedcompute.aidl.IFederatedComputeService",
+ "android.federatedcompute.aidl.IResultHandlingService",
+ "android.flags.IFeatureFlags",
+ "android.frameworks.location.altitude.IAltitudeService",
+ "android.frameworks.vibrator.IVibratorController",
+ "android.frameworks.vibrator.IVibratorControlService",
+ "android.gsi.IGsiServiceCallback",
+ "android.hardware.biometrics.AuthenticationStateListener",
+ "android.hardware.biometrics.common.ICancellationSignal",
+ "android.hardware.biometrics.face.IFace",
+ "android.hardware.biometrics.face.ISession",
+ "android.hardware.biometrics.face.ISessionCallback",
+ "android.hardware.biometrics.fingerprint.IFingerprint",
+ "android.hardware.biometrics.fingerprint.ISession",
+ "android.hardware.biometrics.fingerprint.ISessionCallback",
+ "android.hardware.biometrics.IAuthService",
+ "android.hardware.biometrics.IBiometricAuthenticator",
+ "android.hardware.biometrics.IBiometricContextListener",
+ "android.hardware.biometrics.IBiometricSensorReceiver",
+ "android.hardware.biometrics.IBiometricService",
+ "android.hardware.biometrics.IBiometricStateListener",
+ "android.hardware.biometrics.IBiometricSysuiReceiver",
+ "android.hardware.biometrics.IInvalidationCallback",
+ "android.hardware.biometrics.ITestSession",
+ "android.hardware.broadcastradio.IAnnouncementListener",
+ "android.hardware.broadcastradio.ITunerCallback",
+ "android.hardware.contexthub.IContextHubCallback",
+ "android.hardware.devicestate.IDeviceStateManager",
+ "android.hardware.display.IColorDisplayManager",
+ "android.hardware.display.IDisplayManager",
+ "android.hardware.face.IFaceAuthenticatorsRegisteredCallback",
+ "android.hardware.face.IFaceService",
+ "android.hardware.face.IFaceServiceReceiver",
+ "android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback",
+ "android.hardware.fingerprint.IFingerprintClientActiveCallback",
+ "android.hardware.fingerprint.IFingerprintService",
+ "android.hardware.fingerprint.IFingerprintServiceReceiver",
+ "android.hardware.fingerprint.IUdfpsOverlayControllerCallback",
+ "android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback",
+ "android.hardware.hdmi.IHdmiControlCallback",
+ "android.hardware.hdmi.IHdmiControlService",
+ "android.hardware.hdmi.IHdmiDeviceEventListener",
+ "android.hardware.hdmi.IHdmiHotplugEventListener",
+ "android.hardware.hdmi.IHdmiSystemAudioModeChangeListener",
+ "android.hardware.health.IHealthInfoCallback",
+ "android.hardware.ICameraServiceProxy",
+ "android.hardware.IConsumerIrService",
+ "android.hardware.input.IInputManager",
+ "android.hardware.iris.IIrisService",
+ "android.hardware.ISensorPrivacyManager",
+ "android.hardware.ISerialManager",
+ "android.hardware.lights.ILightsManager",
+ "android.hardware.location.IContextHubClient",
+ "android.hardware.location.IContextHubClientCallback",
+ "android.hardware.location.IContextHubService",
+ "android.hardware.location.IContextHubTransactionCallback",
+ "android.hardware.location.ISignificantPlaceProviderManager",
+ "android.hardware.radio.IAnnouncementListener",
+ "android.hardware.radio.ICloseHandle",
+ "android.hardware.radio.ims.media.IImsMedia",
+ "android.hardware.radio.ims.media.IImsMediaListener",
+ "android.hardware.radio.ims.media.IImsMediaSession",
+ "android.hardware.radio.ims.media.IImsMediaSessionListener",
+ "android.hardware.radio.IRadioService",
+ "android.hardware.radio.ITuner",
+ "android.hardware.radio.sap.ISapCallback",
+ "android.hardware.soundtrigger3.ISoundTriggerHw",
+ "android.hardware.soundtrigger3.ISoundTriggerHwCallback",
+ "android.hardware.soundtrigger3.ISoundTriggerHwGlobalCallback",
+ "android.hardware.soundtrigger.IRecognitionStatusCallback",
+ "android.hardware.tetheroffload.ITetheringOffloadCallback",
+ "android.hardware.thermal.IThermalChangedCallback",
+ "android.hardware.tv.hdmi.cec.IHdmiCecCallback",
+ "android.hardware.tv.hdmi.connection.IHdmiConnectionCallback",
+ "android.hardware.tv.hdmi.earc.IEArcCallback",
+ "android.hardware.usb.gadget.IUsbGadgetCallback",
+ "android.hardware.usb.IUsbCallback",
+ "android.hardware.usb.IUsbManager",
+ "android.hardware.usb.IUsbSerialReader",
+ "android.hardware.wifi.hostapd.IHostapdCallback",
+ "android.hardware.wifi.IWifiChipEventCallback",
+ "android.hardware.wifi.IWifiEventCallback",
+ "android.hardware.wifi.IWifiNanIfaceEventCallback",
+ "android.hardware.wifi.IWifiRttControllerEventCallback",
+ "android.hardware.wifi.IWifiStaIfaceEventCallback",
+ "android.hardware.wifi.supplicant.INonStandardCertCallback",
+ "android.hardware.wifi.supplicant.ISupplicantP2pIfaceCallback",
+ "android.hardware.wifi.supplicant.ISupplicantStaIfaceCallback",
+ "android.hardware.wifi.supplicant.ISupplicantStaNetworkCallback",
+ "android.health.connect.aidl.IAccessLogsResponseCallback",
+ "android.health.connect.aidl.IActivityDatesResponseCallback",
+ "android.health.connect.aidl.IAggregateRecordsResponseCallback",
+ "android.health.connect.aidl.IApplicationInfoResponseCallback",
+ "android.health.connect.aidl.IChangeLogsResponseCallback",
+ "android.health.connect.aidl.IDataStagingFinishedCallback",
+ "android.health.connect.aidl.IEmptyResponseCallback",
+ "android.health.connect.aidl.IGetChangeLogTokenCallback",
+ "android.health.connect.aidl.IGetHealthConnectDataStateCallback",
+ "android.health.connect.aidl.IGetHealthConnectMigrationUiStateCallback",
+ "android.health.connect.aidl.IGetPriorityResponseCallback",
+ "android.health.connect.aidl.IHealthConnectService",
+ "android.health.connect.aidl.IInsertRecordsResponseCallback",
+ "android.health.connect.aidl.IMedicalDataSourceResponseCallback",
+ "android.health.connect.aidl.IMedicalResourcesResponseCallback",
+ "android.health.connect.aidl.IMigrationCallback",
+ "android.health.connect.aidl.IReadMedicalResourcesResponseCallback",
+ "android.health.connect.aidl.IReadRecordsResponseCallback",
+ "android.health.connect.aidl.IRecordTypeInfoResponseCallback",
+ "android.health.connect.exportimport.IImportStatusCallback",
+ "android.health.connect.exportimport.IQueryDocumentProvidersCallback",
+ "android.health.connect.exportimport.IScheduledExportStatusCallback",
+ "android.location.ICountryDetector",
+ "android.location.IGpsGeofenceHardware",
+ "android.location.ILocationManager",
+ "android.location.provider.ILocationProviderManager",
+ "android.media.IAudioRoutesObserver",
+ "android.media.IMediaCommunicationService",
+ "android.media.IMediaCommunicationServiceCallback",
+ "android.media.IMediaController2",
+ "android.media.IMediaRoute2ProviderServiceCallback",
+ "android.media.IMediaRouterService",
+ "android.media.IMediaSession2",
+ "android.media.IMediaSession2Service",
+ "android.media.INativeSpatializerCallback",
+ "android.media.IPlaybackConfigDispatcher",
+ "android.media.IRecordingConfigDispatcher",
+ "android.media.IRemoteDisplayCallback",
+ "android.media.ISoundDoseCallback",
+ "android.media.ISpatializerHeadTrackingCallback",
+ "android.media.ITranscodingClientCallback",
+ "android.media.metrics.IMediaMetricsManager",
+ "android.media.midi.IMidiManager",
+ "android.media.musicrecognition.IMusicRecognitionAttributionTagCallback",
+ "android.media.musicrecognition.IMusicRecognitionManager",
+ "android.media.musicrecognition.IMusicRecognitionServiceCallback",
+ "android.media.projection.IMediaProjection",
+ "android.media.projection.IMediaProjectionCallback",
+ "android.media.projection.IMediaProjectionManager",
+ "android.media.projection.IMediaProjectionWatcherCallback",
+ "android.media.session.ISession",
+ "android.media.session.ISessionController",
+ "android.media.session.ISessionManager",
+ "android.media.soundtrigger.ISoundTriggerDetectionServiceClient",
+ "android.media.soundtrigger_middleware.IInjectGlobalEvent",
+ "android.media.soundtrigger_middleware.IInjectModelEvent",
+ "android.media.soundtrigger_middleware.IInjectRecognitionEvent",
+ "android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService",
+ "android.media.soundtrigger_middleware.ISoundTriggerModule",
+ "android.media.tv.ad.ITvAdManager",
+ "android.media.tv.ad.ITvAdSessionCallback",
+ "android.media.tv.interactive.ITvInteractiveAppManager",
+ "android.media.tv.interactive.ITvInteractiveAppServiceCallback",
+ "android.media.tv.interactive.ITvInteractiveAppSessionCallback",
+ "android.media.tv.ITvInputHardware",
+ "android.media.tv.ITvInputManager",
+ "android.media.tv.ITvInputServiceCallback",
+ "android.media.tv.ITvInputSessionCallback",
+ "android.media.tv.ITvRemoteServiceInput",
+ "android.nearby.aidl.IOffloadCallback",
+ "android.nearby.IBroadcastListener",
+ "android.nearby.INearbyManager",
+ "android.nearby.IScanListener",
+ "android.net.connectivity.aidl.ConnectivityNative",
+ "android.net.dhcp.IDhcpEventCallbacks",
+ "android.net.dhcp.IDhcpServer",
+ "android.net.dhcp.IDhcpServerCallbacks",
+ "android.net.ICaptivePortal",
+ "android.net.IConnectivityDiagnosticsCallback",
+ "android.net.IConnectivityManager",
+ "android.net.IEthernetManager",
+ "android.net.IEthernetServiceListener",
+ "android.net.IIntResultListener",
+ "android.net.IIpConnectivityMetrics",
+ "android.net.IIpMemoryStore",
+ "android.net.IIpMemoryStoreCallbacks",
+ "android.net.IIpSecService",
+ "android.net.INetdEventCallback",
+ "android.net.INetdUnsolicitedEventListener",
+ "android.net.INetworkActivityListener",
+ "android.net.INetworkAgent",
+ "android.net.INetworkAgentRegistry",
+ "android.net.INetworkInterfaceOutcomeReceiver",
+ "android.net.INetworkManagementEventObserver",
+ "android.net.INetworkMonitor",
+ "android.net.INetworkMonitorCallbacks",
+ "android.net.INetworkOfferCallback",
+ "android.net.INetworkPolicyListener",
+ "android.net.INetworkPolicyManager",
+ "android.net.INetworkScoreService",
+ "android.net.INetworkStackConnector",
+ "android.net.INetworkStackStatusCallback",
+ "android.net.INetworkStatsService",
+ "android.net.INetworkStatsSession",
+ "android.net.IOnCompleteListener",
+ "android.net.IPacProxyManager",
+ "android.net.ip.IIpClient",
+ "android.net.ip.IIpClientCallbacks",
+ "android.net.ipmemorystore.IOnBlobRetrievedListener",
+ "android.net.ipmemorystore.IOnL2KeyResponseListener",
+ "android.net.ipmemorystore.IOnNetworkAttributesRetrievedListener",
+ "android.net.ipmemorystore.IOnSameL3NetworkResponseListener",
+ "android.net.ipmemorystore.IOnStatusAndCountListener",
+ "android.net.ipmemorystore.IOnStatusListener",
+ "android.net.IQosCallback",
+ "android.net.ISocketKeepaliveCallback",
+ "android.net.ITestNetworkManager",
+ "android.net.ITetheredInterfaceCallback",
+ "android.net.ITetheringConnector",
+ "android.net.ITetheringEventCallback",
+ "android.net.IVpnManager",
+ "android.net.mdns.aidl.IMDnsEventListener",
+ "android.net.metrics.INetdEventListener",
+ "android.net.netstats.IUsageCallback",
+ "android.net.netstats.provider.INetworkStatsProvider",
+ "android.net.netstats.provider.INetworkStatsProviderCallback",
+ "android.net.nsd.INsdManager",
+ "android.net.nsd.INsdManagerCallback",
+ "android.net.nsd.INsdServiceConnector",
+ "android.net.nsd.IOffloadEngine",
+ "android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener",
+ "android.net.thread.IActiveOperationalDatasetReceiver",
+ "android.net.thread.IConfigurationReceiver",
+ "android.net.thread.IOperationalDatasetCallback",
+ "android.net.thread.IOperationReceiver",
+ "android.net.thread.IStateCallback",
+ "android.net.thread.IThreadNetworkController",
+ "android.net.thread.IThreadNetworkManager",
+ "android.net.vcn.IVcnManagementService",
+ "android.net.wear.ICompanionDeviceManagerProxy",
+ "android.net.wifi.aware.IWifiAwareDiscoverySessionCallback",
+ "android.net.wifi.aware.IWifiAwareEventCallback",
+ "android.net.wifi.aware.IWifiAwareMacAddressProvider",
+ "android.net.wifi.aware.IWifiAwareManager",
+ "android.net.wifi.hotspot2.IProvisioningCallback",
+ "android.net.wifi.IActionListener",
+ "android.net.wifi.IBooleanListener",
+ "android.net.wifi.IByteArrayListener",
+ "android.net.wifi.ICoexCallback",
+ "android.net.wifi.IDppCallback",
+ "android.net.wifi.IIntegerListener",
+ "android.net.wifi.IInterfaceCreationInfoCallback",
+ "android.net.wifi.ILastCallerListener",
+ "android.net.wifi.IListListener",
+ "android.net.wifi.ILocalOnlyConnectionStatusListener",
+ "android.net.wifi.ILocalOnlyHotspotCallback",
+ "android.net.wifi.IMacAddressListListener",
+ "android.net.wifi.IMapListener",
+ "android.net.wifi.INetworkRequestMatchCallback",
+ "android.net.wifi.INetworkRequestUserSelectionCallback",
+ "android.net.wifi.IOnWifiActivityEnergyInfoListener",
+ "android.net.wifi.IOnWifiDriverCountryCodeChangedListener",
+ "android.net.wifi.IOnWifiUsabilityStatsListener",
+ "android.net.wifi.IPnoScanResultsCallback",
+ "android.net.wifi.IScanDataListener",
+ "android.net.wifi.IScanResultsCallback",
+ "android.net.wifi.IScoreUpdateObserver",
+ "android.net.wifi.ISoftApCallback",
+ "android.net.wifi.IStringListener",
+ "android.net.wifi.ISubsystemRestartCallback",
+ "android.net.wifi.ISuggestionConnectionStatusListener",
+ "android.net.wifi.ISuggestionUserApprovalStatusListener",
+ "android.net.wifi.ITrafficStateCallback",
+ "android.net.wifi.ITwtCallback",
+ "android.net.wifi.ITwtCapabilitiesListener",
+ "android.net.wifi.ITwtStatsListener",
+ "android.net.wifi.IWifiBandsListener",
+ "android.net.wifi.IWifiConnectedNetworkScorer",
+ "android.net.wifi.IWifiLowLatencyLockListener",
+ "android.net.wifi.IWifiManager",
+ "android.net.wifi.IWifiNetworkSelectionConfigListener",
+ "android.net.wifi.IWifiNetworkStateChangedListener",
+ "android.net.wifi.IWifiScanner",
+ "android.net.wifi.IWifiScannerListener",
+ "android.net.wifi.IWifiVerboseLoggingStatusChangedListener",
+ "android.net.wifi.p2p.IWifiP2pListener",
+ "android.net.wifi.p2p.IWifiP2pManager",
+ "android.net.wifi.rtt.IRttCallback",
+ "android.net.wifi.rtt.IWifiRttManager",
+ "android.ondevicepersonalization.IOnDevicePersonalizationSystemService",
+ "android.ondevicepersonalization.IOnDevicePersonalizationSystemServiceCallback",
+ "android.os.IBatteryPropertiesRegistrar",
+ "android.os.ICancellationSignal",
+ "android.os.IDeviceIdentifiersPolicyService",
+ "android.os.IDeviceIdleController",
+ "android.os.IDumpstate",
+ "android.os.IDumpstateListener",
+ "android.os.IExternalVibratorService",
+ "android.os.IHardwarePropertiesManager",
+ "android.os.IHintManager",
+ "android.os.IHintSession",
+ "android.os.IIncidentCompanion",
+ "android.os.image.IDynamicSystemService",
+ "android.os.incremental.IStorageHealthListener",
+ "android.os.INetworkManagementService",
+ "android.os.IPendingIntentRef",
+ "android.os.IPowerStatsService",
+ "android.os.IProfilingResultCallback",
+ "android.os.IProfilingService",
+ "android.os.IProgressListener",
+ "android.os.IPullAtomCallback",
+ "android.os.IRecoverySystem",
+ "android.os.IRemoteCallback",
+ "android.os.ISecurityStateManager",
+ "android.os.IServiceCallback",
+ "android.os.IStatsCompanionService",
+ "android.os.IStatsManagerService",
+ "android.os.IStatsQueryCallback",
+ "android.os.ISystemConfig",
+ "android.os.ISystemUpdateManager",
+ "android.os.IThermalEventListener",
+ "android.os.IUpdateLock",
+ "android.os.IUserManager",
+ "android.os.IUserRestrictionsListener",
+ "android.os.IVibratorManagerService",
+ "android.os.IVoldListener",
+ "android.os.IVoldMountCallback",
+ "android.os.IVoldTaskListener",
+ "android.os.logcat.ILogcatManagerService",
+ "android.permission.ILegacyPermissionManager",
+ "android.permission.IPermissionChecker",
+ "android.permission.IPermissionManager",
+ "android.print.IPrintManager",
+ "android.print.IPrintSpoolerCallbacks",
+ "android.print.IPrintSpoolerClient",
+ "android.printservice.IPrintServiceClient",
+ "android.printservice.recommendation.IRecommendationServiceCallbacks",
+ "android.provider.aidl.IDeviceConfigManager",
+ "android.remoteauth.IDeviceDiscoveryListener",
+ "android.safetycenter.IOnSafetyCenterDataChangedListener",
+ "android.safetycenter.ISafetyCenterManager",
+ "android.scheduling.IRebootReadinessManager",
+ "android.scheduling.IRequestRebootReadinessStatusListener",
+ "android.security.attestationverification.IAttestationVerificationManagerService",
+ "android.security.IFileIntegrityService",
+ "android.security.keystore.IKeyAttestationApplicationIdProvider",
+ "android.security.rkp.IRegistration",
+ "android.security.rkp.IRemoteProvisioning",
+ "android.service.appprediction.IPredictionService",
+ "android.service.assist.classification.IFieldClassificationCallback",
+ "android.service.attention.IAttentionCallback",
+ "android.service.attention.IProximityUpdateCallback",
+ "android.service.autofill.augmented.IFillCallback",
+ "android.service.autofill.IConvertCredentialCallback",
+ "android.service.autofill.IFillCallback",
+ "android.service.autofill.IInlineSuggestionUiCallback",
+ "android.service.autofill.ISaveCallback",
+ "android.service.autofill.ISurfacePackageResultCallback",
+ "android.service.contentcapture.IContentCaptureServiceCallback",
+ "android.service.contentcapture.IContentProtectionAllowlistCallback",
+ "android.service.contentcapture.IDataShareCallback",
+ "android.service.credentials.IBeginCreateCredentialCallback",
+ "android.service.credentials.IBeginGetCredentialCallback",
+ "android.service.credentials.IClearCredentialStateCallback",
+ "android.service.dreams.IDreamManager",
+ "android.service.games.IGameServiceController",
+ "android.service.games.IGameSessionController",
+ "android.service.notification.IStatusBarNotificationHolder",
+ "android.service.oemlock.IOemLockService",
+ "android.service.ondeviceintelligence.IProcessingUpdateStatusCallback",
+ "android.service.ondeviceintelligence.IRemoteProcessingService",
+ "android.service.ondeviceintelligence.IRemoteStorageService",
+ "android.service.persistentdata.IPersistentDataBlockService",
+ "android.service.resolver.IResolverRankerResult",
+ "android.service.rotationresolver.IRotationResolverCallback",
+ "android.service.textclassifier.ITextClassifierCallback",
+ "android.service.textclassifier.ITextClassifierService",
+ "android.service.timezone.ITimeZoneProviderManager",
+ "android.service.trust.ITrustAgentServiceCallback",
+ "android.service.voice.IDetectorSessionStorageService",
+ "android.service.voice.IDetectorSessionVisualQueryDetectionCallback",
+ "android.service.voice.IDspHotwordDetectionCallback",
+ "android.service.wallpaper.IWallpaperConnection",
+ "android.speech.IRecognitionListener",
+ "android.speech.IRecognitionService",
+ "android.speech.IRecognitionServiceManager",
+ "android.speech.tts.ITextToSpeechManager",
+ "android.speech.tts.ITextToSpeechSession",
+ "android.system.composd.ICompilationTaskCallback",
+ "android.system.virtualizationmaintenance.IVirtualizationReconciliationCallback",
+ "android.system.virtualizationservice.IVirtualMachineCallback",
+ "android.system.vmtethering.IVmTethering",
+ "android.telephony.imsmedia.IImsAudioSession",
+ "android.telephony.imsmedia.IImsAudioSessionCallback",
+ "android.telephony.imsmedia.IImsMedia",
+ "android.telephony.imsmedia.IImsMediaCallback",
+ "android.telephony.imsmedia.IImsTextSession",
+ "android.telephony.imsmedia.IImsTextSessionCallback",
+ "android.telephony.imsmedia.IImsVideoSession",
+ "android.telephony.imsmedia.IImsVideoSessionCallback",
+ "android.tracing.ITracingServiceProxy",
+ "android.uwb.IOnUwbActivityEnergyInfoListener",
+ "android.uwb.IUwbAdapter",
+ "android.uwb.IUwbAdapterStateCallbacks",
+ "android.uwb.IUwbAdfProvisionStateCallbacks",
+ "android.uwb.IUwbOemExtensionCallback",
+ "android.uwb.IUwbRangingCallbacks",
+ "android.uwb.IUwbVendorUciCallback",
+ "android.view.accessibility.IAccessibilityInteractionConnectionCallback",
+ "android.view.accessibility.IAccessibilityManager",
+ "android.view.accessibility.IMagnificationConnectionCallback",
+ "android.view.accessibility.IRemoteMagnificationAnimationCallback",
+ "android.view.autofill.IAutoFillManager",
+ "android.view.autofill.IAutofillWindowPresenter",
+ "android.view.contentcapture.IContentCaptureManager",
+ "android.view.IDisplayChangeWindowCallback",
+ "android.view.IDisplayWindowListener",
+ "android.view.IInputFilter",
+ "android.view.IInputFilterHost",
+ "android.view.IInputMonitorHost",
+ "android.view.IRecentsAnimationController",
+ "android.view.IRemoteAnimationFinishedCallback",
+ "android.view.ISensitiveContentProtectionManager",
+ "android.view.IWindowId",
+ "android.view.IWindowManager",
+ "android.view.IWindowSession",
+ "android.view.translation.ITranslationManager",
+ "android.view.translation.ITranslationServiceCallback",
+ "android.webkit.IWebViewUpdateService",
+ "android.window.IBackAnimationFinishedCallback",
+ "android.window.IDisplayAreaOrganizerController",
+ "android.window.ITaskFragmentOrganizerController",
+ "android.window.ITaskOrganizerController",
+ "android.window.ITransitionMetricsReporter",
+ "android.window.IUnhandledDragCallback",
+ "android.window.IWindowContainerToken",
+ "android.window.IWindowlessStartingSurfaceCallback",
+ "android.window.IWindowOrganizerController",
+ "androidx.core.uwb.backend.IUwb",
+ "androidx.core.uwb.backend.IUwbClient",
+ "com.android.clockwork.modes.IModeManager",
+ "com.android.clockwork.modes.IStateChangeListener",
+ "com.android.clockwork.power.IWearPowerService",
+ "com.android.devicelockcontroller.IDeviceLockControllerService",
+ "com.android.devicelockcontroller.storage.IGlobalParametersService",
+ "com.android.devicelockcontroller.storage.ISetupParametersService",
+ "com.android.federatedcompute.services.training.aidl.IIsolatedTrainingService",
+ "com.android.federatedcompute.services.training.aidl.ITrainingResultCallback",
+ "com.android.internal.app.IAppOpsActiveCallback",
+ "com.android.internal.app.ILogAccessDialogCallback",
+ "com.android.internal.app.ISoundTriggerService",
+ "com.android.internal.app.ISoundTriggerSession",
+ "com.android.internal.app.IVoiceInteractionAccessibilitySettingsListener",
+ "com.android.internal.app.IVoiceInteractionManagerService",
+ "com.android.internal.app.IVoiceInteractionSessionListener",
+ "com.android.internal.app.IVoiceInteractionSessionShowCallback",
+ "com.android.internal.app.IVoiceInteractionSoundTriggerSession",
+ "com.android.internal.app.procstats.IProcessStats",
+ "com.android.internal.appwidget.IAppWidgetService",
+ "com.android.internal.backup.ITransportStatusCallback",
+ "com.android.internal.compat.IOverrideValidator",
+ "com.android.internal.compat.IPlatformCompat",
+ "com.android.internal.compat.IPlatformCompatNative",
+ "com.android.internal.graphics.fonts.IFontManager",
+ "com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback",
+ "com.android.internal.inputmethod.IConnectionlessHandwritingCallback",
+ "com.android.internal.inputmethod.IImeTracker",
+ "com.android.internal.inputmethod.IInlineSuggestionsRequestCallback",
+ "com.android.internal.inputmethod.IInputContentUriToken",
+ "com.android.internal.inputmethod.IInputMethodPrivilegedOperations",
+ "com.android.internal.inputmethod.IInputMethodSessionCallback",
+ "com.android.internal.net.INetworkWatchlistManager",
+ "com.android.internal.os.IBinaryTransparencyService",
+ "com.android.internal.os.IDropBoxManagerService",
+ "com.android.internal.policy.IKeyguardDismissCallback",
+ "com.android.internal.policy.IKeyguardDrawnCallback",
+ "com.android.internal.policy.IKeyguardExitCallback",
+ "com.android.internal.policy.IKeyguardStateCallback",
+ "com.android.internal.statusbar.IAddTileResultCallback",
+ "com.android.internal.statusbar.ISessionListener",
+ "com.android.internal.statusbar.IStatusBarService",
+ "com.android.internal.telecom.IDeviceIdleControllerAdapter",
+ "com.android.internal.telecom.IInternalServiceRetriever",
+ "com.android.internal.telephony.IMms",
+ "com.android.internal.telephony.ITelephonyRegistry",
+ "com.android.internal.textservice.ISpellCheckerServiceCallback",
+ "com.android.internal.textservice.ITextServicesManager",
+ "com.android.internal.view.IDragAndDropPermissions",
+ "com.android.internal.view.IInputMethodManager",
+ "com.android.internal.view.inline.IInlineContentProvider",
+ "com.android.internal.widget.ILockSettings",
+ "com.android.net.IProxyPortListener",
+ "com.android.net.module.util.IRoutingCoordinator",
+ "com.android.ondevicepersonalization.libraries.plugin.internal.IPluginCallback",
+ "com.android.ondevicepersonalization.libraries.plugin.internal.IPluginExecutorService",
+ "com.android.ondevicepersonalization.libraries.plugin.internal.IPluginStateCallback",
+ "com.android.rkpdapp.IGetKeyCallback",
+ "com.android.rkpdapp.IGetRegistrationCallback",
+ "com.android.rkpdapp.IRegistration",
+ "com.android.rkpdapp.IRemoteProvisioning",
+ "com.android.rkpdapp.IStoreUpgradedKeyCallback",
+ "com.android.sdksandbox.IComputeSdkStorageCallback",
+ "com.android.sdksandbox.ILoadSdkInSandboxCallback",
+ "com.android.sdksandbox.IRequestSurfacePackageFromSdkCallback",
+ "com.android.sdksandbox.ISdkSandboxManagerToSdkSandboxCallback",
+ "com.android.sdksandbox.ISdkSandboxService",
+ "com.android.sdksandbox.IUnloadSdkInSandboxCallback",
+ "com.android.server.profcollect.IProviderStatusCallback",
+ "com.android.server.thread.openthread.IChannelMasksReceiver",
+ "com.android.server.thread.openthread.INsdPublisher",
+ "com.android.server.thread.openthread.IOtDaemonCallback",
+ "com.android.server.thread.openthread.IOtStatusReceiver",
+ "com.google.android.clockwork.ambient.offload.IDisplayOffloadService",
+ "com.google.android.clockwork.ambient.offload.IDisplayOffloadTransitionFinishedCallbacks",
+ "com.google.android.clockwork.healthservices.IHealthService",
+ "vendor.google_clockwork.healthservices.IHealthServicesCallback",
+)
diff --git a/tools/lint/utils/README.md b/tools/lint/utils/README.md
new file mode 100644
index 0000000..b5583c5
--- /dev/null
+++ b/tools/lint/utils/README.md
@@ -0,0 +1,11 @@
+# Utility Android Lint Checks for AOSP
+
+This directory contains scripts that execute utility Android Lint Checks for AOSP, specifically:
+* `enforce_permission_counter.py`: Provides statistics regarding the percentage of annotated/not
+ annotated `AIDL` methods with `@EnforcePermission` annotations.
+* `generate-exempt-aidl-interfaces.sh`: Provides a list of all `AIDL` interfaces in the entire
+ source tree.
+
+When adding a new utility Android Lint check to this directory, consider adding any utility or
+data processing tool you might require. Make sure that your contribution is documented in this
+README file.
diff --git a/tools/lint/utils/checks/src/main/java/com/google/android/lint/AndroidUtilsIssueRegistry.kt b/tools/lint/utils/checks/src/main/java/com/google/android/lint/AndroidUtilsIssueRegistry.kt
index fa61c42..9842881 100644
--- a/tools/lint/utils/checks/src/main/java/com/google/android/lint/AndroidUtilsIssueRegistry.kt
+++ b/tools/lint/utils/checks/src/main/java/com/google/android/lint/AndroidUtilsIssueRegistry.kt
@@ -19,6 +19,7 @@
import com.android.tools.lint.client.api.IssueRegistry
import com.android.tools.lint.client.api.Vendor
import com.android.tools.lint.detector.api.CURRENT_API
+import com.google.android.lint.aidl.ExemptAidlInterfacesGenerator
import com.google.android.lint.aidl.AnnotatedAidlCounter
import com.google.auto.service.AutoService
@@ -27,6 +28,7 @@
class AndroidUtilsIssueRegistry : IssueRegistry() {
override val issues = listOf(
AnnotatedAidlCounter.ISSUE_ANNOTATED_AIDL_COUNTER,
+ ExemptAidlInterfacesGenerator.ISSUE_PERMISSION_ANNOTATION_EXEMPT_AIDL_INTERFACES,
)
override val api: Int
@@ -38,6 +40,6 @@
override val vendor: Vendor = Vendor(
vendorName = "Android",
feedbackUrl = "http://b/issues/new?component=315013",
- contact = "tweek@google.com"
+ contact = "android-platform-abuse-prevention-withfriends@google.com"
)
}
diff --git a/tools/lint/utils/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfacesGenerator.kt b/tools/lint/utils/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfacesGenerator.kt
new file mode 100644
index 0000000..6ad223c
--- /dev/null
+++ b/tools/lint/utils/checks/src/main/java/com/google/android/lint/aidl/ExemptAidlInterfacesGenerator.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2024 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.google.android.lint.aidl
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Context
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UMethod
+
+/**
+ * Generates a set of fully qualified AIDL Interface names present in the entire source tree with
+ * the following requirement: their implementations have to be inside directories whose path
+ * prefixes match `systemServicePathPrefixes`.
+ */
+class ExemptAidlInterfacesGenerator : AidlImplementationDetector() {
+ private val targetExemptAidlInterfaceNames = mutableSetOf<String>()
+ private val systemServicePathPrefixes = setOf(
+ "frameworks/base/services",
+ "frameworks/base/apex",
+ "frameworks/opt/wear",
+ "packages/modules"
+ )
+
+ // We could've improved performance by visiting classes rather than methods, however, this lint
+ // check won't be run regularly, hence we've decided not to add extra overrides to
+ // AidlImplementationDetector.
+ override fun visitAidlMethod(
+ context: JavaContext,
+ node: UMethod,
+ interfaceName: String,
+ body: UBlockExpression
+ ) {
+ val filePath = context.file.path
+
+ // We perform `filePath.contains` instead of `filePath.startsWith` since getting the
+ // relative path of a source file is non-trivial. That is because `context.file.path`
+ // returns the path to where soong builds the file (i.e. /out/soong/...). Moreover, the
+ // logic to extract the relative path would need to consider several /out/soong/...
+ // locations patterns.
+ if (systemServicePathPrefixes.none { filePath.contains(it) }) return
+
+ val fullyQualifiedInterfaceName =
+ getContainingAidlInterfaceQualified(context, node) ?: return
+
+ targetExemptAidlInterfaceNames.add("\"$fullyQualifiedInterfaceName\",")
+ }
+
+ override fun afterCheckEachProject(context: Context) {
+ if (targetExemptAidlInterfaceNames.isEmpty()) return
+
+ val message = targetExemptAidlInterfaceNames.joinToString("\n")
+
+ context.report(
+ ISSUE_PERMISSION_ANNOTATION_EXEMPT_AIDL_INTERFACES,
+ context.getLocation(context.project.dir),
+ "\n" + message + "\n",
+ )
+ }
+
+ companion object {
+ @JvmField
+ val ISSUE_PERMISSION_ANNOTATION_EXEMPT_AIDL_INTERFACES = Issue.create(
+ id = "PermissionAnnotationExemptAidlInterfaces",
+ briefDescription = "Returns a set of all AIDL interfaces",
+ explanation = """
+ Produces the exemptAidlInterfaces set used by PermissionAnnotationDetector
+ """.trimIndent(),
+ category = Category.SECURITY,
+ priority = 5,
+ severity = Severity.INFORMATIONAL,
+ implementation = Implementation(
+ ExemptAidlInterfacesGenerator::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+ }
+}
diff --git a/tools/lint/utils/checks/src/test/java/com/google/android/lint/aidl/ExemptAidlInterfacesGeneratorTest.kt b/tools/lint/utils/checks/src/test/java/com/google/android/lint/aidl/ExemptAidlInterfacesGeneratorTest.kt
new file mode 100644
index 0000000..9a17bb4
--- /dev/null
+++ b/tools/lint/utils/checks/src/test/java/com/google/android/lint/aidl/ExemptAidlInterfacesGeneratorTest.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2024 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.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+class ExemptAidlInterfacesGeneratorTest : LintDetectorTest() {
+ override fun getDetector(): Detector = ExemptAidlInterfacesGenerator()
+
+ override fun getIssues(): List<Issue> = listOf(
+ ExemptAidlInterfacesGenerator.ISSUE_PERMISSION_ANNOTATION_EXEMPT_AIDL_INTERFACES,
+ )
+
+ override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+ fun testMultipleAidlInterfacesImplemented() {
+ lint()
+ .files(
+ java(
+ createVisitedPath("TestClass1.java"),
+ """
+ package com.android.server;
+ public class TestClass1 extends IFoo.Stub {
+ public void testMethod() {}
+ }
+ """
+ )
+ .indented(),
+ java(
+ createVisitedPath("TestClass2.java"),
+ """
+ package com.android.server;
+ public class TestClass2 extends IBar.Stub {
+ public void testMethod() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs,
+ )
+ .run()
+ .expect(
+ """
+ app: Information: "IFoo",
+ "IBar", [PermissionAnnotationExemptAidlInterfaces]
+ 0 errors, 0 warnings
+ """
+ )
+ }
+
+ fun testSingleAidlInterfaceRepeated() {
+ lint()
+ .files(
+ java(
+ createVisitedPath("TestClass1.java"),
+ """
+ package com.android.server;
+ public class TestClass1 extends IFoo.Stub {
+ public void testMethod() {}
+ }
+ """
+ )
+ .indented(),
+ java(
+ createVisitedPath("TestClass2.java"),
+ """
+ package com.android.server;
+ public class TestClass2 extends IFoo.Stub {
+ public void testMethod() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs,
+ )
+ .run()
+ .expect(
+ """
+ app: Information: "IFoo", [PermissionAnnotationExemptAidlInterfaces]
+ 0 errors, 0 warnings
+ """
+ )
+ }
+
+ fun testAnonymousClassExtendsAidlStub() {
+ lint()
+ .files(
+ java(
+ createVisitedPath("TestClass.java"),
+ """
+ package com.android.server;
+ public class TestClass {
+ private IBinder aidlImpl = new IFoo.Stub() {
+ public void testMethod() {}
+ };
+ }
+ """
+ )
+ .indented(),
+ *stubs,
+ )
+ .run()
+ .expect(
+ """
+ app: Information: "IFoo", [PermissionAnnotationExemptAidlInterfaces]
+ 0 errors, 0 warnings
+ """
+ )
+ }
+
+ fun testNoAidlInterfacesImplemented() {
+ lint()
+ .files(
+ java(
+ createVisitedPath("TestClass.java"),
+ """
+ package com.android.server;
+ public class TestClass {
+ public void testMethod() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs
+ )
+ .run()
+ .expectClean()
+ }
+
+ fun testAidlInterfaceImplementedInIgnoredDirectory() {
+ lint()
+ .files(
+ java(
+ ignoredPath,
+ """
+ package com.android.server;
+ public class TestClass1 extends IFoo.Stub {
+ public void testMethod() {}
+ }
+ """
+ )
+ .indented(),
+ *stubs,
+ )
+ .run()
+ .expectClean()
+ }
+
+ private val interfaceIFoo: TestFile = java(
+ """
+ public interface IFoo extends android.os.IInterface {
+ public static abstract class Stub extends android.os.Binder implements IFoo {}
+ public void testMethod();
+ }
+ """
+ ).indented()
+
+ private val interfaceIBar: TestFile = java(
+ """
+ public interface IBar extends android.os.IInterface {
+ public static abstract class Stub extends android.os.Binder implements IBar {}
+ public void testMethod();
+ }
+ """
+ ).indented()
+
+ private val stubs = arrayOf(interfaceIFoo, interfaceIBar)
+
+ private fun createVisitedPath(filename: String) =
+ "src/frameworks/base/services/java/com/android/server/$filename"
+
+ private val ignoredPath = "src/test/pkg/TestClass.java"
+}
diff --git a/tools/lint/utils/generate-exempt-aidl-interfaces.sh b/tools/lint/utils/generate-exempt-aidl-interfaces.sh
new file mode 100755
index 0000000..44dcdd7
--- /dev/null
+++ b/tools/lint/utils/generate-exempt-aidl-interfaces.sh
@@ -0,0 +1,59 @@
+#
+# Copyright (C) 2024 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.
+#
+
+# Create a directory for the results and a nested temporary directory.
+mkdir -p $ANDROID_BUILD_TOP/out/soong/exempt_aidl_interfaces_generator_output/tmp
+
+# Create a copy of `AndroidGlobalLintChecker.jar` to restore it afterwards.
+cp $ANDROID_BUILD_TOP/prebuilts/cmdline-tools/AndroidGlobalLintChecker.jar \
+ $ANDROID_BUILD_TOP/out/soong/exempt_aidl_interfaces_generator_output/AndroidGlobalLintChecker.jar
+
+# Configure the environment variable required for running the lint check on the entire source tree.
+export ANDROID_LINT_CHECK=PermissionAnnotationExemptAidlInterfaces
+
+# Build the target corresponding to the lint checks present in the `utils` directory.
+m AndroidUtilsLintChecker
+
+# Replace `AndroidGlobalLintChecker.jar` with the newly built `jar` file.
+cp $ANDROID_BUILD_TOP/out/host/linux-x86/framework/AndroidUtilsLintChecker.jar \
+ $ANDROID_BUILD_TOP/prebuilts/cmdline-tools/AndroidGlobalLintChecker.jar;
+
+# Run the lint check on the entire source tree.
+m lint-check
+
+# Copy the archive containing the results of `lint-check` into the temporary directory.
+cp $ANDROID_BUILD_TOP/out/soong/lint-report-text.zip \
+ $ANDROID_BUILD_TOP/out/soong/exempt_aidl_interfaces_generator_output/tmp
+
+cd $ANDROID_BUILD_TOP/out/soong/exempt_aidl_interfaces_generator_output/tmp
+
+# Unzip the archive containing the results of `lint-check`.
+unzip lint-report-text.zip
+
+# Concatenate the results of `lint-check` into a single string.
+concatenated_reports=$(find . -type f | xargs cat)
+
+# Extract the fully qualified names of the AIDL Interfaces from the concatenated results. Output
+# this list into `out/soong/exempt_aidl_interfaces_generator_output/exempt_aidl_interfaces`.
+echo $concatenated_reports | grep -Eo '\"([a-zA-Z0-9_]*\.)+[a-zA-Z0-9_]*\",' | sort | uniq > ../exempt_aidl_interfaces
+
+# Remove the temporary directory.
+rm -rf $ANDROID_BUILD_TOP/out/soong/exempt_aidl_interfaces_generator_output/tmp
+
+# Restore the original copy of `AndroidGlobalLintChecker.jar` and delete the copy.
+cp $ANDROID_BUILD_TOP/out/soong/exempt_aidl_interfaces_generator_output/AndroidGlobalLintChecker.jar \
+ $ANDROID_BUILD_TOP/prebuilts/cmdline-tools/AndroidGlobalLintChecker.jar
+rm $ANDROID_BUILD_TOP/out/soong/exempt_aidl_interfaces_generator_output/AndroidGlobalLintChecker.jar