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