• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.systemui.qs;
18 
19 import static android.app.StatusBarManager.DISABLE2_QUICK_SETTINGS;
20 
21 import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
22 
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.res.Configuration;
26 import android.database.ContentObserver;
27 import android.graphics.PorterDuff.Mode;
28 import android.graphics.drawable.Drawable;
29 import android.graphics.drawable.RippleDrawable;
30 import android.net.Uri;
31 import android.os.Build;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.UserHandle;
35 import android.os.UserManager;
36 import android.provider.Settings;
37 import android.util.AttributeSet;
38 import android.view.View;
39 import android.view.View.OnClickListener;
40 import android.view.accessibility.AccessibilityNodeInfo;
41 import android.widget.FrameLayout;
42 import android.widget.ImageView;
43 import android.widget.TextView;
44 import android.widget.Toast;
45 
46 import androidx.annotation.Nullable;
47 import androidx.annotation.VisibleForTesting;
48 
49 import com.android.internal.logging.MetricsLogger;
50 import com.android.internal.logging.nano.MetricsProto;
51 import com.android.keyguard.KeyguardUpdateMonitor;
52 import com.android.settingslib.Utils;
53 import com.android.settingslib.development.DevelopmentSettingsEnabler;
54 import com.android.settingslib.drawable.UserIconDrawable;
55 import com.android.systemui.Dependency;
56 import com.android.systemui.R;
57 import com.android.systemui.R.dimen;
58 import com.android.systemui.plugins.ActivityStarter;
59 import com.android.systemui.qs.TouchAnimator.Builder;
60 import com.android.systemui.statusbar.phone.MultiUserSwitch;
61 import com.android.systemui.statusbar.phone.SettingsButton;
62 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
63 import com.android.systemui.statusbar.policy.UserInfoController;
64 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
65 import com.android.systemui.tuner.TunerService;
66 
67 import javax.inject.Inject;
68 import javax.inject.Named;
69 
70 public class QSFooterImpl extends FrameLayout implements QSFooter,
71         OnClickListener, OnUserInfoChangedListener {
72 
73     private static final String TAG = "QSFooterImpl";
74 
75     private final ActivityStarter mActivityStarter;
76     private final UserInfoController mUserInfoController;
77     private final DeviceProvisionedController mDeviceProvisionedController;
78     private SettingsButton mSettingsButton;
79     protected View mSettingsContainer;
80     private PageIndicator mPageIndicator;
81 
82     private boolean mQsDisabled;
83     private QSPanel mQsPanel;
84 
85     private boolean mExpanded;
86 
87     private boolean mListening;
88 
89     protected MultiUserSwitch mMultiUserSwitch;
90     private ImageView mMultiUserAvatar;
91 
92     protected TouchAnimator mFooterAnimator;
93     private float mExpansionAmount;
94 
95     protected View mEdit;
96     protected View mEditContainer;
97     private TouchAnimator mSettingsCogAnimator;
98 
99     private View mActionsContainer;
100     private View mDragHandle;
101 
102     private OnClickListener mExpandClickListener;
103 
104     private final ContentObserver mDeveloperSettingsObserver = new ContentObserver(
105             new Handler(mContext.getMainLooper())) {
106         @Override
107         public void onChange(boolean selfChange, Uri uri) {
108             super.onChange(selfChange, uri);
109             setBuildText();
110         }
111     };
112 
113     @Inject
QSFooterImpl(@amedVIEW_CONTEXT) Context context, AttributeSet attrs, ActivityStarter activityStarter, UserInfoController userInfoController, DeviceProvisionedController deviceProvisionedController)114     public QSFooterImpl(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
115             ActivityStarter activityStarter, UserInfoController userInfoController,
116             DeviceProvisionedController deviceProvisionedController) {
117         super(context, attrs);
118         mActivityStarter = activityStarter;
119         mUserInfoController = userInfoController;
120         mDeviceProvisionedController = deviceProvisionedController;
121     }
122 
123     @VisibleForTesting
QSFooterImpl(Context context, AttributeSet attrs)124     public QSFooterImpl(Context context, AttributeSet attrs) {
125         this(context, attrs,
126                 Dependency.get(ActivityStarter.class),
127                 Dependency.get(UserInfoController.class),
128                 Dependency.get(DeviceProvisionedController.class));
129     }
130 
131     @Override
onFinishInflate()132     protected void onFinishInflate() {
133         super.onFinishInflate();
134         mEdit = findViewById(android.R.id.edit);
135         mEdit.setOnClickListener(view ->
136                 mActivityStarter.postQSRunnableDismissingKeyguard(() ->
137                         mQsPanel.showEdit(view)));
138 
139         mPageIndicator = findViewById(R.id.footer_page_indicator);
140 
141         mSettingsButton = findViewById(R.id.settings_button);
142         mSettingsContainer = findViewById(R.id.settings_button_container);
143         mSettingsButton.setOnClickListener(this);
144 
145         mMultiUserSwitch = findViewById(R.id.multi_user_switch);
146         mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
147 
148         mDragHandle = findViewById(R.id.qs_drag_handle_view);
149         mActionsContainer = findViewById(R.id.qs_footer_actions_container);
150         mEditContainer = findViewById(R.id.qs_footer_actions_edit_container);
151 
152         // RenderThread is doing more harm than good when touching the header (to expand quick
153         // settings), so disable it for this view
154         ((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true);
155 
156         updateResources();
157 
158         addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight,
159                 oldBottom) -> updateAnimator(right - left));
160         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
161         updateEverything();
162         setBuildText();
163     }
164 
setBuildText()165     private void setBuildText() {
166         TextView v = findViewById(R.id.build);
167         if (v == null) return;
168         if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)) {
169             v.setText(mContext.getString(
170                     com.android.internal.R.string.bugreport_status,
171                     Build.VERSION.RELEASE,
172                     Build.ID));
173             v.setVisibility(View.VISIBLE);
174         } else {
175             v.setVisibility(View.GONE);
176         }
177     }
178 
updateAnimator(int width)179     private void updateAnimator(int width) {
180         int numTiles = QuickQSPanel.getNumQuickTiles(mContext);
181         int size = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size)
182                 - mContext.getResources().getDimensionPixelSize(dimen.qs_quick_tile_padding);
183         int remaining = (width - numTiles * size) / (numTiles - 1);
184         int defSpace = mContext.getResources().getDimensionPixelOffset(R.dimen.default_gear_space);
185 
186         mSettingsCogAnimator = new Builder()
187                 .addFloat(mSettingsContainer, "translationX",
188                         isLayoutRtl() ? (remaining - defSpace) : -(remaining - defSpace), 0)
189                 .addFloat(mSettingsButton, "rotation", -120, 0)
190                 .build();
191 
192         setExpansion(mExpansionAmount);
193     }
194 
195     @Override
onConfigurationChanged(Configuration newConfig)196     protected void onConfigurationChanged(Configuration newConfig) {
197         super.onConfigurationChanged(newConfig);
198         updateResources();
199     }
200 
201     @Override
onRtlPropertiesChanged(int layoutDirection)202     public void onRtlPropertiesChanged(int layoutDirection) {
203         super.onRtlPropertiesChanged(layoutDirection);
204         updateResources();
205     }
206 
updateResources()207     private void updateResources() {
208         updateFooterAnimator();
209     }
210 
updateFooterAnimator()211     private void updateFooterAnimator() {
212         mFooterAnimator = createFooterAnimator();
213     }
214 
215     @Nullable
createFooterAnimator()216     private TouchAnimator createFooterAnimator() {
217         return new TouchAnimator.Builder()
218                 .addFloat(mActionsContainer, "alpha", 0, 1)
219                 .addFloat(mEditContainer, "alpha", 0, 1)
220                 .addFloat(mDragHandle, "alpha", 1, 0, 0)
221                 .addFloat(mPageIndicator, "alpha", 0, 1)
222                 .setStartDelay(0.15f)
223                 .build();
224     }
225 
226     @Override
setKeyguardShowing(boolean keyguardShowing)227     public void setKeyguardShowing(boolean keyguardShowing) {
228         setExpansion(mExpansionAmount);
229     }
230 
231     @Override
setExpandClickListener(OnClickListener onClickListener)232     public void setExpandClickListener(OnClickListener onClickListener) {
233         mExpandClickListener = onClickListener;
234     }
235 
236     @Override
setExpanded(boolean expanded)237     public void setExpanded(boolean expanded) {
238         if (mExpanded == expanded) return;
239         mExpanded = expanded;
240         updateEverything();
241     }
242 
243     @Override
setExpansion(float headerExpansionFraction)244     public void setExpansion(float headerExpansionFraction) {
245         mExpansionAmount = headerExpansionFraction;
246         if (mSettingsCogAnimator != null) mSettingsCogAnimator.setPosition(headerExpansionFraction);
247 
248         if (mFooterAnimator != null) {
249             mFooterAnimator.setPosition(headerExpansionFraction);
250         }
251     }
252 
253     @Override
onAttachedToWindow()254     protected void onAttachedToWindow() {
255         super.onAttachedToWindow();
256         mContext.getContentResolver().registerContentObserver(
257                 Settings.Global.getUriFor(Settings.Global.DEVELOPMENT_SETTINGS_ENABLED), false,
258                 mDeveloperSettingsObserver, UserHandle.USER_ALL);
259     }
260 
261     @Override
262     @VisibleForTesting
onDetachedFromWindow()263     public void onDetachedFromWindow() {
264         setListening(false);
265         mContext.getContentResolver().unregisterContentObserver(mDeveloperSettingsObserver);
266         super.onDetachedFromWindow();
267     }
268 
269     @Override
setListening(boolean listening)270     public void setListening(boolean listening) {
271         if (listening == mListening) {
272             return;
273         }
274         mListening = listening;
275         updateListeners();
276     }
277 
278     @Override
performAccessibilityAction(int action, Bundle arguments)279     public boolean performAccessibilityAction(int action, Bundle arguments) {
280         if (action == AccessibilityNodeInfo.ACTION_EXPAND) {
281             if (mExpandClickListener != null) {
282                 mExpandClickListener.onClick(null);
283                 return true;
284             }
285         }
286         return super.performAccessibilityAction(action, arguments);
287     }
288 
289     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)290     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
291         super.onInitializeAccessibilityNodeInfo(info);
292         info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
293     }
294 
295     @Override
disable(int state1, int state2, boolean animate)296     public void disable(int state1, int state2, boolean animate) {
297         final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
298         if (disabled == mQsDisabled) return;
299         mQsDisabled = disabled;
300         updateEverything();
301     }
302 
updateEverything()303     public void updateEverything() {
304         post(() -> {
305             updateVisibilities();
306             updateClickabilities();
307             setClickable(false);
308         });
309     }
310 
updateClickabilities()311     private void updateClickabilities() {
312         mMultiUserSwitch.setClickable(mMultiUserSwitch.getVisibility() == View.VISIBLE);
313         mEdit.setClickable(mEdit.getVisibility() == View.VISIBLE);
314         mSettingsButton.setClickable(mSettingsButton.getVisibility() == View.VISIBLE);
315     }
316 
updateVisibilities()317     private void updateVisibilities() {
318         mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
319         mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
320                 TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
321         final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
322         mMultiUserSwitch.setVisibility(showUserSwitcher() ? View.VISIBLE : View.INVISIBLE);
323         mEditContainer.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
324         mSettingsButton.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
325     }
326 
showUserSwitcher()327     private boolean showUserSwitcher() {
328         return mExpanded && mMultiUserSwitch.isMultiUserEnabled();
329     }
330 
updateListeners()331     private void updateListeners() {
332         if (mListening) {
333             mUserInfoController.addCallback(this);
334         } else {
335             mUserInfoController.removeCallback(this);
336         }
337     }
338 
339     @Override
setQSPanel(final QSPanel qsPanel)340     public void setQSPanel(final QSPanel qsPanel) {
341         mQsPanel = qsPanel;
342         if (mQsPanel != null) {
343             mMultiUserSwitch.setQsPanel(qsPanel);
344             mQsPanel.setFooterPageIndicator(mPageIndicator);
345         }
346     }
347 
348     @Override
onClick(View v)349     public void onClick(View v) {
350         // Don't do anything until view are unhidden
351         if (!mExpanded) {
352             return;
353         }
354 
355         if (v == mSettingsButton) {
356             if (!mDeviceProvisionedController.isCurrentUserSetup()) {
357                 // If user isn't setup just unlock the device and dump them back at SUW.
358                 mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
359                 });
360                 return;
361             }
362             MetricsLogger.action(mContext,
363                     mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
364                             : MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
365             if (mSettingsButton.isTunerClick()) {
366                 mActivityStarter.postQSRunnableDismissingKeyguard(() -> {
367                     if (TunerService.isTunerEnabled(mContext)) {
368                         TunerService.showResetRequest(mContext, () -> {
369                             // Relaunch settings so that the tuner disappears.
370                             startSettingsActivity();
371                         });
372                     } else {
373                         Toast.makeText(getContext(), R.string.tuner_toast,
374                                 Toast.LENGTH_LONG).show();
375                         TunerService.setTunerEnabled(mContext, true);
376                     }
377                     startSettingsActivity();
378 
379                 });
380             } else {
381                 startSettingsActivity();
382             }
383         }
384     }
385 
startSettingsActivity()386     private void startSettingsActivity() {
387         mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
388                 true /* dismissShade */);
389     }
390 
391     @Override
onUserInfoChanged(String name, Drawable picture, String userAccount)392     public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
393         if (picture != null &&
394                 UserManager.get(mContext).isGuestUser(KeyguardUpdateMonitor.getCurrentUser()) &&
395                 !(picture instanceof UserIconDrawable)) {
396             picture = picture.getConstantState().newDrawable(mContext.getResources()).mutate();
397             picture.setColorFilter(
398                     Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorForeground),
399                     Mode.SRC_IN);
400         }
401         mMultiUserAvatar.setImageDrawable(picture);
402     }
403 }
404