blob: 2be8a9704e1c744ce075ccb1bf2ba7631e5cca7c [file] [log] [blame]
Jason Monk377e7ad2016-02-16 14:03:21 -05001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
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
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
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.
13 */
14
15package com.android.systemui.qs;
16
Jason Monk8c09ac72017-03-16 11:53:40 -040017import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_MORE_SETTINGS;
18
Jason Monk377e7ad2016-02-16 14:03:21 -050019import android.animation.Animator;
20import android.animation.Animator.AnimatorListener;
21import android.animation.AnimatorListenerAdapter;
22import android.annotation.Nullable;
23import android.content.Context;
24import android.content.Intent;
25import android.content.res.Configuration;
26import android.graphics.drawable.Animatable;
27import android.util.AttributeSet;
28import android.util.SparseArray;
29import android.view.View;
30import android.view.ViewGroup;
Fabian Kozynski8a867952019-12-02 10:32:00 -050031import android.view.ViewStub;
Jason Monk377e7ad2016-02-16 14:03:21 -050032import android.view.accessibility.AccessibilityEvent;
33import android.widget.ImageView;
34import android.widget.LinearLayout;
35import android.widget.Switch;
36import android.widget.TextView;
Xiaohui Chen66448932016-04-18 12:53:28 -070037
Jason Monk377e7ad2016-02-16 14:03:21 -050038import com.android.internal.logging.MetricsLogger;
Fabian Kozynski2ff6df92020-04-24 12:00:49 -040039import com.android.internal.logging.UiEventLogger;
Jason Monk9c7844c2017-01-18 15:21:53 -050040import com.android.systemui.Dependency;
Jason Monk377e7ad2016-02-16 14:03:21 -050041import com.android.systemui.FontSizeUtils;
42import com.android.systemui.R;
Jason Monkec34da82017-02-24 15:57:05 -050043import com.android.systemui.plugins.ActivityStarter;
Jason Monke5b770e2017-03-03 21:49:29 -050044import com.android.systemui.plugins.qs.DetailAdapter;
Jason Monk9c7844c2017-01-18 15:21:53 -050045import com.android.systemui.statusbar.CommandQueue;
Jason Monk377e7ad2016-02-16 14:03:21 -050046
47public class QSDetail extends LinearLayout {
48
49 private static final String TAG = "QSDetail";
50 private static final long FADE_DURATION = 300;
51
52 private final SparseArray<View> mDetailViews = new SparseArray<>();
Fabian Kozynski2ff6df92020-04-24 12:00:49 -040053 private final UiEventLogger mUiEventLogger = QSEvents.INSTANCE.getQsUiEventsLogger();
Jason Monk377e7ad2016-02-16 14:03:21 -050054
55 private ViewGroup mDetailContent;
Muyuan Li0e9f5382016-04-27 15:51:15 -070056 protected TextView mDetailSettingsButton;
57 protected TextView mDetailDoneButton;
Jason Monk377e7ad2016-02-16 14:03:21 -050058 private QSDetailClipper mClipper;
59 private DetailAdapter mDetailAdapter;
60 private QSPanel mQsPanel;
61
Muyuan Li0e9f5382016-04-27 15:51:15 -070062 protected View mQsDetailHeader;
63 protected TextView mQsDetailHeaderTitle;
Fabian Kozynski8a867952019-12-02 10:32:00 -050064 private ViewStub mQsDetailHeaderSwitchStub;
65 private Switch mQsDetailHeaderSwitch;
Muyuan Lie3cecd72016-05-14 12:35:12 -070066 protected ImageView mQsDetailHeaderProgress;
Jason Monk377e7ad2016-02-16 14:03:21 -050067
Muyuan Li0e9f5382016-04-27 15:51:15 -070068 protected QSTileHost mHost;
Jason Monk377e7ad2016-02-16 14:03:21 -050069
70 private boolean mScanState;
71 private boolean mClosingDetail;
72 private boolean mFullyExpanded;
Jason Monke5b770e2017-03-03 21:49:29 -050073 private QuickStatusBarHeader mHeader;
Xiaohui Chen66448932016-04-18 12:53:28 -070074 private boolean mTriggeredExpand;
75 private int mOpenX;
76 private int mOpenY;
Jason Monk35a9c142016-07-25 10:18:30 -040077 private boolean mAnimatingOpen;
Jason Monk48e093e2016-07-15 10:37:07 -040078 private boolean mSwitchState;
Jason Monkb4ec0b92017-06-13 13:47:54 -040079 private View mFooter;
Jason Monk377e7ad2016-02-16 14:03:21 -050080
81 public QSDetail(Context context, @Nullable AttributeSet attrs) {
82 super(context, attrs);
83 }
84
85 @Override
86 protected void onConfigurationChanged(Configuration newConfig) {
87 super.onConfigurationChanged(newConfig);
88 FontSizeUtils.updateFontSize(mDetailDoneButton, R.dimen.qs_detail_button_text_size);
89 FontSizeUtils.updateFontSize(mDetailSettingsButton, R.dimen.qs_detail_button_text_size);
90
91 for (int i = 0; i < mDetailViews.size(); i++) {
92 mDetailViews.valueAt(i).dispatchConfigurationChanged(newConfig);
93 }
94 }
95
96 @Override
97 protected void onFinishInflate() {
98 super.onFinishInflate();
Alan Viverette51efddb2017-04-05 10:00:01 -040099 mDetailContent = findViewById(android.R.id.content);
100 mDetailSettingsButton = findViewById(android.R.id.button2);
101 mDetailDoneButton = findViewById(android.R.id.button1);
Jason Monk377e7ad2016-02-16 14:03:21 -0500102
103 mQsDetailHeader = findViewById(R.id.qs_detail_header);
Jason Monk377e7ad2016-02-16 14:03:21 -0500104 mQsDetailHeaderTitle = (TextView) mQsDetailHeader.findViewById(android.R.id.title);
Fabian Kozynski8a867952019-12-02 10:32:00 -0500105 mQsDetailHeaderSwitchStub = mQsDetailHeader.findViewById(R.id.toggle_stub);
Alan Viverette51efddb2017-04-05 10:00:01 -0400106 mQsDetailHeaderProgress = findViewById(R.id.qs_detail_header_progress);
Jason Monk377e7ad2016-02-16 14:03:21 -0500107
108 updateDetailText();
109
110 mClipper = new QSDetailClipper(this);
111
112 final OnClickListener doneListener = new OnClickListener() {
113 @Override
114 public void onClick(View v) {
115 announceForAccessibility(
116 mContext.getString(R.string.accessibility_desc_quick_settings));
117 mQsPanel.closeDetail();
118 }
119 };
Jason Monk377e7ad2016-02-16 14:03:21 -0500120 mDetailDoneButton.setOnClickListener(doneListener);
121 }
122
Jason Monkb4ec0b92017-06-13 13:47:54 -0400123 public void setQsPanel(QSPanel panel, QuickStatusBarHeader header, View footer) {
Jason Monk377e7ad2016-02-16 14:03:21 -0500124 mQsPanel = panel;
Jason Monk589bb702016-04-12 15:44:43 -0400125 mHeader = header;
Jason Monkb4ec0b92017-06-13 13:47:54 -0400126 mFooter = footer;
Xiaohui Chen66448932016-04-18 12:53:28 -0700127 mHeader.setCallback(mQsPanelCallback);
Jason Monk377e7ad2016-02-16 14:03:21 -0500128 mQsPanel.setCallback(mQsPanelCallback);
129 }
130
131 public void setHost(QSTileHost host) {
132 mHost = host;
133 }
134 public boolean isShowingDetail() {
135 return mDetailAdapter != null;
136 }
137
138 public void setFullyExpanded(boolean fullyExpanded) {
139 mFullyExpanded = fullyExpanded;
140 }
141
Xiaohui Chen66448932016-04-18 12:53:28 -0700142 public void setExpanded(boolean qsExpanded) {
143 if (!qsExpanded) {
144 mTriggeredExpand = false;
145 }
146 }
147
Jason Monk377e7ad2016-02-16 14:03:21 -0500148 private void updateDetailText() {
149 mDetailDoneButton.setText(R.string.quick_settings_done);
150 mDetailSettingsButton.setText(R.string.quick_settings_more_settings);
151 }
152
153 public void updateResources() {
154 updateDetailText();
155 }
156
157 public boolean isClosingDetail() {
158 return mClosingDetail;
159 }
160
Jason Monke5b770e2017-03-03 21:49:29 -0500161 public interface Callback {
162 void onShowingDetail(DetailAdapter detail, int x, int y);
163 void onToggleStateChanged(boolean state);
164 void onScanStateChanged(boolean state);
165 }
Muyuan Li0e9f5382016-04-27 15:51:15 -0700166
Jason Monk46767b72016-08-18 10:58:04 -0400167 public void handleShowingDetail(final DetailAdapter adapter, int x, int y,
Muyuan Libc55b2a2016-06-24 15:40:51 -0700168 boolean toggleQs) {
Jason Monk377e7ad2016-02-16 14:03:21 -0500169 final boolean showingDetail = adapter != null;
170 setClickable(showingDetail);
171 if (showingDetail) {
Muyuan Li0e9f5382016-04-27 15:51:15 -0700172 setupDetailHeader(adapter);
Muyuan Libc55b2a2016-06-24 15:40:51 -0700173 if (toggleQs && !mFullyExpanded) {
Xiaohui Chen66448932016-04-18 12:53:28 -0700174 mTriggeredExpand = true;
Dave Mankoffbcaca8a2019-10-31 18:04:08 -0400175 Dependency.get(CommandQueue.class).animateExpandSettingsPanel(null);
Xiaohui Chen66448932016-04-18 12:53:28 -0700176 } else {
177 mTriggeredExpand = false;
178 }
179 mOpenX = x;
180 mOpenY = y;
181 } else {
182 // Ensure we collapse into the same point we opened from.
183 x = mOpenX;
184 y = mOpenY;
Muyuan Libc55b2a2016-06-24 15:40:51 -0700185 if (toggleQs && mTriggeredExpand) {
Dave Mankoffbcaca8a2019-10-31 18:04:08 -0400186 Dependency.get(CommandQueue.class).animateCollapsePanels();
Xiaohui Chen66448932016-04-18 12:53:28 -0700187 mTriggeredExpand = false;
188 }
Jason Monk377e7ad2016-02-16 14:03:21 -0500189 }
190
191 boolean visibleDiff = (mDetailAdapter != null) != (adapter != null);
192 if (!visibleDiff && mDetailAdapter == adapter) return; // already in right state
193 AnimatorListener listener = null;
194 if (adapter != null) {
195 int viewCacheIndex = adapter.getMetricsCategory();
196 View detailView = adapter.createDetailView(mContext, mDetailViews.get(viewCacheIndex),
197 mDetailContent);
198 if (detailView == null) throw new IllegalStateException("Must return detail view");
199
Muyuan Li0e9f5382016-04-27 15:51:15 -0700200 setupDetailFooter(adapter);
Jason Monk377e7ad2016-02-16 14:03:21 -0500201
202 mDetailContent.removeAllViews();
203 mDetailContent.addView(detailView);
204 mDetailViews.put(viewCacheIndex, detailView);
Jason Monk8c09ac72017-03-16 11:53:40 -0400205 Dependency.get(MetricsLogger.class).visible(adapter.getMetricsCategory());
Fabian Kozynski2ff6df92020-04-24 12:00:49 -0400206 mUiEventLogger.log(adapter.openDetailEvent());
Jason Monk377e7ad2016-02-16 14:03:21 -0500207 announceForAccessibility(mContext.getString(
208 R.string.accessibility_quick_settings_detail,
209 adapter.getTitle()));
210 mDetailAdapter = adapter;
211 listener = mHideGridContentWhenDone;
212 setVisibility(View.VISIBLE);
213 } else {
214 if (mDetailAdapter != null) {
Jason Monk8c09ac72017-03-16 11:53:40 -0400215 Dependency.get(MetricsLogger.class).hidden(mDetailAdapter.getMetricsCategory());
Fabian Kozynski2ff6df92020-04-24 12:00:49 -0400216 mUiEventLogger.log(mDetailAdapter.closeDetailEvent());
Jason Monk377e7ad2016-02-16 14:03:21 -0500217 }
218 mClosingDetail = true;
219 mDetailAdapter = null;
220 listener = mTeardownDetailWhenDone;
Jason Monk589bb702016-04-12 15:44:43 -0400221 mHeader.setVisibility(View.VISIBLE);
Jason Monkb4ec0b92017-06-13 13:47:54 -0400222 mFooter.setVisibility(View.VISIBLE);
Jason Monk377e7ad2016-02-16 14:03:21 -0500223 mQsPanel.setGridContentVisibility(true);
224 mQsPanelCallback.onScanStateChanged(false);
225 }
226 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
Muyuan Li0e9f5382016-04-27 15:51:15 -0700227
228 animateDetailVisibleDiff(x, y, visibleDiff, listener);
229 }
230
231 protected void animateDetailVisibleDiff(int x, int y, boolean visibleDiff, AnimatorListener listener) {
Jason Monk377e7ad2016-02-16 14:03:21 -0500232 if (visibleDiff) {
Jason Monkac3dec92016-07-26 09:22:18 -0400233 mAnimatingOpen = mDetailAdapter != null;
Jason Monk377e7ad2016-02-16 14:03:21 -0500234 if (mFullyExpanded || mDetailAdapter != null) {
235 setAlpha(1);
236 mClipper.animateCircularClip(x, y, mDetailAdapter != null, listener);
237 } else {
238 animate().alpha(0)
239 .setDuration(FADE_DURATION)
240 .setListener(listener)
241 .start();
242 }
243 }
244 }
245
Muyuan Li0e9f5382016-04-27 15:51:15 -0700246 protected void setupDetailFooter(DetailAdapter adapter) {
247 final Intent settingsIntent = adapter.getSettingsIntent();
248 mDetailSettingsButton.setVisibility(settingsIntent != null ? VISIBLE : GONE);
Jason Monk8c09ac72017-03-16 11:53:40 -0400249 mDetailSettingsButton.setOnClickListener(v -> {
250 Dependency.get(MetricsLogger.class).action(ACTION_QS_MORE_SETTINGS,
Jason Monk6997da72017-04-03 15:22:54 -0400251 adapter.getMetricsCategory());
Fabian Kozynski2ff6df92020-04-24 12:00:49 -0400252 mUiEventLogger.log(adapter.moreSettingsEvent());
Jason Monk8c09ac72017-03-16 11:53:40 -0400253 Dependency.get(ActivityStarter.class)
254 .postStartActivityDismissingKeyguard(settingsIntent, 0);
255 });
Muyuan Li0e9f5382016-04-27 15:51:15 -0700256 }
257
258 protected void setupDetailHeader(final DetailAdapter adapter) {
259 mQsDetailHeaderTitle.setText(adapter.getTitle());
260 final Boolean toggleState = adapter.getToggleState();
261 if (toggleState == null) {
Fabian Kozynski8a867952019-12-02 10:32:00 -0500262 if (mQsDetailHeaderSwitch != null) mQsDetailHeaderSwitch.setVisibility(INVISIBLE);
Muyuan Li0e9f5382016-04-27 15:51:15 -0700263 mQsDetailHeader.setClickable(false);
264 } else {
Fabian Kozynski8a867952019-12-02 10:32:00 -0500265 if (mQsDetailHeaderSwitch == null) {
266 mQsDetailHeaderSwitch = (Switch) mQsDetailHeaderSwitchStub.inflate();
267 }
Muyuan Li0e9f5382016-04-27 15:51:15 -0700268 mQsDetailHeaderSwitch.setVisibility(VISIBLE);
Jason Monkac3dec92016-07-26 09:22:18 -0400269 handleToggleStateChanged(toggleState, adapter.getToggleEnabled());
Muyuan Li0e9f5382016-04-27 15:51:15 -0700270 mQsDetailHeader.setClickable(true);
271 mQsDetailHeader.setOnClickListener(new OnClickListener() {
272 @Override
273 public void onClick(View v) {
274 boolean checked = !mQsDetailHeaderSwitch.isChecked();
275 mQsDetailHeaderSwitch.setChecked(checked);
276 adapter.setToggleState(checked);
277 }
278 });
279 }
280 }
281
Jason Monk35a9c142016-07-25 10:18:30 -0400282 private void handleToggleStateChanged(boolean state, boolean toggleEnabled) {
Jason Monk48e093e2016-07-15 10:37:07 -0400283 mSwitchState = state;
Jason Monk35a9c142016-07-25 10:18:30 -0400284 if (mAnimatingOpen) {
Jason Monk48e093e2016-07-15 10:37:07 -0400285 return;
286 }
Fabian Kozynski8a867952019-12-02 10:32:00 -0500287 if (mQsDetailHeaderSwitch != null) mQsDetailHeaderSwitch.setChecked(state);
Jason Monkfac25382016-07-19 14:13:37 -0400288 mQsDetailHeader.setEnabled(toggleEnabled);
Fabian Kozynski8a867952019-12-02 10:32:00 -0500289 if (mQsDetailHeaderSwitch != null) mQsDetailHeaderSwitch.setEnabled(toggleEnabled);
Jason Monk377e7ad2016-02-16 14:03:21 -0500290 }
291
292 private void handleScanStateChanged(boolean state) {
293 if (mScanState == state) return;
294 mScanState = state;
295 final Animatable anim = (Animatable) mQsDetailHeaderProgress.getDrawable();
296 if (state) {
Jason Monkd9fcc642017-08-01 10:13:43 -0400297 mQsDetailHeaderProgress.animate().cancel();
298 mQsDetailHeaderProgress.animate()
299 .alpha(1)
300 .withEndAction(anim::start)
301 .start();
Jason Monk377e7ad2016-02-16 14:03:21 -0500302 } else {
Jason Monkd9fcc642017-08-01 10:13:43 -0400303 mQsDetailHeaderProgress.animate().cancel();
304 mQsDetailHeaderProgress.animate()
305 .alpha(0f)
306 .withEndAction(anim::stop)
307 .start();
Jason Monk377e7ad2016-02-16 14:03:21 -0500308 }
309 }
310
Jason Monk48e093e2016-07-15 10:37:07 -0400311 private void checkPendingAnimations() {
Jason Monk35a9c142016-07-25 10:18:30 -0400312 handleToggleStateChanged(mSwitchState,
313 mDetailAdapter != null && mDetailAdapter.getToggleEnabled());
Jason Monk48e093e2016-07-15 10:37:07 -0400314 }
315
Jason Monk46767b72016-08-18 10:58:04 -0400316 protected Callback mQsPanelCallback = new Callback() {
Jason Monk377e7ad2016-02-16 14:03:21 -0500317 @Override
318 public void onToggleStateChanged(final boolean state) {
319 post(new Runnable() {
320 @Override
321 public void run() {
Jason Monk35a9c142016-07-25 10:18:30 -0400322 handleToggleStateChanged(state,
323 mDetailAdapter != null && mDetailAdapter.getToggleEnabled());
Jason Monk377e7ad2016-02-16 14:03:21 -0500324 }
325 });
326 }
327
328 @Override
329 public void onShowingDetail(final DetailAdapter detail, final int x, final int y) {
330 post(new Runnable() {
331 @Override
332 public void run() {
Fabian Kozynski511e8b92018-10-10 12:31:35 -0400333 if (isAttachedToWindow()) {
334 handleShowingDetail(detail, x, y, false /* toggleQs */);
335 }
Jason Monk377e7ad2016-02-16 14:03:21 -0500336 }
337 });
338 }
339
340 @Override
341 public void onScanStateChanged(final boolean state) {
342 post(new Runnable() {
343 @Override
344 public void run() {
345 handleScanStateChanged(state);
346 }
347 });
348 }
349 };
350
351 private final AnimatorListenerAdapter mHideGridContentWhenDone = new AnimatorListenerAdapter() {
352 public void onAnimationCancel(Animator animation) {
353 // If we have been cancelled, remove the listener so that onAnimationEnd doesn't get
354 // called, this will avoid accidentally turning off the grid when we don't want to.
355 animation.removeListener(this);
Jason Monk35a9c142016-07-25 10:18:30 -0400356 mAnimatingOpen = false;
Jason Monk48e093e2016-07-15 10:37:07 -0400357 checkPendingAnimations();
Jason Monk377e7ad2016-02-16 14:03:21 -0500358 };
359
360 @Override
361 public void onAnimationEnd(Animator animation) {
362 // Only hide content if still in detail state.
363 if (mDetailAdapter != null) {
364 mQsPanel.setGridContentVisibility(false);
Jason Monk589bb702016-04-12 15:44:43 -0400365 mHeader.setVisibility(View.INVISIBLE);
Jason Monkb4ec0b92017-06-13 13:47:54 -0400366 mFooter.setVisibility(View.INVISIBLE);
Jason Monk377e7ad2016-02-16 14:03:21 -0500367 }
Jason Monk35a9c142016-07-25 10:18:30 -0400368 mAnimatingOpen = false;
Jason Monk48e093e2016-07-15 10:37:07 -0400369 checkPendingAnimations();
Jason Monk377e7ad2016-02-16 14:03:21 -0500370 }
371 };
372
373 private final AnimatorListenerAdapter mTeardownDetailWhenDone = new AnimatorListenerAdapter() {
374 public void onAnimationEnd(Animator animation) {
375 mDetailContent.removeAllViews();
376 setVisibility(View.INVISIBLE);
377 mClosingDetail = false;
378 };
379 };
380}