Move ClipboardOverlayController broadcast handling to background thread

BroadcastDispatcher and BroadcastSender will handle this on background threads and avoid jank.

Bug: 224545601
Test: Tested ScreenshotView functionality on a physical device
Change-Id: I3b1c0d3f14dc4e1010a2e8754aac57755ce726cc
This commit is contained in:
Jernej Virag
2022-03-29 17:07:31 +02:00
parent 9519e3c94a
commit 590fb801d3
15 changed files with 188 additions and 58 deletions

View File

@@ -41,7 +41,8 @@ data class ReceiverData(
val receiver: BroadcastReceiver,
val filter: IntentFilter,
val executor: Executor,
val user: UserHandle
val user: UserHandle,
val permission: String? = null
)
private const val MSG_ADD_RECEIVER = 0
@@ -96,16 +97,18 @@ open class BroadcastDispatcher constructor (
*
*/
@Deprecated(message = "Replacing Handler for Executor in SystemUI",
replaceWith = ReplaceWith("registerReceiver(receiver, filter, executor, user)"))
replaceWith = ReplaceWith("registerReceiver(receiver, filter, executor, user, permission)")
)
@JvmOverloads
open fun registerReceiverWithHandler(
receiver: BroadcastReceiver,
filter: IntentFilter,
handler: Handler,
user: UserHandle = context.user,
@Context.RegisterReceiverFlags flags: Int = Context.RECEIVER_EXPORTED
@Context.RegisterReceiverFlags flags: Int = Context.RECEIVER_EXPORTED,
permission: String? = null
) {
registerReceiver(receiver, filter, HandlerExecutor(handler), user, flags)
registerReceiver(receiver, filter, HandlerExecutor(handler), user, flags, permission)
}
/**
@@ -130,15 +133,17 @@ open class BroadcastDispatcher constructor (
filter: IntentFilter,
executor: Executor? = null,
user: UserHandle? = null,
@Context.RegisterReceiverFlags flags: Int = Context.RECEIVER_EXPORTED
@Context.RegisterReceiverFlags flags: Int = Context.RECEIVER_EXPORTED,
permission: String? = null,
) {
checkFilter(filter)
val data = ReceiverData(
receiver,
filter,
executor ?: context.mainExecutor,
user ?: context.user
)
user ?: context.user,
permission
)
this.handler
.obtainMessage(MSG_ADD_RECEIVER, flags, 0, data)
.sendToTarget()

View File

@@ -71,9 +71,16 @@ open class UserBroadcastDispatcher(
}
}
// Used for key in actionsToActionsReceivers
internal data class ReceiverProperties(
val action: String,
val flags: Int,
val permission: String?
)
// Only modify in BG thread
@VisibleForTesting
internal val actionsToActionsReceivers = ArrayMap<Pair<String, Int>, ActionReceiver>()
internal val actionsToActionsReceivers = ArrayMap<ReceiverProperties, ActionReceiver>()
private val receiverToActions = ArrayMap<BroadcastReceiver, MutableSet<String>>()
@VisibleForTesting
@@ -106,14 +113,20 @@ open class UserBroadcastDispatcher(
.addAll(receiverData.filter.actionsIterator()?.asSequence() ?: emptySequence())
receiverData.filter.actionsIterator().forEach {
actionsToActionsReceivers
.getOrPut(it to flags, { createActionReceiver(it, flags) })
.addReceiverData(receiverData)
.getOrPut(
ReceiverProperties(it, flags, receiverData.permission),
{ createActionReceiver(it, receiverData.permission, flags) })
.addReceiverData(receiverData)
}
logger.logReceiverRegistered(userId, receiverData.receiver, flags)
}
@VisibleForTesting
internal open fun createActionReceiver(action: String, flags: Int): ActionReceiver {
internal open fun createActionReceiver(
action: String,
permission: String?,
flags: Int
): ActionReceiver {
return ActionReceiver(
action,
userId,
@@ -122,7 +135,7 @@ open class UserBroadcastDispatcher(
this,
UserHandle.of(userId),
it,
null,
permission,
bgHandler,
flags
)
@@ -149,7 +162,7 @@ open class UserBroadcastDispatcher(
if (DEBUG) Log.w(TAG, "Unregister receiver: $receiver")
receiverToActions.getOrDefault(receiver, mutableSetOf()).forEach {
actionsToActionsReceivers.forEach { (key, value) ->
if (key.first == it) {
if (key.action == it) {
value.removeReceiver(receiver)
}
}
@@ -160,9 +173,12 @@ open class UserBroadcastDispatcher(
override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
pw.indentIfPossible {
actionsToActionsReceivers.forEach { (actionAndFlags, actionReceiver) ->
println("(${actionAndFlags.first}: " +
"${BroadcastDispatcherLogger.flagToString(actionAndFlags.second)}):")
actionsToActionsReceivers.forEach { (actionFlagsPerm, actionReceiver) ->
println(
"(${actionFlagsPerm.action}: " +
BroadcastDispatcherLogger.flagToString(actionFlagsPerm.flags) +
if (actionFlagsPerm.permission == null) "):"
else ":${actionFlagsPerm.permission}):")
actionReceiver.dump(fd, pw, args)
}
}

View File

@@ -80,6 +80,8 @@ import android.widget.TextView;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.PhoneWindow;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.screenshot.DraggableConstraintLayout;
import com.android.systemui.screenshot.FloatingWindowUtil;
import com.android.systemui.screenshot.OverlayActionChip;
@@ -105,6 +107,7 @@ public class ClipboardOverlayController {
private final Context mContext;
private final UiEventLogger mUiEventLogger;
private final BroadcastDispatcher mBroadcastDispatcher;
private final DisplayManager mDisplayManager;
private final DisplayMetrics mDisplayMetrics;
private final WindowManager mWindowManager;
@@ -137,8 +140,11 @@ public class ClipboardOverlayController {
private boolean mBlockAttach = false;
public ClipboardOverlayController(
Context context, TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) {
public ClipboardOverlayController(Context context,
BroadcastDispatcher broadcastDispatcher,
BroadcastSender broadcastSender,
TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) {
mBroadcastDispatcher = broadcastDispatcher;
mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
final Context displayContext = context.createDisplayContext(getDefaultDisplay());
mContext = displayContext.createWindowContext(TYPE_SCREENSHOT, null);
@@ -247,9 +253,9 @@ public class ClipboardOverlayController {
}
}
};
mContext.registerReceiver(mCloseDialogsReceiver,
new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS));
mBroadcastDispatcher.registerReceiver(mCloseDialogsReceiver,
new IntentFilter(ACTION_CLOSE_SYSTEM_DIALOGS));
mScreenshotReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -258,14 +264,16 @@ public class ClipboardOverlayController {
}
}
};
mContext.registerReceiver(mScreenshotReceiver, new IntentFilter(SCREENSHOT_ACTION),
SELF_PERMISSION, null);
mBroadcastDispatcher.registerReceiver(mScreenshotReceiver,
new IntentFilter(SCREENSHOT_ACTION), null, null, Context.RECEIVER_EXPORTED,
SELF_PERMISSION);
monitorOutsideTouches();
Intent copyIntent = new Intent(COPY_OVERLAY_ACTION);
// Set package name so the system knows it's safe
copyIntent.setPackage(mContext.getPackageName());
mContext.sendBroadcast(copyIntent, SELF_PERMISSION);
broadcastSender.sendBroadcast(copyIntent, SELF_PERMISSION);
}
void setClipData(ClipData clipData, String clipSource) {

View File

@@ -19,6 +19,8 @@ package com.android.systemui.clipboardoverlay;
import android.content.Context;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.screenshot.TimeoutHandler;
@@ -29,17 +31,24 @@ import javax.inject.Inject;
*/
@SysUISingleton
public class ClipboardOverlayControllerFactory {
private final UiEventLogger mUiEventLogger;
private final BroadcastDispatcher mBroadcastDispatcher;
private final BroadcastSender mBroadcastSender;
@Inject
public ClipboardOverlayControllerFactory(UiEventLogger uiEventLogger) {
mUiEventLogger = uiEventLogger;
public ClipboardOverlayControllerFactory(BroadcastDispatcher broadcastDispatcher,
BroadcastSender broadcastSender, UiEventLogger uiEventLogger) {
this.mBroadcastDispatcher = broadcastDispatcher;
this.mBroadcastSender = broadcastSender;
this.mUiEventLogger = uiEventLogger;
}
/**
* One new ClipboardOverlayController, coming right up!
*/
public ClipboardOverlayController create(Context context) {
return new ClipboardOverlayController(context, new TimeoutHandler(context), mUiEventLogger);
return new ClipboardOverlayController(context, mBroadcastDispatcher, mBroadcastSender,
new TimeoutHandler(context), mUiEventLogger);
}
}

View File

@@ -36,7 +36,6 @@ import android.view.IWindowManager;
import android.view.LayoutInflater;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.NotificationMessagingUtil;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -50,7 +49,6 @@ import com.android.systemui.accessibility.ModeSwitchesController;
import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.logging.BroadcastDispatcherLogger;
import com.android.systemui.clipboardoverlay.ClipboardOverlayControllerFactory;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.AlwaysOnDisplayPolicy;
@@ -293,12 +291,4 @@ public class DependencyProvider {
public ModeSwitchesController providesModeSwitchesController(Context context) {
return new ModeSwitchesController(context);
}
/***/
@Provides
@SysUISingleton
public ClipboardOverlayControllerFactory provideClipboardOverlayControllerFactory(
UiEventLogger uiEventLogger) {
return new ClipboardOverlayControllerFactory(uiEventLogger);
}
}

View File

@@ -56,6 +56,7 @@ class BroadcastDispatcherTest : SysuiTestCase() {
val user0 = UserHandle.of(0)
val user1 = UserHandle.of(1)
const val DEFAULT_FLAG = Context.RECEIVER_EXPORTED
val DEFAULT_PERMISSION: String? = null
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
const val TEST_ACTION = "TEST_ACTION"
@@ -189,6 +190,38 @@ class BroadcastDispatcherTest : SysuiTestCase() {
assertSame(intentFilter, argumentCaptor.value.filter)
}
@Test
fun testAddReceiverCorrectPermission_executor() {
val flag = 3
val permission = "CUSTOM_PERMISSION"
broadcastDispatcher.registerReceiver(
broadcastReceiver,
intentFilter,
flags = flag,
permission = permission
)
testableLooper.processAllMessages()
verify(mockUBRUser0).registerReceiver(capture(argumentCaptor), eq(flag))
assertSame(broadcastReceiver, argumentCaptor.value.receiver)
assertSame(intentFilter, argumentCaptor.value.filter)
assertSame(permission, argumentCaptor.value.permission)
}
@Test
fun testAddReceiverDefaultPermission_executor() {
broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter)
testableLooper.processAllMessages()
verify(mockUBRUser0).registerReceiver(capture(argumentCaptor), eq(DEFAULT_FLAG))
assertSame(broadcastReceiver, argumentCaptor.value.receiver)
assertSame(intentFilter, argumentCaptor.value.filter)
assertSame(DEFAULT_PERMISSION, argumentCaptor.value.permission)
}
@Test
fun testAddReceiverCorrectFlag_executor() {
val flag = 3

View File

@@ -17,6 +17,7 @@
package com.android.systemui.broadcast
import android.content.BroadcastReceiver
import android.content.Context
import android.content.IntentFilter
import android.os.Handler
import android.os.Looper
@@ -45,7 +46,8 @@ class FakeBroadcastDispatcher(
filter: IntentFilter,
handler: Handler,
user: UserHandle,
flags: Int
@Context.RegisterReceiverFlags flags: Int,
permission: String?
) {
registeredReceivers.add(receiver)
}
@@ -55,7 +57,8 @@ class FakeBroadcastDispatcher(
filter: IntentFilter,
executor: Executor?,
user: UserHandle?,
flags: Int
@Context.RegisterReceiverFlags flags: Int,
permission: String?
) {
registeredReceivers.add(receiver)
}

View File

@@ -85,7 +85,11 @@ class UserBroadcastDispatcherTest : SysuiTestCase() {
userBroadcastDispatcher = object : UserBroadcastDispatcher(
mockContext, USER_ID, testableLooper.looper, mock(Executor::class.java), logger) {
override fun createActionReceiver(action: String, flags: Int): ActionReceiver {
override fun createActionReceiver(
action: String,
permission: String?,
flags: Int
): ActionReceiver {
return mock(ActionReceiver::class.java)
}
}
@@ -122,6 +126,24 @@ class UserBroadcastDispatcherTest : SysuiTestCase() {
)
}
@Test
fun testDifferentActionReceiversForDifferentPermissions() {
intentFilter = IntentFilter(ACTION_1)
val receiverData1 =
ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE, "PERMISSION1")
val receiverData2 =
ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE, "PERMISSION2")
userBroadcastDispatcher.registerReceiver(receiverData1, 0)
userBroadcastDispatcher.registerReceiver(receiverData2, 0)
testableLooper.processAllMessages()
assertNotSame(
userBroadcastDispatcher.getActionReceiver(ACTION_1, 0, "PERMISSION1"),
userBroadcastDispatcher.getActionReceiver(ACTION_1, 0, "PERMISSION2")
)
}
@Test
fun testSingleReceiverRegistered_logging() {
intentFilter = IntentFilter(ACTION_1)
@@ -213,8 +235,45 @@ class UserBroadcastDispatcherTest : SysuiTestCase() {
any(), any(), any(), nullable(String::class.java), any(), eq(FLAG))
}
private fun UserBroadcastDispatcher
.getActionReceiver(action: String, flags: Int): ActionReceiver? {
return actionsToActionsReceivers.get(action to flags)
@Test
fun testCreateActionReceiver_registerWithPermission() {
val permission = "CUSTOM_PERMISSION"
val uBR = UserBroadcastDispatcher(
mockContext,
USER_ID,
testableLooper.looper,
fakeExecutor,
logger
)
uBR.registerReceiver(
ReceiverData(
broadcastReceiver,
IntentFilter(ACTION_1),
fakeExecutor,
USER_HANDLE,
permission
),
FLAG
)
testableLooper.processAllMessages()
fakeExecutor.runAllReady()
verify(mockContext).registerReceiverAsUser(
any(), any(), any(), eq(permission), any(), eq(FLAG))
}
private fun UserBroadcastDispatcher.getActionReceiver(
action: String,
flags: Int,
permission: String? = null
): ActionReceiver? {
return actionsToActionsReceivers.get(
UserBroadcastDispatcher.ReceiverProperties(
action,
flags,
permission
)
)
}
}

View File

@@ -168,7 +168,8 @@ class ControlsControllerImplTest : SysuiTestCase() {
controller.auxiliaryPersistenceWrapper = auxiliaryPersistenceWrapper
verify(broadcastDispatcher).registerReceiver(
capture(broadcastReceiverCaptor), any(), any(), eq(UserHandle.ALL), anyInt())
capture(broadcastReceiverCaptor), any(), any(), eq(UserHandle.ALL), anyInt(), any()
)
verify(listingController).addCallback(capture(listingCallbackCaptor))
}

