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 setListenerInfo(ComponentName cn, String label)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 onCreateDialog(Bundle savedInstanceState)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