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
17package com.android.settings.notification;
18
19import android.app.ActivityManager;
20import android.app.AlertDialog;
21import android.app.Dialog;
22import android.app.DialogFragment;
23import android.app.ListFragment;
24import android.content.BroadcastReceiver;
25import android.content.ComponentName;
26import android.content.ContentResolver;
27import android.content.Context;
28import android.content.DialogInterface;
29import android.content.Intent;
30import android.content.IntentFilter;
31import android.content.pm.PackageItemInfo;
32import android.content.pm.PackageManager;
33import android.content.pm.ResolveInfo;
34import android.content.pm.ServiceInfo;
35import android.database.ContentObserver;
36import android.net.Uri;
37import android.os.Bundle;
38import android.os.Handler;
39import android.provider.Settings;
40import android.util.Slog;
41import android.view.LayoutInflater;
42import android.view.View;
43import android.view.ViewGroup;
44import android.widget.ArrayAdapter;
45import android.widget.CheckBox;
46import android.widget.ImageView;
47import android.widget.ListView;
48import android.widget.TextView;
49
50import com.android.settings.R;
51
52import java.util.HashSet;
53import java.util.List;
54
55public 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
65    abstract protected Config getConfig();
66
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
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
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