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 }