• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Build;
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.NonNull;
39 import androidx.annotation.Nullable;
40 import androidx.annotation.VisibleForTesting;
41 import androidx.preference.PreferenceManager;
42 import androidx.preference.PreferenceViewHolder;
43 import androidx.preference.SwitchPreferenceCompat;
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 implements
50         RestrictedPreferenceHelperProvider {
51     private final 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, androidx.preference.R.attr.switchPreferenceCompatStyle);
97     }
98 
RestrictedSwitchPreference(Context context)99     public RestrictedSwitchPreference(Context context) {
100         this(context, null);
101     }
102 
103     @Override
getRestrictedPreferenceHelper()104     public @NonNull RestrictedPreferenceHelper getRestrictedPreferenceHelper() {
105         return mHelper;
106     }
107 
108     @VisibleForTesting
setAppOps(AppOpsManager appOps)109     public void setAppOps(AppOpsManager appOps) {
110         mAppOpsManager = appOps;
111     }
112 
setIconSize(int iconSize)113     public void setIconSize(int iconSize) {
114         mIconSize = iconSize;
115     }
116 
117     @Override
onBindViewHolder(PreferenceViewHolder holder)118     public void onBindViewHolder(PreferenceViewHolder holder) {
119         super.onBindViewHolder(holder);
120         final View switchView = holder.findViewById(androidx.preference.R.id.switchWidget);
121         if (switchView != null) {
122             final View rootView = switchView.getRootView();
123             rootView.setFilterTouchesWhenObscured(true);
124         }
125 
126         mHelper.onBindViewHolder(holder);
127 
128         CharSequence switchSummary;
129         if (mRestrictedSwitchSummary == null) {
130             switchSummary = getRestrictedSwitchSummary();
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() && switchSummary != null) {
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() && switchSummary != null) {
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 (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
168             return context.getString(resId);
169         }
170         return context.getSystemService(DevicePolicyManager.class).getResources().getString(
171                 updatableStringId,
172                 () -> context.getString(resId));
173     }
174 
getRestrictedSwitchSummary()175     private @Nullable String getRestrictedSwitchSummary() {
176         if (mHelper.isRestrictionEnforcedByAdvancedProtection()) {
177             // Advanced Protection doesn't set the summary string, it keeps the current summary.
178             return null;
179         }
180 
181         return isChecked()
182                 ? getUpdatableEnterpriseString(
183                         getContext(), ENABLED_BY_ADMIN_SWITCH_SUMMARY,
184                         com.android.settingslib.widget.restricted.R.string.enabled_by_admin)
185                 : getUpdatableEnterpriseString(
186                         getContext(), DISABLED_BY_ADMIN_SWITCH_SUMMARY,
187                         com.android.settingslib.widget.restricted.R.string.disabled_by_admin);
188     }
189 
190     @Override
performClick()191     public void performClick() {
192         if (!mHelper.performClick()) {
193             super.performClick();
194         }
195     }
196 
useAdminDisabledSummary(boolean useSummary)197     public void useAdminDisabledSummary(boolean useSummary) {
198         mHelper.useAdminDisabledSummary(useSummary);
199     }
200 
201     @Override
onAttachedToHierarchy(PreferenceManager preferenceManager)202     protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
203         mHelper.onAttachedToHierarchy();
204         super.onAttachedToHierarchy(preferenceManager);
205     }
206 
checkRestrictionAndSetDisabled(String userRestriction)207     public void checkRestrictionAndSetDisabled(String userRestriction) {
208         mHelper.checkRestrictionAndSetDisabled(userRestriction, UserHandle.myUserId());
209     }
210 
checkRestrictionAndSetDisabled(String userRestriction, int userId)211     public void checkRestrictionAndSetDisabled(String userRestriction, int userId) {
212         mHelper.checkRestrictionAndSetDisabled(userRestriction, userId);
213     }
214 
215     /**
216      * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
217      * package. Marks the preference as disabled if so.
218      * @param settingIdentifier The key identifying the setting
219      * @param packageName the package to check the settingIdentifier for
220      * @param settingEnabled Whether the setting in question is enabled
221      */
checkEcmRestrictionAndSetDisabled(@onNull String settingIdentifier, @NonNull String packageName, boolean settingEnabled)222     public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
223             @NonNull String packageName, boolean settingEnabled) {
224         mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, settingEnabled);
225     }
226 
227     /**
228      * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
229      * package. Marks the preference as disabled if so.
230      * TODO b/390196024: remove this and update all callers to use the "settingEnabled" version
231      * @param settingIdentifier The key identifying the setting
232      * @param packageName the package to check the settingIdentifier for
233      */
checkEcmRestrictionAndSetDisabled(@onNull String settingIdentifier, @NonNull String packageName)234     public void checkEcmRestrictionAndSetDisabled(@NonNull String settingIdentifier,
235             @NonNull String packageName) {
236         mHelper.checkEcmRestrictionAndSetDisabled(settingIdentifier, packageName, false);
237     }
238 
239     @Override
setEnabled(boolean enabled)240     public void setEnabled(boolean enabled) {
241         boolean changed = false;
242         if (enabled && isDisabledByAdmin()) {
243             mHelper.setDisabledByAdmin(null);
244             changed = true;
245         }
246         if (enabled && isDisabledByEcm()) {
247             mHelper.setDisabledByEcm(null);
248             changed = true;
249         }
250         if (!changed) {
251             super.setEnabled(enabled);
252         }
253     }
254 
setDisabledByAdmin(EnforcedAdmin admin)255     public void setDisabledByAdmin(EnforcedAdmin admin) {
256         if (mHelper.setDisabledByAdmin(admin)) {
257             notifyChanged();
258         }
259     }
260 
isDisabledByAdmin()261     public boolean isDisabledByAdmin() {
262         return mHelper.isDisabledByAdmin();
263     }
264 
isDisabledByEcm()265     public boolean isDisabledByEcm() {
266         return mHelper.isDisabledByEcm();
267     }
268 
269     /**
270      * @deprecated TODO(b/308921175): This will be deleted with the
271      * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
272      * code.
273      */
274     @Deprecated
setDisabledByAppOps(boolean disabled)275     private void setDisabledByAppOps(boolean disabled) {
276         if (mHelper.setDisabledByAppOps(disabled)) {
277             notifyChanged();
278         }
279     }
280 
281     /**
282      * @deprecated TODO(b/308921175): This will be deleted with the
283      * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
284      * code.
285      */
286     @Deprecated
getUid()287     public int getUid() {
288         return mHelper != null ? mHelper.uid : Process.INVALID_UID;
289     }
290 
291     /**
292      * @deprecated TODO(b/308921175): This will be deleted with the
293      * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
294      * code.
295      */
296     @Deprecated
getPackageName()297     public String getPackageName() {
298         return mHelper != null ? mHelper.packageName : null;
299     }
300 
301     /**
302      * Updates enabled state based on associated package
303      *
304      * @deprecated TODO(b/308921175): This will be deleted with the
305      * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
306      * code.
307      */
308     @Deprecated
updateState( @onNull String packageName, int uid, boolean isEnableAllowed, boolean isEnabled)309     public void updateState(
310             @NonNull String packageName, int uid, boolean isEnableAllowed, boolean isEnabled) {
311         mHelper.updatePackageDetails(packageName, uid);
312         if (mAppOpsManager == null) {
313             mAppOpsManager = getContext().getSystemService(AppOpsManager.class);
314         }
315         final int mode = mAppOpsManager.noteOpNoThrow(
316                 AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
317                 uid, packageName);
318         final boolean ecmEnabled = getContext().getResources().getBoolean(
319                 com.android.internal.R.bool.config_enhancedConfirmationModeEnabled);
320         final boolean appOpsAllowed = !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED
321                 || mode == AppOpsManager.MODE_DEFAULT;
322         if (!isEnableAllowed && !isEnabled) {
323             setEnabled(false);
324         } else if (isEnabled) {
325             setEnabled(true);
326         } else if (appOpsAllowed && isDisabledByEcm()) {
327             setEnabled(true);
328         } else if (!appOpsAllowed){
329             setDisabledByAppOps(true);
330         }
331     }
332 }
333