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.Handler;
27 import android.os.UserHandle;
28 import android.provider.Settings;
29 import android.util.AttributeSet;
30 import android.view.View;
31 import android.widget.FrameLayout;
32 import android.widget.TextView;
33 
34 import androidx.annotation.Nullable;
35 import androidx.annotation.VisibleForTesting;
36 
37 import com.android.settingslib.development.DevelopmentSettingsEnabler;
38 import com.android.systemui.R;
39 
40 /**
41  * Footer of expanded Quick Settings, tiles page indicator, (optionally) build number and
42  * {@link FooterActionsView}
43  */
44 public class QSFooterView extends FrameLayout {
45     private PageIndicator mPageIndicator;
46     private TextView mBuildText;
47     private View mEditButton;
48 
49     @Nullable
50     protected TouchAnimator mFooterAnimator;
51 
52     private boolean mQsDisabled;
53     private boolean mExpanded;
54     private float mExpansionAmount;
55 
56     private boolean mShouldShowBuildText;
57 
58     @Nullable
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         mBuildText = findViewById(R.id.build);
79         mEditButton = findViewById(android.R.id.edit);
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 
104     @Override
onConfigurationChanged(Configuration newConfig)105     protected void onConfigurationChanged(Configuration newConfig) {
106         super.onConfigurationChanged(newConfig);
107         updateResources();
108     }
109 
updateResources()110     private void updateResources() {
111         updateFooterAnimator();
112         MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
113         lp.bottomMargin = getResources().getDimensionPixelSize(R.dimen.qs_footers_margin_bottom);
114         setLayoutParams(lp);
115     }
116 
updateFooterAnimator()117     private void updateFooterAnimator() {
118         mFooterAnimator = createFooterAnimator();
119     }
120 
121     @Nullable
createFooterAnimator()122     private TouchAnimator createFooterAnimator() {
123         TouchAnimator.Builder builder = new TouchAnimator.Builder()
124                 .addFloat(mPageIndicator, "alpha", 0, 1)
125                 .addFloat(mBuildText, "alpha", 0, 1)
126                 .addFloat(mEditButton, "alpha", 0, 1)
127                 .setStartDelay(0.9f);
128         return builder.build();
129     }
130 
131     /** */
setKeyguardShowing()132     public void setKeyguardShowing() {
133         setExpansion(mExpansionAmount);
134     }
135 
setExpandClickListener(OnClickListener onClickListener)136     public void setExpandClickListener(OnClickListener onClickListener) {
137         mExpandClickListener = onClickListener;
138     }
139 
setExpanded(boolean expanded)140     void setExpanded(boolean expanded) {
141         if (mExpanded == expanded) return;
142         mExpanded = expanded;
143         updateEverything();
144     }
145 
146     /** */
setExpansion(float headerExpansionFraction)147     public void setExpansion(float headerExpansionFraction) {
148         mExpansionAmount = headerExpansionFraction;
149         if (mFooterAnimator != null) {
150             mFooterAnimator.setPosition(headerExpansionFraction);
151         }
152     }
153 
154     @Override
onAttachedToWindow()155     protected void onAttachedToWindow() {
156         super.onAttachedToWindow();
157         mContext.getContentResolver().registerContentObserver(
158                 Settings.Global.getUriFor(Settings.Global.DEVELOPMENT_SETTINGS_ENABLED), false,
159                 mDeveloperSettingsObserver, UserHandle.USER_ALL);
160     }
161 
162     @Override
163     @VisibleForTesting
onDetachedFromWindow()164     public void onDetachedFromWindow() {
165         mContext.getContentResolver().unregisterContentObserver(mDeveloperSettingsObserver);
166         super.onDetachedFromWindow();
167     }
168 
disable(int state2)169     void disable(int state2) {
170         final boolean disabled = (state2 & DISABLE2_QUICK_SETTINGS) != 0;
171         if (disabled == mQsDisabled) return;
172         mQsDisabled = disabled;
173         updateEverything();
174     }
175 
updateEverything()176     void updateEverything() {
177         post(() -> {
178             updateVisibilities();
179             updateClickabilities();
180             setClickable(false);
181         });
182     }
183 
updateClickabilities()184     private void updateClickabilities() {
185         mBuildText.setLongClickable(mBuildText.getVisibility() == View.VISIBLE);
186     }
187 
updateVisibilities()188     private void updateVisibilities() {
189         mBuildText.setVisibility(mExpanded && mShouldShowBuildText ? View.VISIBLE : View.INVISIBLE);
190     }
191 }