Merge changes from topic "b168904199-qs-injection"

* changes:
  5/N Rename QSFooterImpl to QSFooterView
  4/N Remove ActivityStarter from QSFooterImpl.
  3/N Remove DevProvCtrl & UserTracker from QSFooter
  2/N Move UserInfoController out of QSFooterImpl
  1/N Add Controller for QSFooter
This commit is contained in:
Dave Mankoff
2020-10-20 21:55:53 +00:00
committed by Android (Google) Code Review
16 changed files with 487 additions and 271 deletions

View File

@@ -16,7 +16,7 @@
-->
<!-- Extends FrameLayout -->
<com.android.systemui.qs.QSFooterImpl
<com.android.systemui.qs.QSFooterView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/qs_footer"
android:layout_width="match_parent"
@@ -130,4 +130,4 @@
</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
</com.android.keyguard.AlphaOptimizedLinearLayout>
</LinearLayout>
</com.android.systemui.qs.QSFooterImpl>
</com.android.systemui.qs.QSFooterView>

View File

@@ -76,7 +76,7 @@ public class QSDetail extends LinearLayout {
private int mOpenY;
private boolean mAnimatingOpen;
private boolean mSwitchState;
private View mFooter;
private QSFooter mFooter;
public QSDetail(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -120,7 +120,8 @@ public class QSDetail extends LinearLayout {
mDetailDoneButton.setOnClickListener(doneListener);
}
public void setQsPanel(QSPanel panel, QuickStatusBarHeader header, View footer) {
/** */
public void setQsPanel(QSPanel panel, QuickStatusBarHeader header, QSFooter footer) {
mQsPanel = panel;
mHeader = header;
mFooter = footer;

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 The Android Open Source Project
* Copyright (C) 2020 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.
@@ -11,19 +11,14 @@
* 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
* limitations under the License.
*/
package com.android.systemui.qs;
import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.graphics.PorterDuff.Mode;
@@ -36,50 +31,27 @@ import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.Utils;
import com.android.settingslib.development.DevelopmentSettingsEnabler;
import com.android.settingslib.drawable.UserIconDrawable;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.R.dimen;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.TouchAnimator.Builder;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.MultiUserSwitch;
import com.android.systemui.statusbar.phone.SettingsButton;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
import com.android.systemui.tuner.TunerService;
import javax.inject.Inject;
import javax.inject.Named;
public class QSFooterImpl extends FrameLayout implements QSFooter,
OnClickListener, OnUserInfoChangedListener {
private static final String TAG = "QSFooterImpl";
private final ActivityStarter mActivityStarter;
private final UserInfoController mUserInfoController;
private final DeviceProvisionedController mDeviceProvisionedController;
private final UserTracker mUserTracker;
/** */
public class QSFooterView extends FrameLayout {
private SettingsButton mSettingsButton;
protected View mSettingsContainer;
private PageIndicator mPageIndicator;
@@ -87,7 +59,6 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
private boolean mShouldShowBuildText;
private boolean mQsDisabled;
private QSPanel mQsPanel;
private QuickQSPanel mQuickQsPanel;
private boolean mExpanded;
@@ -117,39 +88,19 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
}
};
@Inject
public QSFooterImpl(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
ActivityStarter activityStarter, UserInfoController userInfoController,
DeviceProvisionedController deviceProvisionedController, UserTracker userTracker) {
public QSFooterView(Context context, AttributeSet attrs) {
super(context, attrs);
mActivityStarter = activityStarter;
mUserInfoController = userInfoController;
mDeviceProvisionedController = deviceProvisionedController;
mUserTracker = userTracker;
}
@VisibleForTesting
public QSFooterImpl(Context context, AttributeSet attrs) {
this(context, attrs,
Dependency.get(ActivityStarter.class),
Dependency.get(UserInfoController.class),
Dependency.get(DeviceProvisionedController.class),
Dependency.get(UserTracker.class));
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mEdit = findViewById(android.R.id.edit);
mEdit.setOnClickListener(view ->
mActivityStarter.postQSRunnableDismissingKeyguard(() ->
mQsPanel.showEdit(view)));
mPageIndicator = findViewById(R.id.footer_page_indicator);
mSettingsButton = findViewById(R.id.settings_button);
mSettingsContainer = findViewById(R.id.settings_button_container);
mSettingsButton.setOnClickListener(this);
mMultiUserSwitch = findViewById(R.id.multi_user_switch);
mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
@@ -157,19 +108,6 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
mActionsContainer = findViewById(R.id.qs_footer_actions_container);
mEditContainer = findViewById(R.id.qs_footer_actions_edit_container);
mBuildText = findViewById(R.id.build);
mBuildText.setOnLongClickListener(view -> {
CharSequence buildText = mBuildText.getText();
if (!TextUtils.isEmpty(buildText)) {
ClipboardManager service =
mUserTracker.getUserContext().getSystemService(ClipboardManager.class);
String label = mContext.getString(R.string.build_number_clip_data_label);
service.setPrimaryClip(ClipData.newPlainText(label, buildText));
Toast.makeText(mContext, R.string.build_number_copy_toast, Toast.LENGTH_SHORT)
.show();
return true;
}
return false;
});
// RenderThread is doing more harm than good when touching the header (to expand quick
// settings), so disable it for this view
@@ -180,7 +118,6 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight,
oldBottom) -> updateAnimator(right - left));
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
updateEverything();
setBuildText();
}
@@ -249,24 +186,22 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
.build();
}
@Override
public void setKeyguardShowing(boolean keyguardShowing) {
/** */
public void setKeyguardShowing() {
setExpansion(mExpansionAmount);
}
@Override
public void setExpandClickListener(OnClickListener onClickListener) {
mExpandClickListener = onClickListener;
}
@Override
public void setExpanded(boolean expanded) {
void setExpanded(boolean expanded, boolean isTunerEnabled) {
if (mExpanded == expanded) return;
mExpanded = expanded;
updateEverything();
updateEverything(isTunerEnabled);
}
@Override
/** */
public void setExpansion(float headerExpansionFraction) {
mExpansionAmount = headerExpansionFraction;
if (mSettingsCogAnimator != null) mSettingsCogAnimator.setPosition(headerExpansionFraction);
@@ -287,18 +222,16 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
@Override
@VisibleForTesting
public void onDetachedFromWindow() {
setListening(false);
mContext.getContentResolver().unregisterContentObserver(mDeveloperSettingsObserver);
super.onDetachedFromWindow();
}
@Override
/** */
public void setListening(boolean listening) {
if (listening == mListening) {
return;
}
mListening = listening;
updateListeners();
}
@Override
@@ -318,17 +251,16 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
}
@Override
public void disable(int state1, int state2, boolean animate) {
void disable(int state2, boolean isTunerEnabled) {
final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
if (disabled == mQsDisabled) return;
mQsDisabled = disabled;
updateEverything();
updateEverything(isTunerEnabled);
}
public void updateEverything() {
void updateEverything(boolean isTunerEnabled) {
post(() -> {
updateVisibilities();
updateVisibilities(isTunerEnabled);
updateClickabilities();
setClickable(false);
});
@@ -341,11 +273,10 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
mBuildText.setLongClickable(mBuildText.getVisibility() == View.VISIBLE);
}
private void updateVisibilities() {
private void updateVisibilities(boolean isTunerEnabled) {
mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
TunerService.isTunerEnabled(mContext, mUserTracker.getUserHandle()) ? View.VISIBLE
: View.INVISIBLE);
isTunerEnabled ? View.VISIBLE : View.INVISIBLE);
final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
mMultiUserSwitch.setVisibility(showUserSwitcher() ? View.VISIBLE : View.INVISIBLE);
mEditContainer.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
@@ -358,78 +289,22 @@ public class QSFooterImpl extends FrameLayout implements QSFooter,
return mExpanded && mMultiUserSwitch.isMultiUserEnabled();
}
private void updateListeners() {
if (mListening) {
mUserInfoController.addCallback(this);
} else {
mUserInfoController.removeCallback(this);
}
}
@Override
/** */
public void setQSPanel(final QSPanel qsPanel) {
mQsPanel = qsPanel;
if (mQsPanel != null) {
if (qsPanel != null) {
mMultiUserSwitch.setQsPanel(qsPanel);
mQsPanel.setFooterPageIndicator(mPageIndicator);
qsPanel.setFooterPageIndicator(mPageIndicator);
}
}
@Override
public void setQQSPanel(@Nullable QuickQSPanel panel) {
mQuickQsPanel = panel;
}
@Override
public void onClick(View v) {
// Don't do anything until view are unhidden
if (!mExpanded) {
return;
}
if (v == mSettingsButton) {
if (!mDeviceProvisionedController.isCurrentUserSetup()) {
// If user isn't setup just unlock the device and dump them back at SUW.
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
});
return;
}
MetricsLogger.action(mContext,
mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
: MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
if (mSettingsButton.isTunerClick()) {
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
if (TunerService.isTunerEnabled(mContext, mUserTracker.getUserHandle())) {
TunerService.showResetRequest(mContext, mUserTracker.getUserHandle(),
() -> {
// Relaunch settings so that the tuner disappears.
startSettingsActivity();
});
} else {
Toast.makeText(getContext(), R.string.tuner_toast,
Toast.LENGTH_LONG).show();
TunerService.setTunerEnabled(mContext, mUserTracker.getUserHandle(), true);
}
startSettingsActivity();
});
} else {
startSettingsActivity();
}
}
}
private void startSettingsActivity() {
mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
true /* dismissShade */);
}
@Override
public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
if (picture != null &&
UserManager.get(mContext).isGuestUser(KeyguardUpdateMonitor.getCurrentUser()) &&
!(picture instanceof UserIconDrawable)) {
picture = picture.getConstantState().newDrawable(mContext.getResources()).mutate();
void onUserInfoChanged(Drawable picture, boolean isGuestUser) {
if (picture != null && isGuestUser && !(picture instanceof UserIconDrawable)) {
picture = picture.getConstantState().newDrawable(getResources()).mutate();
picture.setColorFilter(
Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorForeground),
Mode.SRC_IN);

View File

@@ -0,0 +1,241 @@
/*
* Copyright (C) 2020 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.qs;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.UserManager;
import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.SettingsButton;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
/**
* Controller for {@link QSFooterView}.
*/
@QSScope
public class QSFooterViewController extends ViewController<QSFooterView> implements QSFooter {
private final UserManager mUserManager;
private final UserInfoController mUserInfoController;
private final ActivityStarter mActivityStarter;
private final DeviceProvisionedController mDeviceProvisionedController;
private final UserTracker mUserTracker;
private final QSPanelController mQsPanelController;
private final TunerService mTunerService;
private final MetricsLogger mMetricsLogger;
private final SettingsButton mSettingsButton;
private final TextView mBuildText;
private final View mEdit;
private final UserInfoController.OnUserInfoChangedListener mOnUserInfoChangedListener =
new UserInfoController.OnUserInfoChangedListener() {
@Override
public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
boolean isGuestUser = mUserManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser());
mView.onUserInfoChanged(picture, isGuestUser);
}
};
private final View.OnClickListener mSettingsOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
// Don't do anything until view are unhidden
if (!mExpanded) {
return;
}
if (v == mSettingsButton) {
if (!mDeviceProvisionedController.isCurrentUserSetup()) {
// If user isn't setup just unlock the device and dump them back at SUW.
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
});
return;
}
mMetricsLogger.action(
mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
: MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
if (mSettingsButton.isTunerClick()) {
mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
if (isTunerEnabled()) {
mTunerService.showResetRequest(
mUserTracker.getUserHandle(),
() -> {
// Relaunch settings so that the tuner disappears.
startSettingsActivity();
});
} else {
Toast.makeText(getContext(), R.string.tuner_toast,
Toast.LENGTH_LONG).show();
mTunerService.setTunerEnabled(mUserTracker.getUserHandle(), true);
}
startSettingsActivity();
});
} else {
startSettingsActivity();
}
}
}
};
private boolean mListening;
private boolean mExpanded;
@Inject
QSFooterViewController(QSFooterView view, UserManager userManager,
UserInfoController userInfoController, ActivityStarter activityStarter,
DeviceProvisionedController deviceProvisionedController, UserTracker userTracker,
QSPanelController qsPanelController, TunerService tunerService,
MetricsLogger metricsLogger) {
super(view);
mUserManager = userManager;
mUserInfoController = userInfoController;
mActivityStarter = activityStarter;
mDeviceProvisionedController = deviceProvisionedController;
mUserTracker = userTracker;
mQsPanelController = qsPanelController;
mTunerService = tunerService;
mMetricsLogger = metricsLogger;
mSettingsButton = mView.findViewById(R.id.settings_button);
mBuildText = mView.findViewById(R.id.build);
mEdit = mView.findViewById(android.R.id.edit);
}
@Override
protected void onViewAttached() {
mSettingsButton.setOnClickListener(mSettingsOnClickListener);
mBuildText.setOnLongClickListener(view -> {
CharSequence buildText = mBuildText.getText();
if (!TextUtils.isEmpty(buildText)) {
ClipboardManager service =
mUserTracker.getUserContext().getSystemService(ClipboardManager.class);
String label = getResources().getString(R.string.build_number_clip_data_label);
service.setPrimaryClip(ClipData.newPlainText(label, buildText));
Toast.makeText(getContext(), R.string.build_number_copy_toast, Toast.LENGTH_SHORT)
.show();
return true;
}
return false;
});
mEdit.setOnClickListener(view ->
mActivityStarter.postQSRunnableDismissingKeyguard(() ->
mQsPanelController.showEdit(view)));
mView.updateEverything(isTunerEnabled());
}
@Override
protected void onViewDetached() {
setListening(false);
}
@Override
public void setQSPanel(@Nullable QSPanel panel) {
mView.setQSPanel(panel);
}
@Override
public void setVisibility(int visibility) {
mView.setVisibility(visibility);
}
@Override
public void setExpanded(boolean expanded) {
mExpanded = expanded;
mView.setExpanded(expanded, isTunerEnabled());
}
@Override
public int getHeight() {
return mView.getHeight();
}
@Override
public void setExpansion(float expansion) {
mView.setExpansion(expansion);
}
@Override
public void setListening(boolean listening) {
if (mListening == listening) {
return;
}
mListening = listening;
if (mListening) {
mUserInfoController.addCallback(mOnUserInfoChangedListener);
} else {
mUserInfoController.removeCallback(mOnUserInfoChangedListener);
}
}
@Override
public void setKeyguardShowing(boolean keyguardShowing) {
mView.setKeyguardShowing();
}
/** */
@Override
public void setExpandClickListener(View.OnClickListener onClickListener) {
mView.setExpandClickListener(onClickListener);
}
@Override
public void setQQSPanel(@Nullable QuickQSPanel panel) {
mView.setQQSPanel(panel);
}
@Override
public void disable(int state1, int state2, boolean animate) {
mView.disable(state2, isTunerEnabled());
}
private void startSettingsActivity() {
mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
true /* dismissShade */);
}
private boolean isTunerEnabled() {
return mTunerService.isTunerEnabled(mUserTracker.getUserHandle());
}
}

