Show logout button on keyguard

- Introduced logout button with desnse button style that switch to user 0 and stop the user
- Use owner_info to calculate KeyguardStatusView bottom if that exist, or keyguard_clock_container otherwise, to prevent notification overlapping owenr_info or the clock.

Test: Logout button does not appear in user 0
Test: Logout button does not appear on unmanaged device
Test: Logout button does not appear when isLogoutEnabled is false
Test: Logout button is shown on secondary users when isLogoutEnabled is true, clicking on logout button switch to user 0 and stops the user
Bug: 71786325

Change-Id: I5adfabd3ea4cc2ed78e7bdd31cbb25f2cea4cce2
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
index faa2c17..31635a5 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_view.xml
@@ -33,6 +33,23 @@
         android:layout_height="wrap_content"
         android:layout_marginBottom="@dimen/widget_vertical_padding"
         android:orientation="vertical">
+        <TextView
+            android:id="@+id/logout"
+            android:layout_height="@dimen/logout_button_layout_height"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:layout_centerHorizontal="true"
+            android:layout_marginBottom="@dimen/logout_button_margin_bottom"
+            android:gravity="center"
+            android:paddingLeft="@dimen/logout_button_padding_horizontal"
+            android:paddingRight="@dimen/logout_button_padding_horizontal"
+            android:background="@drawable/logout_button_background"
+            android:fontFamily="roboto-medium"
+            android:textAllCaps="true"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="13sp"
+            android:text="@*android:string/global_action_logout" />
+
         <RelativeLayout
             android:id="@+id/keyguard_clock_container"
             android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/drawable/logout_button_background.xml b/packages/SystemUI/res/drawable/logout_button_background.xml
new file mode 100644
index 0000000..eafd663
--- /dev/null
+++ b/packages/SystemUI/res/drawable/logout_button_background.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <solid android:color="@color/logout_button_bg_color"/>
+    <corners android:radius="@dimen/logout_button_corner_radius"/>
+</shape>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 4fcfdf7..be8e990 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -166,4 +166,6 @@
     <color name="fingerprint_dialog_dim_color">#80000000</color> <!-- 50% black -->
     <color name="fingerprint_error_message_color">#ff5722</color>
 
+    <!-- Logout button -->
+    <color name="logout_button_bg_color">#ccffffff</color>
 </resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c351b94..12fc26d 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -932,4 +932,10 @@
     <integer name="wired_charging_aod_text_animation_duration_up">300</integer>
     <!-- Wired charging on AOD, text animation distance -->
     <integer name="wired_charging_aod_text_animation_distance">-30</integer>
+
+    <!-- Logout button -->
+    <dimen name="logout_button_layout_height">32dp</dimen>
+    <dimen name="logout_button_padding_horizontal">16dp</dimen>
+    <dimen name="logout_button_margin_bottom">12dp</dimen>
+    <dimen name="logout_button_corner_radius">2dp</dimen>
 </resources>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index e440731..3b5f34c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -16,13 +16,16 @@
 
 package com.android.keyguard;
 
+import android.app.ActivityManager;
 import android.app.AlarmManager;
+import android.app.IActivityManager;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.support.v4.graphics.ColorUtils;
 import android.text.TextUtils;
@@ -51,9 +54,11 @@
 
     private final LockPatternUtils mLockPatternUtils;
     private final AlarmManager mAlarmManager;
+    private final IActivityManager mIActivityManager;
     private final float mSmallClockScale;
     private final float mWidgetPadding;
 
+    private TextView mLogoutView;
     private TextClock mClockView;
     private View mClockSeparator;
     private TextView mOwnerInfo;
@@ -80,6 +85,7 @@
                 if (DEBUG) Slog.v(TAG, "refresh statusview showing:" + showing);
                 refresh();
                 updateOwnerInfo();
+                updateLogoutView();
             }
         }
 
@@ -97,6 +103,12 @@
         public void onUserSwitchComplete(int userId) {
             refresh();
             updateOwnerInfo();
+            updateLogoutView();
+        }
+
+        @Override
+        public void onLogoutEnabledChanged() {
+            updateLogoutView();
         }
     };
 
@@ -111,6 +123,7 @@
     public KeyguardStatusView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        mIActivityManager = ActivityManager.getService();
         mLockPatternUtils = new LockPatternUtils(getContext());
         mHandler = new Handler(Looper.myLooper());
         mSmallClockScale = getResources().getDimension(R.dimen.widget_small_font_size)
@@ -145,6 +158,9 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+        mLogoutView = findViewById(R.id.logout);
+        mLogoutView.setOnClickListener(this::onLogoutClicked);
+
         mClockContainer = findViewById(R.id.keyguard_clock_container);
         mClockView = findViewById(R.id.clock_view);
         mClockView.setShowCurrentUserTime(true);
@@ -164,6 +180,7 @@
         setEnableMarquee(shouldMarquee);
         refresh();
         updateOwnerInfo();
+        updateLogoutView();
 
         // Disable elegant text height because our fancy colon makes the ymin value huge for no
         // reason.
