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 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
    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
    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
    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 
    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 
    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
    196     protected void onConfigurationChanged(Configuration newConfig) {
    197         super.onConfigurationChanged(newConfig);
    198         updateResources();
    199     }
    200 
    201     @Override
    202     public void onRtlPropertiesChanged(int layoutDirection) {
    203         super.onRtlPropertiesChanged(layoutDirection);
    204         updateResources();
    205     }
    206 
    207     private void updateResources() {
    208         updateFooterAnimator();
    209     }
    210 
    211     private void updateFooterAnimator() {
    212         mFooterAnimator = createFooterAnimator();
    213     }
    214 
    215     @Nullable
    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
    227     public void setKeyguardShowing(boolean keyguardShowing) {
    228         setExpansion(mExpansionAmount);
    229     }
    230 
    231     @Override
    232     public void setExpandClickListener(OnClickListener onClickListener) {
    233         mExpandClickListener = onClickListener;
    234     }
    235 
    236     @Override
    237     public void setExpanded(boolean expanded) {
    238         if (mExpanded == expanded) return;
    239         mExpanded = expanded;
    240         updateEverything();
    241     }
    242 
    243     @Override
    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
    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
    263     public void onDetachedFromWindow() {
    264         setListening(false);
    265         mContext.getContentResolver().unregisterContentObserver(mDeveloperSettingsObserver);
    266         super.onDetachedFromWindow();
    267     }
    268 
    269     @Override
    270     public void setListening(boolean listening) {
    271         if (listening == mListening) {
    272             return;
    273         }
    274         mListening = listening;
    275         updateListeners();
    276     }
    277 
    278     @Override
    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
    290     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    291         super.onInitializeAccessibilityNodeInfo(info);
    292         info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
    293     }
    294 
    295     @Override
    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 
    303     public void updateEverything() {
    304         post(() -> {
    305             updateVisibilities();
    306             updateClickabilities();
    307             setClickable(false);
    308         });
    309     }
    310 
    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 
    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 
    327     private boolean showUserSwitcher() {
    328         return mExpanded && mMultiUserSwitch.isMultiUserEnabled();
    329     }
    330 
    331     private void updateListeners() {
    332         if (mListening) {
    333             mUserInfoController.addCallback(this);
    334         } else {
    335             mUserInfoController.removeCallback(this);
    336         }
    337     }
    338 
    339     @Override
    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
    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 
    386     private void startSettingsActivity() {
    387         mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
    388                 true /* dismissShade */);
    389     }
    390 
    391     @Override
    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