1 /*
2  * Copyright (C) 2014 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.settings.utils;
18 
19 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED;
20 
21 import android.app.Dialog;
22 import android.app.admin.DevicePolicyManager;
23 import android.app.settings.SettingsEnums;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.pm.PackageItemInfo;
27 import android.content.pm.PackageManager;
28 import android.content.pm.ServiceInfo;
29 import android.os.Bundle;
30 import android.os.UserHandle;
31 import android.os.UserManager;
32 import android.util.IconDrawableFactory;
33 import android.util.Log;
34 import android.view.View;
35 
36 import androidx.annotation.Nullable;
37 import androidx.appcompat.app.AlertDialog;
38 import androidx.fragment.app.Fragment;
39 import androidx.preference.PreferenceScreen;
40 
41 import com.android.settings.R;
42 import com.android.settings.Utils;
43 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
44 import com.android.settings.widget.EmptyTextSettings;
45 import com.android.settingslib.RestrictedSwitchPreference;
46 import com.android.settingslib.applications.ServiceListing;
47 import com.android.settingslib.widget.TwoTargetPreference;
48 
49 import java.util.List;
50 
51 public abstract class ManagedServiceSettings extends EmptyTextSettings {
52     private static final String TAG = "ManagedServiceSettings";
53     private final Config mConfig;
54 
55     protected Context mContext;
56     private PackageManager mPm;
57     private DevicePolicyManager mDpm;
58     private ServiceListing mServiceListing;
59     private IconDrawableFactory mIconDrawableFactory;
60 
getConfig()61     abstract protected Config getConfig();
62 
ManagedServiceSettings()63     public ManagedServiceSettings() {
64         mConfig = getConfig();
65     }
66 
67     @Override
onCreate(Bundle icicle)68     public void onCreate(Bundle icicle) {
69         super.onCreate(icicle);
70 
71         mContext = getActivity();
72         mPm = mContext.getPackageManager();
73         mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
74         mIconDrawableFactory = IconDrawableFactory.newInstance(mContext);
75         mServiceListing = new ServiceListing.Builder(mContext)
76                 .setPermission(mConfig.permission)
77                 .setIntentAction(mConfig.intentAction)
78                 .setNoun(mConfig.noun)
79                 .setSetting(mConfig.setting)
80                 .setTag(mConfig.tag)
81                 .build();
82         mServiceListing.addCallback(this::updateList);
83         setPreferenceScreen(getPreferenceManager().createPreferenceScreen(mContext));
84     }
85 
86     @Override
onViewCreated(View view, @Nullable Bundle savedInstanceState)87     public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
88         super.onViewCreated(view, savedInstanceState);
89         setEmptyText(mConfig.emptyText);
90     }
91 
92     @Override
onResume()93     public void onResume() {
94         super.onResume();
95         mServiceListing.reload();
96         mServiceListing.setListening(true);
97     }
98 
99     @Override
onPause()100     public void onPause() {
101         super.onPause();
102         mServiceListing.setListening(false);
103     }
104 
updateList(List<ServiceInfo> services)105     private void updateList(List<ServiceInfo> services) {
106         UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
107         final int managedProfileId = Utils.getManagedProfileId(um, UserHandle.myUserId());
108 
109         final PreferenceScreen screen = getPreferenceScreen();
110         screen.removeAll();
111         services.sort(new PackageItemInfo.DisplayNameComparator(mPm));
112         for (ServiceInfo service : services) {
113             final ComponentName cn = new ComponentName(service.packageName, service.name);
114             CharSequence title = null;
115             try {
116                 title = mPm.getApplicationInfoAsUser(
117                         service.packageName, 0, UserHandle.myUserId()).loadLabel(mPm);
118             } catch (PackageManager.NameNotFoundException e) {
119                 // unlikely, as we are iterating over live services.
120                 Log.e(TAG, "can't find package name", e);
121             }
122             final CharSequence finalTitle = title;
123             final String summary = service.loadLabel(mPm).toString();
124             final RestrictedSwitchPreference pref =
125                     new RestrictedSwitchPreference(getPrefContext());
126             pref.setPersistent(false);
127             pref.setIcon(mIconDrawableFactory.getBadgedIcon(service, service.applicationInfo,
128                     UserHandle.getUserId(service.applicationInfo.uid)));
129             pref.setIconSize(TwoTargetPreference.ICON_SIZE_MEDIUM);
130             if (title != null && !title.equals(summary)) {
131                 pref.setTitle(title);
132                 pref.setSummary(summary);
133             } else {
134                 pref.setTitle(summary);
135             }
136             pref.setKey(cn.flattenToString());
137             pref.setChecked(isServiceEnabled(cn));
138             if (managedProfileId != UserHandle.USER_NULL
139                     && !mDpm.isNotificationListenerServicePermitted(
140                             service.packageName, managedProfileId)) {
141                 pref.setSummary(mDpm.getResources().getString(
142                         WORK_PROFILE_NOTIFICATION_LISTENER_BLOCKED,
143                         () -> getString(
144                                 R.string.work_profile_notification_access_blocked_summary)));
145             }
146             pref.setOnPreferenceChangeListener((preference, newValue) -> {
147                 final boolean enable = (boolean) newValue;
148                 if (finalTitle != null) {
149                     return setEnabled(cn, finalTitle.toString(), enable);
150                 } else {
151                     return setEnabled(cn, null, enable);
152                 }
153             });
154             pref.setKey(cn.flattenToString());
155             if (!pref.isChecked()) {
156                 pref.checkEcmRestrictionAndSetDisabled(mConfig.permission, service.packageName);
157             }
158             screen.addPreference(pref);
159         }
160         highlightPreferenceIfNeeded();
161     }
162 
getCurrentUser(int managedProfileId)163     private int getCurrentUser(int managedProfileId) {
164         if (managedProfileId != UserHandle.USER_NULL) {
165             return managedProfileId;
166         }
167         return UserHandle.myUserId();
168     }
169 
isServiceEnabled(ComponentName cn)170     protected boolean isServiceEnabled(ComponentName cn) {
171         return mServiceListing.isEnabled(cn);
172     }
173 
setEnabled(ComponentName service, String title, boolean enable)174     protected boolean setEnabled(ComponentName service, String title, boolean enable) {
175         if (!enable) {
176             // the simple version: disabling
177             mServiceListing.setEnabled(service, false);
178             return true;
179         } else {
180             if (mServiceListing.isEnabled(service)) {
181                 return true; // already enabled
182             }
183             // show a scary dialog
184             new ScaryWarningDialogFragment()
185                     .setServiceInfo(service, title, this)
186                     .show(getFragmentManager(), "dialog");
187             return false;
188         }
189     }
190 
enable(ComponentName service)191     protected void enable(ComponentName service) {
192         mServiceListing.setEnabled(service, true);
193     }
194 
195     public static class ScaryWarningDialogFragment extends InstrumentedDialogFragment {
196         private static final String KEY_COMPONENT = "c";
197         private static final String KEY_LABEL = "l";
198 
199         @Override
getMetricsCategory()200         public int getMetricsCategory() {
201             return SettingsEnums.DIALOG_SERVICE_ACCESS_WARNING;
202         }
203 
setServiceInfo(ComponentName cn, String label, Fragment target)204         public ScaryWarningDialogFragment setServiceInfo(ComponentName cn, String label,
205                 Fragment target) {
206             Bundle args = new Bundle();
207             args.putString(KEY_COMPONENT, cn.flattenToString());
208             args.putString(KEY_LABEL, label);
209             setArguments(args);
210             setTargetFragment(target, 0);
211             return this;
212         }
213 
214         @Override
onCreateDialog(Bundle savedInstanceState)215         public Dialog onCreateDialog(Bundle savedInstanceState) {
216             final Bundle args = getArguments();
217             final String label = args.getString(KEY_LABEL);
218             final ComponentName cn = ComponentName.unflattenFromString(args
219                     .getString(KEY_COMPONENT));
220             ManagedServiceSettings parent = (ManagedServiceSettings) getTargetFragment();
221 
222             final String title = getResources().getString(parent.mConfig.warningDialogTitle, label);
223             final String summary = getResources().getString(parent.mConfig.warningDialogSummary,
224                     label);
225             return new AlertDialog.Builder(getContext())
226                     .setMessage(summary)
227                     .setTitle(title)
228                     .setCancelable(true)
229                     .setPositiveButton(R.string.allow,
230                             (dialog, id) -> parent.enable(cn))
231                     .setNegativeButton(R.string.deny,
232                             (dialog, id) -> {
233                                 // pass
234                             })
235                     .create();
236         }
237     }
238 
239     public static class Config {
240         public final String tag;
241         public final String setting;
242         public final String intentAction;
243         public final String permission;
244         public final String noun;
245         public final int warningDialogTitle;
246         public final int warningDialogSummary;
247         public final int emptyText;
248         public final String configIntentAction;
249 
Config(String tag, String setting, String intentAction, String configIntentAction, String permission, String noun, int warningDialogTitle, int warningDialogSummary, int emptyText)250         private Config(String tag, String setting, String intentAction, String configIntentAction,
251                 String permission, String noun, int warningDialogTitle, int warningDialogSummary,
252                 int emptyText) {
253             this.tag = tag;
254             this.setting = setting;
255             this.intentAction = intentAction;
256             this.permission = permission;
257             this.noun = noun;
258             this.warningDialogTitle = warningDialogTitle;
259             this.warningDialogSummary = warningDialogSummary;
260             this.emptyText = emptyText;
261             this.configIntentAction = configIntentAction;
262         }
263 
264         public static class Builder{
265             private String mTag;
266             private String mSetting;
267             private String mIntentAction;
268             private String mPermission;
269             private String mNoun;
270             private int mWarningDialogTitle;
271             private int mWarningDialogSummary;
272             private int mEmptyText;
273             private String mConfigIntentAction;
274 
setTag(String tag)275             public Builder setTag(String tag) {
276                 mTag = tag;
277                 return this;
278             }
279 
setSetting(String setting)280             public Builder setSetting(String setting) {
281                 mSetting = setting;
282                 return this;
283             }
284 
setIntentAction(String intentAction)285             public Builder setIntentAction(String intentAction) {
286                 mIntentAction = intentAction;
287                 return this;
288             }
289 
setConfigurationIntentAction(String action)290             public Builder setConfigurationIntentAction(String action) {
291                 mConfigIntentAction = action;
292                 return this;
293             }
294 
setPermission(String permission)295             public Builder setPermission(String permission) {
296                 mPermission = permission;
297                 return this;
298             }
299 
setNoun(String noun)300             public Builder setNoun(String noun) {
301                 mNoun = noun;
302                 return this;
303             }
304 
setWarningDialogTitle(int warningDialogTitle)305             public Builder setWarningDialogTitle(int warningDialogTitle) {
306                 mWarningDialogTitle = warningDialogTitle;
307                 return this;
308             }
309 
setWarningDialogSummary(int warningDialogSummary)310             public Builder setWarningDialogSummary(int warningDialogSummary) {
311                 mWarningDialogSummary = warningDialogSummary;
312                 return this;
313             }
314 
setEmptyText(int emptyText)315             public Builder setEmptyText(int emptyText) {
316                 mEmptyText = emptyText;
317                 return this;
318             }
319 
build()320             public Config build() {
321                 return new Config(mTag, mSetting, mIntentAction, mConfigIntentAction, mPermission,
322                         mNoun, mWarningDialogTitle, mWarningDialogSummary, mEmptyText);
323             }
324         }
325     }
326 
327 }
328