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