/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.keyguard; import android.app.ActivityManager; import android.app.IActivityManager; import android.content.Context; import android.graphics.Color; import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.view.View; import android.widget.GridLayout; import android.widget.TextView; import androidx.core.graphics.ColorUtils; import com.android.internal.widget.LockPatternUtils; import com.android.systemui.Dependency; import com.android.systemui.R; import java.io.FileDescriptor; import java.io.PrintWriter; /** * View consisting of: * - keyguard clock * - logout button (on certain managed devices) * - owner information (if set) * - notification icons (shown on AOD) */ public class KeyguardStatusView extends GridLayout { private static final boolean DEBUG = KeyguardConstants.DEBUG; private static final String TAG = "KeyguardStatusView"; private static final int MARQUEE_DELAY_MS = 2000; private final LockPatternUtils mLockPatternUtils; private final IActivityManager mIActivityManager; private TextView mLogoutView; private boolean mCanShowLogout = true; // by default, try to show the logout button here private KeyguardClockSwitch mClockView; private TextView mOwnerInfo; private boolean mCanShowOwnerInfo = true; // by default, try to show the owner information here private KeyguardSliceView mKeyguardSlice; private View mNotificationIcons; private Runnable mPendingMarqueeStart; private Handler mHandler; private float mDarkAmount = 0; private int mTextColor; /** * Bottom margin that defines the margin between bottom of smart space and top of notification * icons on AOD. */ private int mIconTopMargin; private int mIconTopMarginWithHeader; private boolean mShowingHeader; public KeyguardStatusView(Context context) { this(context, null, 0); } public KeyguardStatusView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public KeyguardStatusView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mIActivityManager = ActivityManager.getService(); mLockPatternUtils = new LockPatternUtils(getContext()); mHandler = new Handler(); onDensityOrFontScaleChanged(); } void setEnableMarquee(boolean enabled) { if (DEBUG) Log.v(TAG, "Schedule setEnableMarquee: " + (enabled ? "Enable" : "Disable")); if (enabled) { if (mPendingMarqueeStart == null) { mPendingMarqueeStart = () -> { setEnableMarqueeImpl(true); mPendingMarqueeStart = null; }; mHandler.postDelayed(mPendingMarqueeStart, MARQUEE_DELAY_MS); } } else { if (mPendingMarqueeStart != null) { mHandler.removeCallbacks(mPendingMarqueeStart); mPendingMarqueeStart = null; } setEnableMarqueeImpl(false); } } private void setEnableMarqueeImpl(boolean enabled) { if (DEBUG) Log.v(TAG, (enabled ? "Enable" : "Disable") + " transport text marquee"); if (mOwnerInfo != null) mOwnerInfo.setSelected(enabled); } void setCanShowOwnerInfo(boolean canShowOwnerInfo) { mCanShowOwnerInfo = canShowOwnerInfo; mOwnerInfo = findViewById(R.id.owner_info); if (mOwnerInfo != null) { if (mCanShowOwnerInfo) { mOwnerInfo.setVisibility(VISIBLE); updateOwnerInfo(); } else { mOwnerInfo.setVisibility(GONE); mOwnerInfo = null; } } } void setCanShowLogout(boolean canShowLogout) { mCanShowLogout = canShowLogout; updateLogoutView(); } @Override protected void onFinishInflate() { super.onFinishInflate(); mLogoutView = findViewById(R.id.logout); mNotificationIcons = findViewById(R.id.clock_notification_icon_container); if (mLogoutView != null) { mLogoutView.setOnClickListener(this::onLogoutClicked); } mClockView = findViewById(R.id.keyguard_clock_container); mClockView.setShowCurrentUserTime(true); if (KeyguardClockAccessibilityDelegate.isNeeded(mContext)) { mClockView.setAccessibilityDelegate(new KeyguardClockAccessibilityDelegate(mContext)); } if (mCanShowOwnerInfo) { mOwnerInfo = findViewById(R.id.owner_info); } mKeyguardSlice = findViewById(R.id.keyguard_status_area); mTextColor = mClockView.getCurrentTextColor(); mKeyguardSlice.setContentChangeListener(this::onSliceContentChanged); onSliceContentChanged(); boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive(); setEnableMarquee(shouldMarquee); updateOwnerInfo(); updateLogoutView(); updateDark(); } /** * Moves clock, adjusting margins when slice content changes. */ private void onSliceContentChanged() { final boolean hasHeader = mKeyguardSlice.hasHeader(); mClockView.setKeyguardShowingHeader(hasHeader); if (mShowingHeader == hasHeader) { return; } mShowingHeader = hasHeader; if (mNotificationIcons != null) { // Update top margin since header has appeared/disappeared. MarginLayoutParams params = (MarginLayoutParams) mNotificationIcons.getLayoutParams(); params.setMargins(params.leftMargin, hasHeader ? mIconTopMarginWithHeader : mIconTopMargin, params.rightMargin, params.bottomMargin); mNotificationIcons.setLayoutParams(params); } mClockView.setKeyguardHidingBigClock(hasHeader); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); layoutOwnerInfo(); } int getLogoutButtonHeight() { if (mLogoutView == null) { return 0; } return mLogoutView.getVisibility() == VISIBLE ? mLogoutView.getHeight() : 0; } int getOwnerInfoHeight() { if (mOwnerInfo == null) { return 0; } return mOwnerInfo.getVisibility() == VISIBLE ? mOwnerInfo.getHeight() : 0; } void updateLogoutView() { if (mLogoutView == null) { return; } mLogoutView.setVisibility(mCanShowLogout && shouldShowLogout() ? VISIBLE : GONE); // Logout button will stay in language of user 0 if we don't set that manually. mLogoutView.setText(mContext.getResources().getString( com.android.internal.R.string.global_action_logout)); } void onDensityOrFontScaleChanged() { if (mOwnerInfo != null) { mOwnerInfo.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimensionPixelSize( com.android.systemui.R.dimen.widget_label_font_size)); loadBottomMargin(); } } void updateOwnerInfo() { if (mOwnerInfo == null) return; String info = mLockPatternUtils.getDeviceOwnerInfo(); if (info == null) { // Use the current user owner information if enabled. final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled( KeyguardUpdateMonitor.getCurrentUser()); if (ownerInfoEnabled) { info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser()); } } mOwnerInfo.setText(info); updateDark(); } void setDarkAmount(float darkAmount) { if (mDarkAmount == darkAmount) { return; } mDarkAmount = darkAmount; mClockView.setDarkAmount(darkAmount); updateDark(); } void updateDark() { boolean dark = mDarkAmount == 1; if (mLogoutView != null) { mLogoutView.setAlpha(dark ? 0 : 1); } if (mOwnerInfo != null) { boolean hasText = !TextUtils.isEmpty(mOwnerInfo.getText()); mOwnerInfo.setVisibility(hasText ? VISIBLE : GONE); layoutOwnerInfo(); } final int blendedTextColor = ColorUtils.blendARGB(mTextColor, Color.WHITE, mDarkAmount); mKeyguardSlice.setDarkAmount(mDarkAmount); mClockView.setTextColor(blendedTextColor); } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("KeyguardStatusView:"); pw.println(" mOwnerInfo: " + (mOwnerInfo == null ? "null" : mOwnerInfo.getVisibility() == VISIBLE)); pw.println(" mDarkAmount: " + mDarkAmount); pw.println(" mTextColor: " + Integer.toHexString(mTextColor)); if (mLogoutView != null) { pw.println(" logout visible: " + (mLogoutView.getVisibility() == VISIBLE)); } if (mClockView != null) { mClockView.dump(fd, pw, args); } if (mKeyguardSlice != null) { mKeyguardSlice.dump(fd, pw, args); } } private void loadBottomMargin() { mIconTopMargin = getResources().getDimensionPixelSize(R.dimen.widget_vertical_padding); mIconTopMarginWithHeader = getResources().getDimensionPixelSize( R.dimen.widget_vertical_padding_with_header); } private void layoutOwnerInfo() { if (mOwnerInfo != null && mOwnerInfo.getVisibility() != GONE) { // Animate owner info during wake-up transition mOwnerInfo.setAlpha(1f - mDarkAmount); float ratio = mDarkAmount; // Calculate how much of it we should crop in order to have a smooth transition int collapsed = mOwnerInfo.getTop() - mOwnerInfo.getPaddingTop(); int expanded = mOwnerInfo.getBottom() + mOwnerInfo.getPaddingBottom(); int toRemove = (int) ((expanded - collapsed) * ratio); setBottom(getMeasuredHeight() - toRemove); if (mNotificationIcons != null) { // We're using scrolling in order not to overload the translation which is used // when appearing the icons mNotificationIcons.setScrollY(toRemove); } } else if (mNotificationIcons != null){ mNotificationIcons.setScrollY(0); } } private boolean shouldShowLogout() { return Dependency.get(KeyguardUpdateMonitor.class).isLogoutEnabled() && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM; } private void onLogoutClicked(View view) { int currentUserId = KeyguardUpdateMonitor.getCurrentUser(); try { mIActivityManager.switchUser(UserHandle.USER_SYSTEM); mIActivityManager.stopUser(currentUserId, true /*force*/, null); } catch (RemoteException re) { Log.e(TAG, "Failed to logout user", re); } } }