summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Fabián Kozynski <kozynski@google.com> 2024-05-13 10:41:43 -0400
committer Fabian Kozynski <kozynski@google.com> 2024-05-13 16:12:08 +0000
commitdb115452bf1ca9bfa91cc2e483e1b87bafe3579c (patch)
tree6b6e0953472eec6b38734e5a7936ead0df90143c
parentf792c8cf2c6bab8ae38f7964c5bedc7b657d12b8 (diff)
Fix click on TileService bug when closing shade
If an active (unbound) tile is clicked and then the shade is closed immediately, the click would never be sent to the tile. This was because a tile that has `onStopListening` called will immediately stopListening (even if not bound) and reject the click when bound finally happens. Instead, queue the stopListening to happen right after the click is dispatched once the tile is bound. Also, fix when we unbind from active tiles (as together with this it was causing multiple calls to `onStopListening`). Now: * If an active tile requests listening, it will be unbound right after they send a status update. * If an active tile is bound because of a click, it will stop listening and be unbound as if it was not active. Test: atest com.android.systemui.qs Test: atest CtsTileServiceTestCases CtsSystemUiHostTestCases Flag: ACONFIG com.android.systemui.qs_custom_tile_click_guaranteed_bug_fix DISABLED Fixes: 339290820 Change-Id: I0d0a87304e252ad68c48145819098115b00399a1
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CloseShadeRightAfterClickTestB339290820.kt215
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java64
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java98
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java83
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt44
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServicesKosmos.kt50
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt42
15 files changed, 661 insertions, 47 deletions
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index e69ac0a555b6..626e219fca24 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -882,3 +882,13 @@ flag {
description: "Enables Backlinks improvement feature in App Clips"
bug: "300307759"
}
+
+flag {
+ name: "qs_custom_tile_click_guaranteed_bug_fix"
+ namespace: "systemui"
+ description: "Guarantee that clicks on a tile always happen by postponing onStopListening until after the click."
+ bug: "339290820"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CloseShadeRightAfterClickTestB339290820.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CloseShadeRightAfterClickTestB339290820.kt
new file mode 100644
index 000000000000..8d1aa73aa55c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/external/CloseShadeRightAfterClickTestB339290820.kt
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.Intent
+import android.content.ServiceConnection
+import android.content.applicationContext
+import android.content.packageManager
+import android.os.Binder
+import android.os.Handler
+import android.os.RemoteException
+import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
+import android.service.quicksettings.Tile
+import android.testing.TestableContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.testCase
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade
+import com.android.systemui.qs.tiles.impl.custom.customTileSpec
+import com.android.systemui.testKosmos
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.fakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class CloseShadeRightAfterClickTestB339290820 : SysuiTestCase() {
+
+ private val testableContext: TestableContext
+ private val bindDelayExecutor: FakeExecutor
+ private val kosmos =
+ testKosmos().apply {
+ testableContext = testCase.context
+ bindDelayExecutor = FakeExecutor(fakeSystemClock)
+ testableContext.setMockPackageManager(packageManager)
+ customTileSpec = TileSpec.create(testComponentName)
+ applicationContext = ContextWrapperDelayedBind(testableContext, bindDelayExecutor)
+ }
+
+ @Before
+ fun setUp() {
+ kosmos.apply {
+ whenever(packageManager.getPackageUidAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(Binder.getCallingUid())
+ packageManagerAdapterFacade.setIsActive(true)
+ testableContext.addMockService(testComponentName, iQSTileService.asBinder())
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+ fun testStopListeningShortlyAfterClick_clickIsSent() {
+ with(kosmos) {
+ val tile = FakeCustomTileInterface(tileServices)
+ // Flush any bind from startup
+ FakeExecutor.exhaustExecutors(fakeExecutor, bindDelayExecutor)
+
+ // Open QS
+ tile.setListening(true)
+ fakeExecutor.runAllReady()
+ tile.click()
+ fakeExecutor.runAllReady()
+
+ // No clicks yet because the latch is preventing the bind
+ assertThat(iQSTileService.clicks).isEmpty()
+
+ // Close QS
+ tile.setListening(false)
+ fakeExecutor.runAllReady()
+ // And finally bind
+ FakeExecutor.exhaustExecutors(fakeExecutor, bindDelayExecutor)
+
+ assertThat(iQSTileService.clicks).containsExactly(tile.token)
+ }
+ }
+}
+
+private val testComponentName = ComponentName("pkg", "srv")
+
+// This is a fake `CustomTile` that implements what we need for the test. Mainly setListening and
+// click
+private class FakeCustomTileInterface(tileServices: TileServices) : CustomTileInterface {
+ override val user: Int
+ get() = 0
+ override val qsTile: Tile = Tile()
+ override val component: ComponentName = testComponentName
+ private var listening = false
+ private val serviceManager = tileServices.getTileWrapper(this)
+ private val serviceInterface = serviceManager.tileService
+
+ val token = Binder()
+
+ override fun getTileSpec(): String {
+ return CustomTile.toSpec(component)
+ }
+
+ override fun refreshState() {}
+
+ override fun updateTileState(tile: Tile, uid: Int) {}
+
+ override fun onDialogShown() {}
+
+ override fun onDialogHidden() {}
+
+ override fun startActivityAndCollapse(pendingIntent: PendingIntent) {}
+
+ override fun startUnlockAndRun() {}
+
+ fun setListening(listening: Boolean) {
+ if (listening == this.listening) return
+ this.listening = listening
+
+ try {
+ if (listening) {
+ if (!serviceManager.isActiveTile) {
+ serviceManager.setBindRequested(true)
+ serviceInterface.onStartListening()
+ }
+ } else {
+ serviceInterface.onStopListening()
+ serviceManager.setBindRequested(false)
+ }
+ } catch (e: RemoteException) {
+ // Called through wrapper, won't happen here.
+ }
+ }
+
+ fun click() {
+ try {
+ if (serviceManager.isActiveTile) {
+ serviceManager.setBindRequested(true)
+ serviceInterface.onStartListening()
+ }
+ serviceInterface.onClick(token)
+ } catch (e: RemoteException) {
+ // Called through wrapper, won't happen here.
+ }
+ }
+}
+
+private class ContextWrapperDelayedBind(
+ val context: Context,
+ val executor: FakeExecutor,
+) : ContextWrapper(context) {
+ override fun bindServiceAsUser(
+ service: Intent,
+ conn: ServiceConnection,
+ flags: Int,
+ user: UserHandle
+ ): Boolean {
+ executor.execute { super.bindServiceAsUser(service, conn, flags, user) }
+ return true
+ }
+
+ override fun bindServiceAsUser(
+ service: Intent,
+ conn: ServiceConnection,
+ flags: BindServiceFlags,
+ user: UserHandle
+ ): Boolean {
+ executor.execute { super.bindServiceAsUser(service, conn, flags, user) }
+ return true
+ }
+
+ override fun bindServiceAsUser(
+ service: Intent?,
+ conn: ServiceConnection?,
+ flags: Int,
+ handler: Handler?,
+ user: UserHandle?
+ ): Boolean {
+ executor.execute { super.bindServiceAsUser(service, conn, flags, handler, user) }
+ return true
+ }
+
+ override fun bindServiceAsUser(
+ service: Intent,
+ conn: ServiceConnection,
+ flags: BindServiceFlags,
+ handler: Handler,
+ user: UserHandle
+ ): Boolean {
+ executor.execute { super.bindServiceAsUser(service, conn, flags, handler, user) }
+ return true
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 2a726c2835d9..24b7a011f093 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -19,6 +19,8 @@ import static android.os.PowerWhitelistManager.REASON_TILE_ONCLICK;
import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
import static android.service.quicksettings.TileService.START_ACTIVITY_NEEDS_PENDING_INTENT;
+import static com.android.systemui.Flags.qsCustomTileClickGuaranteedBugFix;
+
import android.app.ActivityManager;
import android.app.compat.CompatChanges;
import android.content.BroadcastReceiver;
@@ -88,6 +90,7 @@ public class TileLifecycleManager extends BroadcastReceiver implements
private static final int MSG_ON_REMOVED = 1;
private static final int MSG_ON_CLICK = 2;
private static final int MSG_ON_UNLOCK_COMPLETE = 3;
+ private static final int MSG_ON_STOP_LISTENING = 4;
// Bind retry control.
private static final int MAX_BIND_RETRIES = 5;
@@ -368,6 +371,16 @@ public class TileLifecycleManager extends BroadcastReceiver implements
onUnlockComplete();
}
}
+ if (qsCustomTileClickGuaranteedBugFix()) {
+ if (queue.contains(MSG_ON_STOP_LISTENING)) {
+ if (mDebug) Log.d(TAG, "Handling pending onStopListening " + getComponent());
+ if (mListening) {
+ onStopListening();
+ } else {
+ Log.w(TAG, "Trying to stop listening when not listening " + getComponent());
+ }
+ }
+ }
if (queue.contains(MSG_ON_REMOVED)) {
if (mDebug) Log.d(TAG, "Handling pending onRemoved " + getComponent());
if (mListening) {
@@ -586,10 +599,15 @@ public class TileLifecycleManager extends BroadcastReceiver implements
@Override
public void onStopListening() {
- if (mDebug) Log.d(TAG, "onStopListening " + getComponent());
- mListening = false;
- if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStopListening)) {
- handleDeath();
+ if (qsCustomTileClickGuaranteedBugFix() && hasPendingClick()) {
+ Log.d(TAG, "Enqueue stop listening");
+ queueMessage(MSG_ON_STOP_LISTENING);
+ } else {
+ if (mDebug) Log.d(TAG, "onStopListening " + getComponent());
+ mListening = false;
+ if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStopListening)) {
+ handleDeath();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index f8bf0a684506..6bc5095ed1ea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui.qs.external;
+import static com.android.systemui.Flags.qsCustomTileClickGuaranteedBugFix;
+
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -37,6 +39,7 @@ import com.android.systemui.settings.UserTracker;
import java.util.List;
import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Manages the priority which lets {@link TileServices} make decisions about which tiles
@@ -72,6 +75,8 @@ public class TileServiceManager {
private boolean mPendingBind = true;
private boolean mStarted = false;
+ private final AtomicBoolean mListeningFromRequest = new AtomicBoolean(false);
+
TileServiceManager(TileServices tileServices, Handler handler, ComponentName component,
UserTracker userTracker, TileLifecycleManager.Factory tileLifecycleManagerFactory,
CustomTileAddedRepository customTileAddedRepository) {
@@ -159,15 +164,30 @@ public class TileServiceManager {
}
}
+ void onStartListeningFromRequest() {
+ mListeningFromRequest.set(true);
+ mStateManager.onStartListening();
+ }
+
public void setLastUpdate(long lastUpdate) {
mLastUpdate = lastUpdate;
if (mBound && isActiveTile()) {
- mStateManager.onStopListening();
- setBindRequested(false);
+ if (qsCustomTileClickGuaranteedBugFix()) {
+ if (mListeningFromRequest.compareAndSet(true, false)) {
+ stopListeningAndUnbind();
+ }
+ } else {
+ stopListeningAndUnbind();
+ }
}
mServices.recalculateBindAllowance();
}
+ private void stopListeningAndUnbind() {
+ mStateManager.onStopListening();
+ setBindRequested(false);
+ }
+
public void handleDestroy() {
setBindAllowed(false);
mServices.getContext().unregisterReceiver(mUninstallReceiver);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
index 8278c790226b..d457e88fcf14 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui.qs.external;
+import static com.android.systemui.Flags.qsCustomTileClickGuaranteedBugFix;
+
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -222,9 +224,13 @@ public class TileServices extends IQSService.Stub {
return;
}
service.setBindRequested(true);
- try {
- service.getTileService().onStartListening();
- } catch (RemoteException e) {
+ if (qsCustomTileClickGuaranteedBugFix()) {
+ service.onStartListeningFromRequest();
+ } else {
+ try {
+ service.getTileService().onStartListening();
+ } catch (RemoteException e) {
+ }
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index f57f04000be9..68307b1b905e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -16,10 +16,12 @@
package com.android.systemui.qs.external;
import static android.os.PowerExemptionManager.REASON_TILE_ONCLICK;
+import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf;
import static android.service.quicksettings.TileService.START_ACTIVITY_NEEDS_PENDING_INTENT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -55,13 +57,15 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.IDeviceIdleController;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.service.quicksettings.IQSService;
import android.service.quicksettings.IQSTileService;
import android.service.quicksettings.TileService;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -73,12 +77,24 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
import org.mockito.MockitoSession;
+import java.util.List;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
public class TileLifecycleManagerTest extends SysuiTestCase {
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX);
+ }
+
private final PackageManagerAdapter mMockPackageManagerAdapter =
mock(PackageManagerAdapter.class);
private final BroadcastDispatcher mMockBroadcastDispatcher =
@@ -98,6 +114,11 @@ public class TileLifecycleManagerTest extends SysuiTestCase {
private TestContextWrapper mWrappedContext;
private MockitoSession mMockitoSession;
+ public TileLifecycleManagerTest(FlagsParameterization flags) {
+ super();
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() throws Exception {
setPackageEnabled(true);
@@ -263,7 +284,8 @@ public class TileLifecycleManagerTest extends SysuiTestCase {
}
@Test
- public void testNoClickOfNotListeningAnymore() throws Exception {
+ @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+ public void testNoClickIfNotListeningAnymore() throws Exception {
mStateManager.onTileAdded();
mStateManager.onStartListening();
mStateManager.onClick(null);
@@ -279,6 +301,42 @@ public class TileLifecycleManagerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+ public void testNoClickIfNotListeningBeforeClick() throws Exception {
+ mStateManager.onTileAdded();
+ mStateManager.onStartListening();
+ mStateManager.onStopListening();
+ mStateManager.onClick(null);
+ mStateManager.executeSetBindService(true);
+ mExecutor.runAllReady();
+
+ verifyBind(1);
+ mStateManager.executeSetBindService(false);
+ mExecutor.runAllReady();
+ assertFalse(mContext.isBound(mTileServiceComponentName));
+ verify(mMockTileService, never()).onClick(null);
+ }
+
+ @Test
+ @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+ public void testClickIfStopListeningBeforeProcessedClick() throws Exception {
+ mStateManager.onTileAdded();
+ mStateManager.onStartListening();
+ mStateManager.onClick(null);
+ mStateManager.onStopListening();
+ mStateManager.executeSetBindService(true);
+ mExecutor.runAllReady();
+
+ verifyBind(1);
+ mStateManager.executeSetBindService(false);
+ mExecutor.runAllReady();
+ assertFalse(mContext.isBound(mTileServiceComponentName));
+ InOrder inOrder = Mockito.inOrder(mMockTileService);
+ inOrder.verify(mMockTileService).onClick(null);
+ inOrder.verify(mMockTileService).onStopListening();
+ }
+
+ @Test
public void testComponentEnabling() throws Exception {
mStateManager.onTileAdded();
mStateManager.onStartListening();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
index 0ff29dbbfde7..1c86638c9f27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceManagerTest.java
@@ -15,12 +15,18 @@
*/
package com.android.systemui.qs.external;
+import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf;
+
+import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX;
+import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -32,16 +38,19 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.After;
import org.junit.Before;
@@ -51,10 +60,20 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.List;
+
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
public class TileServiceManagerTest extends SysuiTestCase {
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX);
+ }
+
@Mock
private TileServices mTileServices;
@Mock
@@ -68,17 +87,22 @@ public class TileServiceManagerTest extends SysuiTestCase {
@Mock
private CustomTileAddedRepository mCustomTileAddedRepository;
- private HandlerThread mThread;
- private Handler mHandler;
+ private FakeExecutor mFakeExecutor;
+
private TileServiceManager mTileServiceManager;
private ComponentName mComponentName;
+ public TileServiceManagerTest(FlagsParameterization flags) {
+ super();
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mThread = new HandlerThread("TestThread");
- mThread.start();
- mHandler = Handler.createAsync(mThread.getLooper());
+ mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+ Handler handler = mockExecutorHandler(mFakeExecutor);
+
when(mUserTracker.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
when(mUserTracker.getUserHandle()).thenReturn(UserHandle.SYSTEM);
@@ -90,13 +114,12 @@ public class TileServiceManagerTest extends SysuiTestCase {
mComponentName = new ComponentName(mContext, TileServiceManagerTest.class);
when(mTileLifecycle.getComponent()).thenReturn(mComponentName);
- mTileServiceManager = new TileServiceManager(mTileServices, mHandler, mUserTracker,
+ mTileServiceManager = new TileServiceManager(mTileServices, handler, mUserTracker,
mCustomTileAddedRepository, mTileLifecycle);
}
@After
public void tearDown() throws Exception {
- mThread.quit();
mTileServiceManager.handleDestroy();
}
@@ -201,4 +224,59 @@ public class TileServiceManagerTest extends SysuiTestCase {
verify(mTileLifecycle, times(2)).executeSetBindService(captor.capture());
assertFalse((boolean) captor.getValue());
}
+
+ @Test
+ @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+ public void testStopListeningAndUnbindImmediatelyAfterUpdate() {
+ when(mTileLifecycle.isActiveTile()).thenReturn(true);
+ mTileServiceManager.startLifecycleManagerAndAddTile();
+ mTileServiceManager.setBindAllowed(true);
+ clearInvocations(mTileLifecycle);
+
+ mTileServiceManager.setBindRequested(true);
+ verify(mTileLifecycle).executeSetBindService(true);
+
+ mTileServiceManager.setLastUpdate(0);
+ mFakeExecutor.advanceClockToLast();
+ mFakeExecutor.runAllReady();
+ verify(mTileLifecycle).onStopListening();
+ verify(mTileLifecycle).executeSetBindService(false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+ public void testStopListeningAndUnbindImmediatelyAfterUpdate_ifRequestedFromTileService() {
+ when(mTileLifecycle.isActiveTile()).thenReturn(true);
+ mTileServiceManager.startLifecycleManagerAndAddTile();
+ mTileServiceManager.setBindAllowed(true);
+ clearInvocations(mTileLifecycle);
+
+ mTileServiceManager.setBindRequested(true);
+ mTileServiceManager.onStartListeningFromRequest();
+ verify(mTileLifecycle).onStartListening();
+
+ mTileServiceManager.setLastUpdate(0);
+ mFakeExecutor.advanceClockToLast();
+ mFakeExecutor.runAllReady();
+ verify(mTileLifecycle).onStopListening();
+ verify(mTileLifecycle).executeSetBindService(false);
+ }
+
+ @Test
+ @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+ public void testNotUnbindImmediatelyAfterUpdate_ifRequestedFromSystemUI() {
+ when(mTileLifecycle.isActiveTile()).thenReturn(true);
+ mTileServiceManager.startLifecycleManagerAndAddTile();
+ mTileServiceManager.setBindAllowed(true);
+ clearInvocations(mTileLifecycle);
+
+ mTileServiceManager.setBindRequested(true);
+ // The tile requests startListening (because a click happened)
+
+ mTileServiceManager.setLastUpdate(0);
+ mFakeExecutor.advanceClockToLast();
+ mFakeExecutor.runAllReady();
+ verify(mTileLifecycle, never()).onStopListening();
+ verify(mTileLifecycle, never()).executeSetBindService(false);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index b62d59d3a2f2..bcff88a49ad6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -15,6 +15,10 @@
*/
package com.android.systemui.qs.external;
+import static android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf;
+
+import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertTrue;
@@ -33,8 +37,10 @@ import android.os.Binder;
import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.service.quicksettings.IQSTileService;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -64,13 +70,23 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.List;
import javax.inject.Provider;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(ParameterizedAndroidJunit4.class)
@RunWithLooper
public class TileServicesTest extends SysuiTestCase {
+
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return allCombinationsOf(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX);
+ }
+
private static int NUM_FAKES = TileServices.DEFAULT_MAX_BOUND * 2;
private static final ComponentName TEST_COMPONENT =
@@ -106,6 +122,11 @@ public class TileServicesTest extends SysuiTestCase {
@Mock
private CustomTileAddedRepository mCustomTileAddedRepository;
+ public TileServicesTest(FlagsParameterization flags) {
+ super();
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -194,6 +215,7 @@ public class TileServicesTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
public void testRequestListeningStatusCommand() throws RemoteException {
ArgumentCaptor<CommandQueue.Callbacks> captor =
ArgumentCaptor.forClass(CommandQueue.Callbacks.class);
@@ -213,6 +235,26 @@ public class TileServicesTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+ public void testRequestListeningStatusCommand_onStartListeningFromRequest() {
+ ArgumentCaptor<CommandQueue.Callbacks> captor =
+ ArgumentCaptor.forClass(CommandQueue.Callbacks.class);
+ verify(mCommandQueue).addCallback(captor.capture());
+
+ CustomTile mockTile = mock(CustomTile.class);
+ when(mockTile.getComponent()).thenReturn(TEST_COMPONENT);
+
+ TileServiceManager manager = mTileService.getTileWrapper(mockTile);
+ when(manager.isActiveTile()).thenReturn(true);
+ when(manager.getTileService()).thenReturn(mock(IQSTileService.class));
+
+ captor.getValue().requestTileServiceListeningState(TEST_COMPONENT);
+ mTestableLooper.processAllMessages();
+ verify(manager).setBindRequested(true);
+ verify(manager).onStartListeningFromRequest();
+ }
+
+ @Test
public void testValidCustomTileStartsActivity() {
CustomTile tile = mock(CustomTile.class);
PendingIntent pi = mock(PendingIntent.class);
@@ -263,6 +305,7 @@ public class TileServicesTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
public void tileFreedForCorrectUser() throws RemoteException {
verify(mCommandQueue).addCallback(mCallbacksArgumentCaptor.capture());
@@ -297,6 +340,42 @@ public class TileServicesTest extends SysuiTestCase {
verify(manager1.getTileService()).onStartListening();
}
+ @Test
+ @EnableFlags(FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX)
+ public void tileFreedForCorrectUser_onStartListeningFromRequest() throws RemoteException {
+ verify(mCommandQueue).addCallback(mCallbacksArgumentCaptor.capture());
+
+ ComponentName componentName = new ComponentName("pkg", "cls");
+ CustomTile tileUser0 = mock(CustomTile.class);
+ CustomTile tileUser1 = mock(CustomTile.class);
+
+ when(tileUser0.getComponent()).thenReturn(componentName);
+ when(tileUser1.getComponent()).thenReturn(componentName);
+ when(tileUser0.getUser()).thenReturn(0);
+ when(tileUser1.getUser()).thenReturn(1);
+
+ // Create a tile for user 0
+ TileServiceManager manager0 = mTileService.getTileWrapper(tileUser0);
+ when(manager0.isActiveTile()).thenReturn(true);
+ // Then create a tile for user 1
+ TileServiceManager manager1 = mTileService.getTileWrapper(tileUser1);
+ when(manager1.isActiveTile()).thenReturn(true);
+
+ // When the tile for user 0 gets freed
+ mTileService.freeService(tileUser0, manager0);
+ // and the user is 1
+ when(mUserTracker.getUserId()).thenReturn(1);
+
+ // a call to requestListeningState
+ mCallbacksArgumentCaptor.getValue().requestTileServiceListeningState(componentName);
+ mTestableLooper.processAllMessages();
+
+ // will call in the correct tile
+ verify(manager1).setBindRequested(true);
+ // and set it to listening
+ verify(manager1).onStartListeningFromRequest();
+ }
+
private class TestTileServices extends TileServices {
TestTileServices(QSHost host, Provider<Handler> handlerProvider,
BroadcastDispatcher broadcastDispatcher, UserTracker userTracker,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt
index cff59807e00f..744942c0053b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/FakeIQSTileService.kt
@@ -16,11 +16,11 @@
package com.android.systemui.qs.external
-import android.os.Binder
import android.os.IBinder
+import android.os.IInterface
import android.service.quicksettings.IQSTileService
-class FakeIQSTileService : IQSTileService {
+class FakeIQSTileService : IQSTileService.Stub() {
var isTileAdded: Boolean = false
private set
@@ -31,9 +31,11 @@ class FakeIQSTileService : IQSTileService {
get() = mutableClicks
private val mutableClicks: MutableList<IBinder?> = mutableListOf()
- private val binder = Binder()
+ override fun queryLocalInterface(descriptor: String): IInterface {
+ return this
+ }
- override fun asBinder(): IBinder = binder
+ override fun asBinder(): IBinder = this
override fun onTileAdded() {
isTileAdded = true
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt
new file mode 100644
index 000000000000..a0fc76b3d7de
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerKosmos.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external
+
+import android.app.activityManager
+import android.content.applicationContext
+import android.os.fakeExecutorHandler
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.packageManagerAdapterFacade
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by
+ Kosmos.Fixture {
+ TileLifecycleManager.Factory { intent, userHandle ->
+ TileLifecycleManager(
+ fakeExecutorHandler,
+ applicationContext,
+ tileServices,
+ packageManagerAdapterFacade.packageManagerAdapter,
+ broadcastDispatcher,
+ intent,
+ userHandle,
+ activityManager,
+ mock(),
+ fakeExecutor,
+ )
+ }
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServicesKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServicesKosmos.kt
new file mode 100644
index 000000000000..3f129dac2600
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileServicesKosmos.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.external
+
+import android.content.applicationContext
+import android.os.fakeExecutorHandler
+import com.android.systemui.broadcast.broadcastDispatcher
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository
+import com.android.systemui.qs.pipeline.domain.interactor.panelInteractor
+import com.android.systemui.settings.userTracker
+import com.android.systemui.statusbar.commandQueue
+import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.policy.keyguardStateController
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+
+val Kosmos.tileServices: TileServices by
+ Kosmos.Fixture {
+ val qsHost: QSHost = mock { whenever(context).thenReturn(applicationContext) }
+ TileServices(
+ qsHost,
+ { fakeExecutorHandler },
+ broadcastDispatcher,
+ userTracker,
+ keyguardStateController,
+ commandQueue,
+ mock<StatusBarIconController>(),
+ panelInteractor,
+ tileLifecycleManagerFactory,
+ customTileAddedRepository,
+ fakeExecutor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt
index 36c2c2b6eb23..9a6730e07666 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TilesExternalKosmos.kt
@@ -18,13 +18,9 @@ package com.android.systemui.qs.external
import android.content.ComponentName
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.util.mockito.mock
var Kosmos.componentName: ComponentName by Kosmos.Fixture()
-/** Returns mocks */
-var Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by Kosmos.Fixture { mock {} }
-
val Kosmos.iQSTileService: FakeIQSTileService by Kosmos.Fixture { FakeIQSTileService() }
val Kosmos.tileServiceManagerFacade: FakeTileServiceManagerFacade by
Kosmos.Fixture { FakeTileServiceManagerFacade(iQSTileService) }
@@ -34,4 +30,3 @@ val Kosmos.tileServiceManager: TileServiceManager by
val Kosmos.tileServicesFacade: FakeTileServicesFacade by
Kosmos.Fixture { (FakeTileServicesFacade(tileServiceManager)) }
-val Kosmos.tileServices: TileServices by Kosmos.Fixture { tileServicesFacade.tileServices }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorKosmos.kt
new file mode 100644
index 000000000000..d10780b6b817
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shade.shadeController
+
+val Kosmos.panelInteractor by Kosmos.Fixture { PanelInteractorImpl(shadeController) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
index 7b9992dd5d4f..42437d5a5b81 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
@@ -23,6 +23,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.activityStarter
import com.android.systemui.qs.external.FakeCustomTileStatePersister
import com.android.systemui.qs.external.tileServices
+import com.android.systemui.qs.external.tileServicesFacade
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
import com.android.systemui.qs.tiles.base.logging.QSTileLogger
@@ -86,7 +87,7 @@ val Kosmos.customTileServiceInteractor: CustomTileServiceInteractor by
customTileInteractor,
userRepository,
qsTileLogger,
- tileServices,
+ tileServicesFacade.tileServices,
testScope.backgroundScope,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
index 634d121a556c..fa8d36366415 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles.impl.custom.data.repository
import android.content.ComponentName
+import android.content.pm.PackageInfo
import android.content.pm.ServiceInfo
import android.os.Bundle
import com.android.systemui.qs.external.PackageManagerAdapter
@@ -24,6 +25,7 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import org.mockito.ArgumentMatchers.anyInt
/**
* Facade for [PackageManagerAdapter] to provide a fake-like behaviour. You can create this class
@@ -45,19 +47,33 @@ class FakePackageManagerAdapterFacade(
init {
whenever(packageManagerAdapter.getServiceInfo(eq(componentName), any())).thenAnswer {
- ServiceInfo().apply {
- metaData =
- Bundle().apply {
- putBoolean(
- android.service.quicksettings.TileService.META_DATA_TOGGLEABLE_TILE,
- isToggleable
- )
- putBoolean(
- android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE,
- isActive
- )
- }
- }
+ createServiceInfo()
+ }
+ whenever(
+ packageManagerAdapter.getPackageInfoAsUser(
+ eq(componentName.packageName),
+ anyInt(),
+ anyInt()
+ )
+ )
+ .thenAnswer { PackageInfo().apply { packageName = componentName.packageName } }
+ whenever(packageManagerAdapter.getServiceInfo(eq(componentName), anyInt(), anyInt()))
+ .thenAnswer { createServiceInfo() }
+ }
+
+ private fun createServiceInfo(): ServiceInfo {
+ return ServiceInfo().apply {
+ metaData =
+ Bundle().apply {
+ putBoolean(
+ android.service.quicksettings.TileService.META_DATA_TOGGLEABLE_TILE,
+ isToggleable
+ )
+ putBoolean(
+ android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE,
+ isActive
+ )
+ }
}
}