Home | History | Annotate | Download | only in qs
      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 
    102     public QSFooterImpl(Context context, AttributeSet attrs) {
    103         super(context, attrs);
    104         mColorForeground = Utils.getColorAttr(context, android.R.attr.colorForeground);
    105     }
    106 
    107     @Override
    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 
    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
    165     protected void onConfigurationChanged(Configuration newConfig) {
    166         super.onConfigurationChanged(newConfig);
    167         updateResources();
    168     }
    169 
    170     @Override
    171     public void onRtlPropertiesChanged(int layoutDirection) {
    172         super.onRtlPropertiesChanged(layoutDirection);
    173         updateResources();
    174     }
    175 
    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 
    188     private void updateFooterAnimator() {
    189         mFooterAnimator = createFooterAnimator();
    190     }
    191 
    192     @Nullable
    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
    206     public void setKeyguardShowing(boolean keyguardShowing) {
    207         setExpansion(mExpansionAmount);
    208     }
    209 
    210     @Override
    211     public void setExpandClickListener(OnClickListener onClickListener) {
    212         mExpandClickListener = onClickListener;
    213     }
    214 
    215     @Override
    216     public void setExpanded(boolean expanded) {
    217         if (mExpanded == expanded) return;
    218         mExpanded = expanded;
    219         updateEverything();
    220     }
    221 
    222     @Override
    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
    234     public void onDetachedFromWindow() {
    235         setListening(false);
    236         super.onDetachedFromWindow();
    237     }
    238 
    239     @Override
    240     public void setListening(boolean listening) {
    241         if (listening == mListening) {
    242             return;
    243         }
    244         mListening = listening;
    245         updateListeners();
    246     }
    247 
    248     @Override
    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
    260     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    261         super.onInitializeAccessibilityNodeInfo(info);
    262         info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
    263     }
    264 
    265     @Override
    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 
    273     public void updateEverything() {
    274         post(() -> {
    275             updateVisibilities();
    276             setClickable(false);
    277         });
    278     }
    279 
    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 
    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 
    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
    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
    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 
    370     private void startSettingsActivity() {
    371         mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
    372                 true /* dismissShade */);
    373     }
    374 
    375     @Override
    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
    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 
    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
    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
    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