View File

@@ -26,6 +26,7 @@ import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Before
import org.junit.Test
@@ -68,7 +69,8 @@ class LogBufferFreezerTest : SysuiTestCase() {
any(IntentFilter::class.java),
eq(executor),
any(UserHandle::class.java),
anyInt())
anyInt(),
nullable())
receiver = receiverCaptor.value
}

View File

@@ -168,7 +168,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
resumeBrowserFactory, dumpManager, clock)
listener.setManager(mediaDataManager)
verify(broadcastDispatcher, never()).registerReceiver(eq(listener.userChangeReceiver),
any(), any(), any(), anyInt())
any(), any(), any(), anyInt(), any())
// When data is loaded, we do NOT execute or update anything
listener.onMediaDataLoaded(KEY, OLD_KEY, data)
@@ -291,7 +291,7 @@ class MediaResumeListenerTest : SysuiTestCase() {
// Make sure broadcast receiver is registered
resumeListener.setManager(mediaDataManager)
verify(broadcastDispatcher).registerReceiver(eq(resumeListener.userChangeReceiver),
any(), any(), any(), anyInt())
any(), any(), any(), anyInt(), any())
// When we get an unlock event
val intent = Intent(Intent.ACTION_USER_UNLOCKED)

View File

@@ -145,7 +145,7 @@ public class AutoAddTrackerTest extends SysuiTestCase {
public void testBroadcastReceiverRegistered() {
verify(mBroadcastDispatcher).registerReceiver(
any(), mIntentFilterArgumentCaptor.capture(), any(), eq(UserHandle.of(USER)),
anyInt());
anyInt(), any());
assertTrue(
mIntentFilterArgumentCaptor.getValue().hasAction(Intent.ACTION_SETTING_RESTORED));
@@ -158,13 +158,14 @@ public class AutoAddTrackerTest extends SysuiTestCase {
InOrder inOrder = Mockito.inOrder(mBroadcastDispatcher);
inOrder.verify(mBroadcastDispatcher).unregisterReceiver(any());
inOrder.verify(mBroadcastDispatcher)
.registerReceiver(any(), any(), any(), eq(UserHandle.of(USER + 1)), anyInt());
.registerReceiver(any(), any(), any(), eq(UserHandle.of(USER + 1)), anyInt(),
any());
}
@Test
public void testSettingRestoredWithTilesNotRemovedInSource_noAutoAddedInTarget() {
verify(mBroadcastDispatcher).registerReceiver(
mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt());
mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any());
// These tiles were present in the original device
String restoredTiles = "saver,work,internet,cast";
@@ -188,7 +189,7 @@ public class AutoAddTrackerTest extends SysuiTestCase {
public void testSettingRestoredWithTilesRemovedInSource_noAutoAddedInTarget() {
verify(mBroadcastDispatcher)
.registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(),
anyInt());
anyInt(), any());
// These tiles were present in the original device
String restoredTiles = "saver,internet,cast";
@@ -212,7 +213,7 @@ public class AutoAddTrackerTest extends SysuiTestCase {
public void testSettingRestoredWithTilesRemovedInSource_sameAutoAddedinTarget() {
verify(mBroadcastDispatcher)
.registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(),
anyInt());
anyInt(), any());
// These tiles were present in the original device
String restoredTiles = "saver,internet,cast";
@@ -237,7 +238,7 @@ public class AutoAddTrackerTest extends SysuiTestCase {
public void testSettingRestoredWithTilesRemovedInSource_othersAutoAddedinTarget() {
verify(mBroadcastDispatcher)
.registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(),
anyInt());
anyInt(), any());
// These tiles were present in the original device
String restoredTiles = "saver,internet,cast";

View File

@@ -163,7 +163,7 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() {
val receiverCaptor = argumentCaptor<BroadcastReceiver>()
whenever(safetyCenterManager.isSafetyCenterEnabled).thenReturn(true)
verify(broadcastDispatcher).registerReceiver(capture(receiverCaptor),
any(), any(), nullable(), anyInt())
any(), any(), nullable(), anyInt(), nullable())
receiverCaptor.value.onReceive(
context,
Intent(SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED)
@@ -193,8 +193,10 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() {
val broadcastReceiverCaptor = argumentCaptor<BroadcastReceiver>()
val intentFilterCaptor = argumentCaptor<IntentFilter>()
// Broadcast receiver is registered on init and when privacy chip is attached
verify(broadcastDispatcher, times(2)).registerReceiver(capture(broadcastReceiverCaptor),
capture(intentFilterCaptor), any(), nullable(), anyInt())
verify(broadcastDispatcher, times(2)).registerReceiver(
capture(broadcastReceiverCaptor),
capture(intentFilterCaptor), any(), nullable(), anyInt(), nullable()
)
}
private fun setPrivacyController(micCamera: Boolean, location: Boolean) {

View File

@@ -192,7 +192,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase {
argThat(intentFilter -> intentFilter.hasAction(Intent.ACTION_USER_SWITCHED)),
isNull(),
isNull(),
eq(Context.RECEIVER_EXPORTED));
eq(Context.RECEIVER_EXPORTED),
isNull());
BroadcastReceiver callback = callbackCaptor.getValue();
Consumer<String> listener = mock(Consumer.class);

View File

@@ -88,14 +88,14 @@ class RingerModeLiveDataTest : SysuiTestCase() {
fun testOnActive_broadcastRegistered() {
liveData.observeForever(observer)
verify(broadcastDispatcher)
.registerReceiver(any(), any(), eq(executor), eq(UserHandle.ALL), anyInt())
.registerReceiver(any(), any(), eq(executor), eq(UserHandle.ALL), anyInt(), any())
}
@Test
fun testOnActive_intentFilterHasIntent() {
liveData.observeForever(observer)
verify(broadcastDispatcher).registerReceiver(any(), capture(intentFilterCaptor), any(),
any(), anyInt())
any(), anyInt(), any())
assertTrue(intentFilterCaptor.value.hasAction(INTENT))
}