2 * Copyright (C) 2016 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
15 package com.android.systemui.qs;
17 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_MORE_SETTINGS;
19 import android.animation.Animator;
20 import android.animation.Animator.AnimatorListener;
21 import android.animation.AnimatorListenerAdapter;
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.res.Configuration;
26 import android.graphics.drawable.Animatable;
27 import android.util.AttributeSet;
28 import android.util.SparseArray;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.view.accessibility.AccessibilityEvent;
32 import android.widget.ImageView;
33 import android.widget.LinearLayout;
34 import android.widget.Switch;
35 import android.widget.TextView;
37 import com.android.internal.logging.MetricsLogger;
38 import com.android.systemui.Dependency;
39 import com.android.systemui.FontSizeUtils;
40 import com.android.systemui.R;
41 import com.android.systemui.SysUiServiceProvider;
42 import com.android.systemui.plugins.ActivityStarter;
43 import com.android.systemui.plugins.qs.DetailAdapter;
44 import com.android.systemui.statusbar.CommandQueue;
46 public class QSDetail extends LinearLayout {
48 private static final String TAG = "QSDetail";
49 private static final long FADE_DURATION = 300;
51 private final SparseArray<View> mDetailViews = new SparseArray<>();
53 private ViewGroup mDetailContent;
54 protected TextView mDetailSettingsButton;
55 protected TextView mDetailDoneButton;
56 private QSDetailClipper mClipper;
57 private DetailAdapter mDetailAdapter;
58 private QSPanel mQsPanel;
60 protected View mQsDetailHeader;
61 protected TextView mQsDetailHeaderTitle;
62 protected Switch mQsDetailHeaderSwitch;
63 protected ImageView mQsDetailHeaderProgress;
65 protected QSTileHost mHost;
67 private boolean mScanState;
68 private boolean mClosingDetail;
69 private boolean mFullyExpanded;
70 private QuickStatusBarHeader mHeader;
71 private boolean mTriggeredExpand;
74 private boolean mAnimatingOpen;
75 private boolean mSwitchState;
77 public QSDetail(Context context, @Nullable AttributeSet attrs) {
78 super(context, attrs);
82 protected void onConfigurationChanged(Configuration newConfig) {
83 super.onConfigurationChanged(newConfig);
84 FontSizeUtils.updateFontSize(mDetailDoneButton, R.dimen.qs_detail_button_text_size);
85 FontSizeUtils.updateFontSize(mDetailSettingsButton, R.dimen.qs_detail_button_text_size);
87 for (int i = 0; i < mDetailViews.size(); i++) {
88 mDetailViews.valueAt(i).dispatchConfigurationChanged(newConfig);
93 protected void onFinishInflate() {
94 super.onFinishInflate();
95 mDetailContent = findViewById(android.R.id.content);
96 mDetailSettingsButton = findViewById(android.R.id.button2);
97 mDetailDoneButton = findViewById(android.R.id.button1);
99 mQsDetailHeader = findViewById(R.id.qs_detail_header);
100 mQsDetailHeaderTitle = (TextView) mQsDetailHeader.findViewById(android.R.id.title);
101 mQsDetailHeaderSwitch = (Switch) mQsDetailHeader.findViewById(android.R.id.toggle);
102 mQsDetailHeaderProgress = findViewById(R.id.qs_detail_header_progress);
106 mClipper = new QSDetailClipper(this);
108 final OnClickListener doneListener = new OnClickListener() {
110 public void onClick(View v) {
111 announceForAccessibility(
112 mContext.getString(R.string.accessibility_desc_quick_settings));
113 mQsPanel.closeDetail();
116 mDetailDoneButton.setOnClickListener(doneListener);
119 public void setQsPanel(QSPanel panel, QuickStatusBarHeader header) {
122 mHeader.setCallback(mQsPanelCallback);
123 mQsPanel.setCallback(mQsPanelCallback);
126 public void setHost(QSTileHost host) {
129 public boolean isShowingDetail() {
130 return mDetailAdapter != null;
133 public void setFullyExpanded(boolean fullyExpanded) {
134 mFullyExpanded = fullyExpanded;
137 public void setExpanded(boolean qsExpanded) {
139 mTriggeredExpand = false;
143 private void updateDetailText() {
144 mDetailDoneButton.setText(R.string.quick_settings_done);
145 mDetailSettingsButton.setText(R.string.quick_settings_more_settings);
148 public void updateResources() {
152 public boolean isClosingDetail() {
153 return mClosingDetail;
156 public interface Callback {
157 void onShowingDetail(DetailAdapter detail, int x, int y);
158 void onToggleStateChanged(boolean state);
159 void onScanStateChanged(boolean state);
162 public void handleShowingDetail(final DetailAdapter adapter, int x, int y,
164 final boolean showingDetail = adapter != null;
165 setClickable(showingDetail);
167 setupDetailHeader(adapter);
168 if (toggleQs && !mFullyExpanded) {
169 mTriggeredExpand = true;
170 SysUiServiceProvider.getComponent(mContext, CommandQueue.class)
171 .animateExpandSettingsPanel(null);
173 mTriggeredExpand = false;
178 // Ensure we collapse into the same point we opened from.
181 if (toggleQs && mTriggeredExpand) {
182 SysUiServiceProvider.getComponent(mContext, CommandQueue.class)
183 .animateCollapsePanels();
184 mTriggeredExpand = false;
188 boolean visibleDiff = (mDetailAdapter != null) != (adapter != null);
189 if (!visibleDiff && mDetailAdapter == adapter) return; // already in right state
190 AnimatorListener listener = null;
191 if (adapter != null) {
192 int viewCacheIndex = adapter.getMetricsCategory();
193 View detailView = adapter.createDetailView(mContext, mDetailViews.get(viewCacheIndex),
195 if (detailView == null) throw new IllegalStateException("Must return detail view");
197 setupDetailFooter(adapter);
199 mDetailContent.removeAllViews();
200 mDetailContent.addView(detailView);
201 mDetailViews.put(viewCacheIndex, detailView);
202 Dependency.get(MetricsLogger.class).visible(adapter.getMetricsCategory());
203 announceForAccessibility(mContext.getString(
204 R.string.accessibility_quick_settings_detail,
205 adapter.getTitle()));
206 mDetailAdapter = adapter;
207 listener = mHideGridContentWhenDone;
208 setVisibility(View.VISIBLE);
210 if (mDetailAdapter != null) {
211 Dependency.get(MetricsLogger.class).hidden(mDetailAdapter.getMetricsCategory());
213 mClosingDetail = true;
214 mDetailAdapter = null;
215 listener = mTeardownDetailWhenDone;
216 mHeader.setVisibility(View.VISIBLE);
217 mQsPanel.setGridContentVisibility(true);
218 mQsPanelCallback.onScanStateChanged(false);
220 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
222 animateDetailVisibleDiff(x, y, visibleDiff, listener);
225 protected void animateDetailVisibleDiff(int x, int y, boolean visibleDiff, AnimatorListener listener) {
227 mAnimatingOpen = mDetailAdapter != null;
228 if (mFullyExpanded || mDetailAdapter != null) {
230 mClipper.animateCircularClip(x, y, mDetailAdapter != null, listener);
233 .setDuration(FADE_DURATION)
234 .setListener(listener)
240 protected void setupDetailFooter(DetailAdapter adapter) {
241 final Intent settingsIntent = adapter.getSettingsIntent();
242 mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
243 mDetailSettingsButton.setOnClickListener(v -> {
244 Dependency.get(MetricsLogger.class).action(ACTION_QS_MORE_SETTINGS,
245 adapter.getMetricsCategory());
246 Dependency.get(ActivityStarter.class)
247 .postStartActivityDismissingKeyguard(settingsIntent, 0);
251 protected void setupDetailHeader(final DetailAdapter adapter) {
252 mQsDetailHeaderTitle.setText(adapter.getTitle());
253 final Boolean toggleState = adapter.getToggleState();
254 if (toggleState == null) {
255 mQsDetailHeaderSwitch.setVisibility(INVISIBLE);
256 mQsDetailHeader.setClickable(false);
258 mQsDetailHeaderSwitch.setVisibility(VISIBLE);
259 handleToggleStateChanged(toggleState, adapter.getToggleEnabled());
260 mQsDetailHeader.setClickable(true);
261 mQsDetailHeader.setOnClickListener(new OnClickListener() {
263 public void onClick(View v) {
264 boolean checked = !mQsDetailHeaderSwitch.isChecked();
265 mQsDetailHeaderSwitch.setChecked(checked);
266 adapter.setToggleState(checked);
272 private void handleToggleStateChanged(boolean state, boolean toggleEnabled) {
273 mSwitchState = state;
274 if (mAnimatingOpen) {
277 mQsDetailHeaderSwitch.setChecked(state);
278 mQsDetailHeader.setEnabled(toggleEnabled);
279 mQsDetailHeaderSwitch.setEnabled(toggleEnabled);
282 private void handleScanStateChanged(boolean state) {
283 if (mScanState == state) return;
285 final Animatable anim = (Animatable) mQsDetailHeaderProgress.getDrawable();
287 mQsDetailHeaderProgress.animate().alpha(1f);
290 mQsDetailHeaderProgress.animate().alpha(0f);
295 private void checkPendingAnimations() {
296 handleToggleStateChanged(mSwitchState,
297 mDetailAdapter != null && mDetailAdapter.getToggleEnabled());
300 protected Callback mQsPanelCallback = new Callback() {
302 public void onToggleStateChanged(final boolean state) {
303 post(new Runnable() {
306 handleToggleStateChanged(state,
307 mDetailAdapter != null && mDetailAdapter.getToggleEnabled());
313 public void onShowingDetail(final DetailAdapter detail, final int x, final int y) {
314 post(new Runnable() {
317 handleShowingDetail(detail, x, y, true /* toggleQs */);
323 public void onScanStateChanged(final boolean state) {
324 post(new Runnable() {
327 handleScanStateChanged(state);
333 private final AnimatorListenerAdapter mHideGridContentWhenDone = new AnimatorListenerAdapter() {
334 public void onAnimationCancel(Animator animation) {
335 // If we have been cancelled, remove the listener so that onAnimationEnd doesn't get
336 // called, this will avoid accidentally turning off the grid when we don't want to.
337 animation.removeListener(this);
338 mAnimatingOpen = false;
339 checkPendingAnimations();
343 public void onAnimationEnd(Animator animation) {
344 // Only hide content if still in detail state.
345 if (mDetailAdapter != null) {
346 mQsPanel.setGridContentVisibility(false);
347 mHeader.setVisibility(View.INVISIBLE);
349 mAnimatingOpen = false;
350 checkPendingAnimations();
354 private final AnimatorListenerAdapter mTeardownDetailWhenDone = new AnimatorListenerAdapter() {
355 public void onAnimationEnd(Animator animation) {
356 mDetailContent.removeAllViews();
357 setVisibility(View.INVISIBLE);
358 mClosingDetail = false;