1 /* 2 * Copyright (C) 2016 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.settingslib; 18 19 import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTROLLED_BY_ADMIN_SUMMARY; 20 21 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 22 23 import android.app.admin.DevicePolicyManager; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.res.TypedArray; 27 import android.os.Build; 28 import android.os.UserHandle; 29 import android.text.TextUtils; 30 import android.util.AttributeSet; 31 import android.util.TypedValue; 32 import android.widget.TextView; 33 34 import androidx.annotation.NonNull; 35 import androidx.annotation.Nullable; 36 import androidx.annotation.RequiresApi; 37 import androidx.annotation.VisibleForTesting; 38 import androidx.preference.Preference; 39 import androidx.preference.PreferenceViewHolder; 40 41 import com.android.settingslib.utils.BuildCompatUtils; 42 43 /** 44 * Helper class for managing settings preferences that can be disabled 45 * by device admins via user restrictions. 46 */ 47 public class RestrictedPreferenceHelper { 48 private static final String TAG = "RestrictedPreferenceHelper"; 49 50 private final Context mContext; 51 private final Preference mPreference; 52 String packageName; 53 54 /** 55 * @deprecated TODO(b/308921175): This will be deleted with the 56 * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new 57 * code. 58 */ 59 int uid; 60 61 private boolean mDisabledByAdmin; 62 @VisibleForTesting 63 EnforcedAdmin mEnforcedAdmin; 64 private String mAttrUserRestriction = null; 65 private boolean mDisabledSummary = false; 66 67 private boolean mDisabledByEcm; 68 private Intent mDisabledByEcmIntent = null; 69 RestrictedPreferenceHelper(Context context, Preference preference, AttributeSet attrs, String packageName, int uid)70 public RestrictedPreferenceHelper(Context context, Preference preference, 71 AttributeSet attrs, String packageName, int uid) { 72 mContext = context; 73 mPreference = preference; 74 this.packageName = packageName; 75 this.uid = uid; 76 77 if (attrs != null) { 78 final TypedArray attributes = context.obtainStyledAttributes(attrs, 79 R.styleable.RestrictedPreference); 80 final TypedValue userRestriction = 81 attributes.peekValue(R.styleable.RestrictedPreference_userRestriction); 82 CharSequence data = null; 83 if (userRestriction != null && userRestriction.type == TypedValue.TYPE_STRING) { 84 if (userRestriction.resourceId != 0) { 85 data = context.getText(userRestriction.resourceId); 86 } else { 87 data = userRestriction.string; 88 } 89 } 90 mAttrUserRestriction = data == null ? null : data.toString(); 91 // If the system has set the user restriction, then we shouldn't add the padlock. 92 if (RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext, mAttrUserRestriction, 93 UserHandle.myUserId())) { 94 mAttrUserRestriction = null; 95 return; 96 } 97 98 final TypedValue useAdminDisabledSummary = 99 attributes.peekValue(R.styleable.RestrictedPreference_useAdminDisabledSummary); 100 if (useAdminDisabledSummary != null) { 101 mDisabledSummary = 102 (useAdminDisabledSummary.type == TypedValue.TYPE_INT_BOOLEAN 103 && useAdminDisabledSummary.data != 0); 104 } 105 } 106 } 107 RestrictedPreferenceHelper(Context context, Preference preference, AttributeSet attrs)108 public RestrictedPreferenceHelper(Context context, Preference preference, 109 AttributeSet attrs) { 110 this(context, preference, attrs, null, android.os.Process.INVALID_UID); 111 } 112 113 /** 114 * Modify PreferenceViewHolder to add padlock if restriction is disabled. 115 */ onBindViewHolder(PreferenceViewHolder holder)116 public void onBindViewHolder(PreferenceViewHolder holder) { 117 if (mDisabledByAdmin || mDisabledByEcm) { 118 holder.itemView.setEnabled(true); 119 } 120 if (mDisabledSummary) { 121 final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary); 122 if (summaryView != null) { 123 final CharSequence disabledText = BuildCompatUtils.isAtLeastT() 124 ? getDisabledByAdminUpdatableString() 125 : mContext.getString(R.string.disabled_by_admin_summary_text); 126 if (mDisabledByAdmin) { 127 summaryView.setText(disabledText); 128 } else if (mDisabledByEcm) { 129 summaryView.setText(R.string.disabled_by_app_ops_text); 130 } else if (TextUtils.equals(disabledText, summaryView.getText())) { 131 // It's previously set to disabled text, clear it. 132 summaryView.setText(null); 133 } 134 } 135 } 136 } 137 138 @RequiresApi(Build.VERSION_CODES.TIRAMISU) getDisabledByAdminUpdatableString()139 private String getDisabledByAdminUpdatableString() { 140 return mContext.getSystemService(DevicePolicyManager.class).getResources().getString( 141 CONTROLLED_BY_ADMIN_SUMMARY, 142 () -> mContext.getString(R.string.disabled_by_admin_summary_text)); 143 } 144 useAdminDisabledSummary(boolean useSummary)145 public void useAdminDisabledSummary(boolean useSummary) { 146 mDisabledSummary = useSummary; 147 } 148 149 /** 150 * Check if the preference is disabled if so handle the click by informing the user. 151 * 152 * @return true if the method handled the click. 153 */ 154 @SuppressWarnings("NewApi") performClick()155 public boolean performClick() { 156 if (mDisabledByAdmin) { 157 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mEnforcedAdmin); 158 return true; 159 } 160 if (mDisabledByEcm) { 161 if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled() 162 && android.security.Flags.extendEcmToAllSettings()) { 163 mContext.startActivity(mDisabledByEcmIntent); 164 return true; 165 } else { 166 RestrictedLockUtilsInternal.sendShowRestrictedSettingDialogIntent(mContext, 167 packageName, uid); 168 return true; 169 } 170 } 171 return false; 172 } 173 174 /** 175 * Disable / enable if we have been passed the restriction in the xml. 176 */ onAttachedToHierarchy()177 public void onAttachedToHierarchy() { 178 if (mAttrUserRestriction != null) { 179 checkRestrictionAndSetDisabled(mAttrUserRestriction, UserHandle.myUserId()); 180 } 181 } 182 183 /** 184 * Set the user restriction that is used to disable this preference. 185 * 186 * @param userRestriction constant from {@link android.os.UserManager} 187 * @param userId user to check the restriction for. 188 */ checkRestrictionAndSetDisabled(String userRestriction, int userId)189 public void checkRestrictionAndSetDisabled(String userRestriction, int userId) { 190 EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext, 191 userRestriction, userId); 192 setDisabledByAdmin(admin); 193 } 194 195 /** 196 * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this 197 * package. Marks the preference as disabled if so. 198 * @param settingIdentifier The key identifying the setting 199 * @param packageName the package to check the settingIdentifier for 200 */ checkEcmRestrictionAndSetDisabled(@onNull String settingIdentifier, @NonNull String packageName)201 public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, 202 @NonNull String packageName) { 203 updatePackageDetails(packageName, android.os.Process.INVALID_UID); 204 Intent intent = RestrictedLockUtilsInternal.checkIfRequiresEnhancedConfirmation( 205 mContext, settingIdentifier, packageName); 206 setDisabledByEcm(intent); 207 } 208 209 /** 210 * @return EnforcedAdmin if we have been passed the restriction in the xml. 211 */ checkRestrictionEnforced()212 public EnforcedAdmin checkRestrictionEnforced() { 213 if (mAttrUserRestriction == null) { 214 return null; 215 } 216 return RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext, 217 mAttrUserRestriction, UserHandle.myUserId()); 218 } 219 220 /** 221 * Disable this preference based on the enforce admin. 222 * 223 * @param admin details of the admin who enforced the restriction. If it is 224 * {@code null}, then this preference will be enabled. Otherwise, it will be disabled. 225 * Only gray out the preference which is not {@link RestrictedTopLevelPreference}. 226 * @return true if the disabled state was changed. 227 */ setDisabledByAdmin(EnforcedAdmin admin)228 public boolean setDisabledByAdmin(EnforcedAdmin admin) { 229 boolean disabled = false; 230 mEnforcedAdmin = null; 231 if (admin != null) { 232 disabled = true; 233 // Copy the received instance to prevent pass be reference being overwritten. 234 mEnforcedAdmin = new EnforcedAdmin(admin); 235 } 236 237 boolean changed = false; 238 if (mDisabledByAdmin != disabled) { 239 mDisabledByAdmin = disabled; 240 changed = true; 241 updateDisabledState(); 242 } 243 244 return changed; 245 } 246 247 /** 248 * Disable the preference based on the passed in Intent 249 * @param disabledIntent The intent which is started when the user clicks the disabled 250 * preference. If it is {@code null}, then this preference will be enabled. Otherwise, it will 251 * be disabled. 252 * @return true if the disabled state was changed. 253 */ setDisabledByEcm(@ullable Intent disabledIntent)254 public boolean setDisabledByEcm(@Nullable Intent disabledIntent) { 255 boolean disabled = disabledIntent != null; 256 boolean changed = false; 257 if (mDisabledByEcm != disabled) { 258 mDisabledByEcmIntent = disabledIntent; 259 mDisabledByEcm = disabled; 260 changed = true; 261 updateDisabledState(); 262 } 263 264 return changed; 265 } 266 isDisabledByAdmin()267 public boolean isDisabledByAdmin() { 268 return mDisabledByAdmin; 269 } 270 isDisabledByEcm()271 public boolean isDisabledByEcm() { 272 return mDisabledByEcm; 273 } 274 updatePackageDetails(String packageName, int uid)275 public void updatePackageDetails(String packageName, int uid) { 276 this.packageName = packageName; 277 this.uid = uid; 278 } 279 updateDisabledState()280 private void updateDisabledState() { 281 boolean isEnabled = !(mDisabledByAdmin || mDisabledByEcm); 282 if (!(mPreference instanceof RestrictedTopLevelPreference)) { 283 mPreference.setEnabled(isEnabled); 284 } 285 286 if (mPreference instanceof PrimarySwitchPreference) { 287 ((PrimarySwitchPreference) mPreference).setSwitchEnabled(isEnabled); 288 } 289 290 if (!isEnabled && mDisabledByEcm) { 291 mPreference.setSummary(R.string.disabled_by_app_ops_text); 292 } 293 } 294 295 296 /** 297 * @deprecated TODO(b/308921175): This will be deleted with the 298 * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new 299 * code. 300 */ 301 @Deprecated setDisabledByAppOps(boolean disabled)302 public boolean setDisabledByAppOps(boolean disabled) { 303 boolean changed = false; 304 if (mDisabledByEcm != disabled) { 305 mDisabledByEcm = disabled; 306 changed = true; 307 updateDisabledState(); 308 } 309 310 return changed; 311 } 312 } 313