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

Commit 1d9632df authored by Jason Monk's avatar Jason Monk
Browse files

New system for plugin + tuner integrations called extensions

An ExtensionController provides an easy way to say I need an
object of interface X. Then a plugin or a tuner factory can
actually provide X when needed or fallback to a default implementation.

Test: runtest systemui
Change-Id: I5e1b76def3c790d7f673867648ffeb13c4d0a829
parent ab9ab96d
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -47,6 +47,8 @@ import com.android.systemui.statusbar.policy.DarkIconDispatcher;
import com.android.systemui.statusbar.policy.DataSaverController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedControllerImpl;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.ExtensionControllerImpl;
import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.FlashlightControllerImpl;
import com.android.systemui.statusbar.policy.HotspotController;
@@ -233,6 +235,9 @@ public class Dependency extends SystemUI {
        mProviders.put(FragmentService.class, () ->
                new FragmentService(mContext));

        mProviders.put(ExtensionController.class, () ->
                new ExtensionControllerImpl());

        // Put all dependencies above here so the factory can override them if it wants.
        SystemUIFactory.getInstance().injectDependencies(mProviders, mContext);
    }
+12 −8
Original line number Diff line number Diff line
@@ -133,14 +133,7 @@ public class PluginManager extends BroadcastReceiver {

    public <T extends Plugin> void addPluginListener(PluginListener<T> listener, Class<?> cls,
            boolean allowMultiple) {
        ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class);
        if (info == null) {
            throw new RuntimeException(cls + " doesn't provide an interface");
        }
        if (TextUtils.isEmpty(info.action())) {
            throw new RuntimeException(cls + " doesn't provide an action");
        }
        addPluginListener(info.action(), listener, cls, allowMultiple);
        addPluginListener(getAction(cls), listener, cls, allowMultiple);
    }

    public <T extends Plugin> void addPluginListener(String action, PluginListener<T> listener,
@@ -285,6 +278,17 @@ public class PluginManager extends BroadcastReceiver {
        return new PluginContextWrapper(mContext.createApplicationContext(info, 0), classLoader);
    }

    public static <P> String getAction(Class<P> cls) {
        ProvidesInterface info = cls.getDeclaredAnnotation(ProvidesInterface.class);
        if (info == null) {
            throw new RuntimeException(cls + " doesn't provide an interface");
        }
        if (TextUtils.isEmpty(info.action())) {
            throw new RuntimeException(cls + " doesn't provide an action");
        }
        return info.action();
    }

    private class AllPluginClassLoader extends ClassLoader {
        public AllPluginClassLoader(ClassLoader classLoader) {
            super(classLoader);
+29 −63
Original line number Diff line number Diff line
@@ -19,9 +19,10 @@ package com.android.systemui.statusbar.phone;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;

import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON;
import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK;
import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_BUTTON;
import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_UNLOCK;
import static com.android.systemui.tuner.LockscreenFragment.getIntentButton;

import android.app.ActivityManager;
import android.app.ActivityOptions;
@@ -79,9 +80,12 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.ExtensionController.Extension;
import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.PreviewInflater;
import com.android.systemui.tuner.LockscreenFragment;
import com.android.systemui.tuner.LockscreenFragment.LockButtonFactory;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;

@@ -91,8 +95,7 @@ import com.android.systemui.tuner.TunerService.Tunable;
 */
public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener,
        UnlockMethodCache.OnUnlockMethodChangedListener,
        AccessibilityController.AccessibilityStateChangedCallback, View.OnLongClickListener,
        Tunable {
        AccessibilityController.AccessibilityStateChangedCallback, View.OnLongClickListener {

    final static String TAG = "StatusBar/KeyguardBottomAreaView";

@@ -159,12 +162,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
    private Drawable mLeftAssistIcon;

    private IntentButton mRightButton = new DefaultRightButton();
    private IntentButton mRightDefault = mRightButton;
    private IntentButton mRightPlugin;
    private Extension<IntentButton> mRightExtension;
    private String mRightButtonStr;
    private IntentButton mLeftButton = new DefaultLeftButton();
    private IntentButton mLeftDefault = mLeftButton;
    private IntentButton mLeftPlugin;
    private Extension<IntentButton> mLeftExtension;
    private String mLeftButtonStr;
    private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
    private boolean mDozing;
@@ -261,21 +262,28 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        mAccessibilityController.addStateChangedCallback(this);
        Dependency.get(PluginManager.class).addPluginListener(RIGHT_BUTTON_PLUGIN,
                mRightListener, IntentButtonProvider.class, false /* Only allow one */);
        Dependency.get(PluginManager.class).addPluginListener(LEFT_BUTTON_PLUGIN,
                mLeftListener, IntentButtonProvider.class, false /* Only allow one */);
        Dependency.get(TunerService.class).addTunable(this, LockscreenFragment.LOCKSCREEN_LEFT_BUTTON,
                LockscreenFragment.LOCKSCREEN_RIGHT_BUTTON);
        mRightExtension = Dependency.get(ExtensionController.class).newExtension(IntentButton.class)
                .withPlugin(IntentButtonProvider.class, RIGHT_BUTTON_PLUGIN,
                        p -> p.getIntentButton())
                .withTunerFactory(new LockButtonFactory(mContext, LOCKSCREEN_RIGHT_BUTTON))
                .withDefault(() -> new DefaultRightButton())
                .withCallback(button -> setRightButton(button))
                .build();
        mLeftExtension = Dependency.get(ExtensionController.class).newExtension(IntentButton.class)
                .withPlugin(IntentButtonProvider.class, LEFT_BUTTON_PLUGIN,
                        p -> p.getIntentButton())
                .withTunerFactory(new LockButtonFactory(mContext, LOCKSCREEN_LEFT_BUTTON))
                .withDefault(() -> new DefaultLeftButton())
                .withCallback(button -> setLeftButton(button))
                .build();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mAccessibilityController.removeStateChangedCallback(this);
        Dependency.get(PluginManager.class).removePluginListener(mRightListener);
        Dependency.get(PluginManager.class).removePluginListener(mLeftListener);
        Dependency.get(TunerService.class).removeTunable(this);
        mRightExtension.destroy();
        mLeftExtension.destroy();
    }

    private void initAccessibility() {
@@ -790,62 +798,20 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
        inflateCameraPreview();
    }

    @Override
    public void onTuningChanged(String key, String newValue) {
        if (LockscreenFragment.LOCKSCREEN_LEFT_BUTTON.equals(key)) {
            mLeftButtonStr = newValue;
            mLeftIsVoiceAssist = TextUtils.isEmpty(mLeftButtonStr) && mLeftPlugin == null;
            mLeftButton = getIntentButton(mContext, mLeftButtonStr, mLeftPlugin, mLeftDefault);
            updateLeftAffordance();
        } else {
            mRightButtonStr = newValue;
            mRightButton = getIntentButton(mContext, mRightButtonStr, mRightPlugin, mRightDefault);
            updateRightAffordanceIcon();
            updateCameraVisibility();
            inflateCameraPreview();
        }
    }

    private void setRightButton(IntentButton button) {
        mRightPlugin = button;
        mRightButton = getIntentButton(mContext, mRightButtonStr, mRightPlugin, mRightDefault);
        mRightButton = button;
        updateRightAffordanceIcon();
        updateCameraVisibility();
        inflateCameraPreview();
    }

    private void setLeftButton(IntentButton button) {
        mLeftPlugin = button;
        mLeftButton = getIntentButton(mContext, mLeftButtonStr, mLeftPlugin, mLeftDefault);
        mLeftButton = button;
        if (!(mLeftButton instanceof DefaultLeftButton)) {
            mLeftIsVoiceAssist = false;
        updateLeftAffordance();
    }

    private final PluginListener<IntentButtonProvider> mRightListener =
            new PluginListener<IntentButtonProvider>() {
        @Override
        public void onPluginConnected(IntentButtonProvider plugin, Context pluginContext) {
            setRightButton(plugin.getIntentButton());
        }

        @Override
        public void onPluginDisconnected(IntentButtonProvider plugin) {
            setRightButton(null);
        }
    };

    private final PluginListener<IntentButtonProvider> mLeftListener =
            new PluginListener<IntentButtonProvider>() {
        @Override
        public void onPluginConnected(IntentButtonProvider plugin, Context pluginContext) {
            setLeftButton(plugin.getIntentButton());
        }

        @Override
        public void onPluginDisconnected(IntentButtonProvider plugin) {
            setLeftButton(null);
        updateLeftAffordance();
    }
    };

    public void setDozing(boolean dozing, boolean animate) {
        mDozing = dozing;
+56 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.statusbar.policy;

import com.android.systemui.Dependency;
import com.android.systemui.plugins.Plugin;

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

/**
 * Utility class used to select between a plugin, tuner settings, and a default implementation
 * of an interface.
 */
public interface ExtensionController {

    <T> ExtensionBuilder<T> newExtension(Class<T> cls);

    interface Extension<T> {
        T get();
        void destroy();
    }

    interface ExtensionBuilder<T> {
        ExtensionBuilder<T> withTunerFactory(TunerFactory<T> factory);
        <P extends T> ExtensionBuilder<T> withPlugin(Class<P> cls);
        <P extends T> ExtensionBuilder<T> withPlugin(Class<P> cls, String action);
        <P> ExtensionBuilder<T> withPlugin(Class<P> cls, String action,
                PluginConverter<T, P> converter);
        ExtensionBuilder<T> withDefault(Supplier<T> def);
        ExtensionBuilder<T> withCallback(Consumer<T> callback);
        Extension build();
    }

    public interface PluginConverter<T, P> {
        T getInterfaceFromPlugin(P plugin);
    }

    public interface TunerFactory<T> {
        String[] keys();
        T create(Map<String, String> settings);
    }
}
+236 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.statusbar.policy;

import com.android.systemui.Dependency;
import com.android.systemui.plugins.Plugin;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;

import android.content.Context;
import android.util.ArrayMap;

import java.util.ArrayList;
import java.util.Collections;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class ExtensionControllerImpl implements ExtensionController {

    @Override
    public <T> ExtensionBuilder<T> newExtension(Class<T> cls) {
        return new ExtensionBuilder<>();
    }

    private interface Producer<T> {
        T get();
        void destroy();
    }

    private class ExtensionBuilder<T> implements ExtensionController.ExtensionBuilder<T> {

        private ExtensionImpl<T> mExtension = new ExtensionImpl<>();

        @Override
        public ExtensionController.ExtensionBuilder<T> withTunerFactory(TunerFactory<T> factory) {
            mExtension.addTunerFactory(factory, factory.keys());
            return this;
        }

        @Override
        public <P extends T> ExtensionController.ExtensionBuilder<T> withPlugin(Class<P> cls) {
            return withPlugin(cls, PluginManager.getAction(cls));
        }

        @Override
        public <P extends T> ExtensionController.ExtensionBuilder<T> withPlugin(Class<P> cls,
                String action) {
            return withPlugin(cls, action, null);
        }

        @Override
        public <P> ExtensionController.ExtensionBuilder<T> withPlugin(Class<P> cls,
                String action, PluginConverter<T, P> converter) {
            mExtension.addPlugin(action, cls, converter);
            return this;
        }

        @Override
        public ExtensionController.ExtensionBuilder<T> withDefault(Supplier<T> def) {
            mExtension.addDefault(def);
            return this;
        }

        @Override
        public ExtensionController.ExtensionBuilder<T> withCallback(
                Consumer<T> callback) {
            mExtension.mCallbacks.add(callback);
            return this;
        }

        @Override
        public ExtensionController.Extension build() {
            // Manually sort, plugins first, tuners second, defaults last.
            Collections.sort(mExtension.mProducers, (o1, o2) -> {
                if (o1 instanceof ExtensionImpl.PluginItem) {
                    if (o2 instanceof ExtensionImpl.PluginItem) {
                        return 0;
                    } else {
                        return -1;
                    }
                }
                if (o1 instanceof ExtensionImpl.TunerItem) {
                    if (o2 instanceof ExtensionImpl.PluginItem) {
                        return 1;
                    } else if (o2 instanceof ExtensionImpl.TunerItem) {
                        return 0;
                    } else {
                        return -1;
                    }
                }
                return 0;
            });
            mExtension.notifyChanged();
            return mExtension;
        }
    }

    private class ExtensionImpl<T> implements ExtensionController.Extension<T> {
        private final ArrayList<Producer<T>> mProducers = new ArrayList<>();
        private final ArrayList<Consumer<T>> mCallbacks = new ArrayList<>();
        private T mItem;

        @Override
        public T get() {
            return mItem;
        }

        @Override
        public void destroy() {
            for (int i = 0; i < mProducers.size(); i++) {
                mProducers.get(i).destroy();
            }
        }

        private void notifyChanged() {
            for (int i = 0; i < mProducers.size(); i++) {
                final T item = mProducers.get(i).get();
                if (item != null) {
                    mItem = item;
                    break;
                }
            }
            for (int i = 0; i < mCallbacks.size(); i++) {
                mCallbacks.get(i).accept(mItem);
            }
        }

        public void addDefault(Supplier<T> def) {
            mProducers.add(new Default(def));
        }

        public <P> void addPlugin(String action, Class<P> cls, PluginConverter<T, P> converter) {
            mProducers.add(new PluginItem(action, cls, converter));
        }

        public void addTunerFactory(TunerFactory<T> factory, String[] keys) {
            mProducers.add(new TunerItem(factory, factory.keys()));
        }

        private class PluginItem<P extends Plugin> implements Producer<T>, PluginListener<P> {
            private final PluginConverter<T, P> mConverter;
            private T mItem;

            public PluginItem(String action, Class<P> cls, PluginConverter<T, P> converter) {
                mConverter = converter;
                Dependency.get(PluginManager.class).addPluginListener(action, this, cls);
            }

            @Override
            public void onPluginConnected(P plugin, Context pluginContext) {
                if (mConverter != null) {
                    mItem = mConverter.getInterfaceFromPlugin(plugin);
                } else {
                    mItem = (T) plugin;
                }
                notifyChanged();
            }

            @Override
            public void onPluginDisconnected(P plugin) {
                mItem = null;
                notifyChanged();
            }

            @Override
            public T get() {
                return mItem;
            }

            @Override
            public void destroy() {
                Dependency.get(PluginManager.class).removePluginListener(this);
            }
        }

        private class TunerItem<T> implements Producer<T>, Tunable {
            private final TunerFactory<T> mFactory;
            private final ArrayMap<String, String> mSettings = new ArrayMap<>();
            private T mItem;

            public TunerItem(TunerFactory<T> factory, String... setting) {
                mFactory = factory;
                Dependency.get(TunerService.class).addTunable(this, setting);
            }

            @Override
            public T get() {
                return mItem;
            }

            @Override
            public void destroy() {
                Dependency.get(TunerService.class).removeTunable(this);
            }

            @Override
            public void onTuningChanged(String key, String newValue) {
                mSettings.put(key, newValue);
                mItem = mFactory.create(mSettings);
                notifyChanged();
            }
        }

        private class Default<T> implements Producer<T> {
            private final Supplier<T> mSupplier;

            public Default(Supplier<T> supplier) {
                mSupplier = supplier;
            }

            @Override
            public T get() {
                return mSupplier.get();
            }

            @Override
            public void destroy() {

            }
        }
    }
}
Loading