diff options
author | 2018-12-20 10:01:48 -0500 | |
---|---|---|
committer | 2018-12-20 10:22:47 -0500 | |
commit | ea54e8a756b42077a63c2b09effbdfeebf5efb7d (patch) | |
tree | 16d717fe52d481d438b7bd101135b3eca500a300 | |
parent | b34e8528ca7e6aee84ba5eef9739155f658690c5 (diff) |
Sysui: Add support for view injection
Test: Existing tests pass
Change-Id: Ic6931ebec38ca9514e9368239dd9502ae2dee33c
8 files changed, 258 insertions, 18 deletions
diff --git a/packages/SystemUI/docs/dagger.md b/packages/SystemUI/docs/dagger.md index 8bfa1c28d985..f81e8cce1f91 100644 --- a/packages/SystemUI/docs/dagger.md +++ b/packages/SystemUI/docs/dagger.md @@ -147,6 +147,52 @@ then the FragmentHostManager can do this for you. FragmentHostManager.get(view).create(NavigationBarFragment.class); ``` +### Using injection with Views + +Generally, you shouldn't need to inject for a view, as the view should +be relatively self contained and logic that requires injection should be +moved to a higher level construct such as a Fragment or a top-level SystemUI +component, see above for how to do injection for both of which. + +Still here? Yeah, ok, sysui has a lot of pre-existing views that contain a +lot of code that could benefit from injection and will need to be migrated +off from Dependency#get uses. Similar to how fragments are injected, the view +needs to be added to the interface +com.android.systemui.util.InjectionInflationController$ViewInstanceCreator. + +```java +public interface ViewInstanceCreator { ++ QuickStatusBarHeader createQsHeader(); +} +``` + +Presumably you need to inflate that view from XML (otherwise why do you +need anything special? see earlier sections about generic injection). To obtain +an inflater that supports injected objects, call InjectionInflationController#injectable, +which will wrap the inflater it is passed in one that can create injected +objects when needed. + +```java +@Override +public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, + Bundle savedInstanceState) { + return mInjectionInflater.injectable(inflater).inflate(R.layout.my_layout, container, false); +} +``` + +There is one other important thing to note about injecting with views. SysUI +already has a Context in its global dagger component, so if you simply inject +a Context, you will not get the one that the view should have with proper +theming. Because of this, always ensure to tag views that have @Inject with +the @Named view context. + +```java +public CustomView(@Named(VIEW_CONTEXT) Context themedViewContext, AttributeSet attrs, + OtherCustomDependency something) { + ... +} +``` + ## TODO List - Eliminate usages of Depndency#get diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags index d57fe8a36d61..22b0ab7dde4e 100644 --- a/packages/SystemUI/proguard.flags +++ b/packages/SystemUI/proguard.flags @@ -31,4 +31,7 @@ -keep class com.android.systemui.fragments.FragmentService$FragmentCreator { *; } +-keep class com.android.systemui.util.InjectionInflationController$ViewInstanceCreator { + *; +} -keep class androidx.core.app.CoreComponentFactory diff --git a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java index 76336bb8127f..b11e189e3a9c 100644 --- a/packages/SystemUI/src/com/android/systemui/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/DependencyProvider.java @@ -33,7 +33,6 @@ import android.os.Process; import android.os.ServiceManager; import android.os.UserHandle; import android.util.DisplayMetrics; -import android.util.Log; import android.view.IWindowManager; import android.view.WindowManagerGlobal; @@ -470,7 +469,6 @@ public class DependencyProvider { @Singleton @Provides public UiOffloadThread provideUiOffloadThread() { - Log.d("TestTest", "provideUiOffloadThread"); return new UiOffloadThread(); } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 1a2473e6b858..7689dfc8d6b7 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -42,7 +42,6 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl; import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.NotificationRowBinder; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl; @@ -55,6 +54,7 @@ import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.DeviceProvisionedController; +import com.android.systemui.util.InjectionInflationController; import com.android.systemui.volume.VolumeDialogComponent; import java.util.function.Consumer; @@ -223,5 +223,10 @@ public class SystemUIFactory { */ @Singleton FragmentService.FragmentCreator createFragmentCreator(); + + /** + * ViewCreator generates all Views that need injection. + */ + InjectionInflationController.ViewCreator createViewCreator(); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index fa775c0429b6..93130d4667d0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -45,6 +45,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; +import com.android.systemui.util.InjectionInflationController; import javax.inject.Inject; @@ -76,16 +77,20 @@ public class QSFragment extends Fragment implements QS, CommandQueue.Callbacks { private boolean mQsDisabled; private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; + private final InjectionInflationController mInjectionInflater; @Inject - public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler) { + public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler, + InjectionInflationController injectionInflater) { mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; + mInjectionInflater = injectionInflater; } @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { - inflater = inflater.cloneInContext(new ContextThemeWrapper(getContext(), R.style.qs_theme)); + inflater = mInjectionInflater.injectable( + inflater.cloneInContext(new ContextThemeWrapper(getContext(), R.style.qs_theme))); return inflater.inflate(R.layout.qs_panel, container, false); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index 3cecff033c91..f2f83c0e0aa5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -17,6 +17,8 @@ package com.android.systemui.qs; import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; import static android.provider.Settings.System.SHOW_BATTERY_PERCENT; +import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.annotation.ColorInt; @@ -58,7 +60,6 @@ import androidx.annotation.VisibleForTesting; import com.android.settingslib.Utils; import com.android.systemui.BatteryMeterView; -import com.android.systemui.Dependency; import com.android.systemui.Prefs; import com.android.systemui.R; import com.android.systemui.plugins.ActivityStarter; @@ -84,6 +85,9 @@ import java.util.List; import java.util.Locale; import java.util.Objects; +import javax.inject.Inject; +import javax.inject.Named; + /** * View that contains the top-most bits of the screen (primarily the status bar with date, time, and * battery) and also contains the {@link QuickQSPanel} along with some of the panel's inner @@ -102,6 +106,11 @@ public class QuickStatusBarHeader extends RelativeLayout implements public static final int MAX_TOOLTIP_SHOWN_COUNT = 2; private final Handler mHandler = new Handler(); + private final BatteryController mBatteryController; + private final NextAlarmController mAlarmController; + private final ZenModeController mZenController; + private final StatusBarIconController mStatusBarIconController; + private final ActivityStarter mActivityStarter; private QSPanel mQsPanel; @@ -141,8 +150,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements private TextView mBatteryRemainingText; private boolean mShowBatteryPercentAndEstimate; - private NextAlarmController mAlarmController; - private ZenModeController mZenController; private PrivacyItemController mPrivacyItemController; /** Counts how many times the long press tooltip has been shown to the user. */ private int mShownCount; @@ -172,10 +179,17 @@ public class QuickStatusBarHeader extends RelativeLayout implements } }; - public QuickStatusBarHeader(Context context, AttributeSet attrs) { + @Inject + public QuickStatusBarHeader(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs, + NextAlarmController nextAlarmController, ZenModeController zenModeController, + BatteryController batteryController, StatusBarIconController statusBarIconController, + ActivityStarter activityStarter) { super(context, attrs); - mAlarmController = Dependency.get(NextAlarmController.class); - mZenController = Dependency.get(ZenModeController.class); + mAlarmController = nextAlarmController; + mZenController = zenModeController; + mBatteryController = batteryController; + mStatusBarIconController = statusBarIconController; + mActivityStarter = activityStarter; mPrivacyItemController = new PrivacyItemController(context, mPICCallback); mShownCount = getStoredShownCount(); } @@ -405,8 +419,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements if (!mShowBatteryPercentAndEstimate) { return; } - mBatteryRemainingText.setText( - Dependency.get(BatteryController.class).getEstimatedTimeRemainingString()); + mBatteryRemainingText.setText(mBatteryController.getEstimatedTimeRemainingString()); } public void setExpanded(boolean expanded) { @@ -472,7 +485,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements @Override public void onAttachedToWindow() { super.onAttachedToWindow(); - Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager); + mStatusBarIconController.addIconGroup(mIconManager); requestApplyInsets(); mContext.getContentResolver().registerContentObserver( Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mPercentSettingObserver, @@ -515,7 +528,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements @VisibleForTesting public void onDetachedFromWindow() { setListening(false); - Dependency.get(StatusBarIconController.class).removeIconGroup(mIconManager); + mStatusBarIconController.removeIconGroup(mIconManager); mContext.getContentResolver().unregisterContentObserver(mPercentSettingObserver); super.onDetachedFromWindow(); } @@ -544,10 +557,10 @@ public class QuickStatusBarHeader extends RelativeLayout implements @Override public void onClick(View v) { if (v == mClockView) { - Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(new Intent( + mActivityStarter.postStartActivityDismissingKeyguard(new Intent( AlarmClock.ACTION_SHOW_ALARMS),0); } else if (v == mBatteryMeterView) { - Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(new Intent( + mActivityStarter.postStartActivityDismissingKeyguard(new Intent( Intent.ACTION_POWER_USAGE_SUMMARY),0); } else if (v == mPrivacyChip) { Handler mUiHandler = new Handler(Looper.getMainLooper()); diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java new file mode 100644 index 000000000000..e458e6376580 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2018 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.util; + +import android.content.Context; +import android.util.ArrayMap; +import android.util.AttributeSet; +import android.view.InflateException; +import android.view.LayoutInflater; +import android.view.View; + +import com.android.systemui.SystemUIFactory; +import com.android.systemui.qs.QuickStatusBarHeader; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; + +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; + +/** + * Manages inflation that requires dagger injection. + * See docs/dagger.md for details. + */ +@Singleton +public class InjectionInflationController { + + public static final String VIEW_CONTEXT = "view_context"; + private final ViewCreator mViewCreator; + private final ArrayMap<String, Method> mInjectionMap = new ArrayMap<>(); + private final LayoutInflater.Factory2 mFactory = new InjectionFactory(); + + @Inject + public InjectionInflationController(SystemUIFactory.SystemUIRootComponent rootComponent) { + mViewCreator = rootComponent.createViewCreator(); + initInjectionMap(); + } + + ArrayMap<String, Method> getInjectionMap() { + return mInjectionMap; + } + + ViewCreator getFragmentCreator() { + return mViewCreator; + } + + /** + * Wraps a {@link LayoutInflater} to support creating dagger injected views. + * See docs/dagger.md for details. + */ + public LayoutInflater injectable(LayoutInflater inflater) { + LayoutInflater ret = inflater.cloneInContext(inflater.getContext()); + ret.setPrivateFactory(mFactory); + return ret; + } + + private void initInjectionMap() { + for (Method method : ViewInstanceCreator.class.getDeclaredMethods()) { + if (View.class.isAssignableFrom(method.getReturnType()) + && (method.getModifiers() & Modifier.PUBLIC) != 0) { + mInjectionMap.put(method.getReturnType().getName(), method); + } + } + } + + /** + * The subcomponent of dagger that holds all views that need injection. + */ + @Subcomponent + public interface ViewCreator { + /** + * Creates another subcomponent to actually generate the view. + */ + ViewInstanceCreator createInstanceCreator(ViewAttributeProvider attributeProvider); + } + + /** + * Secondary sub-component that actually creates the views. + * + * Having two subcomponents lets us hide the complexity of providing the named context + * and AttributeSet from the SystemUIRootComponent, instead we have one subcomponent that + * creates a new ViewInstanceCreator any time we need to inflate a view. + */ + @Subcomponent(modules = ViewAttributeProvider.class) + public interface ViewInstanceCreator { + /** + * Creates the QuickStatusBarHeader. + */ + QuickStatusBarHeader createQsHeader(); + } + + /** + * Module for providing view-specific constructor objects. + */ + @Module + public class ViewAttributeProvider { + private final Context mContext; + private final AttributeSet mAttrs; + + private ViewAttributeProvider(Context context, AttributeSet attrs) { + mContext = context; + mAttrs = attrs; + } + + /** + * Provides the view-themed context (as opposed to the global sysui application context). + */ + @Provides + @Named(VIEW_CONTEXT) + public Context provideContext() { + return mContext; + } + + /** + * Provides the AttributeSet for the current view being inflated. + */ + @Provides + public AttributeSet provideAttributeSet() { + return mAttrs; + } + } + + private class InjectionFactory implements LayoutInflater.Factory2 { + + @Override + public View onCreateView(String name, Context context, AttributeSet attrs) { + Method creationMethod = mInjectionMap.get(name); + if (creationMethod != null) { + ViewAttributeProvider provider = new ViewAttributeProvider(context, attrs); + try { + return (View) creationMethod.invoke( + mViewCreator.createInstanceCreator(provider)); + } catch (IllegalAccessException e) { + throw new InflateException("Could not inflate " + name, e); + } catch (InvocationTargetException e) { + throw new InflateException("Could not inflate " + name, e); + } + } + return null; + } + + @Override + public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { + return onCreateView(name, context, attrs); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java index 39afbac15c65..ab508a25e76b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java @@ -34,11 +34,13 @@ import com.android.internal.logging.MetricsLogger; import com.android.keyguard.CarrierText; import com.android.systemui.Dependency; import com.android.systemui.R; +import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; import com.android.systemui.statusbar.policy.UserSwitcherController; +import com.android.systemui.util.InjectionInflationController; import org.junit.Before; import org.junit.Ignore; @@ -122,6 +124,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest { @Override protected Fragment instantiate(Context context, String className, Bundle arguments) { - return new QSFragment(new RemoteInputQuickSettingsDisabler(context)); + return new QSFragment(new RemoteInputQuickSettingsDisabler(context), + new InjectionInflationController(SystemUIFactory.getInstance().getRootComponent())); } } |