Make AutoTileManager lisen for component enable/disable
This will allow the Safety Tile to be shown/hidden.
Test: atest AutoTileManagerTest,SafetyControllerTest
Bug: 222127376
Change-Id: Iad666a9aafe74092871ca18fc8b1973a68e1312b
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index b172e92..535b548 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -490,5 +490,4 @@
static SafetyCenterManager provideSafetyCenterManager(Context context) {
return context.getSystemService(SafetyCenterManager.class);
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index 5d4b3da..628964a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -35,6 +35,7 @@
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.DeviceControlsController;
import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.SafetyController;
import com.android.systemui.statusbar.policy.WalletController;
import com.android.systemui.util.settings.SecureSettings;
@@ -66,6 +67,7 @@
ReduceBrightColorsController reduceBrightColorsController,
DeviceControlsController deviceControlsController,
WalletController walletController,
+ SafetyController safetyController,
@Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
AutoTileManager manager = new AutoTileManager(
context,
@@ -81,6 +83,7 @@
reduceBrightColorsController,
deviceControlsController,
walletController,
+ safetyController,
isReduceBrightColorsAvailable
);
manager.init();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index ccb37ae..61780cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -40,6 +40,7 @@
import com.android.systemui.statusbar.policy.DeviceControlsController;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.HotspotController.Callback;
+import com.android.systemui.statusbar.policy.SafetyController;
import com.android.systemui.statusbar.policy.WalletController;
import com.android.systemui.util.UserAwareController;
import com.android.systemui.util.settings.SecureSettings;
@@ -83,6 +84,7 @@
private final DeviceControlsController mDeviceControlsController;
private final WalletController mWalletController;
private final ReduceBrightColorsController mReduceBrightColorsController;
+ private final SafetyController mSafetyController;
private final boolean mIsReduceBrightColorsAvailable;
private final ArrayList<AutoAddSetting> mAutoAddSettingList = new ArrayList<>();
@@ -98,6 +100,7 @@
ReduceBrightColorsController reduceBrightColorsController,
DeviceControlsController deviceControlsController,
WalletController walletController,
+ SafetyController safetyController,
@Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
mContext = context;
mHost = host;
@@ -114,6 +117,7 @@
mIsReduceBrightColorsAvailable = isReduceBrightColorsAvailable;
mDeviceControlsController = deviceControlsController;
mWalletController = walletController;
+ mSafetyController = safetyController;
String safetySpecRes;
try {
safetySpecRes = context.getResources().getString(R.string.safety_quick_settings_tile);
@@ -163,8 +167,11 @@
if (!mAutoTracker.isAdded(WALLET)) {
initWalletController();
}
- if (mSafetySpec != null && !mAutoTracker.isAdded(mSafetySpec)) {
- initSafetyTile();
+ if (mSafetySpec != null) {
+ if (!mAutoTracker.isAdded(mSafetySpec)) {
+ initSafetyTile();
+ }
+ mSafetyController.addCallback(mSafetyCallback);
}
int settingsN = mAutoAddSettingList.size();
@@ -187,6 +194,9 @@
}
mCastController.removeCallback(mCastCallback);
mDeviceControlsController.removeCallback();
+ if (mSafetySpec != null) {
+ mSafetyController.removeCallback(mSafetyCallback);
+ }
int settingsN = mAutoAddSettingList.size();
for (int i = 0; i < settingsN; i++) {
mAutoAddSettingList.get(i).setListening(false);
@@ -327,10 +337,9 @@
}
private void initSafetyTile() {
- if (mSafetySpec == null) {
+ if (mSafetySpec == null || mAutoTracker.isAdded(mSafetySpec)) {
return;
}
- if (mAutoTracker.isAdded(mSafetySpec)) return;
mHost.addTile(CustomTile.getComponentFromSpec(mSafetySpec), true);
mAutoTracker.setTileAdded(mSafetySpec);
}
@@ -403,6 +412,23 @@
};
@VisibleForTesting
+ final SafetyController.Listener mSafetyCallback = new SafetyController.Listener() {
+ @Override
+ public void onSafetyCenterEnableChanged(boolean isSafetyCenterEnabled) {
+ if (mSafetySpec == null) {
+ return;
+ }
+
+ if (isSafetyCenterEnabled && !mAutoTracker.isAdded(mSafetySpec)) {
+ initSafetyTile();
+ } else if (!isSafetyCenterEnabled && mAutoTracker.isAdded(mSafetySpec)) {
+ mHost.removeTile(CustomTile.getComponentFromSpec(mSafetySpec));
+ mHost.unmarkTileAsAutoAdded(mSafetySpec);
+ }
+ }
+ };
+
+ @VisibleForTesting
protected SettingObserver getSecureSettingForKey(String key) {
for (SettingObserver s : mAutoAddSettingList) {
if (Objects.equals(key, s.getKey())) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java
new file mode 100644
index 0000000..f3d183c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SafetyController.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 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.statusbar.policy;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.safetycenter.SafetyCenterManager;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.qualifiers.Background;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+import javax.inject.Inject;
+
+/**
+ * Controller which calls listeners when a PACKAGE_CHANGED broadcast is sent for the
+ * PermissionController. These broadcasts may be because the PermissionController enabled or
+ * disabled its TileService, and the tile should be added if the component was enabled, or removed
+ * if disabled.
+ */
+public class SafetyController implements
+ CallbackController<SafetyController.Listener> {
+ private boolean mSafetyCenterEnabled;
+ private final Handler mBgHandler;
+ private final ArrayList<Listener> mListeners = new ArrayList<>();
+ private static final IntentFilter PKG_CHANGE_INTENT_FILTER;
+ private final Context mContext;
+ private final SafetyCenterManager mSafetyCenterManager;
+ private final PackageManager mPackageManager;
+
+ static {
+ PKG_CHANGE_INTENT_FILTER = new IntentFilter(Intent.ACTION_PACKAGE_CHANGED);
+ PKG_CHANGE_INTENT_FILTER.addDataScheme("package");
+ }
+
+ @Inject
+ public SafetyController(Context context, PackageManager pm, SafetyCenterManager scm,
+ @Background Handler bgHandler) {
+ mContext = context;
+ mSafetyCenterManager = scm;
+ mPackageManager = pm;
+ mBgHandler = bgHandler;
+ mSafetyCenterEnabled = mSafetyCenterManager.isSafetyCenterEnabled();
+ }
+
+ public boolean isSafetyCenterEnabled() {
+ return mSafetyCenterEnabled;
+ }
+
+ /**
+ * Adds a listener, registering the broadcast receiver if need be. Immediately calls the
+ * provided listener on the calling thread.
+ */
+ @Override
+ public void addCallback(@NonNull Listener listener) {
+ synchronized (mListeners) {
+ mListeners.add(listener);
+ if (mListeners.size() == 1) {
+ mContext.registerReceiver(mPermControllerChangeReceiver, PKG_CHANGE_INTENT_FILTER);
+ mBgHandler.post(() -> {
+ mSafetyCenterEnabled = mSafetyCenterManager.isSafetyCenterEnabled();
+ listener.onSafetyCenterEnableChanged(isSafetyCenterEnabled());
+ });
+ } else {
+ listener.onSafetyCenterEnableChanged(isSafetyCenterEnabled());
+ }
+ }
+ }
+
+ @Override
+ public void removeCallback(@NonNull Listener listener) {
+ synchronized (mListeners) {
+ mListeners.remove(listener);
+ if (mListeners.isEmpty()) {
+ mContext.unregisterReceiver(mPermControllerChangeReceiver);
+ }
+ }
+ }
+
+ private void handleSafetyCenterEnableChange() {
+ synchronized (mListeners) {
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onSafetyCenterEnableChanged(mSafetyCenterEnabled);
+ }
+ }
+ }
+
+ /**
+ * Upon receiving a package changed broadcast for the PermissionController, checks if the
+ * safety center is enabled or disabled, and sends an update on the main thread if the state
+ * changed.
+ */
+ @VisibleForTesting
+ final BroadcastReceiver mPermControllerChangeReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String packageName = intent.getData() != null ? intent.getData().getSchemeSpecificPart()
+ : null;
+ if (!Objects.equals(packageName,
+ mPackageManager.getPermissionControllerPackageName())) {
+ return;
+ }
+
+ boolean wasSafetyCenterEnabled = mSafetyCenterEnabled;
+ mSafetyCenterEnabled = mSafetyCenterManager.isSafetyCenterEnabled();
+ if (wasSafetyCenterEnabled == mSafetyCenterEnabled) {
+ return;
+ }
+
+ mBgHandler.post(() -> handleSafetyCenterEnableChange());
+ }
+ };
+
+ /**
+ * Listener for safety center enabled changes
+ */
+ public interface Listener {
+ /**
+ * Callback to be notified when the safety center is enabled or disabled
+ * @param isSafetyCenterEnabled If the safety center is enabled
+ */
+ void onSafetyCenterEnableChanged(boolean isSafetyCenterEnabled);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 0e12c2a..03a6c19 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -58,6 +58,7 @@
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.DeviceControlsController;
import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.SafetyController;
import com.android.systemui.statusbar.policy.WalletController;
import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SecureSettings;
@@ -101,6 +102,7 @@
@Mock private ReduceBrightColorsController mReduceBrightColorsController;
@Mock private DeviceControlsController mDeviceControlsController;
@Mock private WalletController mWalletController;
+ @Mock private SafetyController mSafetyController;
@Mock(answer = Answers.RETURNS_SELF)
private AutoAddTracker.Builder mAutoAddTrackerBuilder;
@Mock private Context mUserContext;
@@ -150,6 +152,7 @@
ReduceBrightColorsController reduceBrightColorsController,
DeviceControlsController deviceControlsController,
WalletController walletController,
+ SafetyController safetyController,
@Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
return new AutoTileManager(context, autoAddTrackerBuilder, mQsTileHost,
Handler.createAsync(TestableLooper.get(this).getLooper()),
@@ -162,6 +165,7 @@
reduceBrightColorsController,
deviceControlsController,
walletController,
+ safetyController,
isReduceBrightColorsAvailable);
}
@@ -169,7 +173,7 @@
return createAutoTileManager(context, mAutoAddTrackerBuilder, mHotspotController,
mDataSaverController, mManagedProfileController, mNightDisplayListener,
mCastController, mReduceBrightColorsController, mDeviceControlsController,
- mWalletController, mIsReduceBrightColorsAvailable);
+ mWalletController, mSafetyController, mIsReduceBrightColorsAvailable);
}
@Test
@@ -185,10 +189,11 @@
ReduceBrightColorsController rBC = mock(ReduceBrightColorsController.class);
DeviceControlsController dCC = mock(DeviceControlsController.class);
WalletController wC = mock(WalletController.class);
+ SafetyController sC = mock(SafetyController.class);
AutoTileManager manager =
createAutoTileManager(mock(Context.class), builder, hC, dSC, mPC, nDS, cC, rBC,
- dCC, wC, true);
+ dCC, wC, sC, true);
verify(tracker, never()).initialize();
verify(hC, never()).addCallback(any());
@@ -199,6 +204,7 @@
verify(rBC, never()).addCallback(any());
verify(dCC, never()).setCallback(any());
verify(wC, never()).getWalletPosition();
+ verify(sC, never()).addCallback(any());
assertNull(manager.getSecureSettingForKey(TEST_SETTING));
assertNull(manager.getSecureSettingForKey(TEST_SETTING_COMPONENT));
}
@@ -253,6 +259,10 @@
verify(mWalletController, times(2)).getWalletPosition();
+ InOrder inOrderSafety = inOrder(mSafetyController);
+ inOrderSafety.verify(mSafetyController).removeCallback(any());
+ inOrderSafety.verify(mSafetyController).addCallback(any());
+
SettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
assertEquals(USER + 1, setting.getCurrentUser());
assertTrue(setting.isListening());
@@ -303,6 +313,10 @@
verify(mWalletController, times(2)).getWalletPosition();
+ InOrder inOrderSafety = inOrder(mSafetyController);
+ inOrderSafety.verify(mSafetyController).removeCallback(any());
+ inOrderSafety.verify(mSafetyController).addCallback(any());
+
SettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
assertEquals(USER + 1, setting.getCurrentUser());
assertFalse(setting.isListening());
@@ -457,6 +471,26 @@
mAutoTileManager.changeUser(UserHandle.of(USER + 1));
verify(mQsTileHost, times(2)).addTile(safetyComponent, true);
}
+
+ @Test
+ public void testSafetyTileRemoved_onSafetyCenterDisable() {
+ ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
+ mAutoTileManager.init();
+ when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(true);
+ mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(false);
+ verify(mQsTileHost, times(1)).removeTile(safetyComponent);
+ }
+
+ @Test
+ public void testSafetyTileAdded_onSafetyCenterEnable() {
+ ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
+ mAutoTileManager.init();
+ verify(mQsTileHost, times(1)).addTile(safetyComponent, true);
+ mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(false);
+ mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(true);
+ verify(mQsTileHost, times(2)).addTile(safetyComponent, true);
+ }
+
@Test
public void testEmptyArray_doesNotCrash() {
mContext.getOrCreateTestableResources().addOverride(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
new file mode 100644
index 0000000..89989ce
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2022 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.statusbar.policy
+
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.Handler
+import android.safetycenter.SafetyCenterManager
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.same
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SafetyControllerTest : SysuiTestCase() {
+
+ private val TEST_PC_PKG = "testPermissionControllerPackageName"
+ private val OTHER_PKG = "otherPackageName"
+
+ @Mock
+ private lateinit var context: Context
+ @Mock
+ private lateinit var scm: SafetyCenterManager
+ @Mock
+ private lateinit var pm: PackageManager
+ @Mock
+ private lateinit var handler: Handler
+ @Mock
+ private lateinit var listener: SafetyController.Listener
+
+ private val packageDataScheme = "package"
+
+ private lateinit var controller: SafetyController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ `when`(pm.permissionControllerPackageName).thenReturn(TEST_PC_PKG)
+ `when`(scm.isSafetyCenterEnabled).thenReturn(false)
+ `when`(handler.post(any(Runnable::class.java))).thenAnswer {
+ (it.arguments[0] as Runnable).run()
+ true
+ }
+
+ controller = SafetyController(context, pm, scm, handler)
+ }
+
+ @Test
+ fun addingFirstListenerRegistersReceiver() {
+ `when`(scm.isSafetyCenterEnabled).thenReturn(true)
+ controller.addCallback(listener)
+ verify(listener, times(1)).onSafetyCenterEnableChanged(true)
+ val filter = ArgumentCaptor.forClass(IntentFilter::class.java)
+ verify(context, times(1)).registerReceiver(
+ same(controller.mPermControllerChangeReceiver), filter.capture())
+ assertEquals(Intent.ACTION_PACKAGE_CHANGED, filter.value.getAction(0))
+ assertEquals(packageDataScheme, filter.value.getDataScheme(0))
+ }
+
+ @Test
+ fun removingLastListenerDeregistersReceiver() {
+ controller.addCallback(listener)
+ controller.removeCallback(listener)
+ verify(context, times(1)).unregisterReceiver(
+ eq(controller.mPermControllerChangeReceiver))
+ }
+
+ @Test
+ fun listenersCalledWhenBroadcastReceivedWithPCPackageAndStateChange() {
+ `when`(scm.isSafetyCenterEnabled).thenReturn(false)
+ controller.addCallback(listener)
+ reset(listener)
+ `when`(scm.isSafetyCenterEnabled).thenReturn(true)
+ val testIntent = Intent(Intent.ACTION_PACKAGE_CHANGED)
+ testIntent.data = Uri.parse("package:$TEST_PC_PKG")
+ controller.mPermControllerChangeReceiver.onReceive(context, testIntent)
+ verify(listener, times(1)).onSafetyCenterEnableChanged(true)
+ }
+
+ @Test
+ fun listenersNotCalledWhenBroadcastReceivedWithOtherPackage() {
+ `when`(scm.isSafetyCenterEnabled).thenReturn(true)
+ controller.addCallback(listener)
+ reset(listener)
+ val testIntent = Intent(Intent.ACTION_PACKAGE_CHANGED)
+ testIntent.data = Uri.parse("package:$OTHER_PKG")
+ controller.mPermControllerChangeReceiver.onReceive(context, testIntent)
+ verify(listener, never()).onSafetyCenterEnableChanged(true)
+ }
+
+ @Test
+ fun listenersNotCalledWhenBroadcastReceivedWithNoStateChange() {
+ `when`(scm.isSafetyCenterEnabled).thenReturn(false)
+ controller.addCallback(listener)
+ reset(listener)
+ val testIntent = Intent(Intent.ACTION_PACKAGE_CHANGED)
+ testIntent.data = Uri.parse("package:$TEST_PC_PKG")
+ controller.mPermControllerChangeReceiver.onReceive(context, testIntent)
+ verify(listener, never()).onSafetyCenterEnableChanged(true)
+ }
+}
\ No newline at end of file