blob: 94da5f72be10ec6d2d5dbba3ddfab285fe92868c [file] [log] [blame]
Anthony Chen54daefe2017-04-07 17:19:54 -07001/*
2 * Copyright (C) 2017 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
17package com.android.systemui.qs;
18
19import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_QS_DATE;
20
21import android.app.ActivityManager;
22import android.app.AlarmManager;
23import android.app.PendingIntent;
24import android.content.Context;
25import android.content.Intent;
26import android.content.res.Configuration;
27import android.content.res.Resources;
28import android.graphics.PorterDuff.Mode;
29import android.graphics.drawable.Drawable;
30import android.graphics.drawable.RippleDrawable;
31import android.os.UserManager;
32import android.provider.AlarmClock;
33import android.support.annotation.Nullable;
34import android.support.annotation.VisibleForTesting;
35import android.util.AttributeSet;
36import android.view.View;
37import android.view.View.OnClickListener;
38import android.widget.FrameLayout;
39import android.widget.ImageView;
40import android.widget.LinearLayout;
41import android.widget.TextView;
42import android.widget.Toast;
43
44import com.android.internal.logging.MetricsLogger;
45import com.android.internal.logging.nano.MetricsProto;
46import com.android.keyguard.KeyguardStatusView;
47import com.android.settingslib.Utils;
48import com.android.systemui.Dependency;
49import com.android.systemui.FontSizeUtils;
50import com.android.systemui.R;
51import com.android.systemui.R.dimen;
52import com.android.systemui.R.id;
53import com.android.systemui.plugins.ActivityStarter;
54import com.android.systemui.qs.TouchAnimator.Builder;
55import com.android.systemui.qs.TouchAnimator.Listener;
56import com.android.systemui.qs.TouchAnimator.ListenerAdapter;
57import com.android.systemui.statusbar.phone.ExpandableIndicator;
58import com.android.systemui.statusbar.phone.MultiUserSwitch;
59import com.android.systemui.statusbar.phone.SettingsButton;
60import com.android.systemui.statusbar.policy.DeviceProvisionedController;
61import com.android.systemui.statusbar.policy.NetworkController;
62import com.android.systemui.statusbar.policy.NetworkController.EmergencyListener;
63import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
64import com.android.systemui.statusbar.policy.NextAlarmController;
65import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
66import com.android.systemui.statusbar.policy.UserInfoController;
67import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
68import com.android.systemui.tuner.TunerService;
69
70public class QSFooterImpl extends FrameLayout implements QSFooter,
71 NextAlarmChangeCallback, OnClickListener, OnUserInfoChangedListener, EmergencyListener,
72 SignalCallback {
73 private static final float EXPAND_INDICATOR_THRESHOLD = .93f;
74
75 private ActivityStarter mActivityStarter;
76 private NextAlarmController mNextAlarmController;
77 private UserInfoController mUserInfoController;
78 private SettingsButton mSettingsButton;
79 protected View mSettingsContainer;
80
81 private TextView mAlarmStatus;
82 private View mAlarmStatusCollapsed;
83 private View mDate;
84
85 private QSPanel mQsPanel;
86
87 private boolean mExpanded;
88 private boolean mAlarmShowing;
89
90 protected ExpandableIndicator mExpandIndicator;
91
92 private boolean mListening;
93 private AlarmManager.AlarmClockInfo mNextAlarm;
94
95 private boolean mShowEmergencyCallsOnly;
96 protected MultiUserSwitch mMultiUserSwitch;
97 private ImageView mMultiUserAvatar;
98
99 protected TouchAnimator mSettingsAlpha;
100 private float mExpansionAmount;
101
102 protected View mEdit;
103 private TouchAnimator mAnimator;
104 private View mDateTimeGroup;
105 private boolean mKeyguardShowing;
106 private TouchAnimator mAlarmAnimator;
107
108 public QSFooterImpl(Context context, AttributeSet attrs) {
109 super(context, attrs);
110 }
111
112 @Override
113 protected void onFinishInflate() {
114 super.onFinishInflate();
115 Resources res = getResources();
116
117 mEdit = findViewById(android.R.id.edit);
118 mEdit.setOnClickListener(view ->
119 Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() ->
120 mQsPanel.showEdit(view)));
121
122 mDateTimeGroup = findViewById(id.date_time_alarm_group);
123 mDate = findViewById(R.id.date);
124
125 mExpandIndicator = findViewById(R.id.expand_indicator);
126 mSettingsButton = findViewById(R.id.settings_button);
127 mSettingsContainer = findViewById(R.id.settings_button_container);
128 mSettingsButton.setOnClickListener(this);
129
130 mAlarmStatusCollapsed = findViewById(R.id.alarm_status_collapsed);
131 mAlarmStatus = findViewById(R.id.alarm_status);
132 mDateTimeGroup.setOnClickListener(this);
133
134 mMultiUserSwitch = findViewById(R.id.multi_user_switch);
135 mMultiUserAvatar = mMultiUserSwitch.findViewById(R.id.multi_user_avatar);
136
137 // RenderThread is doing more harm than good when touching the header (to expand quick
138 // settings), so disable it for this view
139 ((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true);
140 ((RippleDrawable) mExpandIndicator.getBackground()).setForceSoftware(true);
141
142 updateResources();
143
144 mNextAlarmController = Dependency.get(NextAlarmController.class);
145 mUserInfoController = Dependency.get(UserInfoController.class);
146 mActivityStarter = Dependency.get(ActivityStarter.class);
147 addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight,
148 oldBottom) -> updateAnimator(right - left));
149 }
150
151 private void updateAnimator(int width) {
152 int numTiles = QuickQSPanel.getNumQuickTiles(mContext);
153 int size = mContext.getResources().getDimensionPixelSize(R.dimen.qs_quick_tile_size)
154 - mContext.getResources().getDimensionPixelSize(dimen.qs_quick_tile_padding);
155 int remaining = (width - numTiles * size) / (numTiles - 1);
156 int defSpace = mContext.getResources().getDimensionPixelOffset(R.dimen.default_gear_space);
157
158 mAnimator = new Builder()
159 .addFloat(mSettingsContainer, "translationX", -(remaining - defSpace), 0)
160 .addFloat(mSettingsButton, "rotation", -120, 0)
161 .build();
162 if (mAlarmShowing) {
163 mAlarmAnimator = new Builder().addFloat(mDate, "alpha", 1, 0)
164 .addFloat(mDateTimeGroup, "translationX", 0, -mDate.getWidth())
165 .addFloat(mAlarmStatus, "alpha", 0, 1)
166 .setListener(new ListenerAdapter() {
167 @Override
168 public void onAnimationAtStart() {
169 mAlarmStatus.setVisibility(View.GONE);
170 }
171
172 @Override
173 public void onAnimationStarted() {
174 mAlarmStatus.setVisibility(View.VISIBLE);
175 }
176 }).build();
177 } else {
178 mAlarmAnimator = null;
179 mAlarmStatus.setVisibility(View.GONE);
180 mDate.setAlpha(1);
181 mDateTimeGroup.setTranslationX(0);
182 }
183 setExpansion(mExpansionAmount);
184 }
185
186 @Override
187 protected void onConfigurationChanged(Configuration newConfig) {
188 super.onConfigurationChanged(newConfig);
189 updateResources();
190 }
191
192 @Override
193 public void onRtlPropertiesChanged(int layoutDirection) {
194 super.onRtlPropertiesChanged(layoutDirection);
195 updateResources();
196 }
197
198 private void updateResources() {
199 FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size);
200
201 updateSettingsAnimator();
202 }
203
204 private void updateSettingsAnimator() {
205 mSettingsAlpha = createSettingsAlphaAnimator();
206
207 final boolean isRtl = isLayoutRtl();
208 if (isRtl && mDate.getWidth() == 0) {
209 mDate.addOnLayoutChangeListener(new OnLayoutChangeListener() {
210 @Override
211 public void onLayoutChange(View v, int left, int top, int right, int bottom,
212 int oldLeft, int oldTop, int oldRight, int oldBottom) {
213 mDate.setPivotX(getWidth());
214 mDate.removeOnLayoutChangeListener(this);
215 }
216 });
217 } else {
218 mDate.setPivotX(isRtl ? mDate.getWidth() : 0);
219 }
220 }
221
222 @Nullable
223 private TouchAnimator createSettingsAlphaAnimator() {
224 return new TouchAnimator.Builder()
225 .addFloat(mEdit, "alpha", 0, 1)
226 .addFloat(mMultiUserSwitch, "alpha", 0, 1)
227 .build();
228 }
229
230 @Override
231 public void setKeyguardShowing(boolean keyguardShowing) {
232 mKeyguardShowing = keyguardShowing;
233 setExpansion(mExpansionAmount);
234 }
235
236 @Override
237 public void setExpanded(boolean expanded) {
238 if (mExpanded == expanded) return;
239 mExpanded = expanded;
240 updateEverything();
241 }
242
243 @Override
244 public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
245 mNextAlarm = nextAlarm;
246 if (nextAlarm != null) {
247 String alarmString = KeyguardStatusView.formatNextAlarm(getContext(), nextAlarm);
248 mAlarmStatus.setText(alarmString);
249 mAlarmStatus.setContentDescription(mContext.getString(
250 R.string.accessibility_quick_settings_alarm, alarmString));
251 mAlarmStatusCollapsed.setContentDescription(mContext.getString(
252 R.string.accessibility_quick_settings_alarm, alarmString));
253 }
254 if (mAlarmShowing != (nextAlarm != null)) {
255 mAlarmShowing = nextAlarm != null;
256 updateAnimator(getWidth());
257 updateEverything();
258 }
259 }
260
261 @Override
262 public void setExpansion(float headerExpansionFraction) {
263 mExpansionAmount = headerExpansionFraction;
264 if (mAnimator != null) mAnimator.setPosition(headerExpansionFraction);
265 if (mAlarmAnimator != null) mAlarmAnimator.setPosition(
266 mKeyguardShowing ? 0 : headerExpansionFraction);
267
268 if (mSettingsAlpha != null) {
269 mSettingsAlpha.setPosition(headerExpansionFraction);
270 }
271
272 updateAlarmVisibilities();
273
274 mExpandIndicator.setExpanded(headerExpansionFraction > EXPAND_INDICATOR_THRESHOLD);
275 }
276
277 @Override
278 @VisibleForTesting
279 public void onDetachedFromWindow() {
280 setListening(false);
281 super.onDetachedFromWindow();
282 }
283
284 private void updateAlarmVisibilities() {
285 mAlarmStatusCollapsed.setVisibility(mAlarmShowing ? View.VISIBLE : View.GONE);
286 }
287
288 @Override
289 public void setListening(boolean listening) {
290 if (listening == mListening) {
291 return;
292 }
293 mListening = listening;
294 updateListeners();
295 }
296
297 @Override
298 public View getExpandView() {
299 return findViewById(R.id.expand_indicator);
300 }
301
302 public void updateEverything() {
303 post(() -> {
304 updateVisibilities();
305 setClickable(false);
306 });
307 }
308
309 private void updateVisibilities() {
310 updateAlarmVisibilities();
311 mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
312 TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
313 final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
314
315 mMultiUserSwitch.setVisibility(mExpanded && mMultiUserSwitch.hasMultipleUsers() && !isDemo
316 ? View.VISIBLE : View.INVISIBLE);
317
318 mEdit.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
319 }
320
321 private void updateListeners() {
322 if (mListening) {
323 mNextAlarmController.addCallback(this);
324 mUserInfoController.addCallback(this);
325 if (Dependency.get(NetworkController.class).hasVoiceCallingFeature()) {
326 Dependency.get(NetworkController.class).addEmergencyListener(this);
327 Dependency.get(NetworkController.class).addCallback(this);
328 }
329 } else {
330 mNextAlarmController.removeCallback(this);
331 mUserInfoController.removeCallback(this);
332 Dependency.get(NetworkController.class).removeEmergencyListener(this);
333 Dependency.get(NetworkController.class).removeCallback(this);
334 }
335 }
336
337 @Override
338 public void setQSPanel(final QSPanel qsPanel) {
339 mQsPanel = qsPanel;
340 if (mQsPanel != null) {
341 mMultiUserSwitch.setQsPanel(qsPanel);
342 }
343 }
344
345 @Override
346 public void onClick(View v) {
347 if (v == mSettingsButton) {
348 if (!Dependency.get(DeviceProvisionedController.class).isCurrentUserSetup()) {
349 // If user isn't setup just unlock the device and dump them back at SUW.
350 mActivityStarter.postQSRunnableDismissingKeyguard(() -> { });
351 return;
352 }
353 MetricsLogger.action(mContext,
354 mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
355 : MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
356 if (mSettingsButton.isTunerClick()) {
357 Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> {
358 if (TunerService.isTunerEnabled(mContext)) {
359 TunerService.showResetRequest(mContext, () -> {
360 // Relaunch settings so that the tuner disappears.
361 startSettingsActivity();
362 });
363 } else {
364 Toast.makeText(getContext(), R.string.tuner_toast,
365 Toast.LENGTH_LONG).show();
366 TunerService.setTunerEnabled(mContext, true);
367 }
368 startSettingsActivity();
369
370 });
371 } else {
372 startSettingsActivity();
373 }
374 } else if (v == mDateTimeGroup) {
375 Dependency.get(MetricsLogger.class).action(ACTION_QS_DATE,
376 mNextAlarm != null);
377 if (mNextAlarm != null) {
378 PendingIntent showIntent = mNextAlarm.getShowIntent();
379 mActivityStarter.startPendingIntentDismissingKeyguard(showIntent);
380 } else {
381 mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
382 AlarmClock.ACTION_SHOW_ALARMS), 0);
383 }
384 }
385 }
386
387 private void startSettingsActivity() {
388 mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
389 true /* dismissShade */);
390 }
391
392 @Override
393 public void setEmergencyCallsOnly(boolean show) {
394 boolean changed = show != mShowEmergencyCallsOnly;
395 if (changed) {
396 mShowEmergencyCallsOnly = show;
397 if (mExpanded) {
398 updateEverything();
399 }
400 }
401 }
402
403 @Override
404 public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
405 if (picture != null &&
406 UserManager.get(mContext).isGuestUser(ActivityManager.getCurrentUser())) {
407 picture = picture.getConstantState().newDrawable().mutate();
408 picture.setColorFilter(
409 Utils.getColorAttr(mContext, android.R.attr.colorForeground),
410 Mode.SRC_IN);
411 }
412 mMultiUserAvatar.setImageDrawable(picture);
413 }
414}