1 /*
2  * Copyright (C) 2021 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.tv.settings;
18 
19 import android.content.Context;
20 import android.os.UserHandle;
21 import android.os.UserManager;
22 import android.text.TextUtils;
23 
24 import androidx.preference.Preference;
25 import androidx.preference.PreferenceGroup;
26 
27 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
28 import com.android.settingslib.RestrictedLockUtilsInternal;
29 import com.android.settingslib.RestrictedPreference;
30 
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.function.Consumer;
34 
35 /**
36  * Adapter that wraps a regular Preference, and restricts the preference if user restrictions apply.
37  *
38  * <p>
39  * If the user is restricted by an admin, the preference is replaced by a RestrictedPreference.
40  * Some state from the original preference is copied, such as title, summary, and visibility.
41  * The restricted preference shows an explanatory message dialog on click, and does not show a
42  * preview panel.
43  *
44  * <p>
45  * If a base user restriction applies, the original preference is disabled.
46  *
47  * <p>
48  * Updates to the preference should be made through {@link #updatePreference(Consumer)}, so that the
49  * restricted preference can be updated after changes, or the original preference can be kept
50  * disabled. Otherwise, {@link #updatePreference()} can be called after direct edits to
51  * the underlying preference.
52  *
53  * @param <T> the type of the wrapped preference
54  */
55 public class RestrictedPreferenceAdapter<T extends Preference> {
56     private final Context mContext;
57     private final T mOriginalPreference;
58     private final List<String> mUserRestrictions;
59     private final boolean mRestricted;
60     private final EnforcedAdmin mEnforcingAdmin;
61     private final RestrictedPreference mRestrictedPreference;
62 
RestrictedPreferenceAdapter(Context context, T originalPreference, List<String> userRestrictions)63     public RestrictedPreferenceAdapter(Context context, T originalPreference,
64             List<String> userRestrictions) {
65         this.mContext = context;
66         this.mOriginalPreference = originalPreference;
67         this.mUserRestrictions = userRestrictions;
68 
69         this.mRestricted = isRestricted();
70         this.mEnforcingAdmin = isRestrictedByAdmin();
71 
72         if (mEnforcingAdmin != null) {
73             mRestrictedPreference = new RestrictedPreference(mContext);
74         } else {
75             mRestrictedPreference = null;
76         }
77     }
78 
RestrictedPreferenceAdapter(Context context, T originalPreference, String userRestriction)79     public RestrictedPreferenceAdapter(Context context, T originalPreference,
80             String userRestriction) {
81         this(context, originalPreference, Collections.singletonList(userRestriction));
82     }
83 
84     /**
85      * Create a RestrictedPreferenceAdapter from a preference.
86      *
87      * @param <T> the type of the preference
88      * @param preference the preference to adapt
89      * @param userRestriction the user restriction to enforce for this preference
90      */
adapt(T preference, String userRestriction)91     public static <T extends Preference> RestrictedPreferenceAdapter<T> adapt(T preference,
92             String userRestriction) {
93         RestrictedPreferenceAdapter<T> adapter = new RestrictedPreferenceAdapter<T>(
94                 preference.getContext(), preference, userRestriction);
95         adapter.setup();
96         return adapter;
97     }
98 
setup()99     private void setup() {
100         if (mRestricted) {
101             mOriginalPreference.setEnabled(false);
102 
103             if (mEnforcingAdmin != null) {
104                 updateRestrictedPreference();
105                 replacePreference();
106             }
107         }
108     }
109 
110     /**
111      * Returns {@code true} if a restriction applies to this preference, {@code false} otherwise.
112      */
isRestricted()113     public boolean isRestricted() {
114         if (mUserRestrictions == null) {
115             return false;
116         }
117 
118         UserManager userManager = UserManager.get(mContext);
119         for (String userRestriction : mUserRestrictions) {
120             if (userManager.hasUserRestriction(userRestriction)) {
121                 return true;
122             }
123         }
124 
125         return false;
126     }
127 
isRestrictedByAdmin()128     private EnforcedAdmin isRestrictedByAdmin() {
129         if (mUserRestrictions == null) {
130             return null;
131         }
132 
133         for (String userRestriction : mUserRestrictions) {
134             EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfRestrictionEnforced(mContext,
135                     userRestriction, UserHandle.myUserId());
136             if (admin != null) {
137                 return admin;
138             }
139         }
140         return null;
141     }
142 
replacePreference()143     private void replacePreference() {
144         if (mRestrictedPreference == null) {
145             return;
146         }
147 
148         final String key = mOriginalPreference.getKey();
149         if (TextUtils.isEmpty(key)) {
150             throw new IllegalArgumentException("Can't replace a preference without a key");
151         }
152         final PreferenceGroup screen = mOriginalPreference.getParent();
153         final int order = mOriginalPreference.getOrder();
154         mRestrictedPreference.setOrder(order);
155         screen.removePreference(mOriginalPreference);
156         screen.addPreference(mRestrictedPreference);
157     }
158 
159     /**
160      * Returns the preference to be inserted into the preference screen.
161      *
162      * If the preference is unrestricted, this returns the underlying original preference.
163      * If the preference is restricted, this returns the RestrictedPreference that replaces
164      * the original preference.
165      */
getPreference()166     public Preference getPreference() {
167         if (mRestrictedPreference != null) {
168             return mRestrictedPreference;
169         }
170 
171         return mOriginalPreference;
172     }
173 
174     /** Returns the original preference. */
getOriginalPreference()175     public Preference getOriginalPreference() {
176         return mOriginalPreference;
177     }
178 
179     /**
180      * Update the preference.
181      * The update function will be called with the adapted preference, changes will be reflected
182      * on the restricted preference.
183      *
184      * @param updateFn function that is passed the original preference to make updates to
185      */
updatePreference(Consumer<T> updateFn)186     public void updatePreference(Consumer<T> updateFn) {
187         updateFn.accept(mOriginalPreference);
188         updatePreference();
189     }
190 
191     /**
192      * Update the preference.
193      * This copies some attributes from the original preference to the restricted preference.
194      * Call this after making direct changes to the original preference.
195      */
updatePreference()196     public void updatePreference() {
197         if (mRestricted) {
198             mOriginalPreference.setEnabled(false);
199         }
200         updateRestrictedPreference();
201     }
202 
updateRestrictedPreference()203     private void updateRestrictedPreference() {
204         if (mRestrictedPreference == null) {
205             return;
206         }
207 
208         mRestrictedPreference.setKey(mOriginalPreference.getKey());
209         mRestrictedPreference.setTitle(mOriginalPreference.getTitle());
210         mRestrictedPreference.setSummary(mOriginalPreference.getSummary());
211         mRestrictedPreference.setIcon(mOriginalPreference.getIcon());
212         mRestrictedPreference.setVisible(mOriginalPreference.isVisible());
213         mRestrictedPreference.setDisabledByAdmin(isRestrictedByAdmin());
214     }
215 }
216