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.VisibleForTesting; 37 import androidx.preference.Preference; 38 import androidx.preference.PreferenceViewHolder; 39 40 /** 41 * Helper class for managing settings preferences that can be disabled 42 * by device admins via user restrictions. 43 */ 44 public class RestrictedPreferenceHelper { 45 private static final String TAG = "RestrictedPreferenceHelper"; 46 47 private static final String REASON_PHONE_STATE = "phone_state"; 48 49 private final Context mContext; 50 private final Preference mPreference; 51 String packageName; 52 53 /** 54 * @deprecated TODO(b/308921175): This will be deleted with the 55 * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new 56 * code. 57 */ 58 int uid; 59 60 private boolean mDisabledByAdmin; 61 @VisibleForTesting 62 EnforcedAdmin mEnforcedAdmin; 63 private String mAttrUserRestriction = null; 64 private boolean mDisabledSummary = false; 65 66 private boolean mDisabledByEcm; 67 private Intent mDisabledByEcmIntent = null; 68 RestrictedPreferenceHelper(Context context, Preference preference, AttributeSet attrs, String packageName, int uid)69 public RestrictedPreferenceHelper(Context context, Preference preference, 70 AttributeSet attrs, String packageName, int uid) { 71 mContext = context; 72 mPreference = preference; 73 this.packageName = packageName; 74 this.uid = uid; 75 76 if (attrs != null) { 77 final TypedArray attributes = context.obtainStyledAttributes(attrs, 78 R.styleable.RestrictedPreference); 79 final TypedValue userRestriction = 80 attributes.peekValue(R.styleable.RestrictedPreference_userRestriction); 81 CharSequence data = null; 82 if (userRestriction != null && userRestriction.type == TypedValue.TYPE_STRING) { 83 if (userRestriction.resourceId != 0) { 84 data = context.getText(userRestriction.resourceId); 85 } else { 86 data = userRestriction.string; 87 } 88 } 89 mAttrUserRestriction = data == null ? null : data.toString(); 90 // If the system has set the user restriction, then we shouldn't add the padlock. 91 if (RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext, mAttrUserRestriction, 92 UserHandle.myUserId())) { 93 mAttrUserRestriction = null; 94 return; 95 } 96 97 final TypedValue useAdminDisabledSummary = 98 attributes.peekValue(R.styleable.RestrictedPreference_useAdminDisabledSummary); 99 if (useAdminDisabledSummary != null) { 100 mDisabledSummary = 101 (useAdminDisabledSummary.type == TypedValue.TYPE_INT_BOOLEAN 102 && useAdminDisabledSummary.data != 0); 103 } 104 } 105 } 106 RestrictedPreferenceHelper(Context context, Preference preference, AttributeSet attrs)107 public RestrictedPreferenceHelper(Context context, Preference preference, 108 AttributeSet attrs) { 109 this(context, preference, attrs, null, android.os.Process.INVALID_UID); 110 } 111 112 /** 113 * Modify PreferenceViewHolder to add padlock if restriction is disabled. 114 */ onBindViewHolder(PreferenceViewHolder holder)115 public void onBindViewHolder(PreferenceViewHolder holder) { 116 if (mDisabledByAdmin || mDisabledByEcm) { 117 holder.itemView.setEnabled(true); 118 } 119 if (mDisabledSummary) { 120 final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary); 121 if (summaryView != null) { 122 final CharSequence disabledText = getDisabledByAdminSummaryString(); 123 if (mDisabledByAdmin && disabledText != null) { 124 summaryView.setText(disabledText); 125 } else if (mDisabledByEcm) { 126 summaryView.setText(getEcmTextResId()); 127 } else if (TextUtils.equals(disabledText, summaryView.getText())) { 128 // It's previously set to disabled text, clear it. 129 summaryView.setText(null); 130 } 131 } 132 } 133 } 134 getDisabledByAdminSummaryString()135 private @Nullable String getDisabledByAdminSummaryString() { 136 if (isRestrictionEnforcedByAdvancedProtection()) { 137 // Advanced Protection doesn't set the summary string, it keeps the current summary. 138 return null; 139 } 140 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 141 return mContext.getSystemService(DevicePolicyManager.class).getResources().getString( 142 CONTROLLED_BY_ADMIN_SUMMARY, 143 () -> mContext.getString(R.string.disabled_by_admin_summary_text)); 144 } 145 return mContext.getString(R.string.disabled_by_admin_summary_text); 146 } 147 isRestrictionEnforcedByAdvancedProtection()148 public boolean isRestrictionEnforcedByAdvancedProtection() { 149 return mEnforcedAdmin != null && RestrictedLockUtilsInternal 150 .isPolicyEnforcedByAdvancedProtection(mContext, mEnforcedAdmin.enforcedRestriction, 151 UserHandle.myUserId()); 152 } 153 154 /** 155 * Configures the user restriction that this preference will track. This is equivalent to 156 * specifying {@link R.styleable#RestrictedPreference_userRestriction} in XML and allows 157 * configuring user restriction at runtime. 158 */ setUserRestriction(@ullable String userRestriction)159 public void setUserRestriction(@Nullable String userRestriction) { 160 mAttrUserRestriction = userRestriction == null || 161 RestrictedLockUtilsInternal.hasBaseUserRestriction(mContext, userRestriction, 162 UserHandle.myUserId()) ? null : userRestriction; 163 setDisabledByAdmin(checkRestrictionEnforced()); 164 } 165 useAdminDisabledSummary(boolean useSummary)166 public void useAdminDisabledSummary(boolean useSummary) { 167 mDisabledSummary = useSummary; 168 } 169 170 /** 171 * Check if the preference is disabled if so handle the click by informing the user. 172 * 173 * @return true if the method handled the click. 174 */ 175 @SuppressWarnings("NewApi") performClick()176 public boolean performClick() { 177 if (mDisabledByAdmin) { 178 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mEnforcedAdmin); 179 return true; 180 } 181 if (mDisabledByEcm) { 182 if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled() 183 && android.security.Flags.extendEcmToAllSettings()) { 184 mContext.startActivity(mDisabledByEcmIntent); 185 return true; 186 } else { 187 RestrictedLockUtilsInternal.sendShowRestrictedSettingDialogIntent(mContext, 188 packageName, uid); 189 return true; 190 } 191 } 192 return false; 193 } 194 195 /** 196 * Disable / enable if we have been passed the restriction in the xml. 197 */ onAttachedToHierarchy()198 public void onAttachedToHierarchy() { 199 if (mAttrUserRestriction != null) { 200 checkRestrictionAndSetDisabled(mAttrUserRestriction, UserHandle.myUserId()); 201 } 202 } 203 204 /** 205 * Set the user restriction that is used to disable this preference. 206 * 207 * @param userRestriction constant from {@link android.os.UserManager} 208 * @param userId user to check the restriction for. 209 */ checkRestrictionAndSetDisabled(String userRestriction, int userId)210 public void checkRestrictionAndSetDisabled(String userRestriction, int userId) { 211 EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext, 212 userRestriction, userId); 213 setDisabledByAdmin(admin); 214 } 215 216 /** 217 * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this 218 * package. Marks the preference as disabled if so. 219 * @param settingIdentifier The key identifying the setting 220 * @param packageName the package to check the settingIdentifier for 221 * @param settingEnabled Whether the setting in question is enabled 222 */ checkEcmRestrictionAndSetDisabled(@onNull String settingIdentifier, @NonNull String packageName, boolean settingEnabled)223 public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, 224 @NonNull String packageName, boolean settingEnabled) { 225 updatePackageDetails(packageName, android.os.Process.INVALID_UID); 226 if (settingEnabled) { 227 setDisabledByEcm(null); 228 return; 229 } 230 Intent intent = RestrictedLockUtilsInternal.checkIfRequiresEnhancedConfirmation( 231 mContext, settingIdentifier, packageName); 232 setDisabledByEcm(intent); 233 } 234 235 /** 236 * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this 237 * package. Marks the preference as disabled if so. 238 * TODO b/390196024: remove this and update all callers to use the "settingEnabled" version 239 * @param settingIdentifier The key identifying the setting 240 * @param packageName the package to check the settingIdentifier for 241 */ checkEcmRestrictionAndSetDisabled(@onNull String settingIdentifier, @NonNull String packageName)242 public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, 243 @NonNull String packageName) { 244 checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, false); 245 } 246 247 /** 248 * @return EnforcedAdmin if we have been passed the restriction in the xml. 249 */ checkRestrictionEnforced()250 public EnforcedAdmin checkRestrictionEnforced() { 251 if (mAttrUserRestriction == null) { 252 return null; 253 } 254 return RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext, 255 mAttrUserRestriction, UserHandle.myUserId()); 256 } 257 258 /** 259 * Disable this preference based on the enforce admin. 260 * 261 * @param admin details of the admin who enforced the restriction. If it is 262 * {@code null}, then this preference will be enabled. Otherwise, it will be disabled. 263 * Only gray out the preference which is not {@link RestrictedTopLevelPreference}. 264 * @return true if the disabled state was changed. 265 */ setDisabledByAdmin(EnforcedAdmin admin)266 public boolean setDisabledByAdmin(EnforcedAdmin admin) { 267 boolean disabled = false; 268 boolean changed = false; 269 EnforcedAdmin previousAdmin = mEnforcedAdmin; 270 mEnforcedAdmin = null; 271 if (admin != null) { 272 disabled = true; 273 // Copy the received instance to prevent pass be reference being overwritten. 274 mEnforcedAdmin = new EnforcedAdmin(admin); 275 if (android.security.Flags.aapmApi()) { 276 changed = previousAdmin == null || !previousAdmin.equals(admin); 277 } 278 } 279 280 if (mDisabledByAdmin != disabled) { 281 mDisabledByAdmin = disabled; 282 changed = true; 283 } 284 285 if (changed) { 286 updateDisabledState(); 287 } 288 289 return changed; 290 } 291 292 /** 293 * Disable the preference based on the passed in Intent 294 * @param disabledIntent The intent which is started when the user clicks the disabled 295 * preference. If it is {@code null}, then this preference will be enabled. Otherwise, it will 296 * be disabled. 297 * @return true if the disabled state was changed. 298 */ setDisabledByEcm(@ullable Intent disabledIntent)299 public boolean setDisabledByEcm(@Nullable Intent disabledIntent) { 300 boolean disabled = disabledIntent != null; 301 boolean changed = false; 302 if (mDisabledByEcm != disabled) { 303 mDisabledByEcmIntent = disabledIntent; 304 mDisabledByEcm = disabled; 305 changed = true; 306 updateDisabledState(); 307 } 308 309 return changed; 310 } 311 isDisabledByAdmin()312 public boolean isDisabledByAdmin() { 313 return mDisabledByAdmin; 314 } 315 isDisabledByEcm()316 public boolean isDisabledByEcm() { 317 return mDisabledByEcm; 318 } 319 updatePackageDetails(String packageName, int uid)320 public void updatePackageDetails(String packageName, int uid) { 321 this.packageName = packageName; 322 this.uid = uid; 323 } 324 updateDisabledState()325 private void updateDisabledState() { 326 boolean isEnabled = !(mDisabledByAdmin || mDisabledByEcm); 327 if (!(mPreference instanceof RestrictedTopLevelPreference)) { 328 mPreference.setEnabled(isEnabled); 329 } 330 331 if (mPreference instanceof PrimarySwitchPreference) { 332 ((PrimarySwitchPreference) mPreference).setSwitchEnabled(isEnabled); 333 } 334 335 if (android.security.Flags.aapmApi() && !isEnabled && mDisabledByAdmin) { 336 String summary = getDisabledByAdminSummaryString(); 337 if (summary != null) { 338 mPreference.setSummary(summary); 339 } 340 } 341 342 if (!isEnabled && mDisabledByEcm) { 343 mPreference.setSummary(getEcmTextResId()); 344 } 345 } 346 getEcmTextResId()347 private int getEcmTextResId() { 348 if (mDisabledByEcmIntent != null && REASON_PHONE_STATE.equals( 349 mDisabledByEcmIntent.getStringExtra(Intent.EXTRA_REASON))) { 350 return R.string.disabled_in_phone_call_text; 351 } else { 352 return R.string.disabled_by_app_ops_text; 353 } 354 } 355 356 357 /** 358 * @deprecated TODO(b/308921175): This will be deleted with the 359 * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new 360 * code. 361 */ 362 @Deprecated setDisabledByAppOps(boolean disabled)363 public boolean setDisabledByAppOps(boolean disabled) { 364 boolean changed = false; 365 if (mDisabledByEcm != disabled) { 366 mDisabledByEcm = disabled; 367 changed = true; 368 updateDisabledState(); 369 } 370 371 return changed; 372 } 373 } 374