Creates a BootCompleteCache

This cache is set by SystemUIApplication on boot and can be queried or
subscribed to.

The cache registration turns into no-op after boot, returning the boot
state instead.

LocationControllerImpl uses this cache to determine if it can query
LocationManager

Test: atest BootCompleteCacheTest
Fixes: 145019350
Change-Id: I2e34e53bfb8beca730cec8da6125988554c659e4
This commit is contained in:
Fabian Kozynski
2019-11-25 14:27:00 -05:00
parent e197dad5e9
commit 1cb4aaea51
9 changed files with 298 additions and 16 deletions

View File

@@ -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()
}
}

View File

@@ -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")
}
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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;
@@ -47,6 +48,12 @@ import dagger.Component;
SystemUIDefaultModule.class})
public interface SystemUIRootComponent {
/**
* Provides a BootCompleteCache.
*/
@Singleton
BootCompleteCacheImpl provideBootCacheImpl();
/**
* Creates a ContextComponentHelper.
*/

View File

@@ -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()));
}

View File

@@ -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()
}
}

View File

@@ -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