1 /*
2  * Copyright (C) 2020 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.res.Configuration;
23 import android.database.ContentObserver;
24 import android.net.Uri;
25 import android.os.Build;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.UserHandle;
29 import android.provider.Settings;
30 import android.util.AttributeSet;
31 import android.view.View;
32 import android.view.accessibility.AccessibilityNodeInfo;
33 import android.widget.FrameLayout;
34 import android.widget.TextView;
35 
36 import androidx.annotation.Nullable;
37 import androidx.annotation.VisibleForTesting;
38 
39 import com.android.settingslib.development.DevelopmentSettingsEnabler;
40 import com.android.systemui.R;
41 
42 /**
43  * Footer of expanded Quick Settings, tiles page indicator, (optionally) build number and
44  * {@link FooterActionsView}
45  */
46 public class QSFooterView extends FrameLayout {
47     private PageIndicator mPageIndicator;
48     private TextView mBuildText;
49     private View mActionsContainer;
50 
51     protected TouchAnimator mFooterAnimator;
52 
53     private boolean mQsDisabled;
54     private boolean mExpanded;
55     private float mExpansionAmount;
56 
57     private boolean mShouldShowBuildText;
58 
59     private OnClickListener mExpandClickListener;
60 
61     private final ContentObserver mDeveloperSettingsObserver = new ContentObserver(
62             new Handler(mContext.getMainLooper())) {
63         @Override
64         public void onChange(boolean selfChange, Uri uri) {
65             super.onChange(selfChange, uri);
66             setBuildText();
67         }
68     };
69 
QSFooterView(Context context, AttributeSet attrs)70     public QSFooterView(Context context, AttributeSet attrs) {
71         super(context, attrs);
72     }
73 
74     @Override
onFinishInflate()75     protected void onFinishInflate() {
76         super.onFinishInflate();
77         mPageIndicator = findViewById(R.id.footer_page_indicator);
78         mActionsContainer = requireViewById(R.id.qs_footer_actions);
79         mBuildText = findViewById(R.id.build);
80 
81         updateResources();
82         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
83         setBuildText();
84     }
85 
setBuildText()86     private void setBuildText() {
87         if (mBuildText == null) return;
88         if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(mContext)) {
89             mBuildText.setText(mContext.getString(
90                     com.android.internal.R.string.bugreport_status,
91                     Build.VERSION.RELEASE_OR_CODENAME,
92                     Build.ID));
93             // Set as selected for marquee before its made visible, then it won't be announced when
94             // it's made visible.
95             mBuildText.setSelected(true);
96             mShouldShowBuildText = true;
97         } else {
98             mBuildText.setText(null);
99             mShouldShowBuildText = false;
100             mBuildText.setSelected(false);
101         }
102     }
103 
updateExpansion()104     void updateExpansion() {
105         setExpansion(mExpansionAmount);
106     }
107 
108     @Override
onConfigurationChanged(Configuration newConfig)109     protected void onConfigurationChanged(Configuration newConfig) {
110         super.onConfigurationChanged(newConfig);
111         updateResources();
112     }
113 
updateResources()114     private void updateResources() {
115         updateFooterAnimator();
116         MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
117         lp.bottomMargin = getResources().getDimensionPixelSize(R.dimen.qs_footers_margin_bottom);
118         setLayoutParams(lp);
119     }
120 
updateFooterAnimator()121     private void updateFooterAnimator() {
122         mFooterAnimator = createFooterAnimator();
123     }
124 
125     @Nullable
createFooterAnimator()126     private TouchAnimator createFooterAnimator() {
127         TouchAnimator.Builder builder = new TouchAnimator.Builder()
128                 .addFloat(mActionsContainer, "alpha", 0, 1)
129                 .addFloat(mPageIndicator, "alpha", 0, 1)
130                 .addFloat(mBuildText, "alpha", 0, 1)
131                 .setStartDelay(0.9f);
132         return builder.build();
133     }
134 
135     /** */
setKeyguardShowing()136     public void setKeyguardShowing() {
137         setExpansion(mExpansionAmount);
138     }
139 
setExpandClickListener(OnClickListener onClickListener)140     public void setExpandClickListener(OnClickListener onClickListener) {
141         mExpandClickListener = onClickListener;
142     }
143 
setExpanded(boolean expanded)144     void setExpanded(boolean expanded) {
145         if (mExpanded == expanded) return;
146         mExpanded = expanded;
147         updateEverything();
148     }
149 
150     /** */
setExpansion(float headerExpansionFraction)151     public void setExpansion(float headerExpansionFraction) {
152         mExpansionAmount = headerExpansionFraction;
153         if (mFooterAnimator != null) {
154             mFooterAnimator.setPosition(headerExpansionFraction);
155         }
156     }
157 
158     @Override
onAttachedToWindow()159     protected void onAttachedToWindow() {
160         super.onAttachedToWindow();
161         mContext.getContentResolver().registerContentObserver(
162                 Settings.Global.getUriFor(Settings.Global.DEVELOPMENT_SETTINGS_ENABLED), false,
163                 mDeveloperSettingsObserver, UserHandle.USER_ALL);
164     }
165 
166     @Override
167     @VisibleForTesting
onDetachedFromWindow()168     public void onDetachedFromWindow() {
169         mContext.getContentResolver().unregisterContentObserver(mDeveloperSettingsObserver);
170         super.onDetachedFromWindow();
171     }
172 
173     @Override
performAccessibilityAction(int action, Bundle arguments)174     public boolean performAccessibilityAction(int action, Bundle arguments) {
175         if (action == AccessibilityNodeInfo.ACTION_EXPAND) {
176             if (mExpandClickListener != null) {
177                 mExpandClickListener.onClick(null);
178                 return true;
179             }
180         }
181         return super.performAccessibilityAction(action, arguments);
182     }
183 
184     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)185     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
186         super.onInitializeAccessibilityNodeInfo(info);
187         info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND);
188     }
189 
disable(int state2)190     void disable(int state2) {
191         final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
192         if (disabled == mQsDisabled) return;
193         mQsDisabled = disabled;
194         updateEverything();
195     }
196 
updateEverything()197     void updateEverything() {
198         post(() -> {
199             updateVisibilities();
200             updateClickabilities();
201             setClickable(false);
202         });
203     }
204 
updateClickabilities()205     private void updateClickabilities() {
206         mBuildText.setLongClickable(mBuildText.getVisibility() == View.VISIBLE);
207     }
208 
updateVisibilities()209     private void updateVisibilities() {
210         mBuildText.setVisibility(mExpanded && mShouldShowBuildText ? View.VISIBLE : View.INVISIBLE);
211     }
212 }