/* * Copyright (C) 2016 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.settingslib; import android.app.AppGlobals; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.IPackageManager; import android.content.pm.UserInfo; import android.graphics.drawable.Drawable; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; import android.text.Spanned; import android.text.SpannableStringBuilder; import android.text.style.ForegroundColorSpan; import android.text.style.ImageSpan; import android.view.MenuItem; import android.widget.TextView; import com.android.internal.widget.LockPatternUtils; import java.util.List; /** * Utility class to host methods usable in adding a restricted padlock icon and showing admin * support message dialog. */ public class RestrictedLockUtils { /** * @return drawables for displaying with settings that are locked by a device admin. */ public static Drawable getRestrictedPadlock(Context context) { Drawable restrictedPadlock = context.getDrawable(R.drawable.ic_info); final int iconSize = context.getResources().getDimensionPixelSize( R.dimen.restricted_icon_size); restrictedPadlock.setBounds(0, 0, iconSize, iconSize); return restrictedPadlock; } /** * Checks if a restriction is enforced on a user and returns the enforced admin and * admin userId. * * @param userRestriction Restriction to check * @param userId User which we need to check if restriction is enforced on. * @return EnforcedAdmin Object containing the enforced admin component and admin user details, * or {@code null} If the restriction is not set. If the restriction is set by both device owner * and profile owner, then the admin component will be set to {@code null} and userId to * {@link UserHandle#USER_NULL}. */ public static EnforcedAdmin checkIfRestrictionEnforced(Context context, String userRestriction, int userId) { DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); if (dpm == null) { return null; } UserManager um = UserManager.get(context); int restrictionSource = um.getUserRestrictionSource(userRestriction, UserHandle.of(userId)); // If the restriction is not enforced or enforced only by system then return null if (restrictionSource == UserManager.RESTRICTION_NOT_SET || restrictionSource == UserManager.RESTRICTION_SOURCE_SYSTEM) { return null; } final boolean enforcedByProfileOwner = (restrictionSource & UserManager.RESTRICTION_SOURCE_PROFILE_OWNER) != 0; final boolean enforcedByDeviceOwner = (restrictionSource & UserManager.RESTRICTION_SOURCE_DEVICE_OWNER) != 0; if (enforcedByProfileOwner) { return getProfileOwner(context, userId); } else if (enforcedByDeviceOwner) { // When the restriction is enforced by device owner, return the device owner admin only // if the admin is for the {@param userId} otherwise return a default EnforcedAdmin. final EnforcedAdmin deviceOwner = getDeviceOwner(context); return deviceOwner.userId == userId ? deviceOwner : EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN; } return null; } public static boolean hasBaseUserRestriction(Context context, String userRestriction, int userId) { UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); return um.hasBaseUserRestriction(userRestriction, UserHandle.of(userId)); } /** * Checks if keyguard features are disabled by policy. * * @param keyguardFeatures Could be any of keyguard features that can be * disabled by {@link android.app.admin.DevicePolicyManager#setKeyguardDisabledFeatures}. * @return EnforcedAdmin Object containing the enforced admin component and admin user details, * or {@code null} If the notification features are not disabled. If the restriction is set by * multiple admins, then the admin component will be set to {@code null} and userId to * {@link UserHandle#USER_NULL}. */ public static EnforcedAdmin checkIfKeyguardFeaturesDisabled(Context context, int keyguardFeatures, int userId) { final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); if (dpm == null) { return null; } final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); LockPatternUtils lockPatternUtils = new LockPatternUtils(context); EnforcedAdmin enforcedAdmin = null; if (um.getUserInfo(userId).isManagedProfile()) { final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userId); if (admins == null) { return null; } for (ComponentName admin : admins) { if ((dpm.getKeyguardDisabledFeatures(admin, userId) & keyguardFeatures) != 0) { if (enforcedAdmin == null) { enforcedAdmin = new EnforcedAdmin(admin, userId); } else { return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN; } } } } else { // Consider all admins for this user and the profiles that are visible from this // user that do not use a separate work challenge. for (UserInfo userInfo : um.getProfiles(userId)) { final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userInfo.id); if (admins == null) { continue; } final boolean isSeparateProfileChallengeEnabled = lockPatternUtils.isSeparateProfileChallengeEnabled(userInfo.id); for (ComponentName admin : admins) { if (!isSeparateProfileChallengeEnabled) { if ((dpm.getKeyguardDisabledFeatures(admin, userInfo.id) & keyguardFeatures) != 0) { if (enforcedAdmin == null) { enforcedAdmin = new EnforcedAdmin(admin, userInfo.id); } else { return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN; } // This same admins could have set policies both on the managed profile // and on the parent. So, if the admin has set the policy on the // managed profile here, we don't need to further check if that admin // has set policy on the parent admin. continue; } } if (userInfo.isManagedProfile()) { // If userInfo.id is a managed profile, we also need to look at // the policies set on the parent. DevicePolicyManager parentDpm = dpm.getParentProfileInstance(userInfo); if ((parentDpm.getKeyguardDisabledFeatures(admin, userInfo.id) & keyguardFeatures) != 0) { if (enforcedAdmin == null) { enforcedAdmin = new EnforcedAdmin(admin, userInfo.id); } else { return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN; } } } } } } return enforcedAdmin; } public static EnforcedAdmin checkIfUninstallBlocked(Context context, String packageName, int userId) { EnforcedAdmin allAppsControlDisallowedAdmin = checkIfRestrictionEnforced(context, UserManager.DISALLOW_APPS_CONTROL, userId); if (allAppsControlDisallowedAdmin != null) { return allAppsControlDisallowedAdmin; } EnforcedAdmin allAppsUninstallDisallowedAdmin = checkIfRestrictionEnforced(context, UserManager.DISALLOW_UNINSTALL_APPS, userId); if (allAppsUninstallDisallowedAdmin != null) { return allAppsUninstallDisallowedAdmin; } IPackageManager ipm = AppGlobals.getPackageManager(); try { if (ipm.getBlockUninstallForUser(packageName, userId)) { return getProfileOrDeviceOwner(context, userId); } } catch (RemoteException e) { // Nothing to do } return null; } /** * Check if an application is suspended. * * @return EnforcedAdmin Object containing the enforced admin component and admin user details, * or {@code null} if the application is not suspended. */ public static EnforcedAdmin checkIfApplicationIsSuspended(Context context, String packageName, int userId) { IPackageManager ipm = AppGlobals.getPackageManager(); try { if (ipm.isPackageSuspendedForUser(packageName, userId)) { return getProfileOrDeviceOwner(context, userId); } } catch (RemoteException | IllegalArgumentException e) { // Nothing to do } return null; } public static EnforcedAdmin checkIfInputMethodDisallowed(Context context, String packageName, int userId) { DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); if (dpm == null) { return null; } EnforcedAdmin admin = getProfileOrDeviceOwner(context, userId); boolean permitted = true; if (admin != null) { permitted = dpm.isInputMethodPermittedByAdmin(admin.component, packageName, userId); } int managedProfileId = getManagedProfileId(context, userId); EnforcedAdmin profileAdmin = getProfileOrDeviceOwner(context, managedProfileId); boolean permittedByProfileAdmin = true; if (profileAdmin != null) { permittedByProfileAdmin = dpm.isInputMethodPermittedByAdmin(profileAdmin.component, packageName, managedProfileId); } if (!permitted && !permittedByProfileAdmin) { return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN; } else if (!permitted) { return admin; } else if (!permittedByProfileAdmin) { return profileAdmin; } return null; } /** * @param context * @param userId user id of a managed profile. * @return is remote contacts search disallowed. */ public static EnforcedAdmin checkIfRemoteContactSearchDisallowed(Context context, int userId) { DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); if (dpm == null) { return null; } EnforcedAdmin admin = getProfileOwner(context, userId); if (admin == null) { return null; } UserHandle userHandle = UserHandle.of(userId); if (dpm.getCrossProfileContactsSearchDisabled(userHandle) && dpm.getCrossProfileCallerIdDisabled(userHandle)) { return admin; } return null; } public static EnforcedAdmin checkIfAccessibilityServiceDisallowed(Context context, String packageName, int userId) { DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); if (dpm == null) { return null; } EnforcedAdmin admin = getProfileOrDeviceOwner(context, userId); boolean permitted = true; if (admin != null) { permitted = dpm.isAccessibilityServicePermittedByAdmin(admin.component, packageName, userId); } int managedProfileId = getManagedProfileId(context, userId); EnforcedAdmin profileAdmin = getProfileOrDeviceOwner(context, managedProfileId); boolean permittedByProfileAdmin = true; if (profileAdmin != null) { permittedByProfileAdmin = dpm.isAccessibilityServicePermittedByAdmin( profileAdmin.component, packageName, managedProfileId); } if (!permitted && !permittedByProfileAdmin) { return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN; } else if (!permitted) { return admin; } else if (!permittedByProfileAdmin) { return profileAdmin; } return null; } private static int getManagedProfileId(Context context, int userId) { UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); List<UserInfo> userProfiles = um.getProfiles(userId); for (UserInfo uInfo : userProfiles) { if (uInfo.id == userId) { continue; } if (uInfo.isManagedProfile()) { return uInfo.id; } } return UserHandle.USER_NULL; } /** * Check if account management for a specific type of account is disabled by admin. * Only a profile or device owner can disable account management. So, we check if account * management is disabled and return profile or device owner on the calling user. * * @return EnforcedAdmin Object containing the enforced admin component and admin user details, * or {@code null} if the account management is not disabled. */ public static EnforcedAdmin checkIfAccountManagementDisabled(Context context, String accountType, int userId) { if (accountType == null) { return null; } DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); if (dpm == null) { return null; } boolean isAccountTypeDisabled = false; String[] disabledTypes = dpm.getAccountTypesWithManagementDisabledAsUser(userId); for (String type : disabledTypes) { if (accountType.equals(type)) { isAccountTypeDisabled = true; break; } } if (!isAccountTypeDisabled) { return null; } return getProfileOrDeviceOwner(context, userId); } /** * Checks if {@link android.app.admin.DevicePolicyManager#setAutoTimeRequired} is enforced * on the device. * * @return EnforcedAdmin Object containing the device owner component and * userId the device owner is running as, or {@code null} setAutoTimeRequired is not enforced. */ public static EnforcedAdmin checkIfAutoTimeRequired(Context context) { DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); if (dpm == null || !dpm.getAutoTimeRequired()) { return null; } ComponentName adminComponent = dpm.getDeviceOwnerComponentOnCallingUser(); return new EnforcedAdmin(adminComponent, UserHandle.myUserId()); } /** * Checks if an admin has enforced minimum password quality requirements on the given user. * * @return EnforcedAdmin Object containing the enforced admin component and admin user details, * or {@code null} if no quality requirements are set. If the requirements are set by * multiple device admins, then the admin component will be set to {@code null} and userId to * {@link UserHandle#USER_NULL}. * */ public static EnforcedAdmin checkIfPasswordQualityIsSet(Context context, int userId) { final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); if (dpm == null) { return null; } LockPatternUtils lockPatternUtils = new LockPatternUtils(context); EnforcedAdmin enforcedAdmin = null; if (lockPatternUtils.isSeparateProfileChallengeEnabled(userId)) { // userId is managed profile and has a separate challenge, only consider // the admins in that user. final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userId); if (admins == null) { return null; } for (ComponentName admin : admins) { if (dpm.getPasswordQuality(admin, userId) > DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { if (enforcedAdmin == null) { enforcedAdmin = new EnforcedAdmin(admin, userId); } else { return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN; } } } } else { // Return all admins for this user and the profiles that are visible from this // user that do not use a separate work challenge. final UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); for (UserInfo userInfo : um.getProfiles(userId)) { final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userInfo.id); if (admins == null) { continue; } final boolean isSeparateProfileChallengeEnabled = lockPatternUtils.isSeparateProfileChallengeEnabled(userInfo.id); for (ComponentName admin : admins) { if (!isSeparateProfileChallengeEnabled) { if (dpm.getPasswordQuality(admin, userInfo.id) > DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { if (enforcedAdmin == null) { enforcedAdmin = new EnforcedAdmin(admin, userInfo.id); } else { return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN; } // This same admins could have set policies both on the managed profile // and on the parent. So, if the admin has set the policy on the // managed profile here, we don't need to further check if that admin // has set policy on the parent admin. continue; } } if (userInfo.isManagedProfile()) { // If userInfo.id is a managed profile, we also need to look at // the policies set on the parent. DevicePolicyManager parentDpm = dpm.getParentProfileInstance(userInfo); if (parentDpm.getPasswordQuality(admin, userInfo.id) > DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { if (enforcedAdmin == null) { enforcedAdmin = new EnforcedAdmin(admin, userInfo.id); } else { return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN; } } } } } } return enforcedAdmin; } /** * Checks if any admin has set maximum time to lock. * * @return EnforcedAdmin Object containing the enforced admin component and admin user details, * or {@code null} if no admin has set this restriction. If multiple admins has set this, then * the admin component will be set to {@code null} and userId to {@link UserHandle#USER_NULL} */ public static EnforcedAdmin checkIfMaximumTimeToLockIsSet(Context context) { final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); if (dpm == null) { return null; } LockPatternUtils lockPatternUtils = new LockPatternUtils(context); EnforcedAdmin enforcedAdmin = null; final int userId = UserHandle.myUserId(); final UserManager um = UserManager.get(context); final List<UserInfo> profiles = um.getProfiles(userId); final int profilesSize = profiles.size(); // As we do not have a separate screen lock timeout settings for work challenge, // we need to combine all profiles maximum time to lock even work challenge is // enabled. for (int i = 0; i < profilesSize; i++) { final UserInfo userInfo = profiles.get(i); final List<ComponentName> admins = dpm.getActiveAdminsAsUser(userInfo.id); if (admins == null) { continue; } for (ComponentName admin : admins) { if (dpm.getMaximumTimeToLock(admin, userInfo.id) > 0) { if (enforcedAdmin == null) { enforcedAdmin = new EnforcedAdmin(admin, userInfo.id); } else { return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN; } // This same admins could have set policies both on the managed profile // and on the parent. So, if the admin has set the policy on the // managed profile here, we don't need to further check if that admin // has set policy on the parent admin. continue; } if (userInfo.isManagedProfile()) { // If userInfo.id is a managed profile, we also need to look at // the policies set on the parent. final DevicePolicyManager parentDpm = dpm.getParentProfileInstance(userInfo); if (parentDpm.getMaximumTimeToLock(admin, userInfo.id) > 0) { if (enforcedAdmin == null) { enforcedAdmin = new EnforcedAdmin(admin, userInfo.id); } else { return EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN; } } } } } return enforcedAdmin; } public static EnforcedAdmin getProfileOrDeviceOwner(Context context, int userId) { if (userId == UserHandle.USER_NULL) { return null; } final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); if (dpm == null) { return null; } ComponentName adminComponent = dpm.getProfileOwnerAsUser(userId); if (adminComponent != null) { return new EnforcedAdmin(adminComponent, userId); } if (dpm.getDeviceOwnerUserId() == userId) { adminComponent = dpm.getDeviceOwnerComponentOnAnyUser(); if (adminComponent != null) { return new EnforcedAdmin(adminComponent, userId); } } return null; } public static EnforcedAdmin getDeviceOwner(Context context) { final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); if (dpm == null) { return null; } ComponentName adminComponent = dpm.getDeviceOwnerComponentOnAnyUser(); if (adminComponent != null) { return new EnforcedAdmin(adminComponent, dpm.getDeviceOwnerUserId()); } return null; } private static EnforcedAdmin getProfileOwner(Context context, int userId) { if (userId == UserHandle.USER_NULL) { return null; } final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); if (dpm == null) { return null; } ComponentName adminComponent = dpm.getProfileOwnerAsUser(userId); if (adminComponent != null) { return new EnforcedAdmin(adminComponent, userId); } return null; } /** * Set the menu item as disabled by admin by adding a restricted padlock at the end of the * text and set the click listener which will send an intent to show the admin support details * dialog. If the admin is null, remove the padlock and disabled color span. When the admin is * null, we also set the OnMenuItemClickListener to null, so if you want to set a custom * OnMenuItemClickListener, set it after calling this method. */ public static void setMenuItemAsDisabledByAdmin(final Context context, final MenuItem item, final EnforcedAdmin admin) { SpannableStringBuilder sb = new SpannableStringBuilder(item.getTitle()); removeExistingRestrictedSpans(sb); if (admin != null) { final int disabledColor = context.getColor(R.color.disabled_text_color); sb.setSpan(new ForegroundColorSpan(disabledColor), 0, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); ImageSpan image = new RestrictedLockImageSpan(context); sb.append(" ", image, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { sendShowAdminSupportDetailsIntent(context, admin); return true; } }); } else { item.setOnMenuItemClickListener(null); } item.setTitle(sb); } private static void removeExistingRestrictedSpans(SpannableStringBuilder sb) { final int length = sb.length(); RestrictedLockImageSpan[] imageSpans = sb.getSpans(length - 1, length, RestrictedLockImageSpan.class); for (ImageSpan span : imageSpans) { final int start = sb.getSpanStart(span); final int end = sb.getSpanEnd(span); sb.removeSpan(span); sb.delete(start, end); } ForegroundColorSpan[] colorSpans = sb.getSpans(0, length, ForegroundColorSpan.class); for (ForegroundColorSpan span : colorSpans) { sb.removeSpan(span); } } /** * Send the intent to trigger the {@link android.settings.ShowAdminSupportDetailsDialog}. */ public static void sendShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) { final Intent intent = getShowAdminSupportDetailsIntent(context, admin); int targetUserId = UserHandle.myUserId(); if (admin != null && admin.userId != UserHandle.USER_NULL && isCurrentUserOrProfile(context, admin.userId)) { targetUserId = admin.userId; } context.startActivityAsUser(intent, new UserHandle(targetUserId)); } public static Intent getShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) { final Intent intent = new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS); if (admin != null) { if (admin.component != null) { intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, admin.component); } int adminUserId = UserHandle.myUserId(); if (admin.userId != UserHandle.USER_NULL) { adminUserId = admin.userId; } intent.putExtra(Intent.EXTRA_USER_ID, adminUserId); } return intent; } public static boolean isCurrentUserOrProfile(Context context, int userId) { UserManager um = UserManager.get(context); for (UserInfo userInfo : um.getProfiles(UserHandle.myUserId())) { if (userInfo.id == userId) { return true; } } return false; } public static boolean isAdminInCurrentUserOrProfile(Context context, ComponentName admin) { DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService( Context.DEVICE_POLICY_SERVICE); UserManager um = UserManager.get(context); for (UserInfo userInfo : um.getProfiles(UserHandle.myUserId())) { if (dpm.isAdminActiveAsUser(admin, userInfo.id)) { return true; } } return false; } public static void setTextViewPadlock(Context context, TextView textView, boolean showPadlock) { final SpannableStringBuilder sb = new SpannableStringBuilder(textView.getText()); removeExistingRestrictedSpans(sb); if (showPadlock) { final ImageSpan image = new RestrictedLockImageSpan(context); sb.append(" ", image, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } textView.setText(sb); } /** * Takes a {@link android.widget.TextView} and applies an alpha so that the text looks like * disabled and appends a padlock to the text. This assumes that there are no * ForegroundColorSpans and RestrictedLockImageSpans used on the TextView. */ public static void setTextViewAsDisabledByAdmin(Context context, TextView textView, boolean disabled) { final SpannableStringBuilder sb = new SpannableStringBuilder(textView.getText()); removeExistingRestrictedSpans(sb); if (disabled) { final int disabledColor = context.getColor(R.color.disabled_text_color); sb.setSpan(new ForegroundColorSpan(disabledColor), 0, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); textView.setCompoundDrawables(null, null, getRestrictedPadlock(context), null); textView.setCompoundDrawablePadding(context.getResources().getDimensionPixelSize( R.dimen.restricted_icon_padding)); } else { textView.setCompoundDrawables(null, null, null, null); } textView.setText(sb); } public static class EnforcedAdmin { public ComponentName component = null; public int userId = UserHandle.USER_NULL; // We use this to represent the case where a policy is enforced by multiple admins. public final static EnforcedAdmin MULTIPLE_ENFORCED_ADMIN = new EnforcedAdmin(); public EnforcedAdmin(ComponentName component, int userId) { this.component = component; this.userId = userId; } public EnforcedAdmin(EnforcedAdmin other) { if (other == null) { throw new IllegalArgumentException(); } this.component = other.component; this.userId = other.userId; } public EnforcedAdmin() {} @Override public boolean equals(Object object) { if (object == this) return true; if (!(object instanceof EnforcedAdmin)) return false; EnforcedAdmin other = (EnforcedAdmin) object; if (userId != other.userId) { return false; } if ((component == null && other.component == null) || (component != null && component.equals(other.component))) { return true; } return false; } @Override public String toString() { return "EnforcedAdmin{component=" + component + ",userId=" + userId + "}"; } public void copyTo(EnforcedAdmin other) { if (other == null) { throw new IllegalArgumentException(); } other.component = component; other.userId = userId; } } }