diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml index 436188a83d4f1..0822947e8b162 100644 --- a/packages/SystemUI/res/layout/qs_footer_impl.xml +++ b/packages/SystemUI/res/layout/qs_footer_impl.xml @@ -16,7 +16,7 @@ --> - - + diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java index 2be8a9704e1c7..cfcceb2b2951a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetail.java @@ -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; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java similarity index 61% rename from packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java rename to packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java index 84563a0784472..8b9dae14c8091 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java @@ -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); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java new file mode 100644 index 0000000000000..e3af04bdc31e4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterViewController.java @@ -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 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()); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java index 4ea600ab5e255..1a7d366d84b40 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java @@ -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); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java index 4e2351f66ca25..f222b0d1ccc01 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java @@ -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 { super.refreshAllTiles(); mQsSecurityFooter.refreshState(); } + + /** Start customizing the Quick Settings. */ + public void showEdit(View view) { + mView.showEdit(view); + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java index 7ba3563f3b201..51b2c8dfffbd9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentComponent.java @@ -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(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java index ee3f2f60fccc5..4bf4eff4c27e9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java @@ -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; + } } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java index 70bba263ab90c..b67574d1c4dee 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java @@ -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); diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java index 22f03e074b068..027c282ba3529 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java @@ -94,6 +94,7 @@ public class TunerServiceImpl extends TunerService { DemoModeController demoModeController, BroadcastDispatcher broadcastDispatcher, UserTracker userTracker) { + super(context); mContext = context; mContentResolver = mContext.getContentResolver(); mLeakDetector = leakDetector; diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java index 344f0d2f5506f..e79d432b3b151 100644 --- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java +++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java @@ -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. */ diff --git a/packages/SystemUI/src/com/android/systemui/util/ViewController.java b/packages/SystemUI/src/com/android/systemui/util/ViewController.java index c7aa780fcacb5..3dbc6f101a909 100644 --- a/packages/SystemUI/src/com/android/systemui/util/ViewController.java +++ b/packages/SystemUI/src/com/android/systemui/util/ViewController.java @@ -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 { } } + 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. */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java index 353efeeb21f60..803919200150b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSDetailTest.java @@ -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())) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java deleted file mode 100644 index 99f2d80425470..0000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterImplTest.java +++ /dev/null @@ -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 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()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java new file mode 100644 index 0000000000000..065f236d19b32 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java @@ -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 onLongClickCaptor = + ArgumentCaptor.forClass(View.OnLongClickListener.class); + + verify(mBuildText).setOnLongClickListener(onLongClickCaptor.capture()); + + when(mBuildText.getText()).thenReturn(text); + onLongClickCaptor.getValue().onLongClick(mBuildText); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ClipData.class); + verify(mClipboardManager).setPrimaryClip(captor.capture()); + assertThat(captor.getValue().getItemAt(0).getText()).isEqualTo(text); + } + + @Test + public void testSettings_UserNotSetup() { + ArgumentCaptor 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()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java index 8db82e2a52fc3..97d4aa7e4d19c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeTunerService.java @@ -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 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; + } }