• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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.keyguard;
18 
19 import android.app.ActivityManager;
20 import android.app.IActivityManager;
21 import android.content.Context;
22 import android.content.res.Resources;
23 import android.graphics.Color;
24 import android.os.Handler;
25 import android.os.RemoteException;
26 import android.os.UserHandle;
27 import android.text.TextUtils;
28 import android.text.format.DateFormat;
29 import android.util.AttributeSet;
30 import android.util.Log;
31 import android.util.Slog;
32 import android.util.TypedValue;
33 import android.view.View;
34 import android.widget.GridLayout;
35 import android.widget.LinearLayout;
36 import android.widget.TextView;
37 
38 import androidx.core.graphics.ColorUtils;
39 
40 import com.android.internal.widget.LockPatternUtils;
41 import com.android.systemui.Dependency;
42 import com.android.systemui.R;
43 import com.android.systemui.statusbar.policy.ConfigurationController;
44 
45 import java.io.FileDescriptor;
46 import java.io.PrintWriter;
47 import java.util.Locale;
48 import java.util.TimeZone;
49 
50 public class KeyguardStatusView extends GridLayout implements
51         ConfigurationController.ConfigurationListener {
52     private static final boolean DEBUG = KeyguardConstants.DEBUG;
53     private static final String TAG = "KeyguardStatusView";
54     private static final int MARQUEE_DELAY_MS = 2000;
55 
56     private final LockPatternUtils mLockPatternUtils;
57     private final IActivityManager mIActivityManager;
58 
59     private LinearLayout mStatusViewContainer;
60     private TextView mLogoutView;
61     private KeyguardClockSwitch mClockView;
62     private TextView mOwnerInfo;
63     private KeyguardSliceView mKeyguardSlice;
64     private View mNotificationIcons;
65     private Runnable mPendingMarqueeStart;
66     private Handler mHandler;
67 
68     private boolean mPulsing;
69     private float mDarkAmount = 0;
70     private int mTextColor;
71 
72     /**
73      * Bottom margin that defines the margin between bottom of smart space and top of notification
74      * icons on AOD.
75      */
76     private int mIconTopMargin;
77     private int mIconTopMarginWithHeader;
78     private boolean mShowingHeader;
79 
80     private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
81 
82         @Override
83         public void onTimeChanged() {
84             refreshTime();
85         }
86 
87         @Override
88         public void onTimeZoneChanged(TimeZone timeZone) {
89             updateTimeZone(timeZone);
90         }
91 
92         @Override
93         public void onKeyguardVisibilityChanged(boolean showing) {
94             if (showing) {
95                 if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
96                 refreshTime();
97                 updateOwnerInfo();
98                 updateLogoutView();
99             }
100         }
101 
102         @Override
103         public void onStartedWakingUp() {
104             setEnableMarquee(true);
105         }
106 
107         @Override
108         public void onFinishedGoingToSleep(int why) {
109             setEnableMarquee(false);
110         }
111 
112         @Override
113         public void onUserSwitchComplete(int userId) {
114             refreshFormat();
115             updateOwnerInfo();
116             updateLogoutView();
117         }
118 
119         @Override
120         public void onLogoutEnabledChanged() {
121             updateLogoutView();
122         }
123     };
124 
KeyguardStatusView(Context context)125     public KeyguardStatusView(Context context) {
126         this(context, null, 0);
127     }
128 
KeyguardStatusView(Context context, AttributeSet attrs)129     public KeyguardStatusView(Context context, AttributeSet attrs) {
130         this(context, attrs, 0);
131     }
132 
KeyguardStatusView(Context context, AttributeSet attrs, int defStyle)133     public KeyguardStatusView(Context context, AttributeSet attrs, int defStyle) {
134         super(context, attrs, defStyle);
135         mIActivityManager = ActivityManager.getService();
136         mLockPatternUtils = new LockPatternUtils(getContext());
137         mHandler = new Handler();
138         onDensityOrFontScaleChanged();
139     }
140 
141     /**
142      * If we're presenting a custom clock of just the default one.
143      */
hasCustomClock()144     public boolean hasCustomClock() {
145         return mClockView.hasCustomClock();
146     }
147 
148     /**
149      * Set whether or not the lock screen is showing notifications.
150      */
setHasVisibleNotifications(boolean hasVisibleNotifications)151     public void setHasVisibleNotifications(boolean hasVisibleNotifications) {
152         mClockView.setHasVisibleNotifications(hasVisibleNotifications);
153     }
154 
setEnableMarquee(boolean enabled)155     private void setEnableMarquee(boolean enabled) {
156         if (DEBUG) Log.v(TAG, "Schedule setEnableMarquee: " + (enabled ? "Enable" : "Disable"));
157         if (enabled) {
158             if (mPendingMarqueeStart == null) {
159                 mPendingMarqueeStart = () -> {
160                     setEnableMarqueeImpl(true);
161                     mPendingMarqueeStart = null;
162                 };
163                 mHandler.postDelayed(mPendingMarqueeStart, MARQUEE_DELAY_MS);
164             }
165         } else {
166             if (mPendingMarqueeStart != null) {
167                 mHandler.removeCallbacks(mPendingMarqueeStart);
168                 mPendingMarqueeStart = null;
169             }
170             setEnableMarqueeImpl(false);
171         }
172     }
173 
setEnableMarqueeImpl(boolean enabled)174     private void setEnableMarqueeImpl(boolean enabled) {
175         if (DEBUG) Log.v(TAG, (enabled ? "Enable" : "Disable") + " transport text marquee");
176         if (mOwnerInfo != null) mOwnerInfo.setSelected(enabled);
177     }
178 
179     @Override
onFinishInflate()180     protected void onFinishInflate() {
181         super.onFinishInflate();
182         mStatusViewContainer = findViewById(R.id.status_view_container);
183         mLogoutView = findViewById(R.id.logout);
184         mNotificationIcons = findViewById(R.id.clock_notification_icon_container);
185         if (mLogoutView != null) {
186             mLogoutView.setOnClickListener(this::onLogoutClicked);
187         }
188 
189         mClockView = findViewById(R.id.keyguard_clock_container);
190         mClockView.setShowCurrentUserTime(true);
191         if (KeyguardClockAccessibilityDelegate.isNeeded(mContext)) {
192             mClockView.setAccessibilityDelegate(new KeyguardClockAccessibilityDelegate(mContext));
193         }
194         mOwnerInfo = findViewById(R.id.owner_info);
195         mKeyguardSlice = findViewById(R.id.keyguard_status_area);
196         mTextColor = mClockView.getCurrentTextColor();
197 
198         mKeyguardSlice.setContentChangeListener(this::onSliceContentChanged);
199         onSliceContentChanged();
200 
201         boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
202         setEnableMarquee(shouldMarquee);
203         refreshFormat();
204         updateOwnerInfo();
205         updateLogoutView();
206         updateDark();
207     }
208 
209     /**
210      * Moves clock, adjusting margins when slice content changes.
211      */
onSliceContentChanged()212     private void onSliceContentChanged() {
213         final boolean hasHeader = mKeyguardSlice.hasHeader();
214         mClockView.setKeyguardShowingHeader(hasHeader);
215         if (mShowingHeader == hasHeader) {
216             return;
217         }
218         mShowingHeader = hasHeader;
219         if (mNotificationIcons != null) {
220             // Update top margin since header has appeared/disappeared.
221             MarginLayoutParams params = (MarginLayoutParams) mNotificationIcons.getLayoutParams();
222             params.setMargins(params.leftMargin,
223                     hasHeader ? mIconTopMarginWithHeader : mIconTopMargin,
224                     params.rightMargin,
225                     params.bottomMargin);
226             mNotificationIcons.setLayoutParams(params);
227         }
228     }
229 
230     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)231     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
232         super.onLayout(changed, left, top, right, bottom);
233         layoutOwnerInfo();
234     }
235 
236     @Override
onDensityOrFontScaleChanged()237     public void onDensityOrFontScaleChanged() {
238         if (mClockView != null) {
239             mClockView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
240                     getResources().getDimensionPixelSize(R.dimen.widget_big_font_size));
241         }
242         if (mOwnerInfo != null) {
243             mOwnerInfo.setTextSize(TypedValue.COMPLEX_UNIT_PX,
244                     getResources().getDimensionPixelSize(R.dimen.widget_label_font_size));
245         }
246         loadBottomMargin();
247     }
248 
dozeTimeTick()249     public void dozeTimeTick() {
250         refreshTime();
251         mKeyguardSlice.refresh();
252     }
253 
refreshTime()254     private void refreshTime() {
255         mClockView.refresh();
256     }
257 
updateTimeZone(TimeZone timeZone)258     private void updateTimeZone(TimeZone timeZone) {
259         mClockView.onTimeZoneChanged(timeZone);
260     }
261 
refreshFormat()262     private void refreshFormat() {
263         Patterns.update(mContext);
264         mClockView.setFormat12Hour(Patterns.clockView12);
265         mClockView.setFormat24Hour(Patterns.clockView24);
266     }
267 
getLogoutButtonHeight()268     public int getLogoutButtonHeight() {
269         if (mLogoutView == null) {
270             return 0;
271         }
272         return mLogoutView.getVisibility() == VISIBLE ? mLogoutView.getHeight() : 0;
273     }
274 
getClockTextSize()275     public float getClockTextSize() {
276         return mClockView.getTextSize();
277     }
278 
279     /**
280      * Returns the preferred Y position of the clock.
281      *
282      * @param totalHeight The height available to position the clock.
283      * @return Y position of clock.
284      */
getClockPreferredY(int totalHeight)285     public int getClockPreferredY(int totalHeight) {
286         return mClockView.getPreferredY(totalHeight);
287     }
288 
updateLogoutView()289     private void updateLogoutView() {
290         if (mLogoutView == null) {
291             return;
292         }
293         mLogoutView.setVisibility(shouldShowLogout() ? VISIBLE : GONE);
294         // Logout button will stay in language of user 0 if we don't set that manually.
295         mLogoutView.setText(mContext.getResources().getString(
296                 com.android.internal.R.string.global_action_logout));
297     }
298 
updateOwnerInfo()299     private void updateOwnerInfo() {
300         if (mOwnerInfo == null) return;
301         String info = mLockPatternUtils.getDeviceOwnerInfo();
302         if (info == null) {
303             // Use the current user owner information if enabled.
304             final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled(
305                     KeyguardUpdateMonitor.getCurrentUser());
306             if (ownerInfoEnabled) {
307                 info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser());
308             }
309         }
310         mOwnerInfo.setText(info);
311         updateDark();
312     }
313 
314     @Override
onAttachedToWindow()315     protected void onAttachedToWindow() {
316         super.onAttachedToWindow();
317         Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mInfoCallback);
318         Dependency.get(ConfigurationController.class).addCallback(this);
319     }
320 
321     @Override
onDetachedFromWindow()322     protected void onDetachedFromWindow() {
323         super.onDetachedFromWindow();
324         Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mInfoCallback);
325         Dependency.get(ConfigurationController.class).removeCallback(this);
326     }
327 
328     @Override
onLocaleListChanged()329     public void onLocaleListChanged() {
330         refreshFormat();
331     }
332 
dump(FileDescriptor fd, PrintWriter pw, String[] args)333     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
334         pw.println("KeyguardStatusView:");
335         pw.println("  mOwnerInfo: " + (mOwnerInfo == null
336                 ? "null" : mOwnerInfo.getVisibility() == VISIBLE));
337         pw.println("  mPulsing: " + mPulsing);
338         pw.println("  mDarkAmount: " + mDarkAmount);
339         pw.println("  mTextColor: " + Integer.toHexString(mTextColor));
340         if (mLogoutView != null) {
341             pw.println("  logout visible: " + (mLogoutView.getVisibility() == VISIBLE));
342         }
343         if (mClockView != null) {
344             mClockView.dump(fd, pw, args);
345         }
346         if (mKeyguardSlice != null) {
347             mKeyguardSlice.dump(fd, pw, args);
348         }
349     }
350 
loadBottomMargin()351     private void loadBottomMargin() {
352         mIconTopMargin = getResources().getDimensionPixelSize(R.dimen.widget_vertical_padding);
353         mIconTopMarginWithHeader = getResources().getDimensionPixelSize(
354                 R.dimen.widget_vertical_padding_with_header);
355     }
356 
357     // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often.
358     // This is an optimization to ensure we only recompute the patterns when the inputs change.
359     private static final class Patterns {
360         static String clockView12;
361         static String clockView24;
362         static String cacheKey;
363 
update(Context context)364         static void update(Context context) {
365             final Locale locale = Locale.getDefault();
366             final Resources res = context.getResources();
367             final String clockView12Skel = res.getString(R.string.clock_12hr_format);
368             final String clockView24Skel = res.getString(R.string.clock_24hr_format);
369             final String key = locale.toString() + clockView12Skel + clockView24Skel;
370             if (key.equals(cacheKey)) return;
371 
372             clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel);
373             // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton
374             // format.  The following code removes the AM/PM indicator if we didn't want it.
375             if (!clockView12Skel.contains("a")) {
376                 clockView12 = clockView12.replaceAll("a", "").trim();
377             }
378 
379             clockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel);
380 
381             // Use fancy colon.
382             clockView24 = clockView24.replace(':', '\uee01');
383             clockView12 = clockView12.replace(':', '\uee01');
384 
385             cacheKey = key;
386         }
387     }
388 
setDarkAmount(float darkAmount)389     public void setDarkAmount(float darkAmount) {
390         if (mDarkAmount == darkAmount) {
391             return;
392         }
393         mDarkAmount = darkAmount;
394         mClockView.setDarkAmount(darkAmount);
395         updateDark();
396     }
397 
updateDark()398     private void updateDark() {
399         boolean dark = mDarkAmount == 1;
400         if (mLogoutView != null) {
401             mLogoutView.setAlpha(dark ? 0 : 1);
402         }
403 
404         if (mOwnerInfo != null) {
405             boolean hasText = !TextUtils.isEmpty(mOwnerInfo.getText());
406             mOwnerInfo.setVisibility(hasText ? VISIBLE : GONE);
407             layoutOwnerInfo();
408         }
409 
410         final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount);
411         mKeyguardSlice.setDarkAmount(mDarkAmount);
412         mClockView.setTextColor(blendedTextColor);
413     }
414 
layoutOwnerInfo()415     private void layoutOwnerInfo() {
416         if (mOwnerInfo != null && mOwnerInfo.getVisibility() != GONE) {
417             // Animate owner info during wake-up transition
418             mOwnerInfo.setAlpha(1f - mDarkAmount);
419 
420             float ratio = mDarkAmount;
421             // Calculate how much of it we should crop in order to have a smooth transition
422             int collapsed = mOwnerInfo.getTop() - mOwnerInfo.getPaddingTop();
423             int expanded = mOwnerInfo.getBottom() + mOwnerInfo.getPaddingBottom();
424             int toRemove = (int) ((expanded - collapsed) * ratio);
425             setBottom(getMeasuredHeight() - toRemove);
426             if (mNotificationIcons != null) {
427                 // We're using scrolling in order not to overload the translation which is used
428                 // when appearing the icons
429                 mNotificationIcons.setScrollY(toRemove);
430             }
431         } else if (mNotificationIcons != null){
432             mNotificationIcons.setScrollY(0);
433         }
434     }
435 
setPulsing(boolean pulsing)436     public void setPulsing(boolean pulsing) {
437         if (mPulsing == pulsing) {
438             return;
439         }
440         mPulsing = pulsing;
441     }
442 
shouldShowLogout()443     private boolean shouldShowLogout() {
444         return Dependency.get(KeyguardUpdateMonitor.class).isLogoutEnabled()
445                 && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM;
446     }
447 
onLogoutClicked(View view)448     private void onLogoutClicked(View view) {
449         int currentUserId = KeyguardUpdateMonitor.getCurrentUser();
450         try {
451             mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
452             mIActivityManager.stopUser(currentUserId, true /*force*/, null);
453         } catch (RemoteException re) {
454             Log.e(TAG, "Failed to logout user", re);
455         }
456     }
457 }
458