diff options
10 files changed, 454 insertions, 278 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java index f4d4a9ff327d..cfac2b70c992 100644 --- a/packages/SystemUI/src/com/android/systemui/Dependency.java +++ b/packages/SystemUI/src/com/android/systemui/Dependency.java @@ -248,7 +248,7 @@ public class Dependency extends SystemUI { new FragmentService(mContext)); mProviders.put(ExtensionController.class, () -> - new ExtensionControllerImpl()); + new ExtensionControllerImpl(mContext)); mProviders.put(PluginDependencyProvider.class, () -> new PluginDependencyProvider(get(PluginManager.class))); diff --git a/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java new file mode 100644 index 000000000000..880951036661 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2016 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.fragments; + +import android.app.Fragment; +import android.util.Log; +import android.view.View; + +import com.android.systemui.plugins.FragmentBase; +import com.android.systemui.statusbar.policy.ExtensionController.Extension; + +import java.util.function.Consumer; + +/** + * Wires up an Extension to a Fragment tag/id so that it always contains the class + * selected by the extension. + */ +public class ExtensionFragmentListener<T extends FragmentBase> implements Consumer<T> { + + private static final String TAG = "ExtensionFragmentListener"; + + private final FragmentHostManager mFragmentHostManager; + private final String mTag; + private final Extension<T> mExtension; + private String mOldClass; + + private ExtensionFragmentListener(View view, String tag, int id, Extension<T> extension) { + mTag = tag; + mFragmentHostManager = FragmentHostManager.get(view); + mExtension = extension; + mFragmentHostManager.getFragmentManager().beginTransaction() + .replace(id, (Fragment) mExtension.get(), mTag) + .commit(); + } + + @Override + public void accept(T extension) { + try { + Fragment.class.cast(extension); + mFragmentHostManager.getExtensionManager().setCurrentExtension(mTag, + mOldClass, extension.getClass().getName(), mExtension.getContext()); + mOldClass = extension.getClass().getName(); + } catch (ClassCastException e) { + Log.e(TAG, extension.getClass().getName() + " must be a Fragment", e); + } + } + + public static <T> void attachExtensonToFragment(View view, String tag, int id, + Extension<T> extension) { + extension.addCallback(new ExtensionFragmentListener(view, tag, id, extension)); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java index 8ac97f3306be..4dbf6f98e3e6 100644 --- a/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java +++ b/packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java @@ -28,6 +28,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Parcelable; +import android.support.annotation.NonNull; import android.util.ArrayMap; import android.view.LayoutInflater; import android.view.View; @@ -51,7 +52,7 @@ public class FragmentHostManager { private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges( ActivityInfo.CONFIG_FONT_SCALE); private final FragmentService mManager; - private final PluginFragmentManager mPlugins = new PluginFragmentManager(); + private final ExtensionFragmentManager mPlugins = new ExtensionFragmentManager(); private FragmentController mFragments; private FragmentLifecycleCallbacks mLifecycleCallbacks; @@ -174,7 +175,7 @@ public class FragmentHostManager { return mFragments.getFragmentManager(); } - PluginFragmentManager getPluginManager() { + ExtensionFragmentManager getExtensionManager() { return mPlugins; } @@ -261,22 +262,16 @@ public class FragmentHostManager { } } - class PluginFragmentManager { - private final ArrayMap<String, Context> mPluginLookup = new ArrayMap<>(); + class ExtensionFragmentManager { + private final ArrayMap<String, Context> mExtensionLookup = new ArrayMap<>(); - public void removePlugin(String tag, String currentClass, String defaultClass) { + public void setCurrentExtension(@NonNull String tag, @Nullable String oldClass, + @NonNull String currentClass, @Nullable Context context) { Fragment fragment = getFragmentManager().findFragmentByTag(tag); - mPluginLookup.remove(currentClass); - getFragmentManager().beginTransaction() - .replace(((View) fragment.getView().getParent()).getId(), - instantiate(mContext, defaultClass, null), tag) - .commit(); - reloadFragments(); - } - - public void setCurrentPlugin(String tag, String currentClass, Context context) { - Fragment fragment = getFragmentManager().findFragmentByTag(tag); - mPluginLookup.put(currentClass, context); + if (oldClass != null) { + mExtensionLookup.remove(oldClass); + } + mExtensionLookup.put(currentClass, context); getFragmentManager().beginTransaction() .replace(((View) fragment.getView().getParent()).getId(), instantiate(context, currentClass, null), tag) @@ -292,11 +287,11 @@ public class FragmentHostManager { } Fragment instantiate(Context context, String className, Bundle arguments) { - Context pluginContext = mPluginLookup.get(className); - if (pluginContext != null) { - Fragment f = Fragment.instantiate(pluginContext, className, arguments); + Context extensionContext = mExtensionLookup.get(className); + if (extensionContext != null) { + Fragment f = Fragment.instantiate(extensionContext, className, arguments); if (f instanceof Plugin) { - ((Plugin) f).onCreate(mContext, pluginContext); + ((Plugin) f).onCreate(mContext, extensionContext); } return f; } diff --git a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java b/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java deleted file mode 100644 index 03bb73da3902..000000000000 --- a/packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2016 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.fragments; - -import android.app.Fragment; -import android.content.Context; -import android.util.Log; -import android.view.View; - -import com.android.systemui.Dependency; -import com.android.systemui.plugins.FragmentBase; -import com.android.systemui.plugins.Plugin; -import com.android.systemui.plugins.PluginListener; -import com.android.systemui.plugins.PluginManager; - -public class PluginFragmentListener implements PluginListener<Plugin> { - - private static final String TAG = "PluginFragmentListener"; - - private final FragmentHostManager mFragmentHostManager; - private final PluginManager mPluginManager; - private final Class<? extends Fragment> mDefaultClass; - private final Class<? extends FragmentBase> mExpectedInterface; - private final String mTag; - - public PluginFragmentListener(View view, String tag, Class<? extends Fragment> defaultFragment, - Class<? extends FragmentBase> expectedInterface) { - mTag = tag; - mFragmentHostManager = FragmentHostManager.get(view); - mPluginManager = Dependency.get(PluginManager.class); - mExpectedInterface = expectedInterface; - mDefaultClass = defaultFragment; - } - - public void startListening() { - mPluginManager.addPluginListener(this, mExpectedInterface, - false /* Only allow one */); - } - - public void stopListening() { - mPluginManager.removePluginListener(this); - } - - @Override - public void onPluginConnected(Plugin plugin, Context pluginContext) { - try { - mExpectedInterface.cast(plugin); - Fragment.class.cast(plugin); - mFragmentHostManager.getPluginManager().setCurrentPlugin(mTag, - plugin.getClass().getName(), pluginContext); - } catch (ClassCastException e) { - Log.e(TAG, plugin.getClass().getName() + " must be a Fragment and implement " - + mExpectedInterface.getName(), e); - } - } - - @Override - public void onPluginDisconnected(Plugin plugin) { - mFragmentHostManager.getPluginManager().removePlugin(mTag, - plugin.getClass().getName(), mDefaultClass.getName()); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index e146e93e11c7..b89b062c6d0d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -21,6 +21,7 @@ package com.android.systemui.statusbar.phone; import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.windowStateToString; +import static android.content.res.Configuration.UI_MODE_TYPE_CAR; import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback; import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT; @@ -36,11 +37,17 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.NonNull; import android.app.ActivityManager; +import android.app.ActivityManager.StackId; import android.app.ActivityOptions; +import android.app.INotificationManager; +import android.app.KeyguardManager; import android.app.Notification; +import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; +import android.app.RemoteInput; import android.app.StatusBarManager; +import android.app.TaskStackBuilder; import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.ComponentCallbacks2; @@ -49,8 +56,11 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; +import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.UserInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; @@ -75,7 +85,9 @@ import android.media.session.PlaybackState; import android.metrics.LogMaker; import android.net.Uri; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.PowerManager; @@ -88,63 +100,86 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.Vibrator; import android.provider.Settings; +import android.service.dreams.DreamService; +import android.service.dreams.IDreamManager; +import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.StatusBarNotification; +import android.service.vr.IVrManager; +import android.service.vr.IVrStateCallbacks; +import android.text.TextUtils; import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.view.ContextThemeWrapper; import android.view.Display; +import android.view.IWindowManager; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.ThreadedRenderer; import android.view.View; +import android.view.ViewAnimationUtils; import android.view.ViewGroup; import android.view.ViewParent; import android.view.ViewStub; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.WindowManagerGlobal; +import android.view.accessibility.AccessibilityManager; import android.view.animation.AccelerateInterpolator; import android.view.animation.Interpolator; import android.widget.DateTimeView; import android.widget.ImageView; +import android.widget.RemoteViews; import android.widget.TextView; +import android.widget.Toast; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.util.NotificationMessagingUtil; +import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardHostView.OnDismissAction; import com.android.keyguard.KeyguardStatusView; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.ActivityStarterDelegate; +import com.android.systemui.DejankUtils; import com.android.systemui.DemoMode; import com.android.systemui.Dependency; import com.android.systemui.EventLogTags; import com.android.systemui.Interpolators; import com.android.systemui.Prefs; import com.android.systemui.R; +import com.android.systemui.RecentsComponent; +import com.android.systemui.SwipeHelper; +import com.android.systemui.SystemUI; import com.android.systemui.SystemUIFactory; import com.android.systemui.assist.AssistManager; import com.android.systemui.classifier.FalsingLog; import com.android.systemui.classifier.FalsingManager; import com.android.systemui.doze.DozeHost; import com.android.systemui.doze.DozeLog; +import com.android.systemui.fragments.ExtensionFragmentListener; import com.android.systemui.fragments.FragmentHostManager; -import com.android.systemui.fragments.PluginFragmentListener; import com.android.systemui.keyguard.KeyguardViewMediator; -import com.android.systemui.plugins.qs.QS; import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.plugins.qs.QS; +import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; import com.android.systemui.qs.QSFragment; import com.android.systemui.qs.QSPanel; import com.android.systemui.qs.QSTileHost; +import com.android.systemui.recents.Recents; import com.android.systemui.recents.ScreenPinningRequest; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.AppTransitionFinishedEvent; @@ -183,6 +218,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController.Configurati import com.android.systemui.statusbar.policy.DarkIconDispatcher; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener; +import com.android.systemui.statusbar.policy.ExtensionController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardMonitor; import com.android.systemui.statusbar.policy.KeyguardMonitorImpl; @@ -190,11 +226,15 @@ import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.PreviewInflater; +import com.android.systemui.statusbar.policy.RemoteInputView; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; -import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener; +import com.android.systemui.statusbar.stack.NotificationStackScrollLayout + .OnChildLocationsChangedListener; +import com.android.systemui.statusbar.stack.StackStateAnimator; +import com.android.systemui.util.NotificationChannels; import com.android.systemui.util.leak.LeakDetector; import com.android.systemui.volume.VolumeComponent; @@ -205,50 +245,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import android.app.ActivityManager.StackId; -import android.app.INotificationManager; -import android.app.KeyguardManager; -import android.app.NotificationChannel; -import android.app.RemoteInput; -import android.app.TaskStackBuilder; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.UserInfo; -import android.os.Build; -import android.os.Handler; -import android.service.dreams.DreamService; -import android.service.dreams.IDreamManager; -import android.service.notification.NotificationListenerService; -import android.service.vr.IVrManager; -import android.service.vr.IVrStateCallbacks; -import android.text.TextUtils; -import android.util.Slog; -import android.util.SparseArray; -import android.util.SparseBooleanArray; -import android.view.IWindowManager; -import android.view.ViewAnimationUtils; -import android.view.accessibility.AccessibilityManager; -import android.widget.RemoteViews; -import android.widget.Toast; - -import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.internal.statusbar.IStatusBarService; -import com.android.internal.widget.LockPatternUtils; -import com.android.systemui.DejankUtils; -import com.android.systemui.RecentsComponent; -import com.android.systemui.SwipeHelper; -import com.android.systemui.SystemUI; -import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem; -import com.android.systemui.recents.Recents; -import com.android.systemui.statusbar.policy.RemoteInputView; -import com.android.systemui.statusbar.stack.StackStateAnimator; -import com.android.systemui.util.NotificationChannels; - import java.util.HashSet; +import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.Stack; @@ -1133,11 +1133,12 @@ public class StatusBar extends SystemUI implements DemoMode, View container = mStatusBarWindow.findViewById(R.id.qs_frame); if (container != null) { FragmentHostManager fragmentHostManager = FragmentHostManager.get(container); - fragmentHostManager.getFragmentManager().beginTransaction() - .replace(R.id.qs_frame, new QSFragment(), QS.TAG) - .commit(); - new PluginFragmentListener(container, QS.TAG, QSFragment.class, QS.class) - .startListening(); + ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame, + Dependency.get(ExtensionController.class).newExtension(QS.class) + .withPlugin(QS.class) + .withUiMode(UI_MODE_TYPE_CAR, () -> new QSFragment()) + .withDefault(() -> new QSFragment()) + .build()); final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this, mIconController); mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java index eaf89259ed59..61287491766b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java @@ -14,8 +14,7 @@ package com.android.systemui.statusbar.policy; -import com.android.systemui.Dependency; -import com.android.systemui.plugins.Plugin; +import android.content.Context; import java.util.Map; import java.util.function.Consumer; @@ -31,7 +30,9 @@ public interface ExtensionController { interface Extension<T> { T get(); + Context getContext(); void destroy(); + void addCallback(Consumer<T> callback); } interface ExtensionBuilder<T> { @@ -42,6 +43,7 @@ public interface ExtensionController { PluginConverter<T, P> converter); ExtensionBuilder<T> withDefault(Supplier<T> def); ExtensionBuilder<T> withCallback(Consumer<T> callback); + ExtensionBuilder<T> withUiMode(int mode, Supplier<T> def); Extension build(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java index fefbaa31af2a..91b61607d04d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java @@ -14,23 +14,38 @@ package com.android.systemui.statusbar.policy; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Handler; +import android.util.ArrayMap; + 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.statusbar.policy.ConfigurationController.ConfigurationListener; 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.Comparator; import java.util.function.Consumer; import java.util.function.Supplier; public class ExtensionControllerImpl implements ExtensionController { + public static final int SORT_ORDER_PLUGIN = 0; + public static final int SORT_ORDER_TUNER = 1; + public static final int SORT_ORDER_UI_MODE = 2; + public static final int SORT_ORDER_DEFAULT = 3; + + private final Context mDefaultContext; + + public ExtensionControllerImpl(Context context) { + mDefaultContext = context; + } + @Override public <T> ExtensionBuilder<T> newExtension(Class<T> cls) { return new ExtensionBuilder<>(); @@ -38,6 +53,7 @@ public class ExtensionControllerImpl implements ExtensionController { private interface Producer<T> { T get(); + void destroy(); } @@ -75,6 +91,12 @@ public class ExtensionControllerImpl implements ExtensionController { return this; } + public ExtensionController.ExtensionBuilder<T> withUiMode(int uiMode, + Supplier<T> supplier) { + mExtension.addUiMode(uiMode, supplier); + return this; + } + @Override public ExtensionController.ExtensionBuilder<T> withCallback( Consumer<T> callback) { @@ -85,34 +107,21 @@ public class ExtensionControllerImpl implements ExtensionController { @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; - }); + Collections.sort(mExtension.mProducers, Comparator.comparingInt(Item::sortOrder)); mExtension.notifyChanged(); return mExtension; } } private class ExtensionImpl<T> implements ExtensionController.Extension<T> { - private final ArrayList<Producer<T>> mProducers = new ArrayList<>(); + private final ArrayList<Item<T>> mProducers = new ArrayList<>(); private final ArrayList<Consumer<T>> mCallbacks = new ArrayList<>(); private T mItem; + private Context mPluginContext; + + public void addCallback(Consumer<T> callback) { + mCallbacks.add(callback); + } @Override public T get() { @@ -120,6 +129,11 @@ public class ExtensionControllerImpl implements ExtensionController { } @Override + public Context getContext() { + return mPluginContext != null ? mPluginContext : mDefaultContext; + } + + @Override public void destroy() { for (int i = 0; i < mProducers.size(); i++) { mProducers.get(i).destroy(); @@ -127,6 +141,7 @@ public class ExtensionControllerImpl implements ExtensionController { } private void notifyChanged() { + mItem = null; for (int i = 0; i < mProducers.size(); i++) { final T item = mProducers.get(i).get(); if (item != null) { @@ -151,7 +166,11 @@ public class ExtensionControllerImpl implements ExtensionController { mProducers.add(new TunerItem(factory, factory.keys())); } - private class PluginItem<P extends Plugin> implements Producer<T>, PluginListener<P> { + public void addUiMode(int uiMode, Supplier<T> mode) { + mProducers.add(new UiModeItem(uiMode, mode)); + } + + private class PluginItem<P extends Plugin> implements Item<T>, PluginListener<P> { private final PluginConverter<T, P> mConverter; private T mItem; @@ -162,6 +181,7 @@ public class ExtensionControllerImpl implements ExtensionController { @Override public void onPluginConnected(P plugin, Context pluginContext) { + mPluginContext = pluginContext; if (mConverter != null) { mItem = mConverter.getInterfaceFromPlugin(plugin); } else { @@ -172,6 +192,7 @@ public class ExtensionControllerImpl implements ExtensionController { @Override public void onPluginDisconnected(P plugin) { + mPluginContext = null; mItem = null; notifyChanged(); } @@ -185,9 +206,14 @@ public class ExtensionControllerImpl implements ExtensionController { public void destroy() { Dependency.get(PluginManager.class).removePluginListener(this); } + + @Override + public int sortOrder() { + return SORT_ORDER_PLUGIN; + } } - private class TunerItem<T> implements Producer<T>, Tunable { + private class TunerItem<T> implements Item<T>, Tunable { private final TunerFactory<T> mFactory; private final ArrayMap<String, String> mSettings = new ArrayMap<>(); private T mItem; @@ -213,9 +239,54 @@ public class ExtensionControllerImpl implements ExtensionController { mItem = mFactory.create(mSettings); notifyChanged(); } + + @Override + public int sortOrder() { + return SORT_ORDER_TUNER; + } + } + + private class UiModeItem<T> implements Item<T>, ConfigurationListener { + + private final int mDesiredUiMode; + private final Supplier<T> mSupplier; + private int mUiMode; + private Handler mHandler = new Handler(); + + public UiModeItem(int uiMode, Supplier<T> supplier) { + mDesiredUiMode = uiMode; + mSupplier = supplier; + mUiMode = mDefaultContext.getResources().getConfiguration().uiMode; + Dependency.get(ConfigurationController.class).addCallback(this); + } + + @Override + public void onConfigChanged(Configuration newConfig) { + int newMode = newConfig.uiMode & Configuration.UI_MODE_TYPE_MASK; + if (newMode != mUiMode) { + mUiMode = newMode; + // Post to make sure we don't have concurrent modifications. + mHandler.post(ExtensionImpl.this::notifyChanged); + } + } + + @Override + public T get() { + return (mUiMode == mDesiredUiMode) ? mSupplier.get() : null; + } + + @Override + public void destroy() { + Dependency.get(ConfigurationController.class).removeCallback(this); + } + + @Override + public int sortOrder() { + return SORT_ORDER_UI_MODE; + } } - private class Default<T> implements Producer<T> { + private class Default<T> implements Item<T> { private final Supplier<T> mSupplier; public Default(Supplier<T> supplier) { @@ -231,6 +302,15 @@ public class ExtensionControllerImpl implements ExtensionController { public void destroy() { } + + @Override + public int sortOrder() { + return SORT_ORDER_DEFAULT; + } } } + + private interface Item<T> extends Producer<T> { + int sortOrder(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java new file mode 100644 index 000000000000..564019c2bdc5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java @@ -0,0 +1,197 @@ +/* + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.res.Configuration; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.testing.TestableLooper.RunWithLooper; +import android.util.Log; + +import com.android.systemui.Dependency; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.plugins.OverlayPlugin; +import com.android.systemui.plugins.Plugin; +import com.android.systemui.plugins.PluginListener; +import com.android.systemui.plugins.PluginManager; +import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener; +import com.android.systemui.statusbar.policy.ExtensionController.Extension; +import com.android.systemui.statusbar.policy.ExtensionController.TunerFactory; +import com.android.systemui.tuner.TunerService; +import com.android.systemui.tuner.TunerService.Tunable; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +import java.util.Map; +import java.util.function.Consumer; + +@RunWith(AndroidTestingRunner.class) +public class ExtensionControllerImplTest extends SysuiTestCase { + + private PluginManager mPluginManager; + private TunerService mTunerService; + private ExtensionController mExtensionController; + private ConfigurationController mConfigurationController; + + @Before + public void setup() { + mPluginManager = mDependency.injectMockDependency(PluginManager.class); + mTunerService = mDependency.injectMockDependency(TunerService.class); + mConfigurationController = mDependency.injectMockDependency(ConfigurationController.class); + mExtensionController = Dependency.get(ExtensionController.class); + } + + @Test + public void testPlugin() { + OverlayPlugin plugin = mock(OverlayPlugin.class); + + Extension ext = mExtensionController.newExtension(OverlayPlugin.class) + .withPlugin(OverlayPlugin.class) + .build(); + ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class); + verify(mPluginManager).addPluginListener(eq(OverlayPlugin.ACTION), listener.capture(), + eq(OverlayPlugin.class)); + + listener.getValue().onPluginConnected(plugin, null); + assertEquals(plugin, ext.get()); + + ext.destroy(); + verify(mPluginManager).removePluginListener(any()); + } + + @Test + public void testTuner() { + String[] keys = new String[] { "key1", "key2" }; + TunerFactory<Object> factory = new ExtensionController.TunerFactory() { + @Override + public String[] keys() { + return keys; + } + + @Override + public Object create(Map settings) { + return null; + } + }; + Extension ext = mExtensionController.newExtension(Object.class) + .withTunerFactory(factory) + .build(); + verify(mTunerService).addTunable(any(), eq(keys[0]), eq(keys[1])); + + ext.destroy(); + verify(mTunerService).removeTunable(any()); + } + + @Test + @RunWithLooper + public void testUiMode() { + Object o = new Object(); + Extension ext = mExtensionController.newExtension(Object.class) + .withUiMode(Configuration.UI_MODE_TYPE_CAR, () -> o) + .build(); + ArgumentCaptor<ConfigurationListener> captor = ArgumentCaptor.forClass( + ConfigurationListener.class); + verify(mConfigurationController).addCallback(captor.capture()); + + Configuration c = new Configuration(mContext.getResources().getConfiguration()); + c.uiMode = 0; + captor.getValue().onConfigChanged(c); + TestableLooper.get(this).processAllMessages(); + assertNull(ext.get()); + + c.uiMode = Configuration.UI_MODE_TYPE_CAR; + captor.getValue().onConfigChanged(c); + TestableLooper.get(this).processAllMessages(); + assertEquals(o, ext.get()); + + ext.destroy(); + verify(mConfigurationController).removeCallback(eq(captor.getValue())); + } + + @Test + public void testDefault() { + Object o = new Object(); + Extension ext = mExtensionController.newExtension(Object.class) + .withDefault(() -> o) + .build(); + assertEquals(o, ext.get()); + } + + @Test + @RunWithLooper + public void testSortOrder() { + Log.d("TestTest", "Config " + mContext.getResources().getConfiguration().uiMode); + Object def = new Object(); + Object uiMode = new Object(); + Object tuner = new Object(); + Plugin plugin = mock(Plugin.class); + TunerFactory<Object> factory = mock(TunerFactory.class); + Extension ext = mExtensionController.newExtension(Object.class) + .withDefault(() -> def) + .withUiMode(Configuration.UI_MODE_TYPE_CAR, () -> uiMode) + .withTunerFactory(factory) + .withPlugin(Object.class, "some_action") + .build(); + + // Test default first. + assertEquals(def, ext.get()); + + // Enable a UI mode and check that. + ArgumentCaptor<ConfigurationListener> captor = ArgumentCaptor.forClass( + ConfigurationListener.class); + verify(mConfigurationController).addCallback(captor.capture()); + Configuration c = new Configuration(mContext.getResources().getConfiguration()); + c.uiMode |= Configuration.UI_MODE_TYPE_CAR; + captor.getValue().onConfigChanged(c); + TestableLooper.get(this).processAllMessages(); + assertEquals(uiMode, ext.get()); + + // Turn on tuner item and check that. + when(factory.create(any())).thenReturn(tuner); + ArgumentCaptor<Tunable> tunable = ArgumentCaptor.forClass(Tunable.class); + verify(mTunerService).addTunable(tunable.capture(), any()); + tunable.getValue().onTuningChanged(null, null); + assertEquals(tuner, ext.get()); + + // Lastly, check a plugin. + ArgumentCaptor<PluginListener> listener = ArgumentCaptor.forClass(PluginListener.class); + verify(mPluginManager).addPluginListener(any(), listener.capture(), any()); + listener.getValue().onPluginConnected(plugin, null); + assertEquals(plugin, ext.get()); + } + + @Test + public void testCallback() { + Consumer<Object> callback = mock(Consumer.class); + final Object o = new Object(); + mExtensionController.newExtension(Object.class) + .withDefault(() -> o) + .withCallback(callback) + .build(); + verify(callback).accept(eq(o)); + } + +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerTest.java deleted file mode 100644 index 3e79a0404026..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerTest.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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 static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import com.android.systemui.Dependency; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.plugins.OverlayPlugin; -import com.android.systemui.plugins.PluginManager; -import com.android.systemui.statusbar.policy.ExtensionController.Extension; -import com.android.systemui.statusbar.policy.ExtensionController.TunerFactory; -import com.android.systemui.tuner.TunerService; - -import org.junit.Before; -import org.junit.Test; - -import java.util.Map; -import java.util.function.Consumer; - -public class ExtensionControllerTest extends SysuiTestCase { - - private PluginManager mPluginManager; - private TunerService mTunerService; - private ExtensionController mExtensionController; - - @Before - public void setup() { - mPluginManager = mDependency.injectMockDependency(PluginManager.class); - mTunerService = mDependency.injectMockDependency(TunerService.class); - mExtensionController = Dependency.get(ExtensionController.class); - } - - @Test - public void testPlugin() { - Extension ext = mExtensionController.newExtension(OverlayPlugin.class) - .withPlugin(OverlayPlugin.class) - .build(); - verify(mPluginManager).addPluginListener(eq(OverlayPlugin.ACTION), any(), - eq(OverlayPlugin.class)); - - ext.destroy(); - verify(mPluginManager).removePluginListener(any()); - } - - @Test - public void testTuner() { - String[] keys = new String[] { "key1", "key2" }; - TunerFactory<Object> factory = new ExtensionController.TunerFactory() { - @Override - public String[] keys() { - return keys; - } - - @Override - public Object create(Map settings) { - return null; - } - }; - Extension ext = mExtensionController.newExtension(Object.class) - .withTunerFactory(factory) - .build(); - verify(mTunerService).addTunable(any(), eq(keys[0]), eq(keys[1])); - - ext.destroy(); - verify(mTunerService).removeTunable(any()); - } - - @Test - public void testDefault() { - Object o = new Object(); - Extension ext = mExtensionController.newExtension(Object.class) - .withDefault(() -> o) - .build(); - assertEquals(o, ext.get()); - } - - @Test - public void testCallback() { - Consumer<Object> callback = mock(Consumer.class); - final Object o = new Object(); - mExtensionController.newExtension(Object.class) - .withDefault(() -> o) - .withCallback(callback) - .build(); - verify(callback).accept(eq(o)); - } - -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java index b9d188a3871a..3a0f907b8697 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java @@ -14,6 +14,7 @@ package com.android.systemui.utils.leaks; +import android.content.Context; import android.testing.LeakCheck; import android.testing.LeakCheck.Tracker; @@ -75,6 +76,11 @@ public class FakeExtensionController implements ExtensionController { } @Override + public ExtensionBuilder<T> withUiMode(int mode, Supplier<T> def) { + return null; + } + + @Override public Extension build() { return new FakeExtension(mAllocation); } @@ -94,8 +100,18 @@ public class FakeExtensionController implements ExtensionController { } @Override + public Context getContext() { + return null; + } + + @Override public void destroy() { mTracker.getLeakInfo(mAllocation).clearAllocations(); } + + @Override + public void addCallback(Consumer<T> callback) { + + } } } |