diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java index 2244532cca946..d3018e310c8fe 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSourceMonitor.java @@ -16,17 +16,11 @@ package com.android.systemui.communal; -import android.database.ContentObserver; -import android.os.Handler; -import android.os.UserHandle; -import android.provider.Settings; import android.util.Log; -import androidx.annotation.MainThread; - import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.communal.conditions.CommunalConditionsMonitor; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.util.settings.SecureSettings; import com.google.android.collect.Lists; @@ -46,10 +40,15 @@ public class CommunalSourceMonitor { // A list of {@link Callback} that have registered to receive updates. private final ArrayList> mCallbacks = Lists.newArrayList(); - private final SecureSettings mSecureSettings; + private final CommunalConditionsMonitor mConditionsMonitor; private CommunalSource mCurrentSource; - private boolean mCommunalEnabled; + + // Whether all conditions for communal mode to show have been met. + private boolean mAllCommunalConditionsMet = false; + + // Whether the class is currently listening for condition changes. + private boolean mListeningForConditions = false; private CommunalSource.Callback mSourceCallback = new CommunalSource.Callback() { @Override @@ -59,24 +58,20 @@ public class CommunalSourceMonitor { } }; + private final CommunalConditionsMonitor.Callback mConditionsCallback = + allConditionsMet -> { + if (mAllCommunalConditionsMet != allConditionsMet) { + if (DEBUG) Log.d(TAG, "communal conditions changed: " + allConditionsMet); + + mAllCommunalConditionsMet = allConditionsMet; + executeOnSourceAvailableCallbacks(); + } + }; + @VisibleForTesting @Inject - public CommunalSourceMonitor( - @MainThread Handler mainThreadHandler, - SecureSettings secureSettings) { - mSecureSettings = secureSettings; - - ContentObserver settingsObserver = new ContentObserver(mainThreadHandler) { - @Override - public void onChange(boolean selfChange) { - reloadSettings(); - } - }; - mSecureSettings.registerContentObserverForUser( - Settings.Secure.COMMUNAL_MODE_ENABLED, - /* notifyForDescendants= */false, - settingsObserver, UserHandle.USER_SYSTEM); - reloadSettings(); + public CommunalSourceMonitor(CommunalConditionsMonitor communalConditionsMonitor) { + mConditionsMonitor = communalConditionsMonitor; } /** @@ -92,7 +87,7 @@ public class CommunalSourceMonitor { mCurrentSource = source; - if (mCommunalEnabled) { + if (mAllCommunalConditionsMet) { executeOnSourceAvailableCallbacks(); } @@ -111,7 +106,7 @@ public class CommunalSourceMonitor { itr.remove(); } else { cb.onSourceAvailable( - (mCommunalEnabled && mCurrentSource != null) ? new WeakReference<>( + (mAllCommunalConditionsMet && mCurrentSource != null) ? new WeakReference<>( mCurrentSource) : null); } } @@ -126,9 +121,14 @@ public class CommunalSourceMonitor { mCallbacks.add(new WeakReference<>(callback)); // Inform the callback of any already present CommunalSource. - if (mCommunalEnabled && mCurrentSource != null) { + if (mAllCommunalConditionsMet && mCurrentSource != null) { callback.onSourceAvailable(new WeakReference<>(mCurrentSource)); } + + if (!mListeningForConditions) { + mConditionsMonitor.addCallback(mConditionsCallback); + mListeningForConditions = true; + } } /** @@ -138,21 +138,10 @@ public class CommunalSourceMonitor { */ public void removeCallback(Callback callback) { mCallbacks.removeIf(el -> el.get() == callback); - } - private void reloadSettings() { - boolean newCommunalEnabled = mSecureSettings.getIntForUser( - Settings.Secure.COMMUNAL_MODE_ENABLED, - 1, - UserHandle.USER_SYSTEM) == 1; - - if (DEBUG) { - Log.d(TAG, "communal mode settings reloaded with value:" + newCommunalEnabled); - } - - if (mCommunalEnabled != newCommunalEnabled) { - mCommunalEnabled = newCommunalEnabled; - executeOnSourceAvailableCallbacks(); + if (mCallbacks.isEmpty() && mListeningForConditions) { + mConditionsMonitor.removeCallback(mConditionsCallback); + mListeningForConditions = false; } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalSettingCondition.java b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalSettingCondition.java new file mode 100644 index 0000000000000..1616b18172a9d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/conditions/CommunalSettingCondition.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2021 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.communal.conditions; + +import android.database.ContentObserver; +import android.os.Handler; +import android.os.UserHandle; +import android.provider.Settings; + +import androidx.annotation.MainThread; + +import com.android.systemui.util.settings.SecureSettings; + +import javax.inject.Inject; + +/** + * Monitors the communal setting, and informs any listeners with updates. + */ +public class CommunalSettingCondition extends CommunalCondition { + private final SecureSettings mSecureSettings; + private final ContentObserver mCommunalSettingContentObserver; + + @Inject + public CommunalSettingCondition(@MainThread Handler mainHandler, + SecureSettings secureSettings) { + mSecureSettings = secureSettings; + + mCommunalSettingContentObserver = new ContentObserver(mainHandler) { + @Override + public void onChange(boolean selfChange) { + final boolean communalSettingEnabled = mSecureSettings.getIntForUser( + Settings.Secure.COMMUNAL_MODE_ENABLED, 0, UserHandle.USER_SYSTEM) == 1; + updateCondition(communalSettingEnabled); + } + }; + } + + @Override + protected void start() { + mSecureSettings.registerContentObserverForUser(Settings.Secure.COMMUNAL_MODE_ENABLED, + false /*notifyForDescendants*/, mCommunalSettingContentObserver, + UserHandle.USER_SYSTEM); + + // Fetches setting immediately. + mCommunalSettingContentObserver.onChange(false); + } + + @Override + protected void stop() { + mSecureSettings.unregisterContentObserver(mCommunalSettingContentObserver); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java index f739ae57fc9f0..9b72655c9fb5b 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.java @@ -21,10 +21,12 @@ import android.view.View; import android.widget.FrameLayout; import com.android.systemui.communal.conditions.CommunalCondition; +import com.android.systemui.communal.conditions.CommunalSettingCondition; import com.android.systemui.idle.AmbientLightModeMonitor; import com.android.systemui.idle.LightSensorEventsDebounceAlgorithm; import com.android.systemui.idle.dagger.IdleViewComponent; +import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -69,7 +71,8 @@ public interface CommunalModule { @Provides @ElementsIntoSet @Named(COMMUNAL_CONDITIONS) - static Set provideCommunalConditions() { - return new HashSet<>(); + static Set provideCommunalConditions( + CommunalSettingCondition communalSettingCondition) { + return new HashSet<>(Collections.singletonList(communalSettingCondition)); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java new file mode 100644 index 0000000000000..cf147f06979fa --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSettingConditionTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2021 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.communal; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.os.Looper; +import android.os.UserHandle; +import android.provider.Settings; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.communal.conditions.CommunalCondition; +import com.android.systemui.communal.conditions.CommunalSettingCondition; +import com.android.systemui.util.settings.FakeSettings; +import com.android.systemui.utils.os.FakeHandler; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class CommunalSettingConditionTest extends SysuiTestCase { + private FakeSettings mSecureSettings; + private CommunalSettingCondition mCondition; + + @Before + public void setup() { + final FakeHandler handler = new FakeHandler(Looper.getMainLooper()); + mSecureSettings = new FakeSettings(); + mCondition = new CommunalSettingCondition(handler, mSecureSettings); + } + + @Test + public void addCallback_communalSettingEnabled_immediatelyReportsTrue() { + updateCommunalSetting(true); + + final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class); + mCondition.addCallback(callback); + verify(callback).onConditionChanged(mCondition, true); + } + + @Test + public void addCallback_communalSettingDisabled_noReport() { + updateCommunalSetting(false); + + final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class); + mCondition.addCallback(callback); + verify(callback, never()).onConditionChanged(eq(mCondition), anyBoolean()); + } + + @Test + public void updateCallback_communalSettingEnabled_reportsTrue() { + updateCommunalSetting(false); + + final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class); + mCondition.addCallback(callback); + clearInvocations(callback); + + updateCommunalSetting(true); + verify(callback).onConditionChanged(mCondition, true); + } + + @Test + public void updateCallback_communalSettingDisabled_reportsFalse() { + updateCommunalSetting(true); + + final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class); + mCondition.addCallback(callback); + clearInvocations(callback); + + updateCommunalSetting(false); + verify(callback).onConditionChanged(mCondition, false); + } + + @Test + public void updateCallback_communalSettingDidNotChange_neverReportDup() { + updateCommunalSetting(true); + + final CommunalCondition.Callback callback = mock(CommunalCondition.Callback.class); + mCondition.addCallback(callback); + clearInvocations(callback); + + updateCommunalSetting(true); + verify(callback, never()).onConditionChanged(mCondition, true); + } + + private void updateCommunalSetting(boolean value) { + mSecureSettings.putIntForUser(Settings.Secure.COMMUNAL_MODE_ENABLED, value ? 1 : 0, + UserHandle.USER_SYSTEM); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java index 68600de81085c..df9a2cb1d268a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalSourceMonitorTest.java @@ -20,25 +20,25 @@ package com.android.systemui.communal; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; -import android.os.Handler; -import android.os.UserHandle; -import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; -import com.android.systemui.util.settings.FakeSettings; +import com.android.systemui.communal.conditions.CommunalConditionsMonitor; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.lang.ref.WeakReference; @@ -47,15 +47,16 @@ import java.lang.ref.WeakReference; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper public class CommunalSourceMonitorTest extends SysuiTestCase { + @Mock private CommunalConditionsMonitor mCommunalConditionsMonitor; + + @Captor private ArgumentCaptor mConditionsCallbackCaptor; + private CommunalSourceMonitor mCommunalSourceMonitor; - private FakeSettings mSecureSettings; @Before public void setup() { MockitoAnnotations.initMocks(this); - mSecureSettings = new FakeSettings(); - mCommunalSourceMonitor = new CommunalSourceMonitor( - Handler.createAsync(TestableLooper.get(this).getLooper()), mSecureSettings); + mCommunalSourceMonitor = new CommunalSourceMonitor(mCommunalConditionsMonitor); } @Test @@ -65,6 +66,7 @@ public class CommunalSourceMonitorTest extends SysuiTestCase { mCommunalSourceMonitor.setSource(source); mCommunalSourceMonitor.addCallback(callback); + setConditionsMet(true); verifyOnSourceAvailableCalledWith(callback, source); } @@ -82,33 +84,32 @@ public class CommunalSourceMonitorTest extends SysuiTestCase { } @Test - public void testAddCallbackWithDefaultSetting() { + public void testAddCallbackWithConditionsMet() { final CommunalSourceMonitor.Callback callback = mock(CommunalSourceMonitor.Callback.class); final CommunalSource source = mock(CommunalSource.class); mCommunalSourceMonitor.addCallback(callback); + setConditionsMet(true); + clearInvocations(callback); mCommunalSourceMonitor.setSource(source); verifyOnSourceAvailableCalledWith(callback, source); } @Test - public void testAddCallbackWithSettingDisabled() { - setCommunalEnabled(false); - + public void testAddCallbackWithConditionsNotMet() { final CommunalSourceMonitor.Callback callback = mock(CommunalSourceMonitor.Callback.class); final CommunalSource source = mock(CommunalSource.class); mCommunalSourceMonitor.addCallback(callback); + setConditionsMet(false); mCommunalSourceMonitor.setSource(source); verify(callback, never()).onSourceAvailable(any()); } @Test - public void testSettingEnabledAfterCallbackAdded() { - setCommunalEnabled(false); - + public void testConditionsAreMetAfterCallbackAdded() { final CommunalSourceMonitor.Callback callback = mock(CommunalSourceMonitor.Callback.class); final CommunalSource source = mock(CommunalSource.class); @@ -118,33 +119,27 @@ public class CommunalSourceMonitorTest extends SysuiTestCase { // The callback should not have executed since communal is disabled. verify(callback, never()).onSourceAvailable(any()); - // The callback should execute when the user changes the setting to enabled. - setCommunalEnabled(true); + // The callback should execute when all conditions are met. + setConditionsMet(true); verifyOnSourceAvailableCalledWith(callback, source); } @Test - public void testSettingDisabledAfterCallbackAdded() { + public void testConditionsNoLongerMetAfterCallbackAdded() { final CommunalSourceMonitor.Callback callback = mock(CommunalSourceMonitor.Callback.class); final CommunalSource source = mock(CommunalSource.class); mCommunalSourceMonitor.addCallback(callback); mCommunalSourceMonitor.setSource(source); + setConditionsMet(true); verifyOnSourceAvailableCalledWith(callback, source); - // The callback should execute again when the setting is disabled, with a value of null. - setCommunalEnabled(false); + // The callback should execute again when conditions are no longer met, with a value of + // null. + setConditionsMet(false); verify(callback).onSourceAvailable(null); } - private void setCommunalEnabled(boolean enabled) { - mSecureSettings.putIntForUser( - Settings.Secure.COMMUNAL_MODE_ENABLED, - enabled ? 1 : 0, - UserHandle.USER_SYSTEM); - TestableLooper.get(this).processAllMessages(); - } - private void verifyOnSourceAvailableCalledWith(CommunalSourceMonitor.Callback callback, CommunalSource source) { final ArgumentCaptor> sourceCapture = @@ -152,4 +147,13 @@ public class CommunalSourceMonitorTest extends SysuiTestCase { verify(callback).onSourceAvailable(sourceCapture.capture()); assertThat(sourceCapture.getValue().get()).isEqualTo(source); } + + // Pushes an update on whether the communal conditions are met, assuming that a callback has + // been registered with the communal conditions monitor. + private void setConditionsMet(boolean value) { + verify(mCommunalConditionsMonitor).addCallback(mConditionsCallbackCaptor.capture()); + final CommunalConditionsMonitor.Callback conditionsCallback = + mConditionsCallbackCaptor.getValue(); + conditionsCallback.onConditionsChanged(value); + } }