Revert "Revert "Switch clock face while docked.""
This reverts commit 708e739811328111e40c785e565becb66ed20abd.
Reason for revert: The issue motivating the revert was an earlier presubmit failure for my change.
Change-Id: Icdb7f4dddc5bbee8ce1f54bb8cee550ef0a4639e
Bug: 124252294
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index de84e71..d839f0b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6103,7 +6103,7 @@
* Indicates which clock face to show on lock screen and AOD while docked.
* @hide
*/
- private static final String DOCKED_CLOCK_FACE = "docked_clock_face";
+ public static final String DOCKED_CLOCK_FACE = "docked_clock_face";
/**
* Set by the system to track if the user needs to see the call to action for
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index ec57f79..d5865d2 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -620,6 +620,7 @@
Settings.Secure.DISABLED_PRINT_SERVICES,
Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
Settings.Secure.DISPLAY_DENSITY_FORCED,
+ Settings.Secure.DOCKED_CLOCK_FACE,
Settings.Secure.DOZE_PULSE_ON_LONG_PRESS,
Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION,
Settings.Secure.ENABLED_INPUT_METHODS, // Intentionally removed in P
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index 9598142..078108d 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -15,6 +15,7 @@
*/
package com.android.keyguard.clock;
+import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
@@ -25,16 +26,18 @@
import android.provider.Settings;
import android.view.LayoutInflater;
+import androidx.annotation.VisibleForTesting;
+
import com.android.keyguard.R;
+import com.android.systemui.dock.DockManager;
+import com.android.systemui.dock.DockManager.DockEventListener;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.ExtensionController.Extension;
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
import java.util.function.Consumer;
-import java.util.function.Supplier;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -45,7 +48,6 @@
@Singleton
public final class ClockManager {
- private final LayoutInflater mLayoutInflater;
private final ContentResolver mContentResolver;
private final List<ClockInfo> mClockInfos = new ArrayList<>();
@@ -62,7 +64,6 @@
}
}
};
-
private final ExtensionController mExtensionController;
/**
* Used to select between plugin or default implementations of ClockPlugin interface.
@@ -72,13 +73,35 @@
* Consumer that accepts the a new ClockPlugin implementation when the Extension reloads.
*/
private final Consumer<ClockPlugin> mClockPluginConsumer = this::setClockPlugin;
+ /**
+ * Supplier of default ClockPlugin implementation.
+ */
+ private final DefaultClockSupplier mDefaultClockSupplier;
+ /**
+ * Observe changes to dock state to know when to switch the clock face.
+ */
+ private final DockEventListener mDockEventListener =
+ new DockEventListener() {
+ @Override
+ public void onEvent(int event) {
+ final boolean isDocked = (event == DockManager.STATE_DOCKED
+ || event == DockManager.STATE_DOCKED_HIDE);
+ mDefaultClockSupplier.setDocked(isDocked);
+ if (mClockExtension != null) {
+ mClockExtension.reload();
+ }
+ }
+ };
+ @Nullable
+ private final DockManager mDockManager;
private final List<ClockChangedListener> mListeners = new ArrayList<>();
@Inject
- public ClockManager(Context context, ExtensionController extensionController) {
+ public ClockManager(Context context, ExtensionController extensionController,
+ @Nullable DockManager dockManager) {
mExtensionController = extensionController;
- mLayoutInflater = LayoutInflater.from(context);
+ mDockManager = dockManager;
mContentResolver = context.getContentResolver();
Resources res = context.getResources();
@@ -110,6 +133,9 @@
.setThumbnail(() -> BitmapFactory.decodeResource(res, R.drawable.type_thumbnail))
.setPreview(() -> BitmapFactory.decodeResource(res, R.drawable.type_preview))
.build());
+
+ mDefaultClockSupplier = new DefaultClockSupplier(new SettingsWrapper(mContentResolver),
+ LayoutInflater.from(context));
}
/**
@@ -154,41 +180,32 @@
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
false, mContentObserver);
+ mContentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.DOCKED_CLOCK_FACE),
+ false, mContentObserver);
+ if (mDockManager != null) {
+ mDockManager.addListener(mDockEventListener);
+ }
mClockExtension = mExtensionController.newExtension(ClockPlugin.class)
.withPlugin(ClockPlugin.class)
.withCallback(mClockPluginConsumer)
- // Using withDefault even though this isn't the default as a workaround.
- // ExtensionBuilder doesn't provide the ability to supply a ClockPlugin
- // instance based off of the value of a setting. Since multiple "default"
- // can be provided, using a supplier that changes the settings value.
- // A null return will cause Extension#reload to look at the next "default"
- // supplier.
- .withDefault(
- new SettingsGattedSupplier(
- mContentResolver,
- Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
- BubbleClockController.class.getName(),
- () -> BubbleClockController.build(mLayoutInflater)))
- .withDefault(
- new SettingsGattedSupplier(
- mContentResolver,
- Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
- StretchAnalogClockController.class.getName(),
- () -> StretchAnalogClockController.build(mLayoutInflater)))
- .withDefault(
- new SettingsGattedSupplier(
- mContentResolver,
- Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
- TypeClockController.class.getName(),
- () -> TypeClockController.build(mLayoutInflater)))
+ .withDefault(mDefaultClockSupplier)
.build();
}
private void unregister() {
mContentResolver.unregisterContentObserver(mContentObserver);
+ if (mDockManager != null) {
+ mDockManager.removeListener(mDockEventListener);
+ }
mClockExtension.destroy();
}
+ @VisibleForTesting
+ boolean isDocked() {
+ return mDefaultClockSupplier.isDocked();
+ }
+
/**
* Listener for events that should cause the custom clock face to change.
*/
@@ -200,44 +217,4 @@
*/
void onClockChanged(ClockPlugin clock);
}
-
- /**
- * Supplier that only gets an instance when a settings value matches expected value.
- */
- private static class SettingsGattedSupplier implements Supplier<ClockPlugin> {
-
- private final ContentResolver mContentResolver;
- private final String mKey;
- private final String mValue;
- private final Supplier<ClockPlugin> mSupplier;
-
- /**
- * Constructs a supplier that changes secure setting key against value.
- *
- * @param contentResolver Used to look up settings value.
- * @param key Settings key.
- * @param value If the setting matches this values that get supplies a ClockPlugin
- * instance.
- * @param supplier Supplier of ClockPlugin instance, only used if the setting
- * matches value.
- */
- SettingsGattedSupplier(ContentResolver contentResolver, String key, String value,
- Supplier<ClockPlugin> supplier) {
- mContentResolver = contentResolver;
- mKey = key;
- mValue = value;
- mSupplier = supplier;
- }
-
- /**
- * Returns null if the settings value doesn't match the expected value.
- *
- * A null return causes Extension#reload to skip this supplier and move to the next.
- */
- @Override
- public ClockPlugin get() {
- final String currentValue = Settings.Secure.getString(mContentResolver, mKey);
- return Objects.equals(currentValue, mValue) ? mSupplier.get() : null;
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockSupplier.java b/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockSupplier.java
new file mode 100644
index 0000000..7fdd235
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/DefaultClockSupplier.java
@@ -0,0 +1,101 @@
+/*
+ * 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.keyguard.clock;
+
+import android.util.ArrayMap;
+import android.view.LayoutInflater;
+
+import com.android.systemui.plugins.ClockPlugin;
+
+import java.util.Map;
+import java.util.function.Supplier;
+
+/**
+ * Supplier that only gets an instance when a settings value matches expected value.
+ */
+public class DefaultClockSupplier implements Supplier<ClockPlugin> {
+
+ private final SettingsWrapper mSettingsWrapper;
+ /**
+ * Map from expected value stored in settings to supplier of custom clock face.
+ */
+ private final Map<String, Supplier<ClockPlugin>> mClocks = new ArrayMap<>();
+ /**
+ * When docked, the DOCKED_CLOCK_FACE setting will be checked for the custom clock face
+ * to show.
+ */
+ private boolean mIsDocked;
+
+ /**
+ * Constructs a supplier that changes secure setting key against value.
+ *
+ * @param settingsWrapper Wrapper around settings used to look up the custom clock face.
+ * @param layoutInflater Provided to clocks as dependency to inflate clock views.
+ */
+ public DefaultClockSupplier(SettingsWrapper settingsWrapper, LayoutInflater layoutInflater) {
+ mSettingsWrapper = settingsWrapper;
+
+ mClocks.put(BubbleClockController.class.getName(),
+ () -> BubbleClockController.build(layoutInflater));
+ mClocks.put(StretchAnalogClockController.class.getName(),
+ () -> StretchAnalogClockController.build(layoutInflater));
+ mClocks.put(TypeClockController.class.getName(),
+ () -> TypeClockController.build(layoutInflater));
+ }
+
+ /**
+ * Sets the dock state.
+ *
+ * @param isDocked True when docked, false otherwise.
+ */
+ public void setDocked(boolean isDocked) {
+ mIsDocked = isDocked;
+ }
+
+ boolean isDocked() {
+ return mIsDocked;
+ }
+
+ /**
+ * Get the custom clock face based on values in settings.
+ *
+ * @return Custom clock face, null if the settings value doesn't match a custom clock.
+ */
+ @Override
+ public ClockPlugin get() {
+ ClockPlugin plugin = null;
+ if (mIsDocked) {
+ final String name = mSettingsWrapper.getDockedClockFace();
+ if (name != null) {
+ Supplier<ClockPlugin> supplier = mClocks.get(name);
+ if (supplier != null) {
+ plugin = supplier.get();
+ if (plugin != null) {
+ return plugin;
+ }
+ }
+ }
+ }
+ final String name = mSettingsWrapper.getLockScreenCustomClockFace();
+ if (name != null) {
+ Supplier<ClockPlugin> supplier = mClocks.get(name);
+ if (supplier != null) {
+ plugin = supplier.get();
+ }
+ }
+ return plugin;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java b/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java
new file mode 100644
index 0000000..58e1155
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java
@@ -0,0 +1,48 @@
+/*
+ * 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.keyguard.clock;
+
+import android.content.ContentResolver;
+import android.provider.Settings;
+
+/**
+ * Wrapper around Settings used for testing.
+ */
+public class SettingsWrapper {
+
+ private static final String CUSTOM_CLOCK_FACE = Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE;
+ private static final String DOCKED_CLOCK_FACE = Settings.Secure.DOCKED_CLOCK_FACE;
+
+ private ContentResolver mContentResolver;
+
+ public SettingsWrapper(ContentResolver contentResolver) {
+ mContentResolver = contentResolver;
+ }
+
+ /**
+ * Gets the value stored in settings for the custom clock face.
+ */
+ public String getLockScreenCustomClockFace() {
+ return Settings.Secure.getString(mContentResolver, CUSTOM_CLOCK_FACE);
+ }
+
+ /**
+ * Gets the value stored in settings for the clock face to use when docked.
+ */
+ public String getDockedClockFace() {
+ return Settings.Secure.getString(mContentResolver, DOCKED_CLOCK_FACE);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 755d6fc..d27a903 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -31,6 +31,7 @@
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.classifier.FalsingManager;
+import com.android.systemui.dock.DockManager;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -228,6 +229,16 @@
return SysUiServiceProvider.getComponent(context, StatusBar.class);
}
+ /**
+ * Provides DockManager.
+ */
+ @Singleton
+ @Provides
+ @Nullable
+ public DockManager providesDockManager(Context context) {
+ return SysUiServiceProvider.getComponent(context, DockManager.class);
+ }
+
@Module
protected static class ContextHolder {
private Context mContext;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
new file mode 100644
index 0000000..f813ac6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.keyguard.clock;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.LeakCheck;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dock.DockManager;
+import com.android.systemui.dock.DockManagerFake;
+import com.android.systemui.utils.leaks.FakeExtensionController;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public final class ClockManagerTest extends SysuiTestCase {
+
+ private ClockManager mClockManager;
+ private LeakCheck mLeakCheck;
+ private FakeExtensionController mFakeExtensionController;
+ private DockManagerFake mFakeDockManager;
+ @Mock ClockManager.ClockChangedListener mMockListener;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mLeakCheck = new LeakCheck();
+ mFakeExtensionController = new FakeExtensionController(mLeakCheck);
+ mFakeDockManager = new DockManagerFake();
+ mClockManager = new ClockManager(getContext(), mFakeExtensionController,
+ mFakeDockManager);
+ mClockManager.addOnClockChangedListener(mMockListener);
+ }
+
+ @After
+ public void tearDown() {
+ mClockManager.removeOnClockChangedListener(mMockListener);
+ }
+
+ @Test
+ public void dockEvent() {
+ mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
+ assertThat(mClockManager.isDocked()).isTrue();
+ }
+
+ @Test
+ public void undockEvent() {
+ mFakeDockManager.setDockEvent(DockManager.STATE_NONE);
+ assertThat(mClockManager.isDocked()).isFalse();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/DefaultClockSupplierTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/DefaultClockSupplierTest.java
new file mode 100644
index 0000000..1a3b198
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/DefaultClockSupplierTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.keyguard.clock;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.LayoutInflater;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.plugins.ClockPlugin;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+public final class DefaultClockSupplierTest extends SysuiTestCase {
+
+ private static final String BUBBLE_CLOCK = BubbleClockController.class.getName();
+ private static final Class<?> BUBBLE_CLOCK_CLASS = BubbleClockController.class;
+
+ private DefaultClockSupplier mDefaultClockSupplier;
+ @Mock SettingsWrapper mMockSettingsWrapper;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mDefaultClockSupplier = new DefaultClockSupplier(mMockSettingsWrapper,
+ LayoutInflater.from(getContext()));
+ }
+
+ @Test
+ public void get_default() {
+ // GIVEN that settings doesn't contain any values
+ when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn(null);
+ when(mMockSettingsWrapper.getDockedClockFace()).thenReturn(null);
+ // WHEN get is called
+ ClockPlugin plugin = mDefaultClockSupplier.get();
+ // THEN the result is null, indicated the default clock face should be used.
+ assertThat(plugin).isNull();
+ }
+
+ @Test
+ public void get_customClock() {
+ // GIVEN that settings is set to the bubble clock face
+ when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn(BUBBLE_CLOCK);
+ // WHEN get is called
+ ClockPlugin plugin = mDefaultClockSupplier.get();
+ // THEN the plugin is the bubble clock face.
+ assertThat(plugin).isInstanceOf(BUBBLE_CLOCK_CLASS);
+ }
+
+ @Test
+ public void get_badSettingsValue() {
+ // GIVEN that settings contains a value that doesn't correspond to a
+ // custom clock face.
+ when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn("bad value");
+ // WHEN get is called
+ ClockPlugin plugin = mDefaultClockSupplier.get();
+ // THEN the result is null.
+ assertThat(plugin).isNull();
+ }
+
+ @Test
+ public void get_dockedDefault() {
+ // GIVEN docked
+ mDefaultClockSupplier.setDocked(true);
+ // WHEN get is called
+ ClockPlugin plugin = mDefaultClockSupplier.get();
+ // THEN the result is null, indicating the default clock face.
+ assertThat(plugin).isNull();
+ }
+
+ @Test
+ public void get_dockedCustomClock() {
+ // GIVEN docked and settings is set to the bubble clock face
+ mDefaultClockSupplier.setDocked(true);
+ when(mMockSettingsWrapper.getDockedClockFace()).thenReturn(BUBBLE_CLOCK);
+ // WHEN get is called
+ ClockPlugin plugin = mDefaultClockSupplier.get();
+ // THEN the plugin is the bubble clock face.
+ assertThat(plugin).isInstanceOf(BUBBLE_CLOCK_CLASS);
+ }
+
+ @Test
+ public void get_badDockedSettingsValue() {
+ // GIVEN docked and settings contains a value that doesn't correspond to
+ // an available clock face.
+ mDefaultClockSupplier.setDocked(true);
+ when(mMockSettingsWrapper.getDockedClockFace()).thenReturn("bad value");
+ // WHEN get is called
+ ClockPlugin plugin = mDefaultClockSupplier.get();
+ // THEN the result is null.
+ assertThat(plugin).isNull();
+ }
+
+ @Test
+ public void get_badDockedSettingsFallback() {
+ // GIVEN docked and settings contains a value that doesn't correspond to
+ // an available clock face, but locked screen settings is set to bubble
+ // clock.
+ mDefaultClockSupplier.setDocked(true);
+ when(mMockSettingsWrapper.getDockedClockFace()).thenReturn("bad value");
+ when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn(BUBBLE_CLOCK);
+ // WHEN get is called
+ ClockPlugin plugin = mDefaultClockSupplier.get();
+ // THEN the plugin is the bubble clock face.
+ assertThat(plugin).isInstanceOf(BUBBLE_CLOCK_CLASS);
+ }
+}