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.DISABLED_BY_ADMIN_SWITCH_SUMMARY; 20 import static android.app.admin.DevicePolicyResources.Strings.Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY; 21 22 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; 23 24 import android.app.AppOpsManager; 25 import android.app.admin.DevicePolicyManager; 26 import android.content.Context; 27 import android.content.res.TypedArray; 28 import android.os.Process; 29 import android.os.UserHandle; 30 import android.util.AttributeSet; 31 import android.util.TypedValue; 32 import android.view.View; 33 import android.widget.ImageView; 34 import android.widget.LinearLayout; 35 import android.widget.TextView; 36 37 import androidx.annotation.NonNull; 38 import androidx.annotation.VisibleForTesting; 39 import androidx.preference.PreferenceManager; 40 import androidx.preference.PreferenceViewHolder; 41 import androidx.preference.SwitchPreferenceCompat; 42 43 import com.android.settingslib.utils.BuildCompatUtils; 44 45 /** 46 * Version of SwitchPreferenceCompat that can be disabled by a device admin 47 * using a user restriction. 48 */ 49 public class RestrictedSwitchPreference extends SwitchPreferenceCompat { 50 RestrictedPreferenceHelper mHelper; 51 AppOpsManager mAppOpsManager; 52 boolean mUseAdditionalSummary = false; 53 CharSequence mRestrictedSwitchSummary; 54 private int mIconSize; 55 RestrictedSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)56 public RestrictedSwitchPreference(Context context, AttributeSet attrs, 57 int defStyleAttr, int defStyleRes) { 58 super(context, attrs, defStyleAttr, defStyleRes); 59 mHelper = new RestrictedPreferenceHelper(context, this, attrs); 60 if (attrs != null) { 61 final TypedArray attributes = context.obtainStyledAttributes(attrs, 62 R.styleable.RestrictedSwitchPreference); 63 final TypedValue useAdditionalSummary = attributes.peekValue( 64 R.styleable.RestrictedSwitchPreference_useAdditionalSummary); 65 if (useAdditionalSummary != null) { 66 mUseAdditionalSummary = 67 (useAdditionalSummary.type == TypedValue.TYPE_INT_BOOLEAN 68 && useAdditionalSummary.data != 0); 69 } 70 71 final TypedValue restrictedSwitchSummary = attributes.peekValue( 72 R.styleable.RestrictedSwitchPreference_restrictedSwitchSummary); 73 attributes.recycle(); 74 if (restrictedSwitchSummary != null 75 && restrictedSwitchSummary.type == TypedValue.TYPE_STRING) { 76 if (restrictedSwitchSummary.resourceId != 0) { 77 mRestrictedSwitchSummary = 78 context.getText(restrictedSwitchSummary.resourceId); 79 } else { 80 mRestrictedSwitchSummary = restrictedSwitchSummary.string; 81 } 82 } 83 } 84 if (mUseAdditionalSummary) { 85 setLayoutResource(R.layout.restricted_switch_preference); 86 useAdminDisabledSummary(false); 87 } 88 } 89 RestrictedSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr)90 public RestrictedSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) { 91 this(context, attrs, defStyleAttr, 0); 92 } 93 RestrictedSwitchPreference(Context context, AttributeSet attrs)94 public RestrictedSwitchPreference(Context context, AttributeSet attrs) { 95 this(context, attrs, androidx.preference.R.attr.switchPreferenceCompatStyle); 96 } 97 RestrictedSwitchPreference(Context context)98 public RestrictedSwitchPreference(Context context) { 99 this(context, null); 100 } 101 102 @VisibleForTesting setAppOps(AppOpsManager appOps)103 public void setAppOps(AppOpsManager appOps) { 104 mAppOpsManager = appOps; 105 } 106 setIconSize(int iconSize)107 public void setIconSize(int iconSize) { 108 mIconSize = iconSize; 109 } 110 111 @Override onBindViewHolder(PreferenceViewHolder holder)112 public void onBindViewHolder(PreferenceViewHolder holder) { 113 super.onBindViewHolder(holder); 114 final View switchView = holder.findViewById(androidx.preference.R.id.switchWidget); 115 if (switchView != null) { 116 final View rootView = switchView.getRootView(); 117 rootView.setFilterTouchesWhenObscured(true); 118 } 119 120 mHelper.onBindViewHolder(holder); 121 122 CharSequence switchSummary; 123 if (mRestrictedSwitchSummary == null) { 124 switchSummary = isChecked() 125 ? getUpdatableEnterpriseString( 126 getContext(), ENABLED_BY_ADMIN_SWITCH_SUMMARY, 127 com.android.settingslib.widget.restricted.R.string.enabled_by_admin) 128 : getUpdatableEnterpriseString( 129 getContext(), DISABLED_BY_ADMIN_SWITCH_SUMMARY, 130 com.android.settingslib.widget.restricted.R.string.disabled_by_admin); 131 } else { 132 switchSummary = mRestrictedSwitchSummary; 133 } 134 135 final ImageView icon = holder.itemView.findViewById(android.R.id.icon); 136 137 if (mIconSize > 0) { 138 icon.setLayoutParams(new LinearLayout.LayoutParams(mIconSize, mIconSize)); 139 } 140 141 if (mUseAdditionalSummary) { 142 final TextView additionalSummaryView = (TextView) holder.findViewById( 143 R.id.additional_summary); 144 if (additionalSummaryView != null) { 145 if (isDisabledByAdmin()) { 146 additionalSummaryView.setText(switchSummary); 147 additionalSummaryView.setVisibility(View.VISIBLE); 148 } else { 149 additionalSummaryView.setVisibility(View.GONE); 150 } 151 } 152 } else { 153 final TextView summaryView = (TextView) holder.findViewById(android.R.id.summary); 154 if (summaryView != null) { 155 if (isDisabledByAdmin()) { 156 summaryView.setText(switchSummary); 157 summaryView.setVisibility(View.VISIBLE); 158 } 159 // No need to change the visibility to GONE in the else case here since Preference 160 // class would have already changed it if there is no summary to display. 161 } 162 } 163 } 164 getUpdatableEnterpriseString( Context context, String updatableStringId, int resId)165 private static String getUpdatableEnterpriseString( 166 Context context, String updatableStringId, int resId) { 167 if (!BuildCompatUtils.isAtLeastT()) { 168 return context.getString(resId); 169 } 170 return context.getSystemService(DevicePolicyManager.class).getResources().getString( 171 updatableStringId, 172 () -> context.getString(resId)); 173 } 174 175 @Override performClick()176 public void performClick() { 177 if (!mHelper.performClick()) { 178 super.performClick(); 179 } 180 } 181 useAdminDisabledSummary(boolean useSummary)182 public void useAdminDisabledSummary(boolean useSummary) { 183 mHelper.useAdminDisabledSummary(useSummary); 184 } 185 186 @Override onAttachedToHierarchy(PreferenceManager preferenceManager)187 protected void onAttachedToHierarchy(PreferenceManager preferenceManager) { 188 mHelper.onAttachedToHierarchy(); 189 super.onAttachedToHierarchy(preferenceManager); 190 } 191 checkRestrictionAndSetDisabled(String userRestriction)192 public void checkRestrictionAndSetDisabled(String userRestriction) { 193 mHelper.checkRestrictionAndSetDisabled(userRestriction, UserHandle.myUserId()); 194 } 195 checkRestrictionAndSetDisabled(String userRestriction, int userId)196 public void checkRestrictionAndSetDisabled(String userRestriction, int userId) { 197 mHelper.checkRestrictionAndSetDisabled(userRestriction, userId); 198 } 199 200 /** 201 * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this 202 * package. Marks the preference as disabled if so. 203 * @param settingIdentifier The key identifying the setting 204 * @param packageName the package to check the settingIdentifier for 205 */ checkEcmRestrictionAndSetDisabled(@onNull String settingIdentifier, @NonNull String packageName)206 public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier, 207 @NonNull String packageName) { 208 mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName); 209 } 210 211 @Override setEnabled(boolean enabled)212 public void setEnabled(boolean enabled) { 213 boolean changed = false; 214 if (enabled && isDisabledByAdmin()) { 215 mHelper.setDisabledByAdmin(null); 216 changed = true; 217 } 218 if (enabled && isDisabledByEcm()) { 219 mHelper.setDisabledByEcm(null); 220 changed = true; 221 } 222 if (!changed) { 223 super.setEnabled(enabled); 224 } 225 } 226 setDisabledByAdmin(EnforcedAdmin admin)227 public void setDisabledByAdmin(EnforcedAdmin admin) { 228 if (mHelper.setDisabledByAdmin(admin)) { 229 notifyChanged(); 230 } 231 } 232 isDisabledByAdmin()233 public boolean isDisabledByAdmin() { 234 return mHelper.isDisabledByAdmin(); 235 } 236 isDisabledByEcm()237 public boolean isDisabledByEcm() { 238 return mHelper.isDisabledByEcm(); 239 } 240 241 /** 242 * @deprecated TODO(b/308921175): This will be deleted with the 243 * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new 244 * code. 245 */ 246 @Deprecated setDisabledByAppOps(boolean disabled)247 private void setDisabledByAppOps(boolean disabled) { 248 if (mHelper.setDisabledByAppOps(disabled)) { 249 notifyChanged(); 250 } 251 } 252 253 /** 254 * @deprecated TODO(b/308921175): This will be deleted with the 255 * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new 256 * code. 257 */ 258 @Deprecated getUid()259 public int getUid() { 260 return mHelper != null ? mHelper.uid : Process.INVALID_UID; 261 } 262 263 /** 264 * @deprecated TODO(b/308921175): This will be deleted with the 265 * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new 266 * code. 267 */ 268 @Deprecated getPackageName()269 public String getPackageName() { 270 return mHelper != null ? mHelper.packageName : null; 271 } 272 273 /** 274 * Updates enabled state based on associated package 275 * 276 * @deprecated TODO(b/308921175): This will be deleted with the 277 * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new 278 * code. 279 */ 280 @Deprecated updateState( @onNull String packageName, int uid, boolean isEnableAllowed, boolean isEnabled)281 public void updateState( 282 @NonNull String packageName, int uid, boolean isEnableAllowed, boolean isEnabled) { 283 mHelper.updatePackageDetails(packageName, uid); 284 if (mAppOpsManager == null) { 285 mAppOpsManager = getContext().getSystemService(AppOpsManager.class); 286 } 287 final int mode = mAppOpsManager.noteOpNoThrow( 288 AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, 289 uid, packageName); 290 final boolean ecmEnabled = getContext().getResources().getBoolean( 291 com.android.internal.R.bool.config_enhancedConfirmationModeEnabled); 292 final boolean appOpsAllowed = !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED 293 || mode == AppOpsManager.MODE_DEFAULT; 294 if (!isEnableAllowed && !isEnabled) { 295 setEnabled(false); 296 } else if (isEnabled) { 297 setEnabled(true); 298 } else if (appOpsAllowed && isDisabledByEcm()) { 299 setEnabled(true); 300 } else if (!appOpsAllowed){ 301 setDisabledByAppOps(true); 302 } 303 } 304 } 305