• 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 android.content.Context;
22 import android.content.Intent;
23 import android.content.pm.UserInfo;
24 import android.content.res.ColorStateList;
25 import android.content.res.Configuration;
26 import android.graphics.PorterDuff.Mode;
27 import android.graphics.drawable.Drawable;
28 import android.graphics.drawable.RippleDrawable;
29 import android.os.Bundle;
30 import android.os.UserManager;
31 import android.support.annotation.Nullable;
32 import android.support.annotation.VisibleForTesting;
33 import android.text.TextUtils;
34 import android.util.AttributeSet;
35 import android.view.View;
36 import android.view.View.OnClickListener;
37 import android.view.accessibility.AccessibilityNodeInfo;
38 import android.widget.FrameLayout;
39 import android.widget.ImageView;
40 import android.widget.LinearLayout;
41 import android.widget.Toast;
42 
43 import com.android.internal.logging.MetricsLogger;
44 import com.android.internal.logging.nano.MetricsProto;
45 import com.android.keyguard.CarrierText;
46 import com.android.keyguard.KeyguardUpdateMonitor;
47 import com.android.settingslib.Utils;
48 import com.android.settingslib.drawable.UserIconDrawable;
49 import com.android.settingslib.graph.SignalDrawable;
50 import com.android.systemui.Dependency;
51 import com.android.systemui.R;
52 import com.android.systemui.R.dimen;
53 import com.android.systemui.plugins.ActivityStarter;
54 import com.android.systemui.qs.TouchAnimator.Builder;
55 import com.android.systemui.statusbar.phone.MultiUserSwitch;
56 import com.android.systemui.statusbar.phone.SettingsButton;
57 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
58 import com.android.systemui.statusbar.policy.NetworkController;
59 import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
60 import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
61 import com.android.systemui.statusbar.policy.UserInfoController;
62 import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
63 import com.android.systemui.tuner.TunerService;
64 
65 public class QSFooterImpl extends FrameLayout implements QSFooter,
66         OnClickListener, OnUserInfoChangedListener, EmergencyListener, SignalCallback {
67 
68     private ActivityStarter mActivityStarter;
69     private UserInfoController mUserInfoController;
70     private SettingsButton mSettingsButton;
71     protected View mSettingsContainer;
72     private PageIndicator mPageIndicator;
73     private CarrierText mCarrierText;
74 
75     private boolean mQsDisabled;
76     private QSPanel mQsPanel;
77 
78     private boolean mExpanded;
79 
80     private boolean mListening;
81 
82     private boolean mShowEmergencyCallsOnly;
83     private View mDivider;
84     protected MultiUserSwitch mMultiUserSwitch;
85     private ImageView mMultiUserAvatar;
86 
87     protected TouchAnimator mFooterAnimator;
88     private float mExpansionAmount;
89 
90     protected View mEdit;
91     private TouchAnimator mSettingsCogAnimator;
92 
93     private View mActionsContainer;
94     private View mDragHandle;
95     private View mMobileGroup;
96     private ImageView mMobileSignal;
97     private ImageView mMobileRoaming;
98     private final int mColorForeground;
99     private final CellSignalState mInfo = new CellSignalState();
100     private OnClickListener mExpandClickListener;
101 
QSFooterImpl(Context context, AttributeSet attrs)102     public QSFooterImpl(Context context, AttributeSet attrs) {
103         super(context, attrs);
104         mColorForeground = Utils.getColorAttr(context, android.R.attr.colorForeground);
105     }
106 
107     @Override
onFinishInflate()108     protected void onFinishInflate() {
109         super.onFinishInflate();
110         mDivider = findViewById(R.id.qs_footer_divider);
111         mEdit = findViewById(android.R.id.edit);
112         mEdit.setOnClickListener(view ->
113                 Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() ->
114                         mQsPanel.showEdit(view)));
115 
116         mPageIndicator = findViewById(R.id.footer_page_indicator);
117 
118         mSettingsButton = findViewById(R.id.settings_button);
119         mSettingsContainer = findViewById(R.id.settings_button_container);
120         mSettingsButton.setOnClickListener(this);
121 
122         mMobileGroup = findViewById(R.id.mobile_combo);
123         mMobileSignal = findViewById(R.id.mobile_signal);
124         mMobileRoaming = findViewById(R.id.mobile_roaming);
125         mCarrierText = findViewById(R.id.qs_carrier_text);
126         mCarrierText.setDisplayFlags(
127                 CarrierText.FLAG_HIDE_AIRPLANE_MODE | CarrierText.FLAG_HIDE_MISSING_SIM);
128 
129         mMultiUserSwitch = findViewById(R.id.multi_user_switch);
130         mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
131 
132         mDragHandle = findViewById(R.id.qs_drag_handle_view);
133         mActionsContainer = findViewById(R.id.qs_footer_actions_container);
134 
135         // RenderThread is doing more harm than good when touching the header (to expand quick
136         // settings), so disable it for this view
137         ((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true);
138 
139         updateResources();
140 
141         mUserInfoController = Dependency.get(UserInfoController.class);
142         mActivityStarter = Dependency.get(ActivityStarter.class);
143         addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight,
144                 oldBottom) -> updateAnimator(right - left));
145         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
146     }
147 
updateAnimator(int width)148     private void updateAnimator(int width) {
149         int numTiles = QuickQSPanel.getNumQuickTiles(mContext);
150         int size = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size)
151                 - mContext.getResources().getDimensionPixelSize(dimen.qs_quick_tile_padding);
152         int remaining = (width - numTiles * size) / (numTiles - 1);
153         int defSpace = mContext.getResources().getDimensionPixelOffset(R.dimen.default_gear_space);
154 
155         mSettingsCogAnimator = new Builder()
156                 .addFloat(mSettingsContainer, "translationX",
157                         isLayoutRtl() ? (remaining - defSpace) : -(remaining - defSpace), 0)
158                 .addFloat(mSettingsButton, "rotation", -120, 0)
159                 .build();
160 
161         setExpansion(mExpansionAmount);
162     }
163 
164     @Override
onConfigurationChanged(Configuration newConfig)165     protected void onConfigurationChanged(Configuration newConfig) {
166         super.onConfigurationChanged(newConfig);
167         updateResources();
168     }
169 
170     @Override
onRtlPropertiesChanged(int layoutDirection)171     public void onRtlPropertiesChanged(int layoutDirection) {
172         super.onRtlPropertiesChanged(layoutDirection);
173         updateResources();
174     }
175 
updateResources()176     private void updateResources() {
177         updateFooterAnimator();
178 
179         // Update the width and weight of the actions container as the page indicator can sometimes
180         // show and the layout needs to center it between the carrier text and actions container.
181         LinearLayout.LayoutParams params =
182                 (LinearLayout.LayoutParams) mActionsContainer.getLayoutParams();
183         params.width = mContext.getResources().getInteger(R.integer.qs_footer_actions_width);
184         params.weight = mContext.getResources().getInteger(R.integer.qs_footer_actions_weight);
185         mActionsContainer.setLayoutParams(params);
186     }
187 
updateFooterAnimator()188     private void updateFooterAnimator() {
189         mFooterAnimator = createFooterAnimator();
190     }
191 
192     @Nullable
createFooterAnimator()193     private TouchAnimator createFooterAnimator() {
194         return new TouchAnimator.Builder()
195                 .addFloat(mDivider, "alpha", 0, 1)
196                 .addFloat(mCarrierText, "alpha", 0, 0, 1)
197                 .addFloat(mMobileGroup, "alpha", 0, 1)
198                 .addFloat(mActionsContainer, "alpha", 0, 1)
199                 .addFloat(mDragHandle, "alpha", 1, 0, 0)
200                 .addFloat(mPageIndicator, "alpha", 0, 1)
201                 .setStartDelay(0.15f)
202                 .build();
203     }
204 
205     @Override
setKeyguardShowing(boolean keyguardShowing)206     public void setKeyguardShowing(boolean keyguardShowing) {
207         setExpansion(mExpansionAmount);
208     }
209 
210     @Override
setExpandClickListener(OnClickListener onClickListener)211     public void setExpandClickListener(OnClickListener onClickListener) {
212         mExpandClickListener = onClickListener;
213     }
214 
215     @Override
setExpanded(boolean expanded)216     public void setExpanded(boolean expanded) {
217         if (mExpanded == expanded) return;
218         mExpanded = expanded;
219         updateEverything();
220     }
221 
222     @Override
setExpansion(float headerExpansionFraction)223     public void setExpansion(float headerExpansionFraction) {
224         mExpansionAmount = headerExpansionFraction;
225         if (mSettingsCogAnimator != null) mSettingsCogAnimator.setPosition(headerExpansionFraction);
226 
227         if (mFooterAnimator != null) {
228             mFooterAnimator.setPosition(headerExpansionFraction);
229         }
230     }
231 
232     @Override
233     @VisibleForTesting
onDetachedFromWindow()234     public void onDetachedFromWindow() {
235         setListening(false);
236         super.onDetachedFromWindow();
237     }
238 
239     @Override
setListening(boolean listening)240     public void setListening(boolean listening) {
241         if (listening == mListening) {
242             return;
243         }
244         mListening = listening;
245         updateListeners();
246     }
247 
248     @Override
performAccessibilityAction(int action, Bundle arguments)249     public boolean performAccessibilityAction(int action, Bundle arguments) {
250         if (action == AccessibilityNodeInfo.ACTION_EXPAND) {
251             if (mExpandClickListener != null) {
252                 mExpandClickListener.onClick(null);
253                 return true;
254             }
255         }
256         return super.performAccessibilityAction(action, arguments);
257     }
258 
259     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)260     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
261         super.onInitializeAccessibilityNodeInfo(info);
262         info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
263     }
264 
265     @Override
disable(int state1, int state2, boolean animate)266     public void disable(int state1, int state2, boolean animate) {
267         final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
268         if (disabled == mQsDisabled) return;
269         mQsDisabled = disabled;
270         updateEverything();
271     }
272 
updateEverything()273     public void updateEverything() {
274         post(() -> {
275             updateVisibilities();
276             setClickable(false);
277         });
278     }
279 
updateVisibilities()280     private void updateVisibilities() {
281         mSettingsContainer.setVisibility(mQsDisabled ? View.GONE : View.VISIBLE);
282         mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
283                 TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
284         final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
285         mMultiUserSwitch.setVisibility(showUserSwitcher(isDemo) ? View.VISIBLE : View.INVISIBLE);
286         mEdit.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
287         mSettingsButton.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
288     }
289 
showUserSwitcher(boolean isDemo)290     private boolean showUserSwitcher(boolean isDemo) {
291         if (!mExpanded || isDemo || !UserManager.supportsMultipleUsers()) {
292             return false;
293         }
294         UserManager userManager = UserManager.get(mContext);
295         if (userManager.hasUserRestriction(UserManager.DISALLOW_USER_SWITCH)) {
296             return false;
297         }
298         int switchableUserCount = 0;
299         for (UserInfo user : userManager.getUsers(true)) {
300             if (user.supportsSwitchToByUser()) {
301                 ++switchableUserCount;
302                 if (switchableUserCount > 1) {
303                     return true;
304                 }
305             }
306         }
307         return getResources().getBoolean(R.bool.qs_show_user_switcher_for_single_user);
308     }
309 
updateListeners()310     private void updateListeners() {
311         if (mListening) {
312             mUserInfoController.addCallback(this);
313             if (Dependency.get(NetworkController.class).hasVoiceCallingFeature()) {
314                 Dependency.get(NetworkController.class).addEmergencyListener(this);
315                 Dependency.get(NetworkController.class).addCallback(this);
316             }
317         } else {
318             mUserInfoController.removeCallback(this);
319             Dependency.get(NetworkController.class).removeEmergencyListener(this);
320             Dependency.get(NetworkController.class).removeCallback(this);
321         }
322     }
323 
324     @Override
setQSPanel(final QSPanel qsPanel)325     public void setQSPanel(final QSPanel qsPanel) {
326         mQsPanel = qsPanel;
327         if (mQsPanel != null) {
328             mMultiUserSwitch.setQsPanel(qsPanel);
329             mQsPanel.setFooterPageIndicator(mPageIndicator);
330         }
331     }
332 
333     @Override
onClick(View v)334     public void onClick(View v) {
335         // Don't do anything until view are unhidden
336         if (!mExpanded) {
337             return;
338         }
339 
340         if (v == mSettingsButton) {
341             if (!Dependency.get(DeviceProvisionedController.class).isCurrentUserSetup()) {
342                 // If user isn't setup just unlock the device and dump them back at SUW.
343                 mActivityStarter.postQSRunnableDismissingKeyguard(() -> { });
344                 return;
345             }
346             MetricsLogger.action(mContext,
347                     mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
348                             : MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
349             if (mSettingsButton.isTunerClick()) {
350                 Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> {
351                     if (TunerService.isTunerEnabled(mContext)) {
352                         TunerService.showResetRequest(mContext, () -> {
353                             // Relaunch settings so that the tuner disappears.
354                             startSettingsActivity();
355                         });
356                     } else {
357                         Toast.makeText(getContext(), R.string.tuner_toast,
358                                 Toast.LENGTH_LONG).show();
359                         TunerService.setTunerEnabled(mContext, true);
360                     }
361                     startSettingsActivity();
362 
363                 });
364             } else {
365                 startSettingsActivity();
366             }
367         }
368     }
369 
startSettingsActivity()370     private void startSettingsActivity() {
371         mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
372                 true /* dismissShade */);
373     }
374 
375     @Override
setEmergencyCallsOnly(boolean show)376     public void setEmergencyCallsOnly(boolean show) {
377         boolean changed = show != mShowEmergencyCallsOnly;
378         if (changed) {
379             mShowEmergencyCallsOnly = show;
380             if (mExpanded) {
381                 updateEverything();
382             }
383         }
384     }
385 
386     @Override
onUserInfoChanged(String name, Drawable picture, String userAccount)387     public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
388         if (picture != null &&
389                 UserManager.get(mContext).isGuestUser(KeyguardUpdateMonitor.getCurrentUser()) &&
390                 !(picture instanceof UserIconDrawable)) {
391             picture = picture.getConstantState().newDrawable(mContext.getResources()).mutate();
392             picture.setColorFilter(
393                     Utils.getColorAttr(mContext, android.R.attr.colorForeground),
394                     Mode.SRC_IN);
395         }
396         mMultiUserAvatar.setImageDrawable(picture);
397     }
398 
handleUpdateState()399     private void handleUpdateState() {
400         mMobileGroup.setVisibility(mInfo.visible ? View.VISIBLE : View.GONE);
401         if (mInfo.visible) {
402             mMobileRoaming.setVisibility(mInfo.roaming ? View.VISIBLE : View.GONE);
403             mMobileRoaming.setImageTintList(ColorStateList.valueOf(mColorForeground));
404             SignalDrawable d = new SignalDrawable(mContext);
405             d.setDarkIntensity(QuickStatusBarHeader.getColorIntensity(mColorForeground));
406             mMobileSignal.setImageDrawable(d);
407             mMobileSignal.setImageLevel(mInfo.mobileSignalIconId);
408 
409             StringBuilder contentDescription = new StringBuilder();
410             if (mInfo.contentDescription != null) {
411                 contentDescription.append(mInfo.contentDescription).append(", ");
412             }
413             if (mInfo.roaming) {
414                 contentDescription
415                         .append(mContext.getString(R.string.data_connection_roaming))
416                         .append(", ");
417             }
418             // TODO: show mobile data off/no internet text for 5 seconds before carrier text
419             if (TextUtils.equals(mInfo.typeContentDescription,
420                     mContext.getString(R.string.data_connection_no_internet))
421                 || TextUtils.equals(mInfo.typeContentDescription,
422                     mContext.getString(R.string.cell_data_off_content_description))) {
423                 contentDescription.append(mInfo.typeContentDescription);
424             }
425             mMobileSignal.setContentDescription(contentDescription);
426         }
427     }
428 
429     @Override
setMobileDataIndicators(NetworkController.IconState statusIcon, NetworkController.IconState qsIcon, int statusType, int qsType, boolean activityIn, boolean activityOut, String typeContentDescription, String description, boolean isWide, int subId, boolean roaming)430     public void setMobileDataIndicators(NetworkController.IconState statusIcon,
431             NetworkController.IconState qsIcon, int statusType,
432             int qsType, boolean activityIn, boolean activityOut,
433             String typeContentDescription,
434             String description, boolean isWide, int subId, boolean roaming) {
435         mInfo.visible = statusIcon.visible;
436         mInfo.mobileSignalIconId = statusIcon.icon;
437         mInfo.contentDescription = statusIcon.contentDescription;
438         mInfo.typeContentDescription = typeContentDescription;
439         mInfo.roaming = roaming;
440         handleUpdateState();
441     }
442 
443     @Override
setNoSims(boolean hasNoSims, boolean simDetected)444     public void setNoSims(boolean hasNoSims, boolean simDetected) {
445         if (hasNoSims) {
446             mInfo.visible = false;
447         }
448         handleUpdateState();
449     }
450 
451     private final class CellSignalState {
452         boolean visible;
453         int mobileSignalIconId;
454         public String contentDescription;
455         String typeContentDescription;
456         boolean roaming;
457     }
458 }
459