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

Commit be4dac86 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Generate realistic preview images of custom clock faces."

parents b74366e1 71e50791
Loading
Loading
Loading
Loading
−7.5 KiB
Loading image diff...
−6.6 KiB
Loading image diff...
−20 KiB
Loading image diff...
+151 −51
Original line number Diff line number Diff line
@@ -16,41 +16,58 @@
package com.android.keyguard.clock;

import android.annotation.Nullable;
import android.app.WallpaperManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.MeasureSpec;

import androidx.annotation.VisibleForTesting;

import com.android.internal.colorextraction.ColorExtractor;
import com.android.keyguard.R;
import com.android.systemui.colorextraction.SysuiColorExtractor;
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 com.android.systemui.util.InjectionInflationController;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.Map;
import java.util.function.Supplier;

import javax.inject.Inject;
import javax.inject.Singleton;

/**
 * Manages custom clock faces.
 * Manages custom clock faces for AOD and lock screen.
 */
@Singleton
public final class ClockManager {

    private final ContentResolver mContentResolver;

    private final List<ClockInfo> mClockInfos = new ArrayList<>();
    /**
     * Map from expected value stored in settings to supplier of custom clock face.
     */
    private final Map<String, Supplier<ClockPlugin>> mClocks = new ArrayMap<>();
    @Nullable private ClockPlugin mCurrentClock;