View File

@@ -142,13 +142,13 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
mQSDetail = view.findViewById(R.id.qs_detail);
mHeader = view.findViewById(R.id.header);
mQSPanelController.setHeaderContainer(view.findViewById(R.id.header_text_container));
mFooter = view.findViewById(R.id.qs_footer);
mFooter = qsFragmentComponent.getQSFooter();
mQSContainerImplController = qsFragmentComponent.getQSContainerImplController();
mQSContainerImplController.init();
mContainer = mQSContainerImplController.getView();
mQSDetail.setQsPanel(mQSPanelController.getView(), mHeader, (View) mFooter);
mQSDetail.setQsPanel(mQSPanelController.getView(), mHeader, mFooter);
mQSAnimator = qsFragmentComponent.getQSAnimator();
mQSCustomizer = view.findViewById(R.id.qs_customize);

View File

@@ -20,6 +20,7 @@ import static com.android.systemui.qs.QSPanel.QS_SHOW_BRIGHTNESS;
import android.annotation.NonNull;
import android.content.res.Configuration;
import android.view.View;
import android.view.ViewGroup;
import com.android.internal.logging.MetricsLogger;
@@ -192,4 +193,9 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
super.refreshAllTiles();
mQsSecurityFooter.refreshState();
}
/** Start customizing the Quick Settings. */
public void showEdit(View view) {
mView.showEdit(view);
}
}

