Sysui: Add support for view injection

Test: Existing tests pass
Change-Id: Ic6931ebec38ca9514e9368239dd9502ae2dee33c
This commit is contained in:
Jason Monk
2018-12-20 10:01:48 -05:00
parent b34e8528ca
commit ea54e8a756
8 changed files with 258 additions and 18 deletions

View File

@@ -147,6 +147,52 @@ then the FragmentHostManager can do this for you.
FragmentHostManager.get(view).create(NavigationBarFragment.class); 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 ## TODO List
- Eliminate usages of Depndency#get - Eliminate usages of Depndency#get

View File

@@ -31,4 +31,7 @@
-keep class com.android.systemui.fragments.FragmentService$FragmentCreator { -keep class com.android.systemui.fragments.FragmentService$FragmentCreator {
*; *;
} }
-keep class com.android.systemui.util.InjectionInflationController$ViewInstanceCreator {
*;
}
-keep class androidx.core.app.CoreComponentFactory -keep class androidx.core.app.CoreComponentFactory

View File

@@ -33,7 +33,6 @@ import android.os.Process;
import android.os.ServiceManager; import android.os.ServiceManager;
import android.os.UserHandle; import android.os.UserHandle;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log;
import android.view.IWindowManager; import android.view.IWindowManager;
import android.view.WindowManagerGlobal; import android.view.WindowManagerGlobal;
@@ -470,7 +469,6 @@ public class DependencyProvider {
@Singleton @Singleton
@Provides @Provides
public UiOffloadThread provideUiOffloadThread() { public UiOffloadThread provideUiOffloadThread() {
Log.d("TestTest", "provideUiOffloadThread");
return new UiOffloadThread(); return new UiOffloadThread();
} }

View File

@@ -42,7 +42,6 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
import com.android.systemui.statusbar.ScrimView; import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationData;
import com.android.systemui.statusbar.notification.NotificationEntryManager; 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.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBouncer; import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.KeyguardEnvironmentImpl; 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.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.InjectionInflationController;
import com.android.systemui.volume.VolumeDialogComponent; import com.android.systemui.volume.VolumeDialogComponent;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -223,5 +223,10 @@ public class SystemUIFactory {
*/ */
@Singleton @Singleton
FragmentService.FragmentCreator createFragmentCreator(); FragmentService.FragmentCreator createFragmentCreator();
/**
* ViewCreator generates all Views that need injection.
*/
InjectionInflationController.ViewCreator createViewCreator();
} }
} }

View File

@@ -45,6 +45,7 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator; import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer; import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.util.InjectionInflationController;
import javax.inject.Inject; import javax.inject.Inject;
@@ -76,16 +77,20 @@ public class QSFragment extends Fragment implements QS, CommandQueue.Callbacks {
private boolean mQsDisabled; private boolean mQsDisabled;
private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; private final RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
private final InjectionInflationController mInjectionInflater;
@Inject @Inject
public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler) { public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
InjectionInflationController injectionInflater) {
mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler; mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
mInjectionInflater = injectionInflater;
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) { 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); return inflater.inflate(R.layout.qs_panel, container, false);
} }

View File

