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