1/*
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            int translate = isLayoutRtl() ? mDate.getWidth() : -mDate.getWidth();
164            mAlarmAnimator = new Builder().addFloat(mDate, "alpha", 1, 0)
165                    .addFloat(mDateTimeGroup, "translationX", 0, translate)
166                    .addFloat(mAlarmStatus, "alpha", 0, 1)
167                    .setListener(new ListenerAdapter() {
168                        @Override
169                        public void onAnimationAtStart() {
170                            mAlarmStatus.setVisibility(View.GONE);
171                        }
172
173                        @Override
174                        public void onAnimationStarted() {
175                            mAlarmStatus.setVisibility(View.VISIBLE);
176                        }
177                    }).build();
178        } else {
179            mAlarmAnimator = null;
180            mAlarmStatus.setVisibility(View.GONE);
181            mDate.setAlpha(1);
182            mDateTimeGroup.setTranslationX(0);
183        }
184        setExpansion(mExpansionAmount);
185    }
186
187    @Override
188    protected void onConfigurationChanged(Configuration newConfig) {
189        super.onConfigurationChanged(newConfig);
190        updateResources();
191    }
192
193    @Override
194    public void onRtlPropertiesChanged(int layoutDirection) {
195        super.onRtlPropertiesChanged(layoutDirection);
196        updateResources();
197    }
198
199    private void updateResources() {
200        FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size);
201
202        updateSettingsAnimator();
203    }
204
205    private void updateSettingsAnimator() {
206        mSettingsAlpha = createSettingsAlphaAnimator();
207
208        final boolean isRtl = isLayoutRtl();
209        if (isRtl && mDate.getWidth() == 0) {
210            mDate.addOnLayoutChangeListener(new OnLayoutChangeListener() {
211                @Override
212                public void onLayoutChange(View v, int left, int top, int right, int bottom,
213                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
214                    mDate.setPivotX(getWidth());
215                    mDate.removeOnLayoutChangeListener(this);
216                }
217            });
218        } else {
219            mDate.setPivotX(isRtl ? mDate.getWidth() : 0);
220        }
221    }
222
223    @Nullable
224    private TouchAnimator createSettingsAlphaAnimator() {
225        return new TouchAnimator.Builder()
226                .addFloat(mEdit, "alpha", 0, 1)
227                .addFloat(mMultiUserSwitch, "alpha", 0, 1)
228                .build();
229    }
230
231    @Override
232    public void setKeyguardShowing(boolean keyguardShowing) {
233        mKeyguardShowing = keyguardShowing;
234        setExpansion(mExpansionAmount);
235    }
236
237    @Override
238    public void setExpanded(boolean expanded) {
239        if (mExpanded == expanded) return;
240        mExpanded = expanded;
241        updateEverything();
242    }
243
244    @Override
245    public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
246        mNextAlarm = nextAlarm;
247        if (nextAlarm != null) {
248            String alarmString = KeyguardStatusView.formatNextAlarm(getContext(), nextAlarm);
249            mAlarmStatus.setText(alarmString);
250            mAlarmStatus.setContentDescription(mContext.getString(
251                    R.string.accessibility_quick_settings_alarm, alarmString));
252            mAlarmStatusCollapsed.setContentDescription(mContext.getString(
253                    R.string.accessibility_quick_settings_alarm, alarmString));
254        }
255        if (mAlarmShowing != (nextAlarm != null)) {
256            mAlarmShowing = nextAlarm != null;
257            updateAnimator(getWidth());
258            updateEverything();
259        }
260    }
261
262    @Override
263    public void setExpansion(float headerExpansionFraction) {
264        mExpansionAmount = headerExpansionFraction;
265        if (mAnimator != null) mAnimator.setPosition(headerExpansionFraction);
266        if (mAlarmAnimator != null) mAlarmAnimator.setPosition(
267                mKeyguardShowing ? 0 : headerExpansionFraction);
268
269        if (mSettingsAlpha != null) {
270            mSettingsAlpha.setPosition(headerExpansionFraction);
271        }
272
273        updateAlarmVisibilities();
274
275        mExpandIndicator.setExpanded(headerExpansionFraction > EXPAND_INDICATOR_THRESHOLD);
276    }
277
278    @Override
279    @VisibleForTesting
280    public void onDetachedFromWindow() {
281        setListening(false);
282        super.onDetachedFromWindow();
283    }
284
285    private void updateAlarmVisibilities() {
286        mAlarmStatusCollapsed.setVisibility(mAlarmShowing ? View.VISIBLE : View.GONE);
287    }
288
289    @Override
290    public void setListening(boolean listening) {
291        if (listening == mListening) {
292            return;
293        }
294        mListening = listening;
295        updateListeners();
296    }
297
298    @Override
299    public View getExpandView() {
300        return findViewById(R.id.expand_indicator);
301    }
302
303    public void updateEverything() {
304        post(() -> {
305            updateVisibilities();
306            setClickable(false);
307        });
308    }
309
310    private void updateVisibilities() {
311        updateAlarmVisibilities();
312        mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
313                TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
314        final boolean isDemo = UserManager.isDeviceInDemoMode(mContext);
315
316        mMultiUserSwitch.setVisibility(mExpanded && mMultiUserSwitch.hasMultipleUsers() && !isDemo
317                ? View.VISIBLE : View.INVISIBLE);
318
319        mEdit.setVisibility(isDemo || !mExpanded ? View.INVISIBLE : View.VISIBLE);
320    }
321
322    private void updateListeners() {
323        if (mListening) {
324            mNextAlarmController.addCallback(this);
325            mUserInfoController.addCallback(this);
326            if (Dependency.get(NetworkController.class).hasVoiceCallingFeature()) {
327                Dependency.get(NetworkController.class).addEmergencyListener(this);
328                Dependency.get(NetworkController.class).addCallback(this);
329            }
330        } else {
331            mNextAlarmController.removeCallback(this);
332            mUserInfoController.removeCallback(this);
333            Dependency.get(NetworkController.class).removeEmergencyListener(this);
334            Dependency.get(NetworkController.class).removeCallback(this);
335        }
336    }
337
338    @Override
339    public void setQSPanel(final QSPanel qsPanel) {
340        mQsPanel = qsPanel;
341        if (mQsPanel != null) {
342            mMultiUserSwitch.setQsPanel(qsPanel);
343        }
344    }
345
346    @Override
347    public void onClick(View v) {
348        if (v == mSettingsButton) {
349            if (!Dependency.get(DeviceProvisionedController.class).isCurrentUserSetup()) {
350                // If user isn't setup just unlock the device and dump them back at SUW.
351                mActivityStarter.postQSRunnableDismissingKeyguard(() -> { });
352                return;
353            }
354            MetricsLogger.action(mContext,
355                    mExpanded ? MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH
356                            : MetricsProto.MetricsEvent.ACTION_QS_COLLAPSED_SETTINGS_LAUNCH);
357            if (mSettingsButton.isTunerClick()) {
358                Dependency.get(ActivityStarter.class).postQSRunnableDismissingKeyguard(() -> {
359                    if (TunerService.isTunerEnabled(mContext)) {
360                        TunerService.showResetRequest(mContext, () -> {
361                            // Relaunch settings so that the tuner disappears.
362                            startSettingsActivity();
363                        });
364                    } else {
365                        Toast.makeText(getContext(), R.string.tuner_toast,
366                                Toast.LENGTH_LONG).show();
367                        TunerService.setTunerEnabled(mContext, true);
368                    }
369                    startSettingsActivity();
370
371                });
372            } else {
373                startSettingsActivity();
374            }
375        } else if (v == mDateTimeGroup) {
376            Dependency.get(MetricsLogger.class).action(ACTION_QS_DATE,
377                    mNextAlarm != null);
378            if (mNextAlarm != null) {
379                PendingIntent showIntent = mNextAlarm.getShowIntent();
380                mActivityStarter.startPendingIntentDismissingKeyguard(showIntent);
381            } else {
382                mActivityStarter.postStartActivityDismissingKeyguard(new Intent(
383                        AlarmClock.ACTION_SHOW_ALARMS), 0);
384            }
385        }
386    }
387
388    private void startSettingsActivity() {
389        mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
390                true /* dismissShade */);
391    }
392
393    @Override
394    public void setEmergencyCallsOnly(boolean show) {
395        boolean changed = show != mShowEmergencyCallsOnly;
396        if (changed) {
397            mShowEmergencyCallsOnly = show;
398            if (mExpanded) {
399                updateEverything();
400            }
401        }
402    }
403
404    @Override
405    public void onUserInfoChanged(String name, Drawable picture, String userAccount) {
406        if (picture != null &&
407                UserManager.get(mContext).isGuestUser(ActivityManager.getCurrentUser())) {
408            picture = picture.getConstantState().newDrawable().mutate();
409            picture.setColorFilter(
410                    Utils.getColorAttr(mContext, android.R.attr.colorForeground),
411                    Mode.SRC_IN);
412        }
413        mMultiUserAvatar.setImageDrawable(picture);
414    }
415}
416