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.notification;
18 
19 import android.app.ActivityManager;
20 import android.app.AlertDialog;
21 import android.app.Dialog;
22 import android.app.DialogFragment;
23 import android.app.ListFragment;
24 import android.content.BroadcastReceiver;
25 import android.content.ComponentName;
26 import android.content.ContentResolver;
27 import android.content.Context;
28 import android.content.DialogInterface;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.pm.PackageItemInfo;
32 import android.content.pm.PackageManager;
33 import android.content.pm.ResolveInfo;
34 import android.content.pm.ServiceInfo;
35 import android.database.ContentObserver;
36 import android.net.Uri;
37 import android.os.Bundle;
38 import android.os.Handler;
39 import android.provider.Settings;
40 import android.util.Slog;
41 import android.view.LayoutInflater;
42 import android.view.View;
43 import android.view.ViewGroup;
44 import android.widget.ArrayAdapter;
45 import android.widget.CheckBox;
46 import android.widget.ImageView;
47 import android.widget.ListView;
48 import android.widget.TextView;
49 
50 import com.android.settings.R;
51 
52 import java.util.HashSet;
53 import java.util.List;
54 
55 public abstract class ManagedServiceSettings extends ListFragment {
56     private static final boolean SHOW_PACKAGE_NAME = false;
57 
58     private final Config mConfig;
59     private PackageManager mPM;
60     private ContentResolver mCR;
61 
62     private final HashSet<ComponentName> mEnabledServices = new HashSet<ComponentName>();
63     private ServiceListAdapter mListAdapter;
64 
getConfig()65     abstract protected Config getConfig();
66 
ManagedServiceSettings()67     public ManagedServiceSettings() {
68         mConfig = getConfig();
69     }
70 
71     private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
72         @Override
73         public void onChange(boolean selfChange, Uri uri) {
74             updateList();
75         }
76     };
77 
78     private final BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
79         @Override
80         public void onReceive(Context context, Intent intent) {
81             updateList();
82         }
83     };
84 
85     public class ScaryWarningDialogFragment extends DialogFragment {
86         static final String KEY_COMPONENT = "c";
87         static final String KEY_LABEL = "l";
88 
setServiceInfo(ComponentName cn, String label)89         public ScaryWarningDialogFragment setServiceInfo(ComponentName cn, String label) {
90             Bundle args = new Bundle();
91             args.putString(KEY_COMPONENT, cn.flattenToString());
92             args.putString(KEY_LABEL, label);
93             setArguments(args);
94             return this;
95         }
96 
97         @Override
onCreateDialog(Bundle savedInstanceState)98         public Dialog onCreateDialog(Bundle savedInstanceState) {
99             super.onCreate(savedInstanceState);
100             final Bundle args = getArguments();
101             final String label = args.getString(KEY_LABEL);
102             final ComponentName cn = ComponentName.unflattenFromString(args.getString(KEY_COMPONENT));
103 
104             final String title = getResources().getString(mConfig.warningDialogTitle, label);
105             final String summary = getResources().getString(mConfig.warningDialogSummary, label);
106             return new AlertDialog.Builder(getActivity())
107                     .setMessage(summary)
108                     .setTitle(title)
109                     .setCancelable(true)
110                     .setPositiveButton(android.R.string.ok,
111                             new DialogInterface.OnClickListener() {
112                                 public void onClick(DialogInterface dialog, int id) {
113                                     mEnabledServices.add(cn);
114                                     saveEnabledServices();
115                                 }
116                             })
117                     .setNegativeButton(android.R.string.cancel,
118                             new DialogInterface.OnClickListener() {
119                                 public void onClick(DialogInterface dialog, int id) {
120                                     // pass
121                                 }
122                             })
123                     .create();
124         }
125     }
126 
127     @Override
128     public void onCreate(Bundle icicle) {
129         super.onCreate(icicle);
130 
131         mPM = getActivity().getPackageManager();
132         mCR = getActivity().getContentResolver();
133         mListAdapter = new ServiceListAdapter(getActivity());
134     }
135 
136     @Override
137     public View onCreateView(LayoutInflater inflater, ViewGroup container,
138             Bundle savedInstanceState) {
139         View v =  inflater.inflate(R.layout.managed_service_settings, container, false);
140         TextView empty = (TextView) v.findViewById(android.R.id.empty);
141         empty.setText(mConfig.emptyText);
142         return v;
143     }
144 
145     @Override
146     public void onResume() {
147         super.onResume();
148         updateList();
149 
150         // listen for package changes
151         IntentFilter filter = new IntentFilter();
152         filter.addAction(Intent.ACTION_PACKAGE_ADDED);
153         filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
154         filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
155         filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
156         filter.addDataScheme("package");
157         getActivity().registerReceiver(mPackageReceiver, filter);
158 
159         mCR.registerContentObserver(Settings.Secure.getUriFor(mConfig.setting),
160                 false, mSettingsObserver);
161     }
162 
163     @Override
164     public void onPause() {
165         super.onPause();
166 
167         getActivity().unregisterReceiver(mPackageReceiver);
168         mCR.unregisterContentObserver(mSettingsObserver);
169     }
170 
171     private void loadEnabledServices() {
172         mEnabledServices.clear();
173         final String flat = Settings.Secure.getString(mCR, mConfig.setting);
174         if (flat != null && !"".equals(flat)) {
175             final String[] names = flat.split(":");
176             for (int i = 0; i < names.length; i++) {
177                 final ComponentName cn = ComponentName.unflattenFromString(names[i]);
178                 if (cn != null) {
179                     mEnabledServices.add(cn);
180                 }
181             }
182         }
183     }
184 
185     private void saveEnabledServices() {
186         StringBuilder sb = null;
187         for (ComponentName cn : mEnabledServices) {
188             if (sb == null) {
189                 sb = new StringBuilder();
190             } else {
191                 sb.append(':');
192             }
193             sb.append(cn.flattenToString());
194         }
195         Settings.Secure.putString(mCR,
196                 mConfig.setting,
197                 sb != null ? sb.toString() : "");
198     }
199 
200     private void updateList() {
201         loadEnabledServices();
202 
203         getServices(mConfig, mListAdapter, mPM);
204         mListAdapter.sort(new PackageItemInfo.DisplayNameComparator(mPM));
205 
206         getListView().setAdapter(mListAdapter);
207     }
208 
209     protected static int getEnabledServicesCount(Config config, Context context) {
210         final String flat = Settings.Secure.getString(context.getContentResolver(), config.setting);
211         if (flat == null || "".equals(flat)) return 0;
212         final String[] components = flat.split(":");
213         return components.length;
214     }
215 
216     protected static int getServicesCount(Config c, PackageManager pm) {
217         return getServices(c, null, pm);
218     }
219 
220     private static int getServices(Config c, ArrayAdapter<ServiceInfo> adapter, PackageManager pm) {
221         int services = 0;
222         if (adapter != null) {
223             adapter.clear();
224         }
225         final int user = ActivityManager.getCurrentUser();
226 
227         List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
228                 new Intent(c.intentAction),
229                 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
230                 user);
231 
232         for (int i = 0, count = installedServices.size(); i < count; i++) {
233             ResolveInfo resolveInfo = installedServices.get(i);
234             ServiceInfo info = resolveInfo.serviceInfo;
235 
236             if (!c.permission.equals(info.permission)) {
237                 Slog.w(c.tag, "Skipping " + c.noun + " service "
238                         + info.packageName + "/" + info.name
239                         + ": it does not require the permission "
240                         + c.permission);
241                 continue;
242             }
243             if (adapter != null) {
244                 adapter.add(info);
245             }
246             services++;
247         }
248         return services;
249     }
250 
251     private boolean isServiceEnabled(ServiceInfo info) {
252         final ComponentName cn = new ComponentName(info.packageName, info.name);
253         return mEnabledServices.contains(cn);
254     }
255 
256     @Override
257     public void onListItemClick(ListView l, View v, int position, long id) {
258         ServiceInfo info = mListAdapter.getItem(position);
259         final ComponentName cn = new ComponentName(info.packageName, info.name);
260         if (mEnabledServices.contains(cn)) {
261             // the simple version: disabling
262             mEnabledServices.remove(cn);
263             saveEnabledServices();
264         } else {
265             // show a scary dialog
266             new ScaryWarningDialogFragment()
267                 .setServiceInfo(cn, info.loadLabel(mPM).toString())
268                 .show(getFragmentManager(), "dialog");
269         }
270     }
271 
272     private static class ViewHolder {
273         ImageView icon;
274         TextView name;
275         CheckBox checkbox;
276         TextView description;
277     }
278 
279     private class ServiceListAdapter extends ArrayAdapter<ServiceInfo> {
280         final LayoutInflater mInflater;
281 
282         ServiceListAdapter(Context context) {
283             super(context, 0, 0);
284             mInflater = (LayoutInflater)
285                     getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
286         }
287 
288         public boolean hasStableIds() {
289             return true;
290         }
291 
292         public long getItemId(int position) {
293             return position;
294         }
295 
296         public View getView(int position, View convertView, ViewGroup parent) {
297             View v;
298             if (convertView == null) {
299                 v = newView(parent);
300             } else {
301                 v = convertView;
302             }
303             bindView(v, position);
304             return v;
305         }
306 
307         public View newView(ViewGroup parent) {
308             View v = mInflater.inflate(R.layout.managed_service_item, parent, false);
309             ViewHolder h = new ViewHolder();
310             h.icon = (ImageView) v.findViewById(R.id.icon);
311             h.name = (TextView) v.findViewById(R.id.name);
312             h.checkbox = (CheckBox) v.findViewById(R.id.checkbox);
313             h.description = (TextView) v.findViewById(R.id.description);
314             v.setTag(h);
315             return v;
316         }
317 
318         public void bindView(View view, int position) {
319             ViewHolder vh = (ViewHolder) view.getTag();
320             ServiceInfo info = getItem(position);
321 
322             vh.icon.setImageDrawable(info.loadIcon(mPM));
323             vh.name.setText(info.loadLabel(mPM));
324             if (SHOW_PACKAGE_NAME) {
325                 vh.description.setText(info.packageName);
326                 vh.description.setVisibility(View.VISIBLE);
327             } else {
328                 vh.description.setVisibility(View.GONE);
329             }
330             vh.checkbox.setChecked(isServiceEnabled(info));
331         }
332     }
333 
334     protected static class Config {
335         String tag;
336         String setting;
337         String intentAction;
338         String permission;
339         String noun;
340         int warningDialogTitle;
341         int warningDialogSummary;
342         int emptyText;
343     }
344 }
345