diff options
9 files changed, 298 insertions, 16 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/BootCompleteCache.kt b/packages/SystemUI/src/com/android/systemui/BootCompleteCache.kt new file mode 100644 index 000000000000..c6bdb30fb804 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/BootCompleteCache.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019 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 + +interface BootCompleteCache { + fun isBootComplete(): Boolean + fun addListener(listener: BootCompleteListener): Boolean + fun removeListener(listener: BootCompleteListener) + + interface BootCompleteListener { + fun onBootComplete() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt b/packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt new file mode 100644 index 000000000000..310393bb46eb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2019 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 + +import android.util.Log +import com.android.internal.annotations.GuardedBy +import java.io.FileDescriptor +import java.io.PrintWriter +import java.lang.ref.WeakReference +import java.util.concurrent.atomic.AtomicBoolean +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Caches whether the device has reached [SystemService.PHASE_BOOT_COMPLETED]. + * + * This class is constructed and set by [SystemUIApplication] and will notify all listeners when + * boot is completed. + */ +@Singleton +class BootCompleteCacheImpl @Inject constructor(private val dumpController: DumpController) : + BootCompleteCache, Dumpable { + + companion object { + private const val TAG = "BootCompleteCacheImpl" + private const val DEBUG = false + } + + init { + dumpController.registerDumpable(TAG, this) + } + + @GuardedBy("listeners") + private val listeners = mutableListOf<WeakReference<BootCompleteCache.BootCompleteListener>>() + private val bootComplete = AtomicBoolean(false) + + /** + * Provides the current boot state of the system as determined by [SystemUIApplication]. + * @return `true` if the system has reached [SystemService.PHASE_BOOT_COMPLETED] + */ + override fun isBootComplete(): Boolean = bootComplete.get() + + /** + * Indicates to this object that boot is complete. Subsequent calls to this function will have + * no effect. + */ + fun setBootComplete() { + if (bootComplete.compareAndSet(false, true)) { + if (DEBUG) Log.d(TAG, "Boot complete set") + synchronized(listeners) { + listeners.forEach { + it.get()?.onBootComplete() + } + listeners.clear() + } + } + } + + /** + * Add a listener for boot complete event. It will immediately return the current boot complete + * state. If this value is true, [BootCompleteCache.BootCompleteListener.onBootComplete] will + * never be called. + * + * @param listener a listener for boot complete state. + * @return `true` if boot has been completed. + */ + override fun addListener(listener: BootCompleteCache.BootCompleteListener): Boolean { + if (bootComplete.get()) return true + synchronized(listeners) { + if (bootComplete.get()) return true + listeners.add(WeakReference(listener)) + if (DEBUG) Log.d(TAG, "Adding listener: $listener") + return false + } + } + + /** + * Removes a listener for boot complete event. + * + * @param listener a listener to removed. + */ + override fun removeListener(listener: BootCompleteCache.BootCompleteListener) { + if (bootComplete.get()) return + synchronized(listeners) { + listeners.removeIf { it.get() == null || it.get() === listener } + if (DEBUG) Log.d(TAG, "Removing listener: $listener") + } + } + + override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) { + pw.println("BootCompleteCache state:") + pw.println(" boot complete: ${isBootComplete()}") + if (!isBootComplete()) { + pw.println(" listeners:") + synchronized(listeners) { + listeners.forEach { + pw.println(" $it") + } + } + } + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 1b264e5f4515..e08de3943258 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -32,6 +32,7 @@ import android.util.Log; import android.util.TimingsTraceLog; import com.android.systemui.dagger.ContextComponentHelper; +import com.android.systemui.dagger.SystemUIRootComponent; import com.android.systemui.util.NotificationChannels; import java.lang.reflect.Constructor; @@ -47,13 +48,13 @@ public class SystemUIApplication extends Application implements private static final boolean DEBUG = false; private ContextComponentHelper mComponentHelper; + private BootCompleteCacheImpl mBootCompleteCache; /** * Hold a reference on the stuff we start. */ private SystemUI[] mServices; private boolean mServicesStarted; - private boolean mBootCompleted; private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback; public SystemUIApplication() { @@ -71,8 +72,9 @@ public class SystemUIApplication extends Application implements Trace.TRACE_TAG_APP); log.traceBegin("DependencyInjection"); mContextAvailableCallback.onContextAvailable(this); - mComponentHelper = SystemUIFactory - .getInstance().getRootComponent().getContextComponentHelper(); + SystemUIRootComponent root = SystemUIFactory.getInstance().getRootComponent(); + mComponentHelper = root.getContextComponentHelper(); + mBootCompleteCache = root.provideBootCacheImpl(); log.traceEnd(); // Set the application theme that is inherited by all services. Note that setting the @@ -86,19 +88,17 @@ public class SystemUIApplication extends Application implements registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (mBootCompleted) return; + if (mBootCompleteCache.isBootComplete()) return; if (DEBUG) Log.v(TAG, "BOOT_COMPLETED received"); unregisterReceiver(this); - mBootCompleted = true; + mBootCompleteCache.setBootComplete(); if (mServicesStarted) { final int N = mServices.length; for (int i = 0; i < N; i++) { mServices[i].onBootCompleted(); } } - - } }, bootCompletedFilter); @@ -107,7 +107,7 @@ public class SystemUIApplication extends Application implements @Override public void onReceive(Context context, Intent intent) { if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) { - if (!mBootCompleted) return; + if (!mBootCompleteCache.isBootComplete()) return; // Update names of SystemUi notification channels NotificationChannels.createAll(context); } @@ -159,11 +159,11 @@ public class SystemUIApplication extends Application implements } mServices = new SystemUI[services.length]; - if (!mBootCompleted) { + if (!mBootCompleteCache.isBootComplete()) { // check to see if maybe it was already completed long before we began // see ActivityManagerService.finishBooting() if ("1".equals(SystemProperties.get("sys.boot_completed"))) { - mBootCompleted = true; + mBootCompleteCache.setBootComplete(); if (DEBUG) { Log.v(TAG, "BOOT_COMPLETED was already sent"); } @@ -205,7 +205,7 @@ public class SystemUIApplication extends Application implements if (ti > 1000) { Log.w(TAG, "Initialization of " + clsName + " took " + ti + " ms"); } - if (mBootCompleted) { + if (mBootCompleteCache.isBootComplete()) { mServices[i].onBootCompleted(); } } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 617dbdfe3fcc..75063e482f08 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -57,7 +57,7 @@ public class SystemUIFactory { private static final String TAG = "SystemUIFactory"; static SystemUIFactory mFactory; - protected SystemUIRootComponent mRootComponent; + private SystemUIRootComponent mRootComponent; public static <T extends SystemUIFactory> T getInstance() { return (T) mFactory; diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 0ac158d1b38a..9bd729edd210 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -21,6 +21,8 @@ import android.content.Context; import android.content.pm.PackageManager; import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.systemui.BootCompleteCache; +import com.android.systemui.BootCompleteCacheImpl; import com.android.systemui.DumpController; import com.android.systemui.assist.AssistModule; import com.android.systemui.model.SysUiState; @@ -52,6 +54,10 @@ import dagger.Provides; @Module(includes = {AssistModule.class, PeopleHubModule.class}) public abstract class SystemUIModule { + + @Binds + abstract BootCompleteCache bindBootCompleteCache(BootCompleteCacheImpl bootCompleteCache); + /** */ @Binds public abstract ContextComponentHelper bindComponentHelper( @@ -100,4 +106,5 @@ public abstract class SystemUIModule { @Singleton @Binds abstract NotifListBuilder bindNotifListBuilder(NotifListBuilderImpl impl); + } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java index 3f00f41b0717..e926574977d0 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIRootComponent.java @@ -20,6 +20,7 @@ import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME import android.content.ContentProvider; +import com.android.systemui.BootCompleteCacheImpl; import com.android.systemui.Dependency; import com.android.systemui.SystemUIAppComponentFactory; import com.android.systemui.SystemUIFactory; @@ -48,6 +49,12 @@ import dagger.Component; public interface SystemUIRootComponent { /** + * Provides a BootCompleteCache. + */ + @Singleton + BootCompleteCacheImpl provideBootCacheImpl(); + + /** * Creates a ContextComponentHelper. */ @Singleton diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java index 3d51be7c02f4..d36bd75dfdba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java @@ -35,6 +35,7 @@ import android.provider.Settings; import androidx.annotation.VisibleForTesting; +import com.android.systemui.BootCompleteCache; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.qualifiers.BgLooper; import com.android.systemui.util.Utils; @@ -59,6 +60,7 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio private AppOpsManager mAppOpsManager; private StatusBarManager mStatusBarManager; private BroadcastDispatcher mBroadcastDispatcher; + private BootCompleteCache mBootCompleteCache; private boolean mAreActiveLocationRequests; @@ -68,9 +70,10 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio @Inject public LocationControllerImpl(Context context, @BgLooper Looper bgLooper, - BroadcastDispatcher broadcastDispatcher) { + BroadcastDispatcher broadcastDispatcher, BootCompleteCache bootCompleteCache) { mContext = context; mBroadcastDispatcher = broadcastDispatcher; + mBootCompleteCache = bootCompleteCache; // Register to listen for changes in location settings. IntentFilter filter = new IntentFilter(); @@ -124,14 +127,15 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio } /** - * Returns true if location is enabled in settings. + * Returns true if location is enabled in settings. Will return false if + * {@link LocationManager} service has not been completely initialized */ public boolean isLocationEnabled() { // QuickSettings always runs as the owner, so specifically retrieve the settings // for the current foreground user. LocationManager locationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); - return locationManager.isLocationEnabledForUser( + return mBootCompleteCache.isBootComplete() && locationManager.isLocationEnabledForUser( UserHandle.of(ActivityManager.getCurrentUser())); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/BootCompleteCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/BootCompleteCacheTest.kt new file mode 100644 index 000000000000..6e79157eb582 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/BootCompleteCacheTest.kt @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2019 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 + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class BootCompleteCacheTest : SysuiTestCase() { + + private lateinit var bootCompleteCache: BootCompleteCacheImpl + @Mock + private lateinit var bootCompleteListener: BootCompleteCache.BootCompleteListener + @Mock + private lateinit var dumpController: DumpController + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + bootCompleteCache = BootCompleteCacheImpl(dumpController) + } + + @Test + fun testFlagChange() { + assertFalse(bootCompleteCache.isBootComplete()) + + bootCompleteCache.setBootComplete() + + assertTrue(bootCompleteCache.isBootComplete()) + } + + @Test + fun testDoubleSetIsNoOp() { + assertFalse(bootCompleteCache.isBootComplete()) + + bootCompleteCache.setBootComplete() + bootCompleteCache.setBootComplete() + + assertTrue(bootCompleteCache.isBootComplete()) + } + + @Test + fun testAddListenerGivesCurrentState_false() { + val boot = bootCompleteCache.addListener(bootCompleteListener) + assertFalse(boot) + } + + @Test + fun testAddListenerGivesCurrentState_true() { + bootCompleteCache.setBootComplete() + val boot = bootCompleteCache.addListener(bootCompleteListener) + assertTrue(boot) + } + + @Test + fun testListenerCalledOnBootComplete() { + bootCompleteCache.addListener(bootCompleteListener) + + bootCompleteCache.setBootComplete() + verify(bootCompleteListener).onBootComplete() + } + + @Test + fun testListenerCalledOnBootComplete_onlyOnce() { + bootCompleteCache.addListener(bootCompleteListener) + + bootCompleteCache.setBootComplete() + bootCompleteCache.setBootComplete() + + verify(bootCompleteListener).onBootComplete() + } + + @Test + fun testListenerNotCalledIfBootIsAlreadyComplete() { + bootCompleteCache.setBootComplete() + + bootCompleteCache.addListener(bootCompleteListener) + + bootCompleteCache.setBootComplete() + + verify(bootCompleteListener, never()).onBootComplete() + } + + @Test + fun testListenerRemovedNotCalled() { + bootCompleteCache.addListener(bootCompleteListener) + + bootCompleteCache.removeListener(bootCompleteListener) + + bootCompleteCache.setBootComplete() + + verify(bootCompleteListener, never()).onBootComplete() + } +}
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java index 5f772b2548ff..81c375a2480b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/LocationControllerImplTest.java @@ -26,6 +26,7 @@ import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; +import com.android.systemui.BootCompleteCache; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.statusbar.policy.LocationController.LocationChangeCallback; @@ -46,7 +47,8 @@ public class LocationControllerImplTest extends SysuiTestCase { public void setup() { mLocationController = spy(new LocationControllerImpl(mContext, TestableLooper.get(this).getLooper(), - mock(BroadcastDispatcher.class))); + mock(BroadcastDispatcher.class), + mock(BootCompleteCache.class))); } @Test |