View File

@@ -18,6 +18,7 @@ package com.android.systemui.qs.dagger;
import com.android.systemui.qs.QSAnimator;
import com.android.systemui.qs.QSContainerImplController;
import com.android.systemui.qs.QSFooter;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.qs.QuickQSPanelController;
@@ -48,4 +49,7 @@ public interface QSFragmentComponent {
/** Construct a {@link QSContainerImplController}. */
QSContainerImplController getQSContainerImplController();
/** Construct a {@link QSFooter} */
QSFooter getQSFooter();
}

View File

@@ -22,6 +22,9 @@ import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.RootView;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.QSContainerImpl;
import com.android.systemui.qs.QSFooter;
import com.android.systemui.qs.QSFooterView;
import com.android.systemui.qs.QSFooterViewController;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QuickQSPanel;
@@ -70,4 +73,18 @@ public interface QSFragmentModule {
static QuickQSPanel providesQuickQSPanel(QuickStatusBarHeader quickStatusBarHeader) {
return quickStatusBarHeader.findViewById(R.id.quick_qs_panel);
}
/** */
@Provides
static QSFooterView providesQSFooterView(@RootView View view) {
return view.findViewById(R.id.qs_footer);
}
/** */
@Provides
@QSScope
static QSFooter providesQSFooter(QSFooterViewController qsFooterViewController) {
qsFooterViewController.init();
return qsFooterViewController;
}
}