    private final ContentResolver mContentResolver;
    private final SettingsWrapper mSettingsWrapper;
    /**
     * Observe settings changes to know when to switch the clock face.
     */
@@ -59,24 +76,9 @@ public final class ClockManager {
                @Override
                public void onChange(boolean selfChange) {
                    super.onChange(selfChange);
                    if (mClockExtension != null) {
                        mClockExtension.reload();
                    }
                    reload();
                }
            };
    private final ExtensionController mExtensionController;
    /**
     * Used to select between plugin or default implementations of ClockPlugin interface.
     */
    private Extension<ClockPlugin> mClockExtension;
    /**
     * 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.
     */
@@ -84,25 +86,38 @@ public final class ClockManager {
            new DockEventListener() {
                @Override
                public void onEvent(int event) {
                    final boolean isDocked = (event == DockManager.STATE_DOCKED
                    mIsDocked = (event == DockManager.STATE_DOCKED
                            || event == DockManager.STATE_DOCKED_HIDE);
                    mDefaultClockSupplier.setDocked(isDocked);
                    if (mClockExtension != null) {
                        mClockExtension.reload();
                    }
                    reload();
                }
            };
    @Nullable
    private final DockManager mDockManager;
    @Nullable private final DockManager mDockManager;
    /**
     * When docked, the DOCKED_CLOCK_FACE setting will be checked for the custom clock face
     * to show.
     */
    private boolean mIsDocked;

    private final List<ClockChangedListener> mListeners = new ArrayList<>();

    private final SysuiColorExtractor mColorExtractor;
    private final int mWidth;
    private final int mHeight;

    @Inject
    public ClockManager(Context context, ExtensionController extensionController,
            @Nullable DockManager dockManager) {
        mExtensionController = extensionController;
    public ClockManager(Context context, InjectionInflationController injectionInflater,
            @Nullable DockManager dockManager, SysuiColorExtractor colorExtractor) {
        this(context, injectionInflater, dockManager, colorExtractor, context.getContentResolver(),
                new SettingsWrapper(context.getContentResolver()));
    }

    ClockManager(Context context, InjectionInflationController injectionInflater,
            @Nullable DockManager dockManager, SysuiColorExtractor colorExtractor,
            ContentResolver contentResolver, SettingsWrapper settingsWrapper) {
        mDockManager = dockManager;
        mContentResolver = context.getContentResolver();
        mColorExtractor = colorExtractor;
        mContentResolver = contentResolver;
        mSettingsWrapper = settingsWrapper;

        Resources res = context.getResources();
        mClockInfos.add(ClockInfo.builder()
@@ -117,25 +132,35 @@ public final class ClockManager {
                .setTitle(res.getString(R.string.clock_title_bubble))
                .setId(BubbleClockController.class.getName())
                .setThumbnail(() -> BitmapFactory.decodeResource(res, R.drawable.bubble_thumbnail))
                .setPreview(() -> BitmapFactory.decodeResource(res, R.drawable.bubble_preview))
                .setPreview(() -> getClockPreview(BubbleClockController.class.getName()))
                .build());
        mClockInfos.add(ClockInfo.builder()
                .setName("stretch")
                .setTitle(res.getString(R.string.clock_title_stretch))
                .setId(StretchAnalogClockController.class.getName())
                .setThumbnail(() -> BitmapFactory.decodeResource(res, R.drawable.stretch_thumbnail))
                .setPreview(() -> BitmapFactory.decodeResource(res, R.drawable.stretch_preview))
                .setPreview(() -> getClockPreview(StretchAnalogClockController.class.getName()))
                .build());
        mClockInfos.add(ClockInfo.builder()
                .setName("type")
                .setTitle(res.getString(R.string.clock_title_type))
                .setId(TypeClockController.class.getName())
                .setThumbnail(() -> BitmapFactory.decodeResource(res, R.drawable.type_thumbnail))
                .setPreview(() -> BitmapFactory.decodeResource(res, R.drawable.type_preview))
                .setPreview(() -> getClockPreview(TypeClockController.class.getName()))
                .build());

        mDefaultClockSupplier = new DefaultClockSupplier(new SettingsWrapper(mContentResolver),
                LayoutInflater.from(context));
        LayoutInflater layoutInflater = injectionInflater.injectable(LayoutInflater.from(context));
        mClocks.put(BubbleClockController.class.getName(),
                () -> BubbleClockController.build(layoutInflater));
        mClocks.put(StretchAnalogClockController.class.getName(),
                () -> StretchAnalogClockController.build(layoutInflater));
        mClocks.put(TypeClockController.class.getName(),
                () -> TypeClockController.build(layoutInflater));

        // Store the size of the display for generation of clock preview.
        DisplayMetrics dm = res.getDisplayMetrics();
        mWidth = dm.widthPixels;
        mHeight = dm.heightPixels;
    }

    /**
@@ -146,9 +171,7 @@ public final class ClockManager {
            register();
        }
        mListeners.add(listener);
        if (mClockExtension != null) {
            mClockExtension.reload();
        }
        reload();
    }

    /**
@@ -168,7 +191,66 @@ public final class ClockManager {
        return mClockInfos;
    }

    private void setClockPlugin(ClockPlugin plugin) {
    /**
     * Get the current clock.
     * @returns current custom clock or null for default.
     */
    @Nullable
    ClockPlugin getCurrentClock() {
        return mCurrentClock;
    }

    @VisibleForTesting
    boolean isDocked() {
        return mIsDocked;
    }

    @VisibleForTesting
    ContentObserver getContentObserver() {
        return mContentObserver;
    }

    /**
     * Generate a realistic preview of a clock face.
     * @param clockId ID of clock to use for preview, should be obtained from {@link getClockInfos}.
     *        Returns null if clockId is not found.
     */
    @Nullable
    private Bitmap getClockPreview(String clockId) {
        Supplier<ClockPlugin> supplier = mClocks.get(clockId);
        if (supplier == null) {
            return null;
        }
        ClockPlugin plugin = supplier.get();

        // Use the big clock view for the preview
        View clockView = plugin.getBigClockView();
        if (clockView == null) {
            return null;
        }

        // Initialize state of plugin before generating preview.
        plugin.setDarkAmount(1f);
        plugin.setTextColor(Color.WHITE);

        ColorExtractor.GradientColors colors = mColorExtractor.getColors(WallpaperManager.FLAG_LOCK,
                true);
        plugin.setColorPalette(colors.supportsDarkText(), colors.getColorPalette());
        plugin.dozeTimeTick();

        // Draw clock view hierarchy to canvas.
        Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        clockView.measure(MeasureSpec.makeMeasureSpec(mWidth, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(mHeight, MeasureSpec.EXACTLY));
        clockView.layout(0, 0, mWidth, mHeight);
        canvas.drawColor(Color.BLACK);
        clockView.draw(canvas);

        return bitmap;
    }

    private void notifyClockChanged(ClockPlugin plugin) {
        for (int i = 0; i < mListeners.size(); i++) {
            // It probably doesn't make sense to supply the same plugin instances to multiple
            // listeners. This should be fine for now since there is only a single listener.
@@ -186,11 +268,6 @@ public final class ClockManager {
        if (mDockManager != null) {
            mDockManager.addListener(mDockEventListener);
        }
        mClockExtension = mExtensionController.newExtension(ClockPlugin.class)
            .withPlugin(ClockPlugin.class)
            .withCallback(mClockPluginConsumer)
            .withDefault(mDefaultClockSupplier)
            .build();
    }

    private void unregister() {
@@ -198,12 +275,35 @@ public final class ClockManager {
        if (mDockManager != null) {
            mDockManager.removeListener(mDockEventListener);
        }
        mClockExtension.destroy();
    }

    @VisibleForTesting
    boolean isDocked() {
        return mDefaultClockSupplier.isDocked();
    private void reload() {
        mCurrentClock = getClockPlugin();
        notifyClockChanged(mCurrentClock);
    }

    private ClockPlugin getClockPlugin() {
        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;
    }

    /**
+0 −101
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;
    }
}
Loading