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