summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/Dependency.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/fragments/ExtensionFragmentListener.java64
-rw-r--r--packages/SystemUI/src/com/android/systemui/fragments/FragmentHostManager.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/fragments/PluginFragmentListener.java74
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java101
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ExtensionControllerImpl.java132
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerImplTest.java197
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ExtensionControllerTest.java105
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeExtensionController.java16
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) {
+
+ }
}
}