From fdb3930b79c9b23b06ddb12484e14c07a111879b Mon Sep 17 00:00:00 2001 From: Fabian Kozynski Date: Fri, 15 Oct 2021 15:32:46 -0400 Subject: [PATCH] Rewrite DeviceProvisionedController The old version made IPC calls in the main thread. Instead, fix the following: 1. Calls to settings provider are made in background thread, except maybe on object creation to set initial state or if requesting the state of a user that hasn't been started (unlikely). 2. Cache state of device_provisioned and each user (that has been started) user_setup_complete 3. Do all operations in background (with proper locks) and notify in main thread. 4. Use UserTracker to avoid calls to `ActivityManager`. 5. The tracker is always registered so no changes are missed, and we don't need to retrieve cache every time we would register. The number of calls from the system are pretty low any way. 6. Listeners are not called on addCallback. None of the listeners expected this and at least one assumed that a call meant an actual change in state. Test: atest DeviceProvisionedControllerTest Test: manual, things are not hopelessly broken Fixes: 203138620 Change-Id: I9caccd7593732a07d688d5ca2f8de8cedb8b02ef --- .../dagger/SystemUIDefaultModule.java | 10 +- .../connectivity/NetworkControllerImpl.java | 3 +- .../statusbar/phone/PhoneStatusBarPolicy.java | 3 +- .../systemui/statusbar/phone/StatusBar.java | 7 +- .../policy/DeviceProvisionedController.java | 45 +++- .../DeviceProvisionedControllerImpl.java | 149 ----------- .../policy/DeviceProvisionedControllerImpl.kt | 230 +++++++++++++++++ .../android/systemui/tv/TvSystemUIModule.java | 10 +- .../NetworkControllerBaseTest.java | 2 +- .../NetworkControllerDataTest.java | 3 +- .../DeviceProvisionedControllerImplTest.kt | 241 ++++++++++++++++++ 11 files changed, 533 insertions(+), 170 deletions(-) delete mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java index 0923caaf0d761..50d2dd16b407f 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java @@ -187,9 +187,13 @@ public abstract class SystemUIDefaultModule { return new Recents(context, recentsImplementation, commandQueue); } - @Binds - abstract DeviceProvisionedController bindDeviceProvisionedController( - DeviceProvisionedControllerImpl deviceProvisionedController); + @SysUISingleton + @Provides + static DeviceProvisionedController bindDeviceProvisionedController( + DeviceProvisionedControllerImpl deviceProvisionedController) { + deviceProvisionedController.init(); + return deviceProvisionedController; + } @Binds abstract KeyguardViewController bindKeyguardViewController( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java index d9b0ee7e99212..f72178f0c8b05 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java @@ -332,8 +332,7 @@ public class NetworkControllerImpl extends BroadcastReceiver deviceProvisionedController.addCallback(new DeviceProvisionedListener() { @Override public void onUserSetupChanged() { - setUserSetupComplete(deviceProvisionedController.isUserSetup( - deviceProvisionedController.getCurrentUser())); + setUserSetupComplete(deviceProvisionedController.isCurrentUserSetup()); } }); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java index 7b110a0ec01ff..d19ed28bd823d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -604,8 +604,7 @@ public class PhoneStatusBarPolicy @Override public void onUserSetupChanged() { - boolean userSetup = mProvisionedController.isUserSetup( - mProvisionedController.getCurrentUser()); + boolean userSetup = mProvisionedController.isCurrentUserSetup(); if (mCurrentUserSetup == userSetup) return; mCurrentUserSetup = userSetup; updateAlarm(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 78ab0d7f8a2fe..3b99e8a70fcaa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -4299,10 +4299,9 @@ public class StatusBar extends SystemUI implements private final DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() { @Override public void onUserSetupChanged() { - final boolean userSetup = mDeviceProvisionedController.isUserSetup( - mDeviceProvisionedController.getCurrentUser()); - Log.d(TAG, "mUserSetupObserver - DeviceProvisionedListener called for user " - + mDeviceProvisionedController.getCurrentUser()); + final boolean userSetup = mDeviceProvisionedController.isCurrentUserSetup(); + Log.d(TAG, "mUserSetupObserver - DeviceProvisionedListener called for " + + "current user"); if (MULTIUSER_DEBUG) { Log.d(TAG, String.format("User setup changed: userSetup=%s mUserSetup=%s", userSetup, mUserSetup)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java index 7b4c35a8d25ab..3944c8c77f499 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedController.java @@ -14,23 +14,60 @@ package com.android.systemui.statusbar.policy; +import android.provider.Settings; + import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; +/** + * Controller to cache in process the state of the device provisioning. + *

+ * This controller keeps track of the values of device provisioning and user setup complete + */ public interface DeviceProvisionedController extends CallbackController { + /** + * @return whether the device is provisioned + * @see Settings.Global#DEVICE_PROVISIONED + */ boolean isDeviceProvisioned(); - boolean isUserSetup(int currentUser); + + /** + * @deprecated use {@link com.android.systemui.settings.UserTracker} + */ + @Deprecated int getCurrentUser(); - default boolean isCurrentUserSetup() { - return isUserSetup(getCurrentUser()); - } + /** + * @param user the user to query + * @return whether that user has completed the user setup + * @see Settings.Secure#USER_SETUP_COMPLETE + */ + boolean isUserSetup(int user); + /** + * @see DeviceProvisionedController#isUserSetup + */ + boolean isCurrentUserSetup(); + + /** + * Interface to provide calls when the values tracked change + */ interface DeviceProvisionedListener { + /** + * Call when the device changes from not provisioned to provisioned + */ default void onDeviceProvisionedChanged() { } + + /** + * Call on user switched + */ default void onUserSwitched() { onUserSetupChanged(); } + + /** + * Call when some user changes from not provisioned to provisioned + */ default void onUserSetupChanged() { } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java deleted file mode 100644 index 485b1b109eb4b..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2017 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.app.ActivityManager; -import android.database.ContentObserver; -import android.net.Uri; -import android.os.Handler; -import android.provider.Settings.Global; -import android.provider.Settings.Secure; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.settings.CurrentUserTracker; -import com.android.systemui.util.settings.GlobalSettings; -import com.android.systemui.util.settings.SecureSettings; - -import java.util.ArrayList; - -import javax.inject.Inject; - -/** - */ -@SysUISingleton -public class DeviceProvisionedControllerImpl extends CurrentUserTracker implements - DeviceProvisionedController { - - protected static final String TAG = DeviceProvisionedControllerImpl.class.getSimpleName(); - protected final ArrayList mListeners = new ArrayList<>(); - private final GlobalSettings mGlobalSettings; - private final SecureSettings mSecureSettings; - private final Uri mDeviceProvisionedUri; - private final Uri mUserSetupUri; - protected final ContentObserver mSettingsObserver; - - /** - */ - @Inject - public DeviceProvisionedControllerImpl(@Main Handler mainHandler, - BroadcastDispatcher broadcastDispatcher, GlobalSettings globalSettings, - SecureSettings secureSettings) { - super(broadcastDispatcher); - mGlobalSettings = globalSettings; - mSecureSettings = secureSettings; - mDeviceProvisionedUri = mGlobalSettings.getUriFor(Global.DEVICE_PROVISIONED); - mUserSetupUri = mSecureSettings.getUriFor(Secure.USER_SETUP_COMPLETE); - mSettingsObserver = new ContentObserver(mainHandler) { - @Override - public void onChange(boolean selfChange, Uri uri, int flags) { - Log.d(TAG, "Setting change: " + uri); - if (mUserSetupUri.equals(uri)) { - notifySetupChanged(); - } else { - notifyProvisionedChanged(); - } - } - }; - } - - @Override - public boolean isDeviceProvisioned() { - return mGlobalSettings.getInt(Global.DEVICE_PROVISIONED, 0) != 0; - } - - @Override - public boolean isUserSetup(int currentUser) { - return mSecureSettings.getIntForUser(Secure.USER_SETUP_COMPLETE, 0, currentUser) != 0; - } - - @Override - public int getCurrentUser() { - return ActivityManager.getCurrentUser(); - } - - @Override - public void addCallback(@NonNull DeviceProvisionedListener listener) { - mListeners.add(listener); - if (mListeners.size() == 1) { - startListening(getCurrentUser()); - } - listener.onUserSetupChanged(); - listener.onDeviceProvisionedChanged(); - } - - @Override - public void removeCallback(@NonNull DeviceProvisionedListener listener) { - mListeners.remove(listener); - if (mListeners.size() == 0) { - stopListening(); - } - } - - protected void startListening(int user) { - mGlobalSettings.registerContentObserverForUser(mDeviceProvisionedUri, true, - mSettingsObserver, 0); - mSecureSettings.registerContentObserverForUser(mUserSetupUri, true, - mSettingsObserver, user); - startTracking(); - } - - protected void stopListening() { - stopTracking(); - mGlobalSettings.unregisterContentObserver(mSettingsObserver); - } - - @Override - public void onUserSwitched(int newUserId) { - mGlobalSettings.unregisterContentObserver(mSettingsObserver); - mGlobalSettings.registerContentObserverForUser(mDeviceProvisionedUri, true, - mSettingsObserver, 0); - mSecureSettings.registerContentObserverForUser(mUserSetupUri, true, - mSettingsObserver, newUserId); - notifyUserChanged(); - } - - private void notifyUserChanged() { - for (int i = mListeners.size() - 1; i >= 0; --i) { - mListeners.get(i).onUserSwitched(); - } - } - - private void notifySetupChanged() { - for (int i = mListeners.size() - 1; i >= 0; --i) { - mListeners.get(i).onUserSetupChanged(); - } - } - - private void notifyProvisionedChanged() { - for (int i = mListeners.size() - 1; i >= 0; --i) { - mListeners.get(i).onDeviceProvisionedChanged(); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt new file mode 100644 index 0000000000000..acc12141796e0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt @@ -0,0 +1,230 @@ +/* + * 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.statusbar.policy + +import android.content.Context +import android.content.pm.UserInfo +import android.database.ContentObserver +import android.net.Uri +import android.os.Handler +import android.os.HandlerExecutor +import android.os.UserHandle +import android.provider.Settings +import android.util.ArraySet +import android.util.SparseBooleanArray +import androidx.annotation.GuardedBy +import androidx.annotation.WorkerThread +import com.android.systemui.Dumpable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.dump.DumpManager +import com.android.systemui.settings.UserTracker +import com.android.systemui.util.settings.GlobalSettings +import com.android.systemui.util.settings.SecureSettings +import java.io.FileDescriptor +import java.io.PrintWriter +import java.util.concurrent.Executor +import java.util.concurrent.atomic.AtomicBoolean +import javax.inject.Inject + +@SysUISingleton +open class DeviceProvisionedControllerImpl @Inject constructor( + private val secureSettings: SecureSettings, + private val globalSettings: GlobalSettings, + private val userTracker: UserTracker, + private val dumpManager: DumpManager, + @Background private val backgroundHandler: Handler, + @Main private val mainExecutor: Executor +) : DeviceProvisionedController, + DeviceProvisionedController.DeviceProvisionedListener, + Dumpable { + + companion object { + private const val ALL_USERS = -1 + private const val NO_USERS = -2 + protected const val TAG = "DeviceProvisionedControllerImpl" + } + + private val deviceProvisionedUri = globalSettings.getUriFor(Settings.Global.DEVICE_PROVISIONED) + private val userSetupUri = secureSettings.getUriFor(Settings.Secure.USER_SETUP_COMPLETE) + + private val deviceProvisioned = AtomicBoolean(false) + @GuardedBy("lock") + private val userSetupComplete = SparseBooleanArray() + @GuardedBy("lock") + private val listeners = ArraySet() + + private val lock = Any() + + private val backgroundExecutor = HandlerExecutor(backgroundHandler) + + private val initted = AtomicBoolean(false) + + private val _currentUser: Int + get() = userTracker.userId + + override fun getCurrentUser(): Int { + return _currentUser + } + + private val observer = object : ContentObserver(backgroundHandler) { + override fun onChange( + selfChange: Boolean, + uris: MutableCollection, + flags: Int, + userId: Int + ) { + val updateDeviceProvisioned = deviceProvisionedUri in uris + val updateUser = if (userSetupUri in uris) userId else NO_USERS + updateValues(updateDeviceProvisioned, updateUser) + if (updateDeviceProvisioned) { + onDeviceProvisionedChanged() + } + if (updateUser != NO_USERS) { + onUserSetupChanged() + } + } + } + + private val userChangedCallback = object : UserTracker.Callback { + @WorkerThread + override fun onUserChanged(newUser: Int, userContext: Context) { + updateValues(updateDeviceProvisioned = false, updateUser = newUser) + onUserSwitched() + } + + override fun onProfilesChanged(profiles: List) {} + } + + init { + userSetupComplete.put(currentUser, false) + } + + /** + * Call to initialize values and register observers + */ + open fun init() { + if (!initted.compareAndSet(false, true)) { + return + } + dumpManager.registerDumpable(this) + updateValues() + userTracker.addCallback(userChangedCallback, backgroundExecutor) + globalSettings.registerContentObserver(deviceProvisionedUri, observer) + secureSettings.registerContentObserverForUser(userSetupUri, observer, UserHandle.USER_ALL) + } + + @WorkerThread + private fun updateValues(updateDeviceProvisioned: Boolean = true, updateUser: Int = ALL_USERS) { + if (updateDeviceProvisioned) { + deviceProvisioned + .set(globalSettings.getInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0) + } + synchronized(lock) { + if (updateUser == ALL_USERS) { + val N = userSetupComplete.size() + for (i in 0 until N) { + val user = userSetupComplete.keyAt(i) + val value = secureSettings + .getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, user) != 0 + userSetupComplete.put(user, value) + } + } else if (updateUser != NO_USERS) { + val value = secureSettings + .getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, updateUser) != 0 + userSetupComplete.put(updateUser, value) + } + } + } + + /** + * Adds a listener. + * + * The listener will not be called when this happens. + */ + override fun addCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) { + synchronized(lock) { + listeners.add(listener) + } + } + + override fun removeCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) { + synchronized(lock) { + listeners.remove(listener) + } + } + + override fun isDeviceProvisioned(): Boolean { + return deviceProvisioned.get() + } + + override fun isUserSetup(user: Int): Boolean { + val index = synchronized(lock) { + userSetupComplete.indexOfKey(user) + } + return if (index < 0) { + val value = secureSettings + .getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, user) != 0 + synchronized(lock) { + userSetupComplete.put(user, value) + } + value + } else { + synchronized(lock) { + userSetupComplete.get(user, false) + } + } + } + + override fun isCurrentUserSetup(): Boolean { + return isUserSetup(currentUser) + } + + override fun onDeviceProvisionedChanged() { + dispatchChange( + DeviceProvisionedController.DeviceProvisionedListener::onDeviceProvisionedChanged + ) + } + + override fun onUserSetupChanged() { + dispatchChange(DeviceProvisionedController.DeviceProvisionedListener::onUserSetupChanged) + } + + override fun onUserSwitched() { + dispatchChange(DeviceProvisionedController.DeviceProvisionedListener::onUserSwitched) + } + + protected fun dispatchChange( + callback: DeviceProvisionedController.DeviceProvisionedListener.() -> Unit + ) { + val listenersCopy = synchronized(lock) { + ArrayList(listeners) + } + mainExecutor.execute { + listenersCopy.forEach(callback) + } + } + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array) { + pw.println("Device provisioned: ${deviceProvisioned.get()}") + synchronized(lock) { + pw.println("User setup complete: $userSetupComplete") + pw.println("Listeners: $listeners") + } + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java index 8912448232719..923aff1e6b3d4 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java @@ -180,9 +180,13 @@ public abstract class TvSystemUIModule { return new Recents(context, recentsImplementation, commandQueue); } - @Binds - abstract DeviceProvisionedController bindDeviceProvisionedController( - DeviceProvisionedControllerImpl deviceProvisionedController); + @SysUISingleton + @Provides + static DeviceProvisionedController providesDeviceProvisionedController( + DeviceProvisionedControllerImpl deviceProvisionedController) { + deviceProvisionedController.init(); + return deviceProvisionedController; + } @Binds abstract KeyguardViewController bindKeyguardViewController( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java index 67cab7413f1f6..b23d07a314b43 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java @@ -216,7 +216,7 @@ public class NetworkControllerBaseTest extends SysuiTestCase { mCallbackHandler = mock(CallbackHandler.class); mMockProvisionController = mock(DeviceProvisionedController.class); - when(mMockProvisionController.isUserSetup(anyInt())).thenReturn(true); + when(mMockProvisionController.isCurrentUserSetup()).thenReturn(true); doAnswer(invocation -> { mUserCallback = (DeviceProvisionedListener) invocation.getArguments()[0]; mUserCallback.onUserSetupChanged(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java index 00dedd9610747..12f8282c7aa0b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java @@ -21,7 +21,6 @@ import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -211,7 +210,7 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest { updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0); setConnectivityViaCallbackInNetworkController( NetworkCapabilities.TRANSPORT_CELLULAR, false, false, null); - when(mMockProvisionController.isUserSetup(anyInt())).thenReturn(false); + when(mMockProvisionController.isCurrentUserSetup()).thenReturn(false); mUserCallback.onUserSetupChanged(); TestableLooper.get(this).processAllMessages(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt new file mode 100644 index 0000000000000..5129f85841654 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt @@ -0,0 +1,241 @@ +/* + * 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.statusbar.policy + +import android.os.Handler +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.dump.DumpManager +import com.android.systemui.settings.UserTracker +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.settings.FakeSettings +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.ArgumentCaptor +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class DeviceProvisionedControllerImplTest : SysuiTestCase() { + + companion object { + private const val START_USER = 0 + } + + private lateinit var controller: DeviceProvisionedControllerImpl + + @Mock + private lateinit var userTracker: UserTracker + @Mock + private lateinit var dumpManager: DumpManager + @Mock + private lateinit var listener: DeviceProvisionedController.DeviceProvisionedListener + @Captor + private lateinit var userTrackerCallbackCaptor: ArgumentCaptor + + private lateinit var mainExecutor: FakeExecutor + private lateinit var testableLooper: TestableLooper + private lateinit var settings: FakeSettings + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testableLooper = TestableLooper.get(this) + mainExecutor = FakeExecutor(FakeSystemClock()) + settings = FakeSettings() + `when`(userTracker.userId).thenReturn(START_USER) + + controller = DeviceProvisionedControllerImpl( + settings, + settings, + userTracker, + dumpManager, + Handler(testableLooper.looper), + mainExecutor + ) + } + + @Test + fun testNotProvisionedByDefault() { + init() + assertThat(controller.isDeviceProvisioned).isFalse() + } + + @Test + fun testNotUserSetupByDefault() { + init() + assertThat(controller.isUserSetup(START_USER)).isFalse() + } + + @Test + fun testProvisionedWhenCreated() { + settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) + init() + + assertThat(controller.isDeviceProvisioned).isTrue() + } + + @Test + fun testUserSetupWhenCreated() { + settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) + init() + + assertThat(controller.isUserSetup(START_USER)) + } + + @Test + fun testDeviceProvisionedChange() { + init() + + settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) + testableLooper.processAllMessages() // background observer + + assertThat(controller.isDeviceProvisioned).isTrue() + } + + @Test + fun testUserSetupChange() { + init() + + settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) + testableLooper.processAllMessages() // background observer + + assertThat(controller.isUserSetup(START_USER)).isTrue() + } + + @Test + fun testUserSetupChange_otherUser() { + init() + val otherUser = 10 + + settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser) + testableLooper.processAllMessages() // background observer + + assertThat(controller.isUserSetup(START_USER)).isFalse() + assertThat(controller.isUserSetup(otherUser)).isTrue() + } + + @Test + fun testCurrentUserSetup() { + val otherUser = 10 + settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, otherUser) + init() + + assertThat(controller.isCurrentUserSetup).isFalse() + switchUser(otherUser) + testableLooper.processAllMessages() + + assertThat(controller.isCurrentUserSetup).isTrue() + } + + @Test + fun testListenerNotCalledOnAdd() { + init() + controller.addCallback(listener) + + mainExecutor.runAllReady() + + verify(listener, never()).onDeviceProvisionedChanged() + verify(listener, never()).onUserSetupChanged() + verify(listener, never()).onUserSwitched() + } + + @Test + fun testListenerCalledOnUserSwitched() { + init() + controller.addCallback(listener) + + switchUser(10) + + testableLooper.processAllMessages() + mainExecutor.runAllReady() + + verify(listener).onUserSwitched() + verify(listener, never()).onUserSetupChanged() + verify(listener, never()).onDeviceProvisionedChanged() + } + + @Test + fun testListenerCalledOnUserSetupChanged() { + init() + controller.addCallback(listener) + + settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) + testableLooper.processAllMessages() + mainExecutor.runAllReady() + + verify(listener, never()).onUserSwitched() + verify(listener).onUserSetupChanged() + verify(listener, never()).onDeviceProvisionedChanged() + } + + @Test + fun testListenerCalledOnDeviceProvisionedChanged() { + init() + controller.addCallback(listener) + + settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) + testableLooper.processAllMessages() + mainExecutor.runAllReady() + + verify(listener, never()).onUserSwitched() + verify(listener, never()).onUserSetupChanged() + verify(listener).onDeviceProvisionedChanged() + } + + @Test + fun testRemoveListener() { + init() + controller.addCallback(listener) + controller.removeCallback(listener) + + switchUser(10) + settings.putIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 1, START_USER) + settings.putInt(Settings.Global.DEVICE_PROVISIONED, 1) + + testableLooper.processAllMessages() + mainExecutor.runAllReady() + + verify(listener, never()).onDeviceProvisionedChanged() + verify(listener, never()).onUserSetupChanged() + verify(listener, never()).onUserSwitched() + } + + private fun init() { + controller.init() + verify(userTracker).addCallback(capture(userTrackerCallbackCaptor), any()) + } + + private fun switchUser(toUser: Int) { + `when`(userTracker.userId).thenReturn(toUser) + userTrackerCallbackCaptor.value.onUserChanged(toUser, mContext) + } +} \ No newline at end of file