diff options
| author | 2017-11-27 18:21:23 +0000 | |
|---|---|---|
| committer | 2017-12-04 21:42:44 +0000 | |
| commit | 044588599c97ceff70d74a133c9eb01a028db00c (patch) | |
| tree | 9d95d28f2be0a80b3dfb13e38278fb9f87f1ce03 | |
| parent | 5826e469e9d03eaa5511d413733edde0a1b99048 (diff) | |
Introduce logout button and DevicePolicyManager API to enable it
Bug: 67843538
Test: Manual test with CloudDpc that logout button can be controlled by DevicePolicyManager API and only enabled in shared userse
Test: Logout button can actually exit the user and ActivityManager log shows that user is stopped
Test: CTS is tracked in b/67843605
Change-Id: I9f27050654958ce55f574dd05ff80609255ffeb4
| -rw-r--r-- | api/current.txt | 2 | ||||
| -rw-r--r-- | core/java/android/app/admin/DevicePolicyManager.java | 32 | ||||
| -rw-r--r-- | core/java/android/app/admin/IDevicePolicyManager.aidl | 3 | ||||
| -rw-r--r-- | core/res/res/drawable/ic_logout.xml | 15 | ||||
| -rw-r--r-- | core/res/res/values/config.xml | 2 | ||||
| -rw-r--r-- | core/res/res/values/strings.xml | 3 | ||||
| -rw-r--r-- | core/res/res/values/symbols.xml | 3 | ||||
| -rw-r--r-- | packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java | 49 | ||||
| -rw-r--r-- | services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java | 43 |
9 files changed, 151 insertions, 1 deletions
diff --git a/api/current.txt b/api/current.txt index 279a6f2f69b8..7a091ee3e71f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6388,6 +6388,7 @@ package android.app.admin { method public deprecated boolean isCallerApplicationRestrictionsManagingPackage(); method public boolean isDeviceOwnerApp(java.lang.String); method public boolean isLockTaskPermitted(java.lang.String); + method public boolean isLogoutButtonEnabled(); method public boolean isManagedProfile(android.content.ComponentName); method public boolean isMasterVolumeMuted(android.content.ComponentName); method public boolean isNetworkLoggingEnabled(android.content.ComponentName); @@ -6430,6 +6431,7 @@ package android.app.admin { method public void setKeyguardDisabledFeatures(android.content.ComponentName, int); method public void setLockTaskFeatures(android.content.ComponentName, int); method public void setLockTaskPackages(android.content.ComponentName, java.lang.String[]) throws java.lang.SecurityException; + method public void setLogoutButtonEnabled(android.content.ComponentName, boolean); method public void setLongSupportMessage(android.content.ComponentName, java.lang.CharSequence); method public void setMasterVolumeMuted(android.content.ComponentName, boolean); method public void setMaximumFailedPasswordsForWipe(android.content.ComponentName, int); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 0bca96907988..6b2d904eee0e 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -8444,6 +8444,38 @@ public class DevicePolicyManager { } /** + * Called by a device owner to specify whether a logout button is enabled for all secondary + * users. The system may show a logout button that stops the user and switches back to the + * primary user. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param enabled whether logout button should be enabled or not. + * @throws SecurityException if {@code admin} is not a device owner. + */ + public void setLogoutButtonEnabled(@NonNull ComponentName admin, boolean enabled) { + throwIfParentInstance("setLogoutButtonEnabled"); + try { + mService.setLogoutButtonEnabled(admin, enabled); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** + * Returns whether logout button is enabled by a device owner. + * + * @return {@code true} if logout button is enabled by device owner, {@code false} otherwise. + */ + public boolean isLogoutButtonEnabled() { + throwIfParentInstance("isLogoutButtonEnabled"); + try { + return mService.isLogoutButtonEnabled(); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + /** * Callback used in {@link #clearApplicationUserData} * to indicate that the clearing of an application's user data is done. */ diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index b7740e9e7314..8078d38fa6dd 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -365,4 +365,7 @@ interface IDevicePolicyManager { StringParceledListSlice getOwnerInstalledCaCerts(in UserHandle user); boolean clearApplicationUserData(in ComponentName admin, in String packageName, in IPackageDataObserver callback); + + void setLogoutButtonEnabled(in ComponentName admin, boolean enabled); + boolean isLogoutButtonEnabled(); } diff --git a/core/res/res/drawable/ic_logout.xml b/core/res/res/drawable/ic_logout.xml new file mode 100644 index 000000000000..c016ec818e4f --- /dev/null +++ b/core/res/res/drawable/ic_logout.xml @@ -0,0 +1,15 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <group> + <clip-path android:pathData="M0,0h24v24H0V0z M 0,0" /> + <path + android:fillColor="#FF000000" + android:pathData="M17.0,7.0l-1.4099998,1.4099998l2.58,2.5900002l-10.17,0.0l0.0,2.0l10.17,0.0l-2.58,2.58l1.4099998,1.4200001l5.0,-5.0z"/> + <path + android:fillColor="#FF000000" + android:pathData="M4,5h8V3H4C2.9,3,2,3.9,2,5v14c0,1.1,0.9,2,2,2h8v-2H4V5z"/> + </group> +</vector> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index fc649188fff6..0b38d1b1fca1 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2441,10 +2441,12 @@ "users" = list of users "restart" = restart device "lockdown" = Lock down device until the user authenticates + "logout" = Logout the current user --> <string-array translatable="false" name="config_globalActionsList"> <item>power</item> <item>restart</item> + <item>logout</item> <item>bugreport</item> <item>users</item> <item>lockdown</item> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 93bd4ec3bcf7..8958af4bffc8 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -471,6 +471,9 @@ <!-- label for item that generates a bug report in the phone options dialog --> <string name="global_action_bug_report">Bug report</string> + <!-- label for item that logouts the current user --> + <string name="global_action_logout">End session</string> + <!-- Take bug report menu title [CHAR LIMIT=NONE] --> <string name="bugreport_title">Take bug report</string> <!-- Message in bugreport dialog describing what it does [CHAR LIMIT=NONE] --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d944e4bce579..e0f7435869c8 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3163,4 +3163,7 @@ <java-symbol type="string" name="unsupported_compile_sdk_check_update" /> <java-symbol type="string" name="battery_saver_warning_title" /> + + <java-symbol type="string" name="global_action_logout" /> + <java-symbol type="drawable" name="ic_logout" /> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 00e8b1a44e47..3b54e11447b7 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -19,6 +19,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import android.app.ActivityManager; import android.app.Dialog; import android.app.WallpaperManager; +import android.app.admin.DevicePolicyManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; @@ -112,11 +113,13 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, private static final String GLOBAL_ACTION_KEY_VOICEASSIST = "voiceassist"; private static final String GLOBAL_ACTION_KEY_ASSIST = "assist"; private static final String GLOBAL_ACTION_KEY_RESTART = "restart"; + private static final String GLOBAL_ACTION_KEY_LOGOUT = "logout"; private final Context mContext; private final GlobalActionsManager mWindowManagerFuncs; private final AudioManager mAudioManager; private final IDreamManager mDreamManager; + private final DevicePolicyManager mDevicePolicyManager; private ArrayList<Action> mItems; private ActionsDialog mDialog; @@ -132,6 +135,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, private boolean mIsWaitingForEcmExit = false; private boolean mHasTelephony; private boolean mHasVibrator; + private boolean mHasLogoutButton; private final boolean mShowSilentToggle; private final EmergencyAffordanceManager mEmergencyAffordanceManager; @@ -144,6 +148,8 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); mDreamManager = IDreamManager.Stub.asInterface( ServiceManager.getService(DreamService.DREAM_SERVICE)); + mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService( + Context.DEVICE_POLICY_SERVICE); // receive broadcasts IntentFilter filter = new IntentFilter(); @@ -289,6 +295,7 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, R.array.config_globalActionsList); ArraySet<String> addedKeys = new ArraySet<String>(); + mHasLogoutButton = false; for (int i = 0; i < defaultActions.length; i++) { String actionKey = defaultActions[i]; if (addedKeys.contains(actionKey)) { @@ -325,6 +332,12 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, mItems.add(getAssistAction()); } else if (GLOBAL_ACTION_KEY_RESTART.equals(actionKey)) { mItems.add(new RestartAction()); + } else if (GLOBAL_ACTION_KEY_LOGOUT.equals(actionKey)) { + if (mDevicePolicyManager.isLogoutButtonEnabled() + && getCurrentUser().id != UserHandle.USER_SYSTEM) { + mItems.add(new LogoutAction()); + mHasLogoutButton = true; + } } else { Log.e(TAG, "Invalid global action key " + actionKey); } @@ -490,6 +503,37 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, } } + private final class LogoutAction extends SinglePressAction { + private LogoutAction() { + super(R.drawable.ic_logout, R.string.global_action_logout); + } + + @Override + public boolean showDuringKeyguard() { + return true; + } + + @Override + public boolean showBeforeProvisioning() { + return false; + } + + @Override + public void onPress() { + // Add a little delay before executing, to give the dialog a chance to go away before + // switching user + mHandler.postDelayed(() -> { + try { + ActivityManager.getService().switchUser(UserHandle.USER_SYSTEM); + ActivityManager.getService().stopUser(getCurrentUser().id, true /*force*/, + null); + } catch (RemoteException re) { + Log.e(TAG, "Couldn't logout user " + re); + } + }, 500); + } + } + private Action getSettingsAction() { return new SinglePressAction(R.drawable.ic_settings, R.string.global_action_settings) { @@ -764,7 +808,10 @@ class GlobalActionsDialog implements DialogInterface.OnDismissListener, public View getView(int position, View convertView, ViewGroup parent) { Action action = getItem(position); View view = action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); - if (position == 2) { + // When there is no logout button, only power off and restart should be in white + // background, thus setting division view at third item; with logout button being the + // third item, set the division view at fourth item instead. + if (position == (mHasLogoutButton ? 3 : 2)) { HardwareUiLayout.get(parent).setDivisionView(view); } return view; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index d7e4a6229bd4..b90b1cf47367 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -730,6 +730,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private static final String TAG_ORGANIZATION_NAME = "organization-name"; private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification"; private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications"; + private static final String TAG_IS_LOGOUT_BUTTON_ENABLED = "is_logout_button_enabled"; final DeviceAdminInfo info; @@ -779,6 +780,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { boolean requireAutoTime = false; // Can only be set by a device owner. boolean forceEphemeralUsers = false; // Can only be set by a device owner. boolean isNetworkLoggingEnabled = false; // Can only be set by a device owner. + boolean isLogoutButtonEnabled = false; // Can only be set by a device owner. // one notification after enabling + one more after reboots static final int DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN = 2; @@ -1096,6 +1098,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { out.text(organizationName); out.endTag(null, TAG_ORGANIZATION_NAME); } + if (isLogoutButtonEnabled) { + out.startTag(null, TAG_IS_LOGOUT_BUTTON_ENABLED); + out.attribute(null, ATTR_VALUE, Boolean.toString(isLogoutButtonEnabled)); + out.endTag(null, TAG_IS_LOGOUT_BUTTON_ENABLED); + } } void writePackageListToXml(XmlSerializer out, String outerTag, @@ -1269,6 +1276,9 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } else { Log.w(LOG_TAG, "Missing text when loading organization name"); } + } else if (TAG_IS_LOGOUT_BUTTON_ENABLED.equals(tag)) { + isLogoutButtonEnabled = Boolean.parseBoolean( + parser.getAttributeValue(null, ATTR_VALUE)); } else { Slog.w(LOG_TAG, "Unknown admin tag: " + tag); XmlUtils.skipCurrentTag(parser); @@ -11405,4 +11415,37 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } return false; } + + @Override + public synchronized void setLogoutButtonEnabled(ComponentName admin, boolean enabled) { + if (!mHasFeature) { + return; + } + Preconditions.checkNotNull(admin); + getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER); + + if (enabled == isLogoutButtonEnabledInternalLocked()) { + // already in the requested state + return; + } + ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); + deviceOwner.isLogoutButtonEnabled = enabled; + saveSettingsLocked(mInjector.userHandleGetCallingUserId()); + } + + @Override + public boolean isLogoutButtonEnabled() { + if (!mHasFeature) { + return false; + } + synchronized (this) { + return isLogoutButtonEnabledInternalLocked(); + } + } + + private boolean isLogoutButtonEnabledInternalLocked() { + ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); + return (deviceOwner != null) && deviceOwner.isLogoutButtonEnabled; + } + } |