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