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
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 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
110    public QSFooter(Context context, AttributeSet attrs) {
111        super(context, attrs);
112    }
113
114    @Override
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
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
200    protected void onConfigurationChanged(Configuration newConfig) {
201        super.onConfigurationChanged(newConfig);
202        updateResources();
203    }
204
205    @Override
206    public void onRtlPropertiesChanged(int layoutDirection) {
207        super.onRtlPropertiesChanged(layoutDirection);
208        updateResources();
209    }
210
211    private void updateResources() {
212        FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size);
213
214        updateSettingsAnimator();
215    }
216
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
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
257    public void setKeyguardShowing(boolean keyguardShowing) {
258        mKeyguardShowing = keyguardShowing;
259        setExpansion(mExpansionAmount);
260    }
261
262    public void setExpanded(boolean expanded) {
263        if (mExpanded == expanded) return;
264        mExpanded = expanded;
265        updateEverything();
266    }
267
268    @Override
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
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
303    public void onDetachedFromWindow() {
304        setListening(false);
305        super.onDetachedFromWindow();
306    }
307
308    private void updateAlarmVisibilities() {
309        mAlarmStatusCollapsed.setVisibility(mAlarmShowing ? View.VISIBLE : View.GONE);
310    }
311
312    public void setListening(boolean listening) {
313        if (listening == mListening) {
314            return;
315        }
316        mListening = listening;
317        updateListeners();
318    }
319
320    public View getExpandView() {
321        return findViewById(R.id.expand_indicator);
322    }
323
324    public void updateEverything() {
325        post(() -> {
326            updateVisibilities();
327            setClickable(false);
328        });
329    }
330
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
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
362    public void setQSPanel(final QSPanel qsPanel) {
363        mQsPanel = qsPanel;
364        if (mQsPanel != null) {
365            mMultiUserSwitch.setQsPanel(qsPanel);
366        }
367    }
368
369    @Override
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
411    private void startSettingsActivity() {
412        mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS),
413                true /* dismissShade */);
414    }
415
416    @Override
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
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