• 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.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