@@ -17,6 +17,8 @@ package com.android.systemui.qs;
import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS; import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
import static android.provider.Settings.System.SHOW_BATTERY_PERCENT; 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.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
import android.annotation.ColorInt; import android.annotation.ColorInt;
@@ -58,7 +60,6 @@ import androidx.annotation.VisibleForTesting;
import com.android.settingslib.Utils; import com.android.settingslib.Utils;
import com.android.systemui.BatteryMeterView; import com.android.systemui.BatteryMeterView;
import com.android.systemui.Dependency;
import com.android.systemui.Prefs; import com.android.systemui.Prefs;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.ActivityStarter;
@@ -84,6 +85,9 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; 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 * 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 * 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; public static final int MAX_TOOLTIP_SHOWN_COUNT = 2;
private final Handler mHandler = new Handler(); 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; private QSPanel mQsPanel;
@@ -141,8 +150,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
private TextView mBatteryRemainingText; private TextView mBatteryRemainingText;
private boolean mShowBatteryPercentAndEstimate; private boolean mShowBatteryPercentAndEstimate;
private NextAlarmController mAlarmController;
private ZenModeController mZenController;
private PrivacyItemController mPrivacyItemController; private PrivacyItemController mPrivacyItemController;
/** Counts how many times the long press tooltip has been shown to the user. */ /** Counts how many times the long press tooltip has been shown to the user. */
private int mShownCount; 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); super(context, attrs);
mAlarmController = Dependency.get(NextAlarmController.class); mAlarmController = nextAlarmController;
mZenController = Dependency.get(ZenModeController.class); mZenController = zenModeController;
mBatteryController = batteryController;
mStatusBarIconController = statusBarIconController;
mActivityStarter = activityStarter;
mPrivacyItemController = new PrivacyItemController(context, mPICCallback); mPrivacyItemController = new PrivacyItemController(context, mPICCallback);
mShownCount = getStoredShownCount(); mShownCount = getStoredShownCount();
} }
@@ -405,8 +419,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements
if (!mShowBatteryPercentAndEstimate) { if (!mShowBatteryPercentAndEstimate) {
return; return;
} }
mBatteryRemainingText.setText( mBatteryRemainingText.setText(mBatteryController.getEstimatedTimeRemainingString());
Dependency.get(BatteryController.class).getEstimatedTimeRemainingString());
} }
public void setExpanded(boolean expanded) { public void setExpanded(boolean expanded) {
@@ -472,7 +485,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements
@Override @Override
public void onAttachedToWindow() { public void onAttachedToWindow() {
super.onAttachedToWindow(); super.onAttachedToWindow();
Dependency.get(StatusBarIconController.class).addIconGroup(mIconManager); mStatusBarIconController.addIconGroup(mIconManager);
requestApplyInsets(); requestApplyInsets();
mContext.getContentResolver().registerContentObserver( mContext.getContentResolver().registerContentObserver(
Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mPercentSettingObserver, Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mPercentSettingObserver,
@@ -515,7 +528,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements
@VisibleForTesting @VisibleForTesting
public void onDetachedFromWindow() { public void onDetachedFromWindow() {
setListening(false); setListening(false);
Dependency.get(StatusBarIconController.class).removeIconGroup(mIconManager); mStatusBarIconController.removeIconGroup(mIconManager);
mContext.getContentResolver().unregisterContentObserver(mPercentSettingObserver); mContext.getContentResolver().unregisterContentObserver(mPercentSettingObserver);
super.onDetachedFromWindow(); super.onDetachedFromWindow();
} }
@@ -544,10 +557,10 @@ public class QuickStatusBarHeader extends RelativeLayout implements
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (v == mClockView) { if (v == mClockView) {
Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(new Intent( mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
AlarmClock.ACTION_SHOW_ALARMS),0); AlarmClock.ACTION_SHOW_ALARMS),0);
} else if (v == mBatteryMeterView) { } else if (v == mBatteryMeterView) {
Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(new Intent( mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
Intent.ACTION_POWER_USAGE_SUMMARY),0); Intent.ACTION_POWER_USAGE_SUMMARY),0);
} else if (v == mPrivacyChip) { } else if (v == mPrivacyChip) {
Handler mUiHandler = new Handler(Looper.getMainLooper()); Handler mUiHandler = new Handler(Looper.getMainLooper());

View File

@@ -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);
}
}
}

View File

@@ -34,11 +34,13 @@ import com.android.internal.logging.MetricsLogger;
import com.android.keyguard.CarrierText; import com.android.keyguard.CarrierText;
import com.android.systemui.Dependency; import com.android.systemui.Dependency;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiBaseFragmentTest; import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.statusbar.phone.StatusBarIconController; import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.Clock; import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler; import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.InjectionInflationController;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore; import org.junit.Ignore;
@@ -122,6 +124,7 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
@Override @Override
protected Fragment instantiate(Context context, String className, Bundle arguments) { 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()));
} }
} }