@@ -213,14 +230,28 @@
     }
 
     public int getClockBottom() {
-        return mKeyguardSlice.getVisibility() == VISIBLE ? mKeyguardSlice.getBottom()
-                : mClockView.getBottom();
+        if (mOwnerInfo != null && mOwnerInfo.getVisibility() == VISIBLE) {
+            return mOwnerInfo.getBottom();
+        } else {
+            return mClockContainer.getBottom();
+        }
+    }
+
+    public int getLogoutButtonHeight() {
+        return mLogoutView.getVisibility() == VISIBLE ? mLogoutView.getHeight() : 0;
     }
 
     public float getClockTextSize() {
         return mClockView.getTextSize();
     }
 
+    private void updateLogoutView() {
+        mLogoutView.setVisibility(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));
+    }
+
     private void updateOwnerInfo() {
         if (mOwnerInfo == null) return;
         String ownerInfo = getOwnerInfo();
@@ -309,6 +340,7 @@
         mDarkAmount = darkAmount;
 
         boolean dark = darkAmount == 1;
+        mLogoutView.setAlpha(dark ? 0 : 1);
         final int N = mClockContainer.getChildCount();
         for (int i = 0; i < N; i++) {
             View child = mClockContainer.getChildAt(i);
@@ -340,4 +372,19 @@
             child.setAlpha(mDarkAmount == 1 && mPulsing ? 0.8f : 1);
         }
     }
+
+    private boolean shouldShowLogout() {
+        return KeyguardUpdateMonitor.getInstance(mContext).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);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 9e4b405..f3f8d91f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -141,6 +141,7 @@
     private static final int MSG_USER_UNLOCKED = 334;
     private static final int MSG_ASSISTANT_STACK_CHANGED = 335;
     private static final int MSG_FINGERPRINT_AUTHENTICATION_CONTINUE = 336;
+    private static final int MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED = 337;
 
     /** Fingerprint state: Not listening to fingerprint. */
     private static final int FINGERPRINT_STATE_STOPPED = 0;
@@ -225,6 +226,8 @@
     private LockPatternUtils mLockPatternUtils;
     private final IDreamManager mDreamManager;
     private boolean mIsDreaming;
+    private final DevicePolicyManager mDevicePolicyManager;
+    private boolean mLogoutEnabled;
 
     /**
      * Short delay before restarting fingerprint authentication after a successful try
@@ -330,6 +333,9 @@
                 case MSG_FINGERPRINT_AUTHENTICATION_CONTINUE:
                     updateFingerprintListeningState();
                     break;
+                case MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED:
+                    updateLogoutEnabled();
+                    break;
             }
         }
     };
@@ -795,6 +801,9 @@
                 }
                 mHandler.sendMessage(
                         mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, 0, serviceState));
+            } else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(
+                    action)) {
+                mHandler.sendEmptyMessage(MSG_DEVICE_POLICY_MANAGER_STATE_CHANGED);
             }
         }
     };
@@ -1159,6 +1168,7 @@
         filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
         filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
         filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
+        filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
         context.registerReceiver(mBroadcastReceiver, filter);
 
         final IntentFilter bootCompleteFilter = new IntentFilter();
@@ -1213,6 +1223,8 @@
 
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
         mUserManager = context.getSystemService(UserManager.class);
+        mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
+        mLogoutEnabled = mDevicePolicyManager.isLogoutEnabled();
     }
 
     private void updateFingerprintListeningState() {
@@ -1936,6 +1948,26 @@
         return null; // not found
     }
 
+    /**
+     * @return a cached version of DevicePolicyManager.isLogoutEnabled()
+     */
+    public boolean isLogoutEnabled() {
+        return mLogoutEnabled;
+    }
+
+    private void updateLogoutEnabled() {
+        boolean logoutEnabled = mDevicePolicyManager.isLogoutEnabled();
+        if (mLogoutEnabled != logoutEnabled) {
+            mLogoutEnabled = logoutEnabled;
+            for (int i = 0; i < mCallbacks.size(); i++) {
+                KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+                if (cb != null) {
+                    cb.onLogoutEnabledChanged();
+                }
+            }
+        }
+    }
+
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("KeyguardUpdateMonitor state:");
         pw.println("  SIM States:");
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 1afcca6..67571bb 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -283,4 +283,11 @@
      * @see KeyguardIndicationController#showTransientIndication(CharSequence)
      */
     public void onTrustAgentErrorMessage(CharSequence message) { }
+
+
+    /**
+     * Called when a value of logout enabled is change.
+     */
+    public void onLogoutEnabledChanged() { }
+
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 52d005c..41515eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -498,7 +498,8 @@
         float shelfSize = shelf.getVisibility() == GONE ? 0
                 : shelf.getIntrinsicHeight() + notificationPadding;
         float availableSpace = mNotificationStackScroller.getHeight() - minPadding - shelfSize
-                - Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
+                - Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding)
+                - mKeyguardStatusView.getLogoutButtonHeight();
         int count = 0;
         for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
             ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);