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