View File

@@ -32,6 +32,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog;
public abstract class TunerService {
public static final String ACTION_CLEAR = "com.android.systemui.action.CLEAR_TUNER";
private final Context mContext;
public abstract void clearAll();
public abstract void destroy();
@@ -50,6 +51,10 @@ public abstract class TunerService {
void onTuningChanged(String key, String newValue);
}
public TunerService(Context context) {
mContext = context;
}
private static Context userContext(Context context, UserHandle user) {
try {
return context.createPackageContextAsUser(context.getPackageName(), 0, user);
@@ -58,6 +63,11 @@ public abstract class TunerService {
}
}
/** Enables or disables the tuner for the supplied user. */
public void setTunerEnabled(UserHandle user, boolean enabled) {
setTunerEnabled(mContext, user, enabled);
}
public static final void setTunerEnabled(Context context, UserHandle user, boolean enabled) {
userContext(context, user).getPackageManager().setComponentEnabledSetting(
new ComponentName(context, TunerActivity.class),
@@ -66,6 +76,11 @@ public abstract class TunerService {
PackageManager.DONT_KILL_APP);
}
/** Returns true if the tuner is enabled for the supplied user. */
public boolean isTunerEnabled(UserHandle user) {
return isTunerEnabled(mContext, user);
}
public static final boolean isTunerEnabled(Context context, UserHandle user) {
return userContext(context, user).getPackageManager().getComponentEnabledSetting(
new ComponentName(context, TunerActivity.class))
@@ -81,6 +96,11 @@ public abstract class TunerService {
}
}
/** */
public void showResetRequest(UserHandle user, final Runnable onDisabled) {
showResetRequest(mContext, user, onDisabled);
}
public static final void showResetRequest(final Context context, UserHandle user,
final Runnable onDisabled) {
SystemUIDialog dialog = new SystemUIDialog(context);

View File

@@ -94,6 +94,7 @@ public class TunerServiceImpl extends TunerService {
DemoModeController demoModeController,
BroadcastDispatcher broadcastDispatcher,
UserTracker userTracker) {
super(context);
mContext = context;
mContentResolver = mContext.getContentResolver();
mLeakDetector = leakDetector;

View File

@@ -24,7 +24,6 @@ import android.view.LayoutInflater;
import android.view.View;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.qs.QSFooterImpl;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QuickQSPanel;
import com.android.systemui.qs.customize.QSCustomizer;
@@ -91,11 +90,6 @@ public class InjectionInflationController {
@BindsInstance AttributeSet attributeSet);
}
/**
* Creates the QSFooterImpl.
*/
QSFooterImpl createQsFooter();
/**
* Creates the NotificationStackScrollLayout.
*/

View File

@@ -16,6 +16,8 @@
package com.android.systemui.util;
import android.content.Context;
import android.content.res.Resources;
import android.view.View;
import android.view.View.OnAttachStateChangeListener;
@@ -75,6 +77,14 @@ public abstract class ViewController<T extends View> {
}
}
protected Context getContext() {
return mView.getContext();
}
protected Resources getResources() {
return mView.getResources();
}
/**
* Called when the view is attached and a call to {@link #init()} has been made in either order.
*/

View File

@@ -70,7 +70,7 @@ public class QSDetailTest extends SysuiTestCase {
mQsDetail = (QSDetail) LayoutInflater.from(mContext).inflate(R.layout.qs_detail, null);
mQsPanel = mock(QSPanel.class);
mQuickHeader = mock(QuickStatusBarHeader.class);
mQsDetail.setQsPanel(mQsPanel, mQuickHeader, mock(View.class));
mQsDetail.setQsPanel(mQsPanel, mQuickHeader, mock(QSFooter.class));
mMockDetailAdapter = mock(DetailAdapter.class);
when(mMockDetailAdapter.createDetailView(any(), any(), any()))

View File

@@ -1,109 +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.qs;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.R.id;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
public class QSFooterImplTest extends LeakCheckedTest {
private QSFooterImpl mFooter;
private ActivityStarter mActivityStarter;
private DeviceProvisionedController mDeviceProvisionedController;
private UserInfoController mUserInfoController;
private UserTracker mUserTracker;
@Mock
private ClipboardManager mClipboardManager;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
mActivityStarter = mDependency.injectMockDependency(ActivityStarter.class);
mDeviceProvisionedController = mDependency.injectMockDependency(
DeviceProvisionedController.class);
mUserInfoController = mDependency.injectMockDependency(UserInfoController.class);
mUserTracker = mDependency.injectMockDependency(UserTracker.class);
mContext.addMockSystemService(ClipboardManager.class, mClipboardManager);
when(mUserTracker.getUserContext()).thenReturn(mContext);
TestableLooper.get(this).runWithLooper(
() -> mFooter = (QSFooterImpl) LayoutInflater.from(mContext).inflate(
R.layout.qs_footer_impl, null));
}
@Test
public void testBuildTextCopy() {
TextView buildTextView = mFooter.requireViewById(R.id.build);
CharSequence buildText = "TEST";
buildTextView.setText(buildText);
buildTextView.setLongClickable(true);
buildTextView.performLongClick();
ArgumentCaptor<ClipData> captor = ArgumentCaptor.forClass(ClipData.class);
verify(mClipboardManager).setPrimaryClip(captor.capture());
assertThat(captor.getValue().getItemAt(0).getText()).isEqualTo(buildText);
}
@Test
@Ignore("failing")
public void testSettings_UserNotSetup() {
View settingsButton = mFooter.findViewById(id.settings_button);
when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(false);
mFooter.onClick(settingsButton);
// Verify Settings wasn't launched.
verify(mActivityStarter, never()).startActivity(any(), anyBoolean());
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright (C) 2020 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.qs;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.os.UserManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.View;
import android.widget.TextView;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.testing.FakeMetricsLogger;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.phone.SettingsButton;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.utils.leaks.FakeTunerService;
import com.android.systemui.utils.leaks.LeakCheckedTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
public class QSFooterViewControllerTest extends LeakCheckedTest {
@Mock
private QSFooterView mView;
@Mock
private UserManager mUserManager;
@Mock
private ActivityStarter mActivityStarter;
@Mock
private DeviceProvisionedController mDeviceProvisionedController;
@Mock
private UserInfoController mUserInfoController;
@Mock
private UserTracker mUserTracker;
@Mock
private QSPanelController mQSPanelController;
@Mock
private ClipboardManager mClipboardManager;
private FakeTunerService mFakeTunerService;
private MetricsLogger mMetricsLogger = new FakeMetricsLogger();
@Mock
private SettingsButton mSettingsButton;
@Mock
private TextView mBuildText;
@Mock
private View mEdit;
private QSFooterViewController mController;
@Before
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
mFakeTunerService = (FakeTunerService) Dependency.get(TunerService.class);
mContext.addMockSystemService(ClipboardManager.class, mClipboardManager);
when(mView.getContext()).thenReturn(mContext);
when(mView.getResources()).thenReturn(mContext.getResources());
when(mUserTracker.getUserContext()).thenReturn(mContext);
when(mView.isAttachedToWindow()).thenReturn(true);
when(mView.findViewById(R.id.settings_button)).thenReturn(mSettingsButton);
when(mView.findViewById(R.id.build)).thenReturn(mBuildText);
when(mView.findViewById(android.R.id.edit)).thenReturn(mEdit);
mController = new QSFooterViewController(mView, mUserManager, mUserInfoController,
mActivityStarter, mDeviceProvisionedController, mUserTracker, mQSPanelController,
mFakeTunerService, mMetricsLogger);
mController.init();
}
@Test
public void testBuildTextCopy() {
String text = "TEST";
ArgumentCaptor<View.OnLongClickListener> onLongClickCaptor =
ArgumentCaptor.forClass(View.OnLongClickListener.class);
verify(mBuildText).setOnLongClickListener(onLongClickCaptor.capture());
when(mBuildText.getText()).thenReturn(text);
onLongClickCaptor.getValue().onLongClick(mBuildText);
ArgumentCaptor<ClipData> captor = ArgumentCaptor.forClass(ClipData.class);
verify(mClipboardManager).setPrimaryClip(captor.capture());
assertThat(captor.getValue().getItemAt(0).getText()).isEqualTo(text);
}
@Test
public void testSettings_UserNotSetup() {
ArgumentCaptor<View.OnClickListener> onClickCaptor =
ArgumentCaptor.forClass(View.OnClickListener.class);
verify(mSettingsButton).setOnClickListener(onClickCaptor.capture());
when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(false);
onClickCaptor.getValue().onClick(mSettingsButton);
// Verify Settings wasn't launched.
verify(mActivityStarter, never()).startActivity(any(), anyBoolean());
}
}

View File

@@ -14,7 +14,7 @@
package com.android.systemui.utils.leaks;
import android.content.Context;
import android.os.UserHandle;
import android.testing.LeakCheck;
import com.android.systemui.tuner.TunerService;
@@ -22,8 +22,10 @@ import com.android.systemui.tuner.TunerService;
public class FakeTunerService extends TunerService {
private final BaseLeakChecker<Tunable> mBaseLeakChecker;
private boolean mEnabled;
public FakeTunerService(LeakCheck test) {
super(null);
mBaseLeakChecker = new BaseLeakChecker<>(test, "tunable");
}
@@ -74,4 +76,14 @@ public class FakeTunerService extends TunerService {
public void setValue(String setting, int value) {
}
@Override
public void setTunerEnabled(UserHandle user, boolean enabled) {
mEnabled = enabled;
}
@Override
public boolean isTunerEnabled(UserHandle user) {
return mEnabled;
}
}