Merge "Add onProcessCaptureRequest to IVirtualCameraCallback" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 275fe77..c282e4b6 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -307,7 +307,7 @@
field public static final String RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE";
field public static final String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER";
field public static final String REGISTER_CONNECTION_MANAGER = "android.permission.REGISTER_CONNECTION_MANAGER";
- field public static final String REGISTER_NSD_OFFLOAD_ENGINE = "android.permission.REGISTER_NSD_OFFLOAD_ENGINE";
+ field @FlaggedApi("com.android.net.flags.register_nsd_offload_engine") public static final String REGISTER_NSD_OFFLOAD_ENGINE = "android.permission.REGISTER_NSD_OFFLOAD_ENGINE";
field public static final String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION";
field public static final String REGISTER_STATS_PULL_ATOM = "android.permission.REGISTER_STATS_PULL_ATOM";
field public static final String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER";
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index bb335fa..6c10f49 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -20,6 +20,7 @@
import static android.Manifest.permission.DETECT_SCREEN_CAPTURE;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.inMultiWindowMode;
import static android.os.Process.myUid;
@@ -7603,15 +7604,17 @@
* @param taskDescription The TaskDescription properties that describe the task with this activity
*/
public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
- if (mTaskDescription != taskDescription) {
- mTaskDescription.copyFromPreserveHiddenFields(taskDescription);
- // Scale the icon down to something reasonable if it is provided
- if (taskDescription.getIconFilename() == null && taskDescription.getIcon() != null) {
- final int size = ActivityManager.getLauncherLargeIconSizeInner(this);
- final Bitmap icon = Bitmap.createScaledBitmap(taskDescription.getIcon(), size, size,
- true);
- mTaskDescription.setIcon(Icon.createWithBitmap(icon));
- }
+ if (taskDescription == null || mTaskDescription.equals(taskDescription)) {
+ return;
+ }
+
+ mTaskDescription.copyFromPreserveHiddenFields(taskDescription);
+ // Scale the icon down to something reasonable if it is provided
+ if (taskDescription.getIconFilename() == null && taskDescription.getIcon() != null) {
+ final int size = ActivityManager.getLauncherLargeIconSizeInner(this);
+ final Bitmap icon = Bitmap.createScaledBitmap(taskDescription.getIcon(), size, size,
+ true);
+ mTaskDescription.setIcon(Icon.createWithBitmap(icon));
}
ActivityClient.getInstance().setTaskDescription(mToken, mTaskDescription);
}
@@ -9439,6 +9442,15 @@
ActivityClient.getInstance().enableTaskLocaleOverride(mToken);
}
+ /**
+ * Request ActivityRecordInputSink to enable or disable blocking input events.
+ * @hide
+ */
+ @RequiresPermission(INTERNAL_SYSTEM_WINDOW)
+ public void setActivityRecordInputSinkEnabled(boolean enabled) {
+ ActivityClient.getInstance().setActivityRecordInputSinkEnabled(mToken, enabled);
+ }
+
class HostCallbacks extends FragmentHostCallback<Activity> {
public HostCallbacks() {
super(Activity.this /*activity*/);
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index b35e87b..b8bd030 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -16,6 +16,8 @@
package android.app;
+import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.content.ComponentName;
@@ -614,6 +616,15 @@
}
}
+ @RequiresPermission(INTERNAL_SYSTEM_WINDOW)
+ void setActivityRecordInputSinkEnabled(IBinder activityToken, boolean enabled) {
+ try {
+ getActivityClientController().setActivityRecordInputSinkEnabled(activityToken, enabled);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Shows or hides a Camera app compat toggle for stretched issues with the requested state.
*
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index a3c5e1c..7370fc3 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -191,4 +191,14 @@
*/
boolean isRequestedToLaunchInTaskFragment(in IBinder activityToken,
in IBinder taskFragmentToken);
+
+ /**
+ * Enable or disable ActivityRecordInputSink to block input events.
+ *
+ * @param token The token for the activity that requests to toggle.
+ * @param enabled Whether the input evens are blocked by ActivityRecordInputSink.
+ */
+ @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ + ".permission.INTERNAL_SYSTEM_WINDOW)")
+ oneway void setActivityRecordInputSinkEnabled(in IBinder activityToken, boolean enabled);
}
diff --git a/core/java/android/companion/utils/FeatureUtils.java b/core/java/android/companion/utils/FeatureUtils.java
deleted file mode 100644
index a382e09..0000000
--- a/core/java/android/companion/utils/FeatureUtils.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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 android.companion.utils;
-
-import android.os.Binder;
-import android.os.Build;
-import android.provider.DeviceConfig;
-
-/**
- * Util class for feature flags
- *
- * @hide
- */
-public final class FeatureUtils {
-
- private static final String NAMESPACE_COMPANION = "companion";
-
- private static final String PROPERTY_PERM_SYNC_ENABLED = "perm_sync_enabled";
-
- public static boolean isPermSyncEnabled() {
- // Permissions sync is always enabled in debuggable mode.
- if (Build.isDebuggable()) {
- return true;
- }
-
- // Clear app identity to read the device config for feature flag.
- final long identity = Binder.clearCallingIdentity();
- try {
- return DeviceConfig.getBoolean(NAMESPACE_COMPANION,
- PROPERTY_PERM_SYNC_ENABLED, false);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- private FeatureUtils() {
- }
-}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 89f889f..fe95a2a 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1414,7 +1414,7 @@
* otherwise the value will be equal to 1.
* Note that this level is just a number of supported levels (the granularity of control).
* There is no actual physical power units tied to this level.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p>This key is available on all devices.</p>
*
* @see CaptureRequest#FLASH_MODE
*/
@@ -1430,7 +1430,7 @@
* or equal to <code>android.flash.info.singleStrengthMaxLevel</code>.
* Note for devices that do not support the manual flash strength control
* feature, this level will always be equal to 1.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p>This key is available on all devices.</p>
*/
@PublicKey
@NonNull
@@ -1450,7 +1450,7 @@
* android.flash.info.singleStrengthMaxLevel i.e. the ratio of
* android.flash.info.torchStrengthMaxLevel:android.flash.info.singleStrengthMaxLevel
* is not guaranteed to be the ratio of actual brightness.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p>This key is available on all devices.</p>
*
* @see CaptureRequest#FLASH_MODE
*/
@@ -1466,7 +1466,7 @@
* or equal to android.flash.info.torchStrengthMaxLevel.
* Note for the devices that do not support the manual flash strength control feature,
* this level will always be equal to 1.</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p>This key is available on all devices.</p>
*/
@PublicKey
@NonNull
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 5d06978..93cae54 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -2688,7 +2688,7 @@
* set to TORCH;
* <code>[1-android.flash.info.singleStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
* set to SINGLE</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p>This key is available on all devices.</p>
*
* @see CaptureRequest#CONTROL_AE_MODE
* @see CaptureRequest#FLASH_MODE
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 0d204f3..12ab0f6 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2974,7 +2974,7 @@
* set to TORCH;
* <code>[1-android.flash.info.singleStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
* set to SINGLE</p>
- * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p>This key is available on all devices.</p>
*
* @see CaptureRequest#CONTROL_AE_MODE
* @see CaptureRequest#FLASH_MODE
diff --git a/core/java/android/permission/IOnPermissionsChangeListener.aidl b/core/java/android/permission/IOnPermissionsChangeListener.aidl
index afacf1a..c68c0c9 100644
--- a/core/java/android/permission/IOnPermissionsChangeListener.aidl
+++ b/core/java/android/permission/IOnPermissionsChangeListener.aidl
@@ -21,5 +21,5 @@
* {@hide}
*/
oneway interface IOnPermissionsChangeListener {
- void onPermissionsChanged(int uid, String deviceId);
+ void onPermissionsChanged(int uid, String persistentDeviceId);
}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 7a158c5..91adc37 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -1738,8 +1738,9 @@
}
@Override
- public void onPermissionsChanged(int uid, String deviceId) {
- mHandler.obtainMessage(MSG_PERMISSIONS_CHANGED, uid, 0, deviceId).sendToTarget();
+ public void onPermissionsChanged(int uid, String persistentDeviceId) {
+ mHandler.obtainMessage(MSG_PERMISSIONS_CHANGED, uid, 0, persistentDeviceId)
+ .sendToTarget();
}
@Override
@@ -1747,8 +1748,8 @@
switch (msg.what) {
case MSG_PERMISSIONS_CHANGED: {
final int uid = msg.arg1;
- final String deviceId = msg.obj.toString();
- mListener.onPermissionsChanged(uid, deviceId);
+ final String persistentDeviceId = msg.obj.toString();
+ mListener.onPermissionsChanged(uid, persistentDeviceId);
return true;
}
default:
diff --git a/core/java/android/service/notification/OWNERS b/core/java/android/service/notification/OWNERS
index bb0e6ab..cb0b5fa 100644
--- a/core/java/android/service/notification/OWNERS
+++ b/core/java/android/service/notification/OWNERS
@@ -2,6 +2,7 @@
juliacr@google.com
yurilin@google.com
+matiashe@google.com
jeffdq@google.com
dsandler@android.com
dsandler@google.com
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 11bd22f..0da03fb 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -39,4 +39,12 @@
description: "Remove uses of ScreenCapture#captureDisplay"
is_fixed_read_only: true
bug: "293445881"
-}
\ No newline at end of file
+}
+
+flag {
+ namespace: "window_surfaces"
+ name: "allow_disable_activity_record_input_sink"
+ description: "Whether to allow system activity to disable ActivityRecordInputSink"
+ is_fixed_read_only: true
+ bug: "262477923"
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7b075e6..4d208c6 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2301,6 +2301,7 @@
<!-- Allows system apps to call methods to register itself as a mDNS offload engine.
<p>Not for use by third-party or privileged applications.
@SystemApi
+ @FlaggedApi("com.android.net.flags.register_nsd_offload_engine")
@hide This should only be used by system apps.
-->
<permission android:name="android.permission.REGISTER_NSD_OFFLOAD_ENGINE"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 27dc870..b158f88 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -250,10 +250,10 @@
SyncTransactionQueue syncQueue,
@ShellMainThread ShellExecutor mainExecutor,
Lazy<Transitions> transitionsLazy,
- DockStateReader dockStateReader,
- CompatUIConfiguration compatUIConfiguration,
- CompatUIShellCommandHandler compatUIShellCommandHandler,
- AccessibilityManager accessibilityManager) {
+ Lazy<DockStateReader> dockStateReader,
+ Lazy<CompatUIConfiguration> compatUIConfiguration,
+ Lazy<CompatUIShellCommandHandler> compatUIShellCommandHandler,
+ Lazy<AccessibilityManager> accessibilityManager) {
if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) {
return Optional.empty();
}
@@ -268,10 +268,10 @@
syncQueue,
mainExecutor,
transitionsLazy,
- dockStateReader,
- compatUIConfiguration,
- compatUIShellCommandHandler,
- accessibilityManager));
+ dockStateReader.get(),
+ compatUIConfiguration.get(),
+ compatUIShellCommandHandler.get(),
+ accessibilityManager.get()));
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index a5629c8..b355ab0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -14,8 +14,6 @@
import android.window.TransitionInfo
import android.window.TransitionInfo.FLAG_IS_WALLPAPER
import androidx.test.filters.SmallTest
-import com.android.server.testutils.any
-import com.android.server.testutils.mock
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
@@ -28,8 +26,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
+import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyZeroInteractions
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index d056248..8748dab 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -603,7 +603,7 @@
std::unique_ptr<Asset> asset = assets->GetAssetsProvider()->Open(filename, mode);
if (asset) {
if (out_cookie != nullptr) {
- *out_cookie = i;
+ *out_cookie = i - 1;
}
return asset;
}
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index b63ee1b..a9d1a2a 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -25,8 +25,9 @@
#include "MinikinSkia.h"
#include "SkPaint.h"
-#include "SkStream.h" // Fot tests.
+#include "SkStream.h" // For tests.
#include "SkTypeface.h"
+#include "utils/TypefaceUtils.h"
#include <minikin/FontCollection.h>
#include <minikin/FontFamily.h>
@@ -186,7 +187,9 @@
LOG_ALWAYS_FATAL_IF(fstat(fd, &st) == -1, "Failed to stat file %s", kRobotoFont);
void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(data, st.st_size));
- sk_sp<SkTypeface> typeface = SkTypeface::MakeFromStream(std::move(fontData));
+ sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
+ LOG_ALWAYS_FATAL_IF(fm == nullptr, "Could not load FreeType SkFontMgr");
+ sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(fontData));
LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont);
std::shared_ptr<minikin::MinikinFont> font =
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index de842e6..9f63dfd 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1065,17 +1065,8 @@
* @see #isVolumeFixed()
*/
public void adjustVolume(int direction, @PublicVolumeFlags int flags) {
- if (autoPublicVolumeApiHardening()) {
- final IAudioService service = getService();
- try {
- service.adjustVolume(direction, flags);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- } else {
- MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
- helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags);
- }
+ MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
+ helper.sendAdjustVolumeBy(USE_DEFAULT_STREAM_TYPE, direction, flags);
}
/**
@@ -1104,17 +1095,8 @@
*/
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType,
@PublicVolumeFlags int flags) {
- if (autoPublicVolumeApiHardening()) {
- final IAudioService service = getService();
- try {
- service.adjustSuggestedStreamVolume(direction, suggestedStreamType, flags);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- } else {
- MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
- helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags);
- }
+ MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(getContext());
+ helper.sendAdjustVolumeBy(suggestedStreamType, direction, flags);
}
/** @hide */
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 11e3a08..b4ca485 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -500,10 +500,6 @@
in String packageName, int uid, int pid, in UserHandle userHandle,
int targetSdkVersion);
- oneway void adjustVolume(int direction, int flags);
-
- oneway void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags);
-
boolean isMusicActive(in boolean remotely);
int getDeviceMaskForStream(in int streamType);
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
index 93a5082..071f9f4 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_uk.kcm
@@ -116,6 +116,9 @@
base: 'w'
shift, capslock: 'W'
shift+capslock: 'w'
+ ralt: '\u1e83'
+ shift+ralt, capslock+ralt: '\u1e82'
+ shift+capslock+ralt: '\u1e83'
}
key E {
@@ -147,6 +150,9 @@
base: 'y'
shift, capslock: 'Y'
shift+capslock: 'y'
+ ralt: '\u00fd'
+ shift+ralt, capslock+ralt: '\u00dd'
+ shift+capslock+ralt: '\u00fd'
}
key U {
@@ -313,6 +319,9 @@
base: 'c'
shift, capslock: 'C'
shift+capslock: 'c'
+ ralt: '\u00e7'
+ shift+ralt, capslock+ralt: '\u00c7'
+ shift+capslock+ralt: '\u00e7'
}
key V {
diff --git a/packages/InputDevices/res/raw/keyboard_layout_french.kcm b/packages/InputDevices/res/raw/keyboard_layout_french.kcm
index 4906304..636f98d 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_french.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_french.kcm
@@ -44,7 +44,7 @@
label: '2'
base: '\u00e9'
shift: '2'
- ralt: '~'
+ ralt: '\u0303'
}
key 3 {
@@ -79,7 +79,7 @@
label: '7'
base: '\u00e8'
shift: '7'
- ralt: '`'
+ ralt: '\u0300'
}
key 8 {
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 7f23f74..ee49b23 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -94,7 +94,7 @@
android:name="keyboard_layout_swiss_german"
android:label="@string/keyboard_layout_swiss_german_label"
android:keyboardLayout="@raw/keyboard_layout_swiss_german"
- android:keyboardLocale="de-Latn-CH"
+ android:keyboardLocale="de-Latn-CH|gsw-Latn-CH"
android:keyboardLayoutType="qwertz" />
<keyboard-layout
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
index f83e37b..3774b88 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
@@ -37,9 +37,7 @@
import java.util.List;
import java.util.concurrent.Executor;
-/**
- * VolumeControlProfile handles Bluetooth Volume Control Controller role
- */
+/** VolumeControlProfile handles Bluetooth Volume Control Controller role */
public class VolumeControlProfile implements LocalBluetoothProfile {
private static final String TAG = "VolumeControlProfile";
private static boolean DEBUG = true;
@@ -77,8 +75,8 @@
}
device = mDeviceManager.addDevice(nextDevice);
}
- device.onProfileStateChanged(VolumeControlProfile.this,
- BluetoothProfile.STATE_CONNECTED);
+ device.onProfileStateChanged(
+ VolumeControlProfile.this, BluetoothProfile.STATE_CONNECTED);
device.refresh();
}
@@ -95,32 +93,36 @@
}
}
- VolumeControlProfile(Context context, CachedBluetoothDeviceManager deviceManager,
+ VolumeControlProfile(
+ Context context,
+ CachedBluetoothDeviceManager deviceManager,
LocalBluetoothProfileManager profileManager) {
mContext = context;
mDeviceManager = deviceManager;
mProfileManager = profileManager;
- BluetoothAdapter.getDefaultAdapter().getProfileProxy(context,
- new VolumeControlProfile.VolumeControlProfileServiceListener(),
- BluetoothProfile.VOLUME_CONTROL);
+ BluetoothAdapter.getDefaultAdapter()
+ .getProfileProxy(
+ context,
+ new VolumeControlProfile.VolumeControlProfileServiceListener(),
+ BluetoothProfile.VOLUME_CONTROL);
}
-
/**
- * Registers a {@link BluetoothVolumeControl.Callback} that will be invoked during the
- * operation of this profile.
+ * Registers a {@link BluetoothVolumeControl.Callback} that will be invoked during the operation
+ * of this profile.
*
- * Repeated registration of the same <var>callback</var> object will have no effect after
- * the first call to this method, even when the <var>executor</var> is different. API caller
- * would have to call {@link #unregisterCallback(BluetoothVolumeControl.Callback)} with
- * the same callback object before registering it again.
+ * <p>Repeated registration of the same <var>callback</var> object will have no effect after the
+ * first call to this method, even when the <var>executor</var> is different. API caller would
+ * have to call {@link #unregisterCallback(BluetoothVolumeControl.Callback)} with the same
+ * callback object before registering it again.
*
* @param executor an {@link Executor} to execute given callback
* @param callback user implementation of the {@link BluetoothVolumeControl.Callback}
* @throws IllegalArgumentException if a null executor or callback is given
*/
- public void registerCallback(@NonNull @CallbackExecutor Executor executor,
+ public void registerCallback(
+ @NonNull @CallbackExecutor Executor executor,
@NonNull BluetoothVolumeControl.Callback callback) {
if (mService == null) {
Log.w(TAG, "Proxy not attached to service. Cannot register callback.");
@@ -131,8 +133,9 @@
/**
* Unregisters the specified {@link BluetoothVolumeControl.Callback}.
- * <p>The same {@link BluetoothVolumeControl.Callback} object used when calling
- * {@link #registerCallback(Executor, BluetoothVolumeControl.Callback)} must be used.
+ *
+ * <p>The same {@link BluetoothVolumeControl.Callback} object used when calling {@link
+ * #registerCallback(Executor, BluetoothVolumeControl.Callback)} must be used.
*
* <p>Callbacks are automatically unregistered when application process goes away
*
@@ -153,8 +156,8 @@
* @param device {@link BluetoothDevice} representing the remote device
* @param volumeOffset volume offset to be set on the remote device
*/
- public void setVolumeOffset(BluetoothDevice device,
- @IntRange(from = -255, to = 255) int volumeOffset) {
+ public void setVolumeOffset(
+ BluetoothDevice device, @IntRange(from = -255, to = 255) int volumeOffset) {
if (mService == null) {
Log.w(TAG, "Proxy not attached to service. Cannot set volume offset.");
return;
@@ -165,16 +168,13 @@
}
mService.setVolumeOffset(device, volumeOffset);
}
-
/**
- * Provides information about the possibility to set volume offset on the remote device.
- * If the remote device supports Volume Offset Control Service, it is automatically
- * connected.
+ * Provides information about the possibility to set volume offset on the remote device. If the
+ * remote device supports Volume Offset Control Service, it is automatically connected.
*
* @param device {@link BluetoothDevice} representing the remote device
* @return {@code true} if volume offset function is supported and available to use on the
- * remote device. When Bluetooth is off, the return value should always be
- * {@code false}.
+ * remote device. When Bluetooth is off, the return value should always be {@code false}.
*/
public boolean isVolumeOffsetAvailable(BluetoothDevice device) {
if (mService == null) {
@@ -188,6 +188,28 @@
return mService.isVolumeOffsetAvailable(device);
}
+ /**
+ * Tells the remote device to set a volume.
+ *
+ * @param device {@link BluetoothDevice} representing the remote device
+ * @param volume volume to be set on the remote device
+ * @param isGroupOp whether to set the volume to remote devices within the same CSIP group
+ */
+ public void setDeviceVolume(
+ BluetoothDevice device,
+ @IntRange(from = 0, to = 255) int volume,
+ boolean isGroupOp) {
+ if (mService == null) {
+ Log.w(TAG, "Proxy not attached to service. Cannot set volume offset.");
+ return;
+ }
+ if (device == null) {
+ Log.w(TAG, "Device is null. Cannot set volume offset.");
+ return;
+ }
+ mService.setDeviceVolume(device, volume, isGroupOp);
+ }
+
@Override
public boolean accessProfileEnabled() {
return false;
@@ -199,10 +221,9 @@
}
/**
- * Gets VolumeControlProfile devices matching connection states{
- * {@code BluetoothProfile.STATE_CONNECTED},
- * {@code BluetoothProfile.STATE_CONNECTING},
- * {@code BluetoothProfile.STATE_DISCONNECTING}}
+ * Gets VolumeControlProfile devices matching connection states{ {@code
+ * BluetoothProfile.STATE_CONNECTED}, {@code BluetoothProfile.STATE_CONNECTING}, {@code
+ * BluetoothProfile.STATE_DISCONNECTING}}
*
* @return Matching device list
*/
@@ -211,8 +232,11 @@
return new ArrayList<BluetoothDevice>(0);
}
return mService.getDevicesMatchingConnectionStates(
- new int[]{BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTING,
- BluetoothProfile.STATE_DISCONNECTING});
+ new int[] {
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING
+ });
}
@Override
@@ -285,7 +309,7 @@
@Override
public int getSummaryResourceForDevice(BluetoothDevice device) {
- return 0; // VCP profile not displayed in UI
+ return 0; // VCP profile not displayed in UI
}
@Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
index c560627..fe1529d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/VolumeControlProfileTest.java
@@ -54,18 +54,14 @@
public class VolumeControlProfileTest {
private static final int TEST_VOLUME_OFFSET = 10;
+ private static final int TEST_VOLUME_VALUE = 10;
- @Rule
- public final MockitoRule mockito = MockitoJUnit.rule();
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
- @Mock
- private CachedBluetoothDeviceManager mDeviceManager;
- @Mock
- private LocalBluetoothProfileManager mProfileManager;
- @Mock
- private BluetoothDevice mBluetoothDevice;
- @Mock
- private BluetoothVolumeControl mService;
+ @Mock private CachedBluetoothDeviceManager mDeviceManager;
+ @Mock private LocalBluetoothProfileManager mProfileManager;
+ @Mock private BluetoothDevice mBluetoothDevice;
+ @Mock private BluetoothVolumeControl mService;
private final Context mContext = ApplicationProvider.getApplicationContext();
private BluetoothProfile.ServiceListener mServiceListener;
@@ -177,14 +173,14 @@
@Test
public void getConnectedDevices_returnCorrectList() {
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
- int[] connectedStates = new int[] {
- BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_CONNECTING,
- BluetoothProfile.STATE_DISCONNECTING};
- List<BluetoothDevice> connectedList = Arrays.asList(
- mBluetoothDevice,
- mBluetoothDevice,
- mBluetoothDevice);
+ int[] connectedStates =
+ new int[] {
+ BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTING
+ };
+ List<BluetoothDevice> connectedList =
+ Arrays.asList(mBluetoothDevice, mBluetoothDevice, mBluetoothDevice);
when(mService.getDevicesMatchingConnectionStates(connectedStates))
.thenReturn(connectedList);
@@ -222,6 +218,16 @@
}
@Test
+ public void setDeviceVolume_verifyIsCalled() {
+ mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
+
+ mProfile.setDeviceVolume(mBluetoothDevice, TEST_VOLUME_VALUE, /* isGroupOp= */ true);
+
+ verify(mService)
+ .setDeviceVolume(mBluetoothDevice, TEST_VOLUME_VALUE, /* isGroupOp= */ true);
+ }
+
+ @Test
public void isVolumeOffsetAvailable_verifyIsCalledAndReturnTrue() {
mServiceListener.onServiceConnected(BluetoothProfile.VOLUME_CONTROL, mService);
when(mService.isVolumeOffsetAvailable(mBluetoothDevice)).thenReturn(true);
diff --git a/packages/SystemUI/res/drawable/notification_material_bg.xml b/packages/SystemUI/res/drawable/notification_material_bg.xml
index 355e75d..9c08f5e 100644
--- a/packages/SystemUI/res/drawable/notification_material_bg.xml
+++ b/packages/SystemUI/res/drawable/notification_material_bg.xml
@@ -18,7 +18,7 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:color="?android:attr/colorControlHighlight">
- <item>
+ <item android:id="@+id/notification_background_color_layer">
<shape>
<solid android:color="?androidprv:attr/materialColorSurfaceContainerHigh" />
</shape>
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 98fda3e..1710a9f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -707,7 +707,7 @@
/** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */
@JvmField
- val BLUETOOTH_QS_TILE_DIALOG = unreleasedFlag("bluetooth_qs_tile_dialog")
+ val BLUETOOTH_QS_TILE_DIALOG = releasedFlag("bluetooth_qs_tile_dialog")
// TODO(b/300995746): Tracking Bug
/** A resource flag for whether the communal service is enabled. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index 8eda96f..64f61d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -27,6 +27,7 @@
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
@@ -42,9 +43,11 @@
* A view that can be used for both the dimmed and normal background of an notification.
*/
public class NotificationBackgroundView extends View implements Dumpable {
+ private static final String TAG = "NotificationBackgroundView";
private final boolean mDontModifyCorners;
private Drawable mBackground;
+ private Drawable mBackgroundDrawableToTint;
private int mClipTopAmount;
private int mClipBottomAmount;
private int mTintColor;
@@ -131,6 +134,7 @@
unscheduleDrawable(mBackground);
}
mBackground = background;
+ mBackgroundDrawableToTint = findBackgroundDrawableToTint(mBackground);
mRippleColor = null;
mBackground.mutate();
if (mBackground != null) {
@@ -144,25 +148,46 @@
invalidate();
}
+ // setCustomBackground should be called from ActivatableNotificationView.initBackground
+ // with R.drawable.notification_material_bg, which is a layer-list with a lower layer
+ // for the background color (annotated with an ID so we can find it) and an upper layer
+ // to blend in the stateful @color/notification_overlay_color.
+ //
+ // If the notification is tinted, we want to set a tint list on *just that lower layer* that
+ // will replace the default materialColorSurfaceContainerHigh *without* wiping out the stateful
+ // tints in the upper layer that make the hovered and pressed states visible.
+ //
+ // This function fishes that lower layer out, or makes a fuss in logcat if it can't find it.
+ private @Nullable Drawable findBackgroundDrawableToTint(@Nullable Drawable background) {
+ if (background == null) {
+ return null;
+ }
+
+ if (!(background instanceof LayerDrawable)) {
+ Log.wtf(TAG, "background is not a LayerDrawable: " + background);
+ return background;
+ }
+
+ final Drawable backgroundColorLayer = ((LayerDrawable) background).findDrawableByLayerId(
+ R.id.notification_background_color_layer);
+
+ if (backgroundColorLayer == null) {
+ Log.wtf(TAG, "background is missing background color layer: " + background);
+ return background;
+ }
+
+ return backgroundColorLayer;
+ }
+
public void setCustomBackground(int drawableResId) {
final Drawable d = mContext.getDrawable(drawableResId);
setCustomBackground(d);
}
public void setTint(int tintColor) {
- if (tintColor != 0) {
- ColorStateList stateList = new ColorStateList(new int[][]{
- new int[]{com.android.internal.R.attr.state_pressed},
- new int[]{com.android.internal.R.attr.state_hovered},
- new int[]{}},
+ mBackgroundDrawableToTint.setTint(tintColor);
+ mBackgroundDrawableToTint.setTintMode(PorterDuff.Mode.SRC_ATOP);
- new int[]{tintColor, tintColor, tintColor}
- );
- mBackground.setTintMode(PorterDuff.Mode.SRC_ATOP);
- mBackground.setTintList(stateList);
- } else {
- mBackground.setTintList(null);
- }
mTintColor = tintColor;
invalidate();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 61a79b7..6944453 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -565,6 +565,7 @@
private NotificationStackScrollLayoutController.TouchHandler mTouchHandler;
private final ScreenOffAnimationController mScreenOffAnimationController;
private boolean mShouldUseSplitNotificationShade;
+ private boolean mShouldSkipTopPaddingAnimationAfterFold = false;
private boolean mHasFilteredOutSeenNotifications;
@Nullable private SplitShadeStateController mSplitShadeStateController = null;
private boolean mIsSmallLandscapeLockscreenEnabled = false;
@@ -1364,7 +1365,11 @@
mTopPadding = topPadding;
updateAlgorithmHeightAndPadding();
updateContentHeight();
- if (shouldAnimate && mAnimationsEnabled && mIsExpanded) {
+ if (mAmbientState.isOnKeyguard()
+ && !mShouldUseSplitNotificationShade
+ && mShouldSkipTopPaddingAnimationAfterFold) {
+ mShouldSkipTopPaddingAnimationAfterFold = false;
+ } else if (shouldAnimate && mAnimationsEnabled && mIsExpanded) {
mTopPaddingNeedsAnimation = true;
mNeedsAnimation = true;
}
@@ -5741,6 +5746,7 @@
boolean split = mSplitShadeStateController.shouldUseSplitNotificationShade(getResources());
if (split != mShouldUseSplitNotificationShade) {
mShouldUseSplitNotificationShade = split;
+ mShouldSkipTopPaddingAnimationAfterFold = true;
mAmbientState.setUseSplitShade(split);
updateDismissBehavior();
updateUseRoundedRectClipping();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
index 1c7fd56..7361f6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
@@ -18,7 +18,6 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider.FullScreenIntentDecision.FSI_DEVICE_NOT_INTERACTIVE
@@ -37,7 +36,7 @@
@RunWith(AndroidTestingRunner::class)
class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecisionProviderTestBase() {
init {
- setFlagsRule.disableFlags(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR)
+ mSetFlagsRule.disableFlags(VisualInterruptionRefactor.FLAG_NAME)
}
override val provider by lazy {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index df6f0d7..d2c046c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -18,7 +18,6 @@
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
@@ -30,7 +29,7 @@
@RunWith(AndroidTestingRunner::class)
class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionProviderTestBase() {
init {
- setFlagsRule.enableFlags(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR)
+ mSetFlagsRule.enableFlags(VisualInterruptionRefactor.FLAG_NAME)
}
override val provider by lazy {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index 7babff5..2ac0cb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -44,7 +44,6 @@
import android.hardware.display.FakeAmbientDisplayConfiguration
import android.os.Looper
import android.os.PowerManager
-import android.platform.test.flag.junit.SetFlagsRule
import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
import android.provider.Settings.Global.HEADS_UP_OFF
import android.provider.Settings.Global.HEADS_UP_ON
@@ -84,15 +83,10 @@
import junit.framework.Assert.assertTrue
import org.junit.Assert.assertEquals
import org.junit.Before
-import org.junit.Rule
import org.junit.Test
import org.mockito.Mockito.`when` as whenever
abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
- @JvmField
- @Rule
- val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
-
private val fakeLogBuffer =
LogBuffer(
name = "FakeLog",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 4b1c7e8..4422764 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -157,6 +157,7 @@
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper;
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -347,6 +348,8 @@
mFeatureFlags.set(Flags.ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD, true);
when(mDozeParameters.getAlwaysOn()).thenReturn(true);
mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR);
+ // TODO: b/312476335 - Update to check flag and instantiate old or new implementation.
+ mSetFlagsRule.disableFlags(VisualInterruptionRefactor.FLAG_NAME);
IThermalService thermalService = mock(IThermalService.class);
mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index df7609c..200cfd3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -146,6 +146,7 @@
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderWrapper;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
@@ -366,6 +367,9 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ // TODO: b/312476335 - Update to check flag and instantiate old or new implementation.
+ mSetFlagsRule.disableFlags(VisualInterruptionRefactor.FLAG_NAME);
+
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
doReturn(true).when(mTransitions).isRegistered();
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 5635dd5..42ab05f 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -21,6 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityOptions;
import android.app.Dialog;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -205,7 +206,10 @@
intent,
PendingIntent.FLAG_MUTABLE
| PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT,
- /* options= */ null, UserHandle.CURRENT);
+ ActivityOptions.makeBasic()
+ .setPendingIntentCreatorBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+ .toBundle(), UserHandle.CURRENT);
if (sDebug) {
Slog.d(TAG, "startActivity add save UI restored with intent=" + intent);
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index b9c269c..71a1f01 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -75,7 +75,6 @@
import android.companion.IOnTransportsChangedListener;
import android.companion.ISystemDataTransferCallback;
import android.companion.datatransfer.PermissionSyncRequest;
-import android.companion.utils.FeatureUtils;
import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
@@ -829,11 +828,6 @@
@Override
public PendingIntent buildPermissionTransferUserConsentIntent(String packageName,
int userId, int associationId) {
- if (!FeatureUtils.isPermSyncEnabled()) {
- throw new UnsupportedOperationException("Calling"
- + " buildPermissionTransferUserConsentIntent, but this API is disabled by"
- + " the system.");
- }
return mSystemDataTransferProcessor.buildPermissionTransferUserConsentIntent(
packageName, userId, associationId);
}
@@ -841,10 +835,6 @@
@Override
public void startSystemDataTransfer(String packageName, int userId, int associationId,
ISystemDataTransferCallback callback) {
- if (!FeatureUtils.isPermSyncEnabled()) {
- throw new UnsupportedOperationException("Calling startSystemDataTransfer, but this"
- + " API is disabled by the system.");
- }
mSystemDataTransferProcessor.startSystemDataTransfer(packageName, userId,
associationId, callback);
}
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 7907d61..77b6d583 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -1182,8 +1182,8 @@
// we are only interested in doing things at PHASE_BOOT_COMPLETED
if (phase == PHASE_BOOT_COMPLETED) {
- Slog.i(TAG, "Boot completed. Getting VBMeta Digest.");
- getVBMetaDigestInformation();
+ Slog.i(TAG, "Boot completed. Getting boot integrity data.");
+ collectBootIntegrityInfo();
// Log to statsd
// TODO(b/264061957): For now, biometric system properties are always collected if users
@@ -1458,10 +1458,19 @@
}
}
- private void getVBMetaDigestInformation() {
+ private void collectBootIntegrityInfo() {
mVbmetaDigest = SystemProperties.get(SYSPROP_NAME_VBETA_DIGEST, VBMETA_DIGEST_UNAVAILABLE);
Slog.d(TAG, String.format("VBMeta Digest: %s", mVbmetaDigest));
FrameworkStatsLog.write(FrameworkStatsLog.VBMETA_DIGEST_REPORTED, mVbmetaDigest);
+
+ if (android.security.Flags.binaryTransparencySepolicyHash()) {
+ byte[] sepolicyHash = PackageUtils.computeSha256DigestForLargeFileAsBytes(
+ "/sys/fs/selinux/policy", PackageUtils.createLargeFileBuffer());
+ String sepolicyHashEncoded = HexEncoding.encodeToString(sepolicyHash, false);
+ Slog.d(TAG, "sepolicy hash: " + sepolicyHashEncoded);
+ FrameworkStatsLog.write(FrameworkStatsLog.BOOT_INTEGRITY_INFO_REPORTED,
+ sepolicyHashEncoded, mVbmetaDigest);
+ }
}
/**
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 5b54561..e07631c 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -193,6 +193,12 @@
private @Nullable BroadcastProcessQueue mRunningColdStart;
/**
+ * Indicates whether we have queued a message to check pending cold start validity.
+ */
+ @GuardedBy("mService")
+ private boolean mCheckPendingColdStartQueued;
+
+ /**
* Collection of latches waiting for device to reach specific state. The
* first argument is a function to test for the desired state, and the
* second argument is the latch to release once that state is reached.
@@ -302,7 +308,11 @@
return true;
}
case MSG_CHECK_PENDING_COLD_START_VALIDITY: {
- checkPendingColdStartValidity();
+ synchronized (mService) {
+ /* Clear this as we have just received the broadcast. */
+ mCheckPendingColdStartQueued = false;
+ checkPendingColdStartValidityLocked();
+ }
return true;
}
case MSG_PROCESS_FREEZABLE_CHANGED: {
@@ -549,7 +559,7 @@
mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
}
- checkPendingColdStartValidity();
+ checkPendingColdStartValidityLocked();
checkAndRemoveWaitingFor();
traceEnd(cookie);
@@ -573,22 +583,24 @@
enqueueUpdateRunningList();
}
- private void checkPendingColdStartValidity() {
+ @GuardedBy("mService")
+ private void checkPendingColdStartValidityLocked() {
// There are a few cases where a starting process gets killed but AMS doesn't report
// this event. So, once we start waiting for a pending cold start, periodically check
// if the pending start is still valid and if not, clear it so that the queue doesn't
// keep waiting for the process start forever.
- synchronized (mService) {
- // If there is no pending cold start, then nothing to do.
- if (mRunningColdStart == null) {
- return;
- }
- if (isPendingColdStartValid()) {
+ // If there is no pending cold start, then nothing to do.
+ if (mRunningColdStart == null) {
+ return;
+ }
+ if (isPendingColdStartValid()) {
+ if (!mCheckPendingColdStartQueued) {
mLocalHandler.sendEmptyMessageDelayed(MSG_CHECK_PENDING_COLD_START_VALIDITY,
mConstants.PENDING_COLD_START_CHECK_INTERVAL_MILLIS);
- } else {
- clearInvalidPendingColdStart();
+ mCheckPendingColdStartQueued = true;
}
+ } else {
+ clearInvalidPendingColdStart();
}
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 1ef4333..df106a7 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -154,7 +154,6 @@
import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjectionCallback;
import android.media.projection.IMediaProjectionManager;
-import android.media.session.MediaSessionManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
@@ -312,9 +311,6 @@
private final ContentResolver mContentResolver;
private final AppOpsManager mAppOps;
- /** do not use directly, use getMediaSessionManager() which handles lazy initialization */
- @Nullable private volatile MediaSessionManager mMediaSessionManager;
-
// the platform type affects volume and silent mode behavior
private final int mPlatformType;
@@ -945,8 +941,6 @@
private final SoundDoseHelper mSoundDoseHelper;
- private final HardeningEnforcer mHardeningEnforcer;
-
private final Object mSupportedSystemUsagesLock = new Object();
@GuardedBy("mSupportedSystemUsagesLock")
private @AttributeSystemUsage int[] mSupportedSystemUsages =
@@ -1321,8 +1315,6 @@
mDisplayManager = context.getSystemService(DisplayManager.class);
mMusicFxHelper = new MusicFxHelper(mContext, mAudioHandler);
-
- mHardeningEnforcer = new HardeningEnforcer(mContext, isPlatformAutomotive());
}
private void initVolumeStreamStates() {
@@ -1394,6 +1386,7 @@
// check on volume initialization
checkVolumeRangeInitialization("AudioService()");
+
}
private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener =
@@ -1406,14 +1399,6 @@
}
};
- private MediaSessionManager getMediaSessionManager() {
- if (mMediaSessionManager == null) {
- mMediaSessionManager = (MediaSessionManager) mContext
- .getSystemService(Context.MEDIA_SESSION_SERVICE);
- }
- return mMediaSessionManager;
- }
-
/**
* Initialize intent receives and settings observers for this service.
* Must be called after createStreamStates() as the handling of some events
@@ -3442,10 +3427,6 @@
* Part of service interface, check permissions here */
public void adjustStreamVolumeWithAttribution(int streamType, int direction, int flags,
String callingPackage, String attributionTag) {
- if (mHardeningEnforcer.blockVolumeMethod(
- HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_STREAM_VOLUME)) {
- return;
- }
if ((streamType == AudioManager.STREAM_ACCESSIBILITY) && !canChangeAccessibilityVolume()) {
Log.w(TAG, "Trying to call adjustStreamVolume() for a11y without"
+ "CHANGE_ACCESSIBILITY_VOLUME / callingPackage=" + callingPackage);
@@ -4222,10 +4203,6 @@
* Part of service interface, check permissions here */
public void setStreamVolumeWithAttribution(int streamType, int index, int flags,
String callingPackage, String attributionTag) {
- if (mHardeningEnforcer.blockVolumeMethod(
- HardeningEnforcer.METHOD_AUDIO_MANAGER_SET_STREAM_VOLUME)) {
- return;
- }
setStreamVolumeWithAttributionInt(streamType, index, flags, /*device*/ null,
callingPackage, attributionTag);
}
@@ -5080,7 +5057,6 @@
/** @see AudioManager#setMasterMute(boolean, int) */
public void setMasterMute(boolean mute, int flags, String callingPackage, int userId,
String attributionTag) {
-
super.setMasterMute_enforcePermission();
setMasterMuteInternal(mute, flags, callingPackage,
@@ -5446,10 +5422,6 @@
}
public void setRingerModeExternal(int ringerMode, String caller) {
- if (mHardeningEnforcer.blockVolumeMethod(
- HardeningEnforcer.METHOD_AUDIO_MANAGER_SET_RINGER_MODE)) {
- return;
- }
if (isAndroidNPlus(caller) && wouldToggleZenMode(ringerMode)
&& !mNm.isNotificationPolicyAccessGrantedForPackage(caller)) {
throw new SecurityException("Not allowed to change Do Not Disturb state");
@@ -6202,35 +6174,6 @@
AudioDeviceVolumeManager.ADJUST_MODE_NORMAL);
}
- /**
- * @see AudioManager#adjustVolume(int, int)
- * This method is redirected from AudioManager to AudioService for API hardening rules
- * enforcement then to MediaSession for implementation.
- */
- @Override
- public void adjustVolume(int direction, int flags) {
- if (mHardeningEnforcer.blockVolumeMethod(
- HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_VOLUME)) {
- return;
- }
- getMediaSessionManager().dispatchAdjustVolume(AudioManager.USE_DEFAULT_STREAM_TYPE,
- direction, flags);
- }
-
- /**
- * @see AudioManager#adjustSuggestedStreamVolume(int, int, int)
- * This method is redirected from AudioManager to AudioService for API hardening rules
- * enforcement then to MediaSession for implementation.
- */
- @Override
- public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags) {
- if (mHardeningEnforcer.blockVolumeMethod(
- HardeningEnforcer.METHOD_AUDIO_MANAGER_ADJUST_SUGGESTED_STREAM_VOLUME)) {
- return;
- }
- getMediaSessionManager().dispatchAdjustVolume(suggestedStreamType, direction, flags);
- }
-
/** @see AudioManager#setStreamVolumeForUid(int, int, int, String, int, int, int) */
@Override
public void setStreamVolumeForUid(int streamType, int index, int flags,
diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java
deleted file mode 100644
index 4ceb83b2..0000000
--- a/services/core/java/com/android/server/audio/HardeningEnforcer.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright 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.audio;
-
-import static android.media.audio.Flags.autoPublicVolumeApiHardening;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.AudioManager;
-import android.os.Binder;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Log;
-
-/**
- * Class to encapsulate all audio API hardening operations
- */
-public class HardeningEnforcer {
-
- private static final String TAG = "AS.HardeningEnforcer";
-
- final Context mContext;
- final boolean mIsAutomotive;
-
- /**
- * Matches calls from {@link AudioManager#setStreamVolume(int, int, int)}
- */
- public static final int METHOD_AUDIO_MANAGER_SET_STREAM_VOLUME = 100;
- /**
- * Matches calls from {@link AudioManager#adjustVolume(int, int)}
- */
- public static final int METHOD_AUDIO_MANAGER_ADJUST_VOLUME = 101;
- /**
- * Matches calls from {@link AudioManager#adjustSuggestedStreamVolume(int, int, int)}
- */
- public static final int METHOD_AUDIO_MANAGER_ADJUST_SUGGESTED_STREAM_VOLUME = 102;
- /**
- * Matches calls from {@link AudioManager#adjustStreamVolume(int, int, int)}
- */
- public static final int METHOD_AUDIO_MANAGER_ADJUST_STREAM_VOLUME = 103;
- /**
- * Matches calls from {@link AudioManager#setRingerMode(int)}
- */
- public static final int METHOD_AUDIO_MANAGER_SET_RINGER_MODE = 200;
-
- public HardeningEnforcer(Context ctxt, boolean isAutomotive) {
- mContext = ctxt;
- mIsAutomotive = isAutomotive;
- }
-
- /**
- * Checks whether the call in the current thread should be allowed or blocked
- * @param volumeMethod name of the method to check, for logging purposes
- * @return false if the method call is allowed, true if it should be a no-op
- */
- protected boolean blockVolumeMethod(int volumeMethod) {
- // for Auto, volume methods require MODIFY_AUDIO_SETTINGS_PRIVILEGED
- if (mIsAutomotive) {
- if (!autoPublicVolumeApiHardening()) {
- // automotive hardening flag disabled, no blocking on auto
- return false;
- }
- if (mContext.checkCallingOrSelfPermission(
- Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED)
- == PackageManager.PERMISSION_GRANTED) {
- return false;
- }
- if (Binder.getCallingUid() < UserHandle.AID_APP_START) {
- return false;
- }
- // TODO metrics?
- // TODO log for audio dumpsys?
- Log.e(TAG, "Preventing volume method " + volumeMethod + " for "
- + getPackNameForUid(Binder.getCallingUid()));
- return true;
- }
- // not blocking
- return false;
- }
-
- private String getPackNameForUid(int uid) {
- final long token = Binder.clearCallingIdentity();
- try {
- final String[] names = mContext.getPackageManager().getPackagesForUid(uid);
- if (names == null
- || names.length == 0
- || TextUtils.isEmpty(names[0])) {
- return "[" + uid + "]";
- }
- return names[0];
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c2a1b6b..0492f43 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -828,6 +828,22 @@
}
}
+ // Removes all notifications with the specified user & package.
+ public void removePackageNotifications(String pkg, @UserIdInt int userId) {
+ synchronized (mBufferLock) {
+ Iterator<Pair<StatusBarNotification, Integer>> bufferIter = descendingIterator();
+ while (bufferIter.hasNext()) {
+ final Pair<StatusBarNotification, Integer> pair = bufferIter.next();
+ if (pair.first != null
+ && userId == pair.first.getNormalizedUserId()
+ && pkg != null && pkg.equals(pair.first.getPackageName())
+ && pair.first.getNotification() != null) {
+ bufferIter.remove();
+ }
+ }
+ }
+ }
+
void dumpImpl(PrintWriter pw, @NonNull DumpFilter filter) {
synchronized (mBufferLock) {
Iterator<Pair<StatusBarNotification, Integer>> iter = descendingIterator();
@@ -1902,7 +1918,6 @@
unhideNotificationsForPackages(pkgList, uidList);
}
}
-
mHandler.scheduleOnPackageChanged(removingPackage, changeUserId, pkgList, uidList);
}
}
@@ -4216,7 +4231,8 @@
boolean previouslyExisted = mPreferencesHelper.deleteNotificationChannel(
pkg, callingUid, channelId, callingUid, isSystemOrSystemUi);
if (previouslyExisted) {
- // Remove from both recent notification archive and notification history
+ // Remove from both recent notification archive (recently dismissed notifications)
+ // and notification history
mArchive.removeChannelNotifications(pkg, callingUser, channelId);
mHistoryManager.deleteNotificationChannel(pkg, callingUid, channelId);
mListeners.notifyNotificationChannelChanged(pkg,
@@ -9418,7 +9434,11 @@
for (int i = 0; i < size; i++) {
final String pkg = pkgList[i];
final int uid = uidList[i];
- mHistoryManager.onPackageRemoved(UserHandle.getUserId(uid), pkg);
+ final int userHandle = UserHandle.getUserId(uid);
+ // Removes this package's notifications from both recent notification archive
+ // (recently dismissed notifications) and notification history.
+ mArchive.removePackageNotifications(pkg, userHandle);
+ mHistoryManager.onPackageRemoved(userHandle, pkg);
}
}
if (preferencesChanged) {
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index faccca8..315e7d8 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -55,6 +55,7 @@
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH;
import static com.android.server.wm.ActivityTaskManagerService.enforceNotIsolatedCaller;
+import static com.android.window.flags.Flags.allowDisableActivityRecordInputSink;
import android.Manifest;
import android.annotation.ColorInt;
@@ -1688,4 +1689,20 @@
return r.mRequestedLaunchingTaskFragmentToken == taskFragmentToken;
}
}
+
+ @Override
+ public void setActivityRecordInputSinkEnabled(IBinder activityToken, boolean enabled) {
+ if (!allowDisableActivityRecordInputSink()) {
+ return;
+ }
+
+ mService.mAmInternal.enforceCallingPermission(
+ Manifest.permission.INTERNAL_SYSTEM_WINDOW, "setActivityRecordInputSinkEnabled");
+ synchronized (mGlobalLock) {
+ final ActivityRecord r = ActivityRecord.forTokenLocked(activityToken);
+ if (r != null) {
+ r.mActivityRecordInputSinkEnabled = enabled;
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 081759d..d90d4ff 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -970,6 +970,8 @@
boolean mWaitForEnteringPinnedMode;
final ActivityRecordInputSink mActivityRecordInputSink;
+ // System activities with INTERNAL_SYSTEM_WINDOW can disable ActivityRecordInputSink.
+ boolean mActivityRecordInputSinkEnabled = true;
// Activities with this uid are allowed to not create an input sink while being in the same
// task and directly above this ActivityRecord. This field is updated whenever a new activity
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index be7d9b6..c61d863 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -86,7 +86,8 @@
final boolean allowPassthrough = activityBelowInTask != null && (
activityBelowInTask.mAllowedTouchUid == mActivityRecord.getUid()
|| activityBelowInTask.isUid(mActivityRecord.getUid()));
- if (allowPassthrough || !mIsCompatEnabled || mActivityRecord.isInTransition()) {
+ if (allowPassthrough || !mIsCompatEnabled || mActivityRecord.isInTransition()
+ || !mActivityRecord.mActivityRecordInputSinkEnabled) {
mInputWindowHandleWrapper.setInputConfigMasked(InputConfig.NOT_TOUCHABLE,
InputConfig.NOT_TOUCHABLE);
} else {
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index 28f656e..8b282dd3 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -58,6 +58,23 @@
}
/**
+ * Similar to {@link #scheduleTransactionItem}, but is called without WM lock.
+ *
+ * @see WindowProcessController#setReportedProcState(int)
+ */
+ void scheduleTransactionItemUnlocked(@NonNull IApplicationThread client,
+ @NonNull ClientTransactionItem transactionItem) throws RemoteException {
+ // Immediately dispatching to client, and must not access WMS.
+ final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
+ if (transactionItem.isActivityLifecycleItem()) {
+ clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
+ } else {
+ clientTransaction.addCallback(transactionItem);
+ }
+ scheduleTransaction(clientTransaction);
+ }
+
+ /**
* Schedules a single transaction item, either a callback or a lifecycle request, delivery to
* client application.
* @throws RemoteException
@@ -65,6 +82,7 @@
*/
void scheduleTransactionItem(@NonNull IApplicationThread client,
@NonNull ClientTransactionItem transactionItem) throws RemoteException {
+ // TODO(b/260873529): queue the transaction items.
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
if (transactionItem.isActivityLifecycleItem()) {
clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
@@ -82,6 +100,7 @@
void scheduleTransactionAndLifecycleItems(@NonNull IApplicationThread client,
@NonNull ClientTransactionItem transactionItem,
@NonNull ActivityLifecycleItem lifecycleItem) throws RemoteException {
+ // TODO(b/260873529): replace with #scheduleTransactionItem after launch for cleanup.
final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
clientTransaction.addCallback(transactionItem);
clientTransaction.setLifecycleStateRequest(lifecycleItem);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 558bf9d..2b18f07 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -388,13 +388,22 @@
final IApplicationThread thread = mThread;
if (prevProcState >= CACHED_CONFIG_PROC_STATE && repProcState < CACHED_CONFIG_PROC_STATE
&& thread != null && mHasCachedConfiguration) {
- final Configuration config;
+ final ConfigurationChangeItem configurationChangeItem;
synchronized (mLastReportedConfiguration) {
- config = new Configuration(mLastReportedConfiguration);
+ onConfigurationChangePreScheduled(mLastReportedConfiguration);
+ configurationChangeItem = ConfigurationChangeItem.obtain(
+ mLastReportedConfiguration, mLastTopActivityDeviceId);
}
// Schedule immediately to make sure the app component (e.g. receiver, service) can get
// the latest configuration in their lifecycle callbacks (e.g. onReceive, onCreate).
- scheduleConfigurationChange(thread, config);
+ try {
+ // No WM lock here.
+ mAtm.getLifecycleManager().scheduleTransactionItemUnlocked(
+ thread, configurationChangeItem);
+ } catch (Exception e) {
+ Slog.e(TAG_CONFIGURATION, "Failed to schedule ConfigurationChangeItem="
+ + configurationChangeItem + " owner=" + mOwner, e);
+ }
}
}
@@ -1634,11 +1643,12 @@
}
}
- scheduleConfigurationChange(thread, config);
+ onConfigurationChangePreScheduled(config);
+ scheduleClientTransactionItem(thread, ConfigurationChangeItem.obtain(
+ config, mLastTopActivityDeviceId));
}
- private void scheduleConfigurationChange(@NonNull IApplicationThread thread,
- @NonNull Configuration config) {
+ private void onConfigurationChangePreScheduled(@NonNull Configuration config) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s new config %s", mName,
config);
if (Build.IS_DEBUGGABLE && mHasImeService) {
@@ -1646,8 +1656,6 @@
Slog.v(TAG_CONFIGURATION, "Sending to IME proc " + mName + " new config " + config);
}
mHasCachedConfiguration = false;
- scheduleClientTransactionItem(thread, ConfigurationChangeItem.obtain(
- config, mLastTopActivityDeviceId));
}
@VisibleForTesting
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 0d196b4..7c53950 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -2175,9 +2175,9 @@
userState.appIdDevicePermissionFlags[appId]?.forEachIndexed {
_,
- deviceId,
+ persistentDeviceId,
devicePermissionFlags ->
- println("Permissions (Device $deviceId):")
+ println("Permissions (Device $persistentDeviceId):")
withIndent {
devicePermissionFlags.forEachIndexed { _, permissionName, flags ->
val isGranted = PermissionFlags.isPermissionGranted(flags)
@@ -2658,7 +2658,7 @@
override fun onDevicePermissionFlagsChanged(
appId: Int,
userId: Int,
- deviceId: String,
+ persistentDeviceId: String,
permissionName: String,
oldFlags: Int,
newFlags: Int
@@ -2681,7 +2681,8 @@
permissionName in NOTIFICATIONS_PERMISSIONS &&
runtimePermissionRevokedUids.get(uid, true)
}
- runtimePermissionChangedUidDevices.getOrPut(uid) { mutableSetOf() } += deviceId
+ runtimePermissionChangedUidDevices
+ .getOrPut(uid) { mutableSetOf() } += persistentDeviceId
}
if (permission.hasGids && !wasPermissionGranted && isPermissionGranted) {
@@ -2695,9 +2696,9 @@
isPermissionFlagsChanged = false
}
- runtimePermissionChangedUidDevices.forEachIndexed { _, uid, deviceIds ->
- deviceIds.forEach { deviceId ->
- onPermissionsChangeListeners.onPermissionsChanged(uid, deviceId)
+ runtimePermissionChangedUidDevices.forEachIndexed { _, uid, persistentDeviceIds ->
+ persistentDeviceIds.forEach { persistentDeviceId ->
+ onPermissionsChangeListeners.onPermissionsChanged(uid, persistentDeviceId)
}
}
runtimePermissionChangedUidDevices.clear()
@@ -2772,16 +2773,16 @@
when (msg.what) {
MSG_ON_PERMISSIONS_CHANGED -> {
val uid = msg.arg1
- val deviceId = msg.obj as String
- handleOnPermissionsChanged(uid, deviceId)
+ val persistentDeviceId = msg.obj as String
+ handleOnPermissionsChanged(uid, persistentDeviceId)
}
}
}
- private fun handleOnPermissionsChanged(uid: Int, deviceId: String) {
+ private fun handleOnPermissionsChanged(uid: Int, persistentDeviceId: String) {
listeners.broadcast { listener ->
try {
- listener.onPermissionsChanged(uid, deviceId)
+ listener.onPermissionsChanged(uid, persistentDeviceId)
} catch (e: RemoteException) {
Slog.e(LOG_TAG, "Error when calling OnPermissionsChangeListener", e)
}
@@ -2796,9 +2797,10 @@
listeners.unregister(listener)
}
- fun onPermissionsChanged(uid: Int, deviceId: String) {
+ fun onPermissionsChanged(uid: Int, persistentDeviceId: String) {
if (listeners.registeredCallbackCount > 0) {
- obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0, deviceId).sendToTarget()
+ obtainMessage(MSG_ON_PERMISSIONS_CHANGED, uid, 0, persistentDeviceId)
+ .sendToTarget()
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java
index 4b6183d..8bc027d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ArchiveTest.java
@@ -31,6 +31,7 @@
import android.app.Notification;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
@@ -39,6 +40,7 @@
import com.android.server.UiServiceTestCase;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -55,6 +57,9 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class ArchiveTest extends UiServiceTestCase {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private static final int SIZE = 5;
private NotificationManagerService.Archive mArchive;
@@ -249,4 +254,29 @@
assertThat(expected).contains(sbn.getKey());
}
}
+
+ @Test
+ public void testRemoveNotificationsByPackage() {
+ List<String> expected = new ArrayList<>();
+
+ StatusBarNotification sbn_remove = getNotification("pkg_remove", 0,
+ UserHandle.of(USER_CURRENT));
+ mArchive.record(sbn_remove, REASON_CANCEL);
+
+ StatusBarNotification sbn_keep = getNotification("pkg_keep", 1,
+ UserHandle.of(USER_CURRENT));
+ mArchive.record(sbn_keep, REASON_CANCEL);
+ expected.add(sbn_keep.getKey());
+
+ StatusBarNotification sbn_remove2 = getNotification("pkg_remove", 2,
+ UserHandle.of(USER_CURRENT));
+ mArchive.record(sbn_remove2, REASON_CANCEL);
+
+ mArchive.removePackageNotifications("pkg_remove", USER_CURRENT);
+ List<StatusBarNotification> actual = Arrays.asList(mArchive.getArray(mUm, SIZE, true));
+ assertThat(actual).hasSize(expected.size());
+ for (StatusBarNotification sbn : actual) {
+ assertThat(expected).contains(sbn.getKey());
+ }
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index a47fbce..b45dcd4 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -65,6 +65,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.P;
+import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
@@ -72,7 +73,6 @@
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
-import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
@@ -307,7 +307,6 @@
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
-
@SmallTest
@RunWith(AndroidTestingRunner.class)
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
@@ -813,6 +812,20 @@
mPackageIntentReceiver.onReceive(getContext(), intent);
}
+ private void simulatePackageRemovedBroadcast(String pkg, int uid) {
+ // mimics receive broadcast that package is removed, but doesn't remove the package.
+ final Bundle extras = new Bundle();
+ extras.putStringArray(Intent.EXTRA_CHANGED_PACKAGE_LIST,
+ new String[]{pkg});
+ extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, new int[]{uid});
+
+ final Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
+ intent.setData(Uri.parse("package:" + pkg));
+ intent.putExtras(extras);
+
+ mPackageIntentReceiver.onReceive(getContext(), intent);
+ }
+
private void simulatePackageDistractionBroadcast(int flag, String[] pkgs, int[] uids) {
// mimics receive broadcast that package is (un)distracting
// but does not actually register that info with packagemanager
@@ -878,6 +891,22 @@
mTestNotificationChannel.setAllowBubbles(channelEnabled);
}
+ private void setUpPrefsForHistory(int uid, boolean globalEnabled) {
+ // Sets NOTIFICATION_HISTORY_ENABLED setting for calling process uid
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ Settings.Secure.NOTIFICATION_HISTORY_ENABLED, globalEnabled ? 1 : 0, uid);
+ // Sets NOTIFICATION_HISTORY_ENABLED setting for uid 0
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.NOTIFICATION_HISTORY_ENABLED, globalEnabled ? 1 : 0);
+
+ // Forces an update by calling observe on mSettingsObserver, which picks up the settings
+ // changes above.
+ mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START, mMainLooper);
+
+ assertEquals(globalEnabled, Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0 /* =def */, uid) != 0);
+ }
+
private StatusBarNotification generateSbn(String pkg, int uid, long postTime, int userId) {
Notification.Builder nb = new Notification.Builder(mContext, "a")
.setContentTitle("foo")
@@ -9831,6 +9860,43 @@
}
@Test
+ public void testHandleOnPackageRemoved_ClearsHistory() throws RemoteException {
+ // Enables Notification History setting
+ setUpPrefsForHistory(mUid, true /* =enabled */);
+
+ // Posts a notification to the mTestNotificationChannel.
+ final NotificationRecord notif = generateNotificationRecord(
+ mTestNotificationChannel, 1, null, false);
+ mService.addNotification(notif);
+ StatusBarNotification[] notifs = mBinderService.getActiveNotifications(
+ notif.getSbn().getPackageName());
+ assertEquals(1, notifs.length);
+
+ // Cancels all notifications.
+ mService.cancelAllNotificationsInt(mUid, 0, PKG, null, 0, 0,
+ notif.getUserId(), REASON_CANCEL);
+ waitForIdle();
+ notifs = mBinderService.getActiveNotifications(notif.getSbn().getPackageName());
+ assertEquals(0, notifs.length);
+
+ // Checks that notification history's recently canceled archive contains the notification.
+ notifs = mBinderService.getHistoricalNotificationsWithAttribution(PKG,
+ mContext.getAttributionTag(), 5 /* count */, false /* includeSnoozed */);
+ waitForIdle();
+ assertEquals(1, notifs.length);
+
+ // Remove sthe package that contained the channel
+ simulatePackageRemovedBroadcast(PKG, mUid);
+ waitForIdle();
+
+ // Checks that notification history no longer contains the notification.
+ notifs = mBinderService.getHistoricalNotificationsWithAttribution(
+ PKG, mContext.getAttributionTag(), 5 /* count */, false /* includeSnoozed */);
+ waitForIdle();
+ assertEquals(0, notifs.length);
+ }
+
+ @Test
public void testNotificationHistory_addNoisyNotification() throws Exception {
NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
null /* tvExtender */);
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index 1b8d746..e83f03d 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -99,4 +99,7 @@
enabled: false,
},
+ data: [
+ ":OverlayTestApp",
+ ],
}
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 762e23c..f2a1fe8 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -119,6 +119,9 @@
</intent-filter>
</activity>
+ <activity android:name="com.android.server.wm.ActivityRecordInputSinkTests$TestActivity"
+ android:exported="true">
+ </activity>
</application>
<instrumentation
diff --git a/services/tests/wmtests/AndroidTest.xml b/services/tests/wmtests/AndroidTest.xml
index 2717ef90..f8ebead 100644
--- a/services/tests/wmtests/AndroidTest.xml
+++ b/services/tests/wmtests/AndroidTest.xml
@@ -21,6 +21,7 @@
<option name="cleanup-apks" value="true" />
<option name="install-arg" value="-t" />
<option name="test-file-name" value="WmTests.apk" />
+ <option name="test-file-name" value="OverlayTestApp.apk" />
</target_preparer>
<option name="test-tag" value="WmTests" />
diff --git a/services/tests/wmtests/OverlayApp/Android.bp b/services/tests/wmtests/OverlayApp/Android.bp
new file mode 100644
index 0000000..77d5b22
--- /dev/null
+++ b/services/tests/wmtests/OverlayApp/Android.bp
@@ -0,0 +1,19 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+ name: "OverlayTestApp",
+
+ srcs: ["**/*.java"],
+
+ resource_dirs: ["res"],
+
+ certificate: "platform",
+ platform_apis: true,
+}
diff --git a/services/tests/wmtests/OverlayApp/AndroidManifest.xml b/services/tests/wmtests/OverlayApp/AndroidManifest.xml
new file mode 100644
index 0000000..5b4ef57
--- /dev/null
+++ b/services/tests/wmtests/OverlayApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.wm.overlay_app">
+ <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW" />
+
+ <application>
+ <activity android:name=".OverlayApp"
+ android:exported="true"
+ android:theme="@style/TranslucentFloatingTheme">
+ </activity>
+ </application>
+</manifest>
diff --git a/services/tests/wmtests/OverlayApp/res/values/styles.xml b/services/tests/wmtests/OverlayApp/res/values/styles.xml
new file mode 100644
index 0000000..fff10a3
--- /dev/null
+++ b/services/tests/wmtests/OverlayApp/res/values/styles.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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.
+ -->
+
+<resources>
+ <style name="TranslucentFloatingTheme" >
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowIsFloating">true</item>
+ <item name="android:windowNoTitle">true</item>
+
+ <!-- Disables starting window. -->
+ <item name="android:windowDisablePreview">true</item>
+ </style>
+</resources>
diff --git a/services/tests/wmtests/OverlayApp/src/com/android/server/wm/overlay_app/OverlayApp.java b/services/tests/wmtests/OverlayApp/src/com/android/server/wm/overlay_app/OverlayApp.java
new file mode 100644
index 0000000..89161c5
--- /dev/null
+++ b/services/tests/wmtests/OverlayApp/src/com/android/server/wm/overlay_app/OverlayApp.java
@@ -0,0 +1,51 @@
+/*
+ * 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.wm.overlay_app;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.WindowManager;
+import android.widget.LinearLayout;
+
+/**
+ * Test app that is translucent not touchable modal.
+ * If launched with "disableInputSink" extra boolean value, this activity disables
+ * ActivityRecordInputSinkEnabled as long as the permission is granted.
+ */
+public class OverlayApp extends Activity {
+ private static final String KEY_DISABLE_INPUT_SINK = "disableInputSink";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ LinearLayout tv = new LinearLayout(this);
+ tv.setBackgroundColor(Color.GREEN);
+ tv.setPadding(50, 50, 50, 50);
+ tv.setGravity(Gravity.CENTER);
+ setContentView(tv);
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+
+ if (getIntent().getBooleanExtra(KEY_DISABLE_INPUT_SINK, false)) {
+ setActivityRecordInputSinkEnabled(false);
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordInputSinkTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordInputSinkTests.java
new file mode 100644
index 0000000..3b280d9
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordInputSinkTests.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright 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.wm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.UiAutomation;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.MotionEvent;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.window.WindowInfosListenerForTest;
+
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.window.flags.Flags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * Internal variant of {@link android.server.wm.window.ActivityRecordInputSinkTests}.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ActivityRecordInputSinkTests {
+ private static final String OVERLAY_APP_PKG = "com.android.server.wm.overlay_app";
+ private static final String OVERLAY_ACTIVITY = OVERLAY_APP_PKG + "/.OverlayApp";
+ private static final String KEY_DISABLE_INPUT_SINK = "disableInputSink";
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Rule
+ public final ActivityScenarioRule<TestActivity> mActivityRule =
+ new ActivityScenarioRule<>(TestActivity.class);
+
+ private UiAutomation mUiAutomation;
+
+ @Before
+ public void setUp() {
+ mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ }
+
+ @After
+ public void tearDown() {
+ ActivityManager am =
+ InstrumentationRegistry.getInstrumentation().getContext().getSystemService(
+ ActivityManager.class);
+ mUiAutomation.adoptShellPermissionIdentity();
+ try {
+ am.forceStopPackage(OVERLAY_APP_PKG);
+ } finally {
+ mUiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ @Test
+ public void testSimpleButtonPress() {
+ injectTapOnButton();
+
+ mActivityRule.getScenario().onActivity(a -> {
+ assertEquals(1, a.mNumClicked);
+ });
+ }
+
+ @Test
+ public void testSimpleButtonPress_withOverlay() throws InterruptedException {
+ startOverlayApp(false);
+ waitForOverlayApp();
+
+ injectTapOnButton();
+
+ mActivityRule.getScenario().onActivity(a -> {
+ assertEquals(0, a.mNumClicked);
+ });
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK)
+ public void testSimpleButtonPress_withOverlayDisableInputSink() throws InterruptedException {
+ startOverlayApp(true);
+ waitForOverlayApp();
+
+ injectTapOnButton();
+
+ mActivityRule.getScenario().onActivity(a -> {
+ assertEquals(1, a.mNumClicked);
+ });
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_ALLOW_DISABLE_ACTIVITY_RECORD_INPUT_SINK)
+ public void testSimpleButtonPress_withOverlayDisableInputSink_flagDisabled()
+ throws InterruptedException {
+ startOverlayApp(true);
+ waitForOverlayApp();
+
+ injectTapOnButton();
+
+ mActivityRule.getScenario().onActivity(a -> {
+ assertEquals(0, a.mNumClicked);
+ });
+ }
+
+ private void startOverlayApp(boolean disableInputSink) {
+ String launchCommand = "am start -n " + OVERLAY_ACTIVITY;
+ if (disableInputSink) {
+ launchCommand += " --ez " + KEY_DISABLE_INPUT_SINK + " true";
+ }
+
+ mUiAutomation.adoptShellPermissionIdentity();
+ try {
+ mUiAutomation.executeShellCommand(launchCommand);
+ } finally {
+ mUiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ private void waitForOverlayApp() throws InterruptedException {
+ final var listenerHost = new WindowInfosListenerForTest();
+ final var latch = new CountDownLatch(1);
+ final Consumer<List<WindowInfosListenerForTest.WindowInfo>> listener = windowInfos -> {
+ final boolean inputSinkReady = windowInfos.stream().anyMatch(info ->
+ info.isVisible
+ && info.name.contains("ActivityRecordInputSink " + OVERLAY_ACTIVITY));
+ if (inputSinkReady) {
+ latch.countDown();
+ }
+ };
+
+ listenerHost.addWindowInfosListener(listener);
+ try {
+ assertTrue(latch.await(5, TimeUnit.SECONDS));
+ } finally {
+ listenerHost.removeWindowInfosListener(listener);
+ }
+ }
+
+ private void injectTapOnButton() {
+ Rect buttonBounds = new Rect();
+ mActivityRule.getScenario().onActivity(a -> {
+ a.mButton.getBoundsOnScreen(buttonBounds);
+ });
+ final int x = buttonBounds.centerX();
+ final int y = buttonBounds.centerY();
+
+ MotionEvent down = MotionEvent.obtain(SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, x, y, 0);
+ mUiAutomation.injectInputEvent(down, true);
+
+ SystemClock.sleep(10);
+
+ MotionEvent up = MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_UP, x, y, 0);
+ mUiAutomation.injectInputEvent(up, true);
+
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ public static class TestActivity extends Activity {
+ int mNumClicked = 0;
+ Button mButton;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mButton = new Button(this);
+ mButton.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ setContentView(mButton);
+ mButton.setOnClickListener(v -> mNumClicked++);
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index e152feb..e31ee11 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -306,7 +306,7 @@
@Test
public void testCachedStateConfigurationChange() throws RemoteException {
- doNothing().when(mClientLifecycleManager).scheduleTransactionItem(any(), any());
+ doNothing().when(mClientLifecycleManager).scheduleTransactionItemUnlocked(any(), any());
final IApplicationThread thread = mWpc.getThread();
final Configuration newConfig = new Configuration(mWpc.getConfiguration());
newConfig.densityDpi += 100;
@@ -322,18 +322,17 @@
newConfig.densityDpi += 100;
mWpc.onConfigurationChanged(newConfig);
verify(mClientLifecycleManager, never()).scheduleTransactionItem(eq(thread), any());
+ verify(mClientLifecycleManager, never()).scheduleTransactionItemUnlocked(eq(thread), any());
// Cached -> non-cached will send the previous deferred config immediately.
mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER);
final ArgumentCaptor<ConfigurationChangeItem> captor =
ArgumentCaptor.forClass(ConfigurationChangeItem.class);
- verify(mClientLifecycleManager).scheduleTransactionItem(eq(thread), captor.capture());
+ verify(mClientLifecycleManager).scheduleTransactionItemUnlocked(
+ eq(thread), captor.capture());
final ClientTransactionHandler client = mock(ClientTransactionHandler.class);
captor.getValue().preExecute(client);
- final ArgumentCaptor<Configuration> configCaptor =
- ArgumentCaptor.forClass(Configuration.class);
- verify(client).updatePendingConfiguration(configCaptor.capture());
- assertEquals(newConfig, configCaptor.getValue());
+ verify(client).updatePendingConfiguration(newConfig);
}
@Test