Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0ac3cdfa authored by Robert Snoeberger's avatar Robert Snoeberger
Browse files

Switch clock face while docked.

Bug: 120772327
Test: Added tests ClockManagerTest and DefaultClockSupplierTest
Change-Id: I404c6d52e1bd087d453d928faf373cf8dea9e2b2
parent fc6e5674
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -6103,7 +6103,7 @@ public final class Settings {
         * 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
+1 −0
Original line number Diff line number Diff line
@@ -620,6 +620,7 @@ public class SettingsBackupTest {
                 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
+47 −70
Original line number Diff line number Diff line
@@ -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.os.Looper;
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 @@ import javax.inject.Singleton;
@Singleton
public final class ClockManager {

    private final LayoutInflater mLayoutInflater;
    private final ContentResolver mContentResolver;

    private final List<ClockInfo> mClockInfos = new ArrayList<>();
@@ -62,7 +64,6 @@ public final class ClockManager {
                    }
                }
            };

    private final ExtensionController mExtensionController;
    /**
     * Used to select between plugin or default implementations of ClockPlugin interface.
@@ -72,13 +73,35 @@ public final class ClockManager {
     * 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 @@ public final class ClockManager {
                .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 @@ public final class ClockManager {
        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 @@ public final class ClockManager {
         */
        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;
        }
    }
}
+101 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+48 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
Loading