Settings: External automatic rule settings.

 - Add external automatic rule settings page with the common
   settings for all rules (enabled, name, zen mode).
 - Pull common rule-instance settings into settings base class, share
   with existing schedule rule settings.
 - New page not searchable since it is at the rule-instance level.
 - Obtain external rule information from existing conditions provider
   metadata.  Includes rule type caption, sub-configuration activity,
   and default condition id.
 - If external condition providers exist with the appropriate metadata,
   display the external rule types as options in the new rule dialog.
   (max of 3 external types)
 - Pull common managed service listing code out of common settings base
   class and into a more reusable helper class.

Bug: 20064962
Change-Id: Ibc13607490b7312a7d9f7f3bd61c3cfcf71a2794
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index fe0df59..ba08036 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -98,6 +98,7 @@
     public static class ZenModePrioritySettingsActivity extends SettingsActivity { /* empty */ }
     public static class ZenModeAutomationSettingsActivity extends SettingsActivity { /* empty */ }
     public static class ZenModeScheduleRuleSettingsActivity extends SettingsActivity { /* empty */ }
+    public static class ZenModeExternalRuleSettingsActivity extends SettingsActivity { /* empty */ }
     public static class NotificationSettingsActivity extends SettingsActivity { /* empty */ }
     public static class NotificationAppListActivity extends SettingsActivity { /* empty */ }
     public static class AppNotificationSettingsActivity extends SettingsActivity { /* empty */ }
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index 7bfd249..0fdf04b 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -95,11 +95,11 @@
 import com.android.settings.nfc.AndroidBeam;
 import com.android.settings.nfc.PaymentSettings;
 import com.android.settings.notification.AppNotificationSettings;
-import com.android.settings.notification.ConditionProviderSettings;
 import com.android.settings.notification.NotificationAccessSettings;
 import com.android.settings.notification.NotificationSettings;
 import com.android.settings.notification.NotificationStation;
 import com.android.settings.notification.OtherSoundSettings;
+import com.android.settings.notification.ZenModeExternalRuleSettings;
 import com.android.settings.notification.ZenModeSettings;
 import com.android.settings.notification.ZenModeScheduleRuleSettings;
 import com.android.settings.print.PrintJobSettingsFragment;
@@ -320,7 +320,6 @@
             DreamSettings.class.getName(),
             UserSettings.class.getName(),
             NotificationAccessSettings.class.getName(),
-            ConditionProviderSettings.class.getName(),
             PrintSettingsFragment.class.getName(),
             PrintJobSettingsFragment.class.getName(),
             TrustedCredentialsSettings.class.getName(),
@@ -337,6 +336,7 @@
             ApnSettings.class.getName(),
             WifiCallingSettings.class.getName(),
             ZenModeScheduleRuleSettings.class.getName(),
+            ZenModeExternalRuleSettings.class.getName(),
     };
 
 
diff --git a/src/com/android/settings/notification/ConditionProviderSettings.java b/src/com/android/settings/notification/ConditionProviderSettings.java
deleted file mode 100644
index 76576ab..0000000
--- a/src/com/android/settings/notification/ConditionProviderSettings.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings.notification;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.provider.Settings;
-import android.service.notification.ConditionProviderService;
-
-public class ConditionProviderSettings extends ManagedServiceSettings {
-    private static final String TAG = ConditionProviderSettings.class.getSimpleName();
-    private static final Config CONFIG = getConditionProviderConfig();
-
-    private static Config getConditionProviderConfig() {
-        final Config c = new Config();
-        c.tag = TAG;
-        c.setting = Settings.Secure.ENABLED_CONDITION_PROVIDERS;
-        c.intentAction = ConditionProviderService.SERVICE_INTERFACE;
-        c.permission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
-        c.noun = "condition provider";
-        return c;
-    }
-
-    @Override
-    protected Config getConfig() {
-        return CONFIG;
-    }
-
-    public static int getProviderCount(PackageManager pm) {
-        return getServicesCount(CONFIG, pm);
-    }
-
-    public static int getEnabledProviderCount(Context context) {
-        return getEnabledServicesCount(CONFIG, context);
-    }
-}
diff --git a/src/com/android/settings/notification/ManagedServiceSettings.java b/src/com/android/settings/notification/ManagedServiceSettings.java
index 7be644e..cc9e734 100644
--- a/src/com/android/settings/notification/ManagedServiceSettings.java
+++ b/src/com/android/settings/notification/ManagedServiceSettings.java
@@ -16,28 +16,17 @@
 
 package com.android.settings.notification;
 
-import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
 import android.app.ListFragment;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
-import android.database.ContentObserver;
-import android.net.Uri;
 import android.os.Bundle;
-import android.os.Handler;
-import android.provider.Settings;
-import android.util.Slog;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -49,17 +38,15 @@
 
 import com.android.settings.R;
 
-import java.util.HashSet;
 import java.util.List;
 
 public abstract class ManagedServiceSettings extends ListFragment {
     private static final boolean SHOW_PACKAGE_NAME = false;
 
     private final Config mConfig;
-    private PackageManager mPM;
-    private ContentResolver mCR;
 
-    private final HashSet<ComponentName> mEnabledServices = new HashSet<ComponentName>();
+    private PackageManager mPM;
+    private ServiceListing mServiceListing;
     private ServiceListAdapter mListAdapter;
 
     abstract protected Config getConfig();
@@ -68,19 +55,65 @@
         mConfig = getConfig();
     }
 
-    private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
-        @Override
-        public void onChange(boolean selfChange, Uri uri) {
-            updateList();
-        }
-    };
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
 
-    private final BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            updateList();
+        mPM = getActivity().getPackageManager();
+        mServiceListing = new ServiceListing(getActivity(), mConfig);
+        mServiceListing.addCallback(new ServiceListing.Callback() {
+            @Override
+            public void onServicesReloaded(List<ServiceInfo> services) {
+                updateList(services);
+            }
+        });
+        mListAdapter = new ServiceListAdapter(getActivity());
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        View v =  inflater.inflate(R.layout.managed_service_settings, container, false);
+        TextView empty = (TextView) v.findViewById(android.R.id.empty);
+        empty.setText(mConfig.emptyText);
+        return v;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mServiceListing.reload();
+        mServiceListing.setListening(true);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mServiceListing.setListening(false);
+    }
+
+    private void updateList(List<ServiceInfo> services) {
+        mListAdapter.clear();
+        mListAdapter.addAll(services);
+        mListAdapter.sort(new PackageItemInfo.DisplayNameComparator(mPM));
+
+        getListView().setAdapter(mListAdapter);
+    }
+
+    @Override
+    public void onListItemClick(ListView l, View v, int position, long id) {
+        ServiceInfo info = mListAdapter.getItem(position);
+        final ComponentName cn = new ComponentName(info.packageName, info.name);
+        if (mServiceListing.isEnabled(cn)) {
+            // the simple version: disabling
+            mServiceListing.setEnabled(cn, false);
+        } else {
+            // show a scary dialog
+            new ScaryWarningDialogFragment()
+                .setServiceInfo(cn, info.loadLabel(mPM).toString())
+                .show(getFragmentManager(), "dialog");
         }
-    };
+    }
 
     public class ScaryWarningDialogFragment extends DialogFragment {
         static final String KEY_COMPONENT = "c";
@@ -99,7 +132,8 @@
             super.onCreate(savedInstanceState);
             final Bundle args = getArguments();
             final String label = args.getString(KEY_LABEL);
-            final ComponentName cn = ComponentName.unflattenFromString(args.getString(KEY_COMPONENT));
+            final ComponentName cn = ComponentName.unflattenFromString(args
+                    .getString(KEY_COMPONENT));
 
             final String title = getResources().getString(mConfig.warningDialogTitle, label);
             final String summary = getResources().getString(mConfig.warningDialogSummary, label);
@@ -110,8 +144,7 @@
                     .setPositiveButton(android.R.string.ok,
                             new DialogInterface.OnClickListener() {
                                 public void onClick(DialogInterface dialog, int id) {
-                                    mEnabledServices.add(cn);
-                                    saveEnabledServices();
+                                    mServiceListing.setEnabled(cn, true);
                                 }
                             })
                     .setNegativeButton(android.R.string.cancel,
@@ -124,151 +157,6 @@
         }
     }
 
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        mPM = getActivity().getPackageManager();
-        mCR = getActivity().getContentResolver();
-        mListAdapter = new ServiceListAdapter(getActivity());
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-            Bundle savedInstanceState) {
-        View v =  inflater.inflate(R.layout.managed_service_settings, container, false);
-        TextView empty = (TextView) v.findViewById(android.R.id.empty);
-        empty.setText(mConfig.emptyText);
-        return v;
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        updateList();
-
-        // listen for package changes
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
-        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
-        filter.addDataScheme("package");
-        getActivity().registerReceiver(mPackageReceiver, filter);
-
-        mCR.registerContentObserver(Settings.Secure.getUriFor(mConfig.setting),
-                false, mSettingsObserver);
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-
-        getActivity().unregisterReceiver(mPackageReceiver);
-        mCR.unregisterContentObserver(mSettingsObserver);
-    }
-
-    private void loadEnabledServices() {
-        mEnabledServices.clear();
-        final String flat = Settings.Secure.getString(mCR, mConfig.setting);
-        if (flat != null && !"".equals(flat)) {
-            final String[] names = flat.split(":");
-            for (int i = 0; i < names.length; i++) {
-                final ComponentName cn = ComponentName.unflattenFromString(names[i]);
-                if (cn != null) {
-                    mEnabledServices.add(cn);
-                }
-            }
-        }
-    }
-
-    private void saveEnabledServices() {
-        StringBuilder sb = null;
-        for (ComponentName cn : mEnabledServices) {
-            if (sb == null) {
-                sb = new StringBuilder();
-            } else {
-                sb.append(':');
-            }
-            sb.append(cn.flattenToString());
-        }
-        Settings.Secure.putString(mCR,
-                mConfig.setting,
-                sb != null ? sb.toString() : "");
-    }
-
-    private void updateList() {
-        loadEnabledServices();
-
-        getServices(mConfig, mListAdapter, mPM);
-        mListAdapter.sort(new PackageItemInfo.DisplayNameComparator(mPM));
-
-        getListView().setAdapter(mListAdapter);
-    }
-
-    protected static int getEnabledServicesCount(Config config, Context context) {
-        final String flat = Settings.Secure.getString(context.getContentResolver(), config.setting);
-        if (flat == null || "".equals(flat)) return 0;
-        final String[] components = flat.split(":");
-        return components.length;
-    }
-
-    protected static int getServicesCount(Config c, PackageManager pm) {
-        return getServices(c, null, pm);
-    }
-
-    private static int getServices(Config c, ArrayAdapter<ServiceInfo> adapter, PackageManager pm) {
-        int services = 0;
-        if (adapter != null) {
-            adapter.clear();
-        }
-        final int user = ActivityManager.getCurrentUser();
-
-        List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
-                new Intent(c.intentAction),
-                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
-                user);
-
-        for (int i = 0, count = installedServices.size(); i < count; i++) {
-            ResolveInfo resolveInfo = installedServices.get(i);
-            ServiceInfo info = resolveInfo.serviceInfo;
-
-            if (!c.permission.equals(info.permission)) {
-                Slog.w(c.tag, "Skipping " + c.noun + " service "
-                        + info.packageName + "/" + info.name
-                        + ": it does not require the permission "
-                        + c.permission);
-                continue;
-            }
-            if (adapter != null) {
-                adapter.add(info);
-            }
-            services++;
-        }
-        return services;
-    }
-
-    private boolean isServiceEnabled(ServiceInfo info) {
-        final ComponentName cn = new ComponentName(info.packageName, info.name);
-        return mEnabledServices.contains(cn);
-    }
-
-    @Override
-    public void onListItemClick(ListView l, View v, int position, long id) {
-        ServiceInfo info = mListAdapter.getItem(position);
-        final ComponentName cn = new ComponentName(info.packageName, info.name);
-        if (mEnabledServices.contains(cn)) {
-            // the simple version: disabling
-            mEnabledServices.remove(cn);
-            saveEnabledServices();
-        } else {
-            // show a scary dialog
-            new ScaryWarningDialogFragment()
-                .setServiceInfo(cn, info.loadLabel(mPM).toString())
-                .show(getFragmentManager(), "dialog");
-        }
-    }
-
     private static class ViewHolder {
         ImageView icon;
         TextView name;
@@ -327,7 +215,8 @@
             } else {
                 vh.description.setVisibility(View.GONE);
             }
-            vh.checkbox.setChecked(isServiceEnabled(info));
+            final ComponentName cn = new ComponentName(info.packageName, info.name);
+            vh.checkbox.setChecked(mServiceListing.isEnabled(cn));
         }
     }
 
diff --git a/src/com/android/settings/notification/NotificationAccessSettings.java b/src/com/android/settings/notification/NotificationAccessSettings.java
index ced71a4..5104d4a 100644
--- a/src/com/android/settings/notification/NotificationAccessSettings.java
+++ b/src/com/android/settings/notification/NotificationAccessSettings.java
@@ -46,10 +46,10 @@
     }
 
     public static int getListenersCount(PackageManager pm) {
-        return getServicesCount(CONFIG, pm);
+        return ServiceListing.getServicesCount(CONFIG, pm);
     }
 
     public static int getEnabledListenersCount(Context context) {
-        return getEnabledServicesCount(CONFIG, context);
+        return ServiceListing.getEnabledServicesCount(CONFIG, context);
     }
 }
diff --git a/src/com/android/settings/notification/ServiceListing.java b/src/com/android/settings/notification/ServiceListing.java
new file mode 100644
index 0000000..d296139
--- /dev/null
+++ b/src/com/android/settings/notification/ServiceListing.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.Slog;
+
+import com.android.settings.notification.ManagedServiceSettings.Config;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+public class ServiceListing {
+    private final ContentResolver mContentResolver;
+    private final Context mContext;
+    private final Config mConfig;
+    private final HashSet<ComponentName> mEnabledServices = new HashSet<ComponentName>();
+    private final List<ServiceInfo> mServices = new ArrayList<ServiceInfo>();
+    private final List<Callback> mCallbacks = new ArrayList<Callback>();
+
+    private boolean mListening;
+
+    public ServiceListing(Context context, Config config) {
+        mContext = context;
+        mConfig = config;
+        mContentResolver = context.getContentResolver();
+    }
+
+    public void addCallback(Callback callback) {
+        mCallbacks.add(callback);
+    }
+
+    public void removeCallback(Callback callback) {
+        mCallbacks.remove(callback);
+    }
+
+    public void setListening(boolean listening) {
+        if (mListening == listening) return;
+        mListening = listening;
+        if (mListening) {
+            // listen for package changes
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+            filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+            filter.addDataScheme("package");
+            mContext.registerReceiver(mPackageReceiver, filter);
+            mContentResolver.registerContentObserver(Settings.Secure.getUriFor(mConfig.setting),
+                    false, mSettingsObserver);
+        } else {
+            mContext.unregisterReceiver(mPackageReceiver);
+            mContentResolver.unregisterContentObserver(mSettingsObserver);
+        }
+    }
+
+    public static int getEnabledServicesCount(Config config, Context context) {
+        final String flat = Settings.Secure.getString(context.getContentResolver(), config.setting);
+        if (flat == null || "".equals(flat)) return 0;
+        final String[] components = flat.split(":");
+        return components.length;
+    }
+
+    public static int getServicesCount(Config c, PackageManager pm) {
+        return getServices(c, null, pm);
+    }
+
+    public static ServiceInfo findService(Context context, Config config, final ComponentName cn) {
+        final ServiceListing listing = new ServiceListing(context, config);
+        final List<ServiceInfo> services = listing.reload();
+        for (ServiceInfo service : services) {
+            final ComponentName serviceCN = new ComponentName(service.packageName, service.name);
+            if (serviceCN.equals(cn)) {
+                return service;
+            }
+        }
+        return null;
+    }
+
+    private static int getServices(Config c, List<ServiceInfo> list, PackageManager pm) {
+        int services = 0;
+        if (list != null) {
+            list.clear();
+        }
+        final int user = ActivityManager.getCurrentUser();
+
+        List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
+                new Intent(c.intentAction),
+                PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+                user);
+
+        for (int i = 0, count = installedServices.size(); i < count; i++) {
+            ResolveInfo resolveInfo = installedServices.get(i);
+            ServiceInfo info = resolveInfo.serviceInfo;
+
+            if (!c.permission.equals(info.permission)) {
+                Slog.w(c.tag, "Skipping " + c.noun + " service "
+                        + info.packageName + "/" + info.name
+                        + ": it does not require the permission "
+                        + c.permission);
+                continue;
+            }
+            if (list != null) {
+                list.add(info);
+            }
+            services++;
+        }
+        return services;
+    }
+
+    private void saveEnabledServices() {
+        StringBuilder sb = null;
+        for (ComponentName cn : mEnabledServices) {
+            if (sb == null) {
+                sb = new StringBuilder();
+            } else {
+                sb.append(':');
+            }
+            sb.append(cn.flattenToString());
+        }
+        Settings.Secure.putString(mContentResolver, mConfig.setting,
+                sb != null ? sb.toString() : "");
+    }
+
+    private void loadEnabledServices() {
+        mEnabledServices.clear();
+        final String flat = Settings.Secure.getString(mContentResolver, mConfig.setting);
+        if (flat != null && !"".equals(flat)) {
+            final String[] names = flat.split(":");
+            for (int i = 0; i < names.length; i++) {
+                final ComponentName cn = ComponentName.unflattenFromString(names[i]);
+                if (cn != null) {
+                    mEnabledServices.add(cn);
+                }
+            }
+        }
+    }
+
+    public List<ServiceInfo> reload() {
+        loadEnabledServices();
+        getServices(mConfig, mServices, mContext.getPackageManager());
+        for (Callback callback : mCallbacks) {
+            callback.onServicesReloaded(mServices);
+        }
+        return mServices;
+    }
+
+    public boolean isEnabled(ComponentName cn) {
+        return mEnabledServices.contains(cn);
+    }
+
+    public void setEnabled(ComponentName cn, boolean enabled) {
+        if (enabled) {
+            mEnabledServices.add(cn);
+        } else {
+            mEnabledServices.remove(cn);
+        }
+        saveEnabledServices();
+    }
+
+    private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            reload();
+        }
+    };
+
+    private final BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            reload();
+        }
+    };
+
+    public interface Callback {
+        void onServicesReloaded(List<ServiceInfo> services);
+    }
+}
diff --git a/src/com/android/settings/notification/ZenModeAutomationSettings.java b/src/com/android/settings/notification/ZenModeAutomationSettings.java
index c43d99e..f2ee71d 100644
--- a/src/com/android/settings/notification/ZenModeAutomationSettings.java
+++ b/src/com/android/settings/notification/ZenModeAutomationSettings.java
@@ -18,12 +18,16 @@
 
 import static android.service.notification.ZenModeConfig.ALL_DAYS;
 
+import android.content.ComponentName;
 import android.content.Intent;
+import android.content.pm.ServiceInfo;
 import android.os.Bundle;
 import android.preference.Preference;
 import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceScreen;
+import android.provider.Settings;
 import android.provider.Settings.Global;
+import android.service.notification.ConditionProviderService;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ScheduleInfo;
 import android.service.notification.ZenModeConfig.ZenRule;
@@ -35,30 +39,39 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.settings.R;
+import com.android.settings.notification.ManagedServiceSettings.Config;
+import com.android.settings.notification.ZenRuleNameDialog.RuleInfo;
 
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
+import java.util.List;
 import java.util.TreeSet;
 
 public class ZenModeAutomationSettings extends ZenModeSettingsBase {
     private static final SimpleDateFormat DAY_FORMAT = new SimpleDateFormat("EEE");
 
+    static final Config CONFIG = getConditionProviderConfig();
+
     private final Calendar mCalendar = Calendar.getInstance();
 
+    private ServiceListing mServiceListing;
+
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
-
         setHasOptionsMenu(true);
-
         addPreferencesFromResource(R.xml.zen_mode_automation_settings);
+        mServiceListing = new ServiceListing(mContext, CONFIG);
+        mServiceListing.addCallback(mServiceListingCallback);
+        mServiceListing.reload();
+        mServiceListing.setListening(true);
     }
 
-    private void showRule(String ruleId, String ruleName) {
-        if (DEBUG) Log.d(TAG, "showRule " + ruleId + " name=" + ruleName);
-        mContext.startActivity(new Intent(ZenModeScheduleRuleSettings.ACTION)
-                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
-                .putExtra(ZenModeScheduleRuleSettings.EXTRA_RULE_ID, ruleId));
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mServiceListing.setListening(false);
+        mServiceListing.removeCallback(mServiceListingCallback);
     }
 
     @Override
@@ -75,29 +88,6 @@
         return super.onOptionsItemSelected(item);
     }
 
-    private void showAddRuleDialog() {
-        new ZenRuleNameDialog(mContext, "", mConfig.getAutomaticRuleNames()) {
-            @Override
-            public void onOk(String ruleName) {
-                final ScheduleInfo schedule = new ScheduleInfo();
-                schedule.days = ZenModeConfig.ALL_DAYS;
-                schedule.startHour = 22;
-                schedule.endHour = 7;
-                final ZenRule rule = new ZenRule();
-                rule.name = ruleName;
-                rule.enabled = true;
-                rule.zenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
-                rule.conditionId = ZenModeConfig.toScheduleConditionId(schedule);
-                final ZenModeConfig newConfig = mConfig.copy();
-                final String ruleId = newConfig.newRuleId();
-                newConfig.automaticRules.put(ruleId, rule);
-                if (setZenModeConfig(newConfig)) {
-                    showRule(ruleId, rule.name);
-                }
-            }
-        }.show();
-    }
-
     @Override
     protected void onZenModeChanged() {
         // don't care
@@ -114,15 +104,42 @@
         updateControls();
     }
 
+    private void showAddRuleDialog() {
+        new ZenRuleNameDialog(mContext, mServiceListing, null, mConfig.getAutomaticRuleNames()) {
+            @Override
+            public void onOk(String ruleName, RuleInfo ri) {
+                final ZenRule rule = new ZenRule();
+                rule.name = ruleName;
+                rule.enabled = true;
+                rule.zenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+                rule.conditionId = ri.defaultConditionId;
+                rule.component = ri.serviceComponent;
+                final ZenModeConfig newConfig = mConfig.copy();
+                final String ruleId = newConfig.newRuleId();
+                newConfig.automaticRules.put(ruleId, rule);
+                if (setZenModeConfig(newConfig)) {
+                    showRule(ri.settingsAction, ri.configurationActivity, ruleId, rule.name);
+                }
+            }
+        }.show();
+    }
+
+    private void showRule(String settingsAction, ComponentName configurationActivity,
+            String ruleId, String ruleName) {
+        if (DEBUG) Log.d(TAG, "showRule " + ruleId + " name=" + ruleName);
+        mContext.startActivity(new Intent(settingsAction)
+                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+                .putExtra(ZenModeRuleSettingsBase.EXTRA_RULE_ID, ruleId));
+    }
+
     private void updateControls() {
         final PreferenceScreen root = getPreferenceScreen();
         root.removeAll();
-
         if (mConfig == null) return;
         for (int i = 0; i < mConfig.automaticRules.size(); i++) {
             final String id = mConfig.automaticRules.keyAt(i);
             final ZenRule rule = mConfig.automaticRules.valueAt(i);
-            if (!ZenModeConfig.isValidScheduleConditionId(rule.conditionId)) continue;
+            final boolean isSchedule = ZenModeConfig.isValidScheduleConditionId(rule.conditionId);
             final Preference p = new Preference(mContext);
             p.setTitle(rule.name);
             p.setSummary(computeRuleSummary(rule));
@@ -130,7 +147,9 @@
             p.setOnPreferenceClickListener(new OnPreferenceClickListener() {
                 @Override
                 public boolean onPreferenceClick(Preference preference) {
-                    showRule(id, rule.name);
+                    final String action = isSchedule ? ZenModeScheduleRuleSettings.ACTION
+                            : ZenModeExternalRuleSettings.ACTION;
+                    showRule(action, null, id, rule.name);
                     return true;
                 }
             });
@@ -146,13 +165,16 @@
     private String computeRuleSummary(ZenRule rule) {
         if (rule == null || !rule.enabled) return getString(R.string.switch_off_text);
         final ScheduleInfo schedule = ZenModeConfig.tryParseScheduleConditionId(rule.conditionId);
-        if (schedule == null) return getString(R.string.switch_on_text);
-        final String days = computeContiguousDayRanges(schedule.days);
-        final String start = getTime(schedule.startHour, schedule.startMinute);
-        final String end = getTime(schedule.endHour, schedule.endMinute);
-        final String time = getString(R.string.summary_range_verbal_combination, start, end);
         final String mode = ZenModeSettings.computeZenModeCaption(getResources(), rule.zenMode);
-        return getString(R.string.zen_mode_rule_summary_template, days, time, mode);
+        String summary = getString(R.string.switch_on_text);
+        if (schedule != null) {
+            final String days = computeContiguousDayRanges(schedule.days);
+            final String start = getTime(schedule.startHour, schedule.startMinute);
+            final String end = getTime(schedule.endHour, schedule.endMinute);
+            final String time = getString(R.string.summary_range_verbal_combination, start, end);
+            summary = getString(R.string.zen_mode_rule_summary_combination, days, time);
+        }
+        return getString(R.string.zen_mode_rule_summary_combination, summary, mode);
     }
 
     private String getTime(int hour, int minute) {
@@ -199,4 +221,30 @@
         return DAY_FORMAT.format(mCalendar.getTime());
     }
 
+    private static Config getConditionProviderConfig() {
+        final Config c = new Config();
+        c.tag = TAG;
+        c.setting = Settings.Secure.ENABLED_CONDITION_PROVIDERS;
+        c.intentAction = ConditionProviderService.SERVICE_INTERFACE;
+        c.permission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
+        c.noun = "condition provider";
+        return c;
+    }
+
+    private final ServiceListing.Callback mServiceListingCallback = new ServiceListing.Callback() {
+        @Override
+        public void onServicesReloaded(List<ServiceInfo> services) {
+            for (ServiceInfo service : services) {
+                final RuleInfo ri = ZenModeExternalRuleSettings.getRuleInfo(service);
+                if (ri != null && ri.serviceComponent != null
+                        && ri.settingsAction == ZenModeExternalRuleSettings.ACTION) {
+                    if (!mServiceListing.isEnabled(ri.serviceComponent)) {
+                        Log.i(TAG, "Enabling external condition provider: " + ri.serviceComponent);
+                        mServiceListing.setEnabled(ri.serviceComponent, true);
+                    }
+                }
+            }
+        }
+    };
+
 }
diff --git a/src/com/android/settings/notification/ZenModeExternalRuleSettings.java b/src/com/android/settings/notification/ZenModeExternalRuleSettings.java
new file mode 100644
index 0000000..9f9dc8a
--- /dev/null
+++ b/src/com/android/settings/notification/ZenModeExternalRuleSettings.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ServiceInfo;
+import android.net.Uri;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+import android.service.notification.ZenModeConfig.ZenRule;
+import android.util.Log;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.settings.R;
+import com.android.settings.notification.ZenRuleNameDialog.RuleInfo;
+
+public class ZenModeExternalRuleSettings extends ZenModeRuleSettingsBase {
+    private static final String KEY_TYPE = "type";
+    private static final String KEY_CONFIGURE = "configure";
+
+    public static final String ACTION = Settings.ACTION_ZEN_MODE_EXTERNAL_RULE_SETTINGS;
+    private static final int REQUEST_CODE_CONFIGURE = 1;
+
+    private static final String MD_RULE_TYPE = "automatic.ruleType";
+    private static final String MD_DEFAULT_CONDITION_ID = "automatic.defaultConditionId";
+    private static final String MD_CONFIGURATION_ACTIVITY = "automatic.configurationActivity";
+    private static final String EXTRA_CONDITION_ID = "automatic.conditionId";
+
+    private Preference mType;
+    private Preference mConfigure;
+
+    @Override
+    protected boolean setRule(ZenRule rule) {
+        return rule != null;
+    }
+
+    @Override
+    protected String getZenModeDependency() {
+        return null;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        addPreferencesFromResource(R.xml.zen_mode_external_rule_settings);
+        final PreferenceScreen root = getPreferenceScreen();
+        final ServiceInfo si = ServiceListing.findService(mContext,
+                ZenModeAutomationSettings.CONFIG, mRule.component);
+        if (DEBUG) Log.d(TAG, "ServiceInfo: " + si);
+        final RuleInfo ri = getRuleInfo(si);
+        if (DEBUG) Log.d(TAG, "RuleInfo: " + ri);
+        mType = root.findPreference(KEY_TYPE);
+        if (ri == null) {
+            mType.setSummary(R.string.zen_mode_rule_type_unknown);
+        } else {
+            mType.setSummary(ri.caption);
+        }
+
+        mConfigure = root.findPreference(KEY_CONFIGURE);
+        if (ri == null || ri.configurationActivity == null) {
+            mConfigure.setEnabled(false);
+        } else {
+            mConfigure.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+                @Override
+                public boolean onPreferenceClick(Preference preference) {
+                    startActivityForResult(new Intent().setComponent(ri.configurationActivity),
+                            REQUEST_CODE_CONFIGURE);
+                    return true;
+                }
+            });
+        }
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        if (requestCode == REQUEST_CODE_CONFIGURE) {
+            if (resultCode == Activity.RESULT_OK && data != null) {
+                final Uri conditionId = data.getParcelableExtra(EXTRA_CONDITION_ID);
+                if (conditionId != null && !conditionId.equals(mRule.conditionId)) {
+                    updateRule(conditionId);
+                }
+            }
+        }
+    }
+
+    public static RuleInfo getRuleInfo(ServiceInfo si) {
+        if (si == null || si.metaData == null) return null;
+        final String ruleType = si.metaData.getString(MD_RULE_TYPE);
+        final String defaultConditionId = si.metaData.getString(MD_DEFAULT_CONDITION_ID);
+        final String configurationActivity = si.metaData.getString(MD_CONFIGURATION_ACTIVITY);
+        if (ruleType != null && !ruleType.trim().isEmpty() && defaultConditionId != null) {
+            final RuleInfo ri = new RuleInfo();
+            ri.serviceComponent = new ComponentName(si.packageName, si.name);
+            ri.settingsAction = ZenModeExternalRuleSettings.ACTION;
+            ri.caption = ruleType;
+            ri.defaultConditionId = Uri.parse(defaultConditionId);
+            if (configurationActivity != null) {
+                ri.configurationActivity = ComponentName.unflattenFromString(configurationActivity);
+            }
+            return ri;
+        }
+        return null;
+    }
+
+    @Override
+    protected void updateControlsInternal() {
+        // everything done up front
+    }
+
+    @Override
+    protected int getMetricsCategory() {
+        return MetricsLogger.NOTIFICATION_ZEN_MODE_EXTERNAL_RULE;
+    }
+
+}
diff --git a/src/com/android/settings/notification/ZenModeRuleSettingsBase.java b/src/com/android/settings/notification/ZenModeRuleSettingsBase.java
new file mode 100644
index 0000000..f6bc75f
--- /dev/null
+++ b/src/com/android/settings/notification/ZenModeRuleSettingsBase.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.notification;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceScreen;
+import android.provider.Settings.Global;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.Switch;
+import android.widget.Toast;
+
+import com.android.settings.DropDownPreference;
+import com.android.settings.R;
+import com.android.settings.SettingsActivity;
+import com.android.settings.widget.SwitchBar;
+
+public abstract class ZenModeRuleSettingsBase extends ZenModeSettingsBase
+        implements SwitchBar.OnSwitchChangeListener {
+    protected static final String TAG = ZenModeSettingsBase.TAG;
+    protected static final boolean DEBUG = ZenModeSettingsBase.DEBUG;
+
+    public static final String EXTRA_RULE_ID = "rule_id";
+    private static final String KEY_RULE_NAME = "rule_name";
+    private static final String KEY_ZEN_MODE = "zen_mode";
+
+    protected Context mContext;
+    protected boolean mDisableListeners;
+    protected ZenRule mRule;
+
+    private String mRuleId;
+    private boolean mDeleting;
+    private Preference mRuleName;
+    private SwitchBar mSwitchBar;
+    private DropDownPreference mZenMode;
+
+    abstract protected void onCreateInternal();
+    abstract protected boolean setRule(ZenRule rule);
+    abstract protected String getZenModeDependency();
+    abstract protected void updateControlsInternal();
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        mContext = getActivity();
+
+        final Intent intent = getActivity().getIntent();
+        if (DEBUG) Log.d(TAG, "onCreate getIntent()=" + intent);
+        if (intent == null) {
+            Log.w(TAG, "No intent");
+            toastAndFinish();
+            return;
+        }
+
+        mRuleId = intent.getStringExtra(EXTRA_RULE_ID);
+        if (DEBUG) Log.d(TAG, "mRuleId=" + mRuleId);
+        if (refreshRuleOrFinish()) {
+            return;
+        }
+
+        setHasOptionsMenu(true);
+
+        onCreateInternal();
+
+        final PreferenceScreen root = getPreferenceScreen();
+        mRuleName = root.findPreference(KEY_RULE_NAME);
+        mRuleName.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+            @Override
+            public boolean onPreferenceClick(Preference preference) {
+                showRuleNameDialog();
+                return true;
+            }
+        });
+
+        mZenMode = (DropDownPreference) root.findPreference(KEY_ZEN_MODE);
+        mZenMode.addItem(R.string.zen_mode_option_important_interruptions,
+                Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+        mZenMode.addItem(R.string.zen_mode_option_alarms, Global.ZEN_MODE_ALARMS);
+        mZenMode.addItem(R.string.zen_mode_option_no_interruptions,
+                Global.ZEN_MODE_NO_INTERRUPTIONS);
+        mZenMode.setCallback(new DropDownPreference.Callback() {
+            @Override
+            public boolean onItemSelected(int pos, Object value) {
+                if (mDisableListeners) return true;
+                final int zenMode = (Integer) value;
+                if (zenMode == mRule.zenMode) return true;
+                if (DEBUG) Log.d(TAG, "onPrefChange zenMode=" + zenMode);
+                mRule.zenMode = zenMode;
+                setZenModeConfig(mConfig);
+                return true;
+            }
+        });
+        mZenMode.setOrder(10);  // sort at the bottom of the category
+        mZenMode.setDependency(getZenModeDependency());
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        updateControls();
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        final SettingsActivity activity = (SettingsActivity) getActivity();
+        mSwitchBar = activity.getSwitchBar();
+        mSwitchBar.addOnSwitchChangeListener(this);
+        mSwitchBar.show();
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mSwitchBar.removeOnSwitchChangeListener(this);
+        mSwitchBar.hide();
+    }
+
+    @Override
+    public void onSwitchChanged(Switch switchView, boolean isChecked) {
+        if (DEBUG) Log.d(TAG, "onSwitchChanged " + isChecked);
+        if (mDisableListeners) return;
+        final boolean enabled = isChecked;
+        if (enabled == mRule.enabled) return;
+        if (DEBUG) Log.d(TAG, "onSwitchChanged enabled=" + enabled);
+        mRule.enabled = enabled;
+        mRule.snoozing = false;
+        setZenModeConfig(mConfig);
+    }
+
+    protected void updateRule(Uri newConditionId) {
+        mRule.conditionId = newConditionId;
+        mRule.condition = null;
+        mRule.snoozing = false;
+        setZenModeConfig(mConfig);
+    }
+
+    @Override
+    protected void onZenModeChanged() {
+        // noop
+    }
+
+    @Override
+    protected void onZenModeConfigChanged() {
+        if (!refreshRuleOrFinish()) {
+            updateControls();
+        }
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        if (DEBUG) Log.d(TAG, "onCreateOptionsMenu");
+        inflater.inflate(R.menu.zen_mode_rule, menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        if (DEBUG) Log.d(TAG, "onOptionsItemSelected " + item.getItemId());
+        if (item.getItemId() == R.id.delete) {
+            showDeleteRuleDialog();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void showRuleNameDialog() {
+        new ZenRuleNameDialog(mContext, null, mRule.name, mConfig.getAutomaticRuleNames()) {
+            @Override
+            public void onOk(String ruleName, RuleInfo type) {
+                final ZenModeConfig newConfig = mConfig.copy();
+                final ZenRule rule = newConfig.automaticRules.get(mRuleId);
+                if (rule == null) return;
+                rule.name = ruleName;
+                setZenModeConfig(newConfig);
+            }
+        }.show();
+    }
+
+    private boolean refreshRuleOrFinish() {
+        mRule = mConfig.automaticRules.get(mRuleId);
+        if (DEBUG) Log.d(TAG, "mRule=" + mRule);
+        if (!setRule(mRule)) {
+            toastAndFinish();
+            return true;
+        }
+        return false;
+    }
+
+    private void showDeleteRuleDialog() {
+        new AlertDialog.Builder(mContext)
+                .setMessage(getString(R.string.zen_mode_delete_rule_confirmation, mRule.name))
+                .setNegativeButton(R.string.cancel, null)
+                .setPositiveButton(R.string.zen_mode_delete_rule_button, new OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        mDeleting = true;
+                        mConfig.automaticRules.remove(mRuleId);
+                        setZenModeConfig(mConfig);
+                    }
+                })
+                .show();
+    }
+
+    private void toastAndFinish() {
+        if (!mDeleting) {
+            Toast.makeText(mContext, R.string.zen_mode_rule_not_found_text, Toast.LENGTH_SHORT)
+                    .show();
+        }
+        getActivity().finish();
+    }
+
+    private void updateRuleName() {
+        getActivity().setTitle(mRule.name);
+        mRuleName.setSummary(mRule.name);
+    }
+
+    private void updateControls() {
+        mDisableListeners = true;
+        updateRuleName();
+        updateControlsInternal();
+        mZenMode.setSelectedValue(mRule.zenMode);
+        mDisableListeners = false;
+        if (mSwitchBar != null) {
+            mSwitchBar.setChecked(mRule.enabled);
+        }
+    }
+
+}
diff --git a/src/com/android/settings/notification/ZenModeScheduleRuleSettings.java b/src/com/android/settings/notification/ZenModeScheduleRuleSettings.java
index f0f0294..f7015d3 100644
--- a/src/com/android/settings/notification/ZenModeScheduleRuleSettings.java
+++ b/src/com/android/settings/notification/ZenModeScheduleRuleSettings.java
@@ -25,141 +25,58 @@
 import android.app.TimePickerDialog;
 import android.content.Context;
 import android.content.DialogInterface;
-import android.content.DialogInterface.OnClickListener;
 import android.content.DialogInterface.OnDismissListener;
-import android.content.Intent;
 import android.os.Bundle;
 import android.preference.Preference;
 import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceScreen;
 import android.provider.Settings;
-import android.provider.Settings.Global;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ScheduleInfo;
 import android.service.notification.ZenModeConfig.ZenRule;
 import android.text.format.DateFormat;
 import android.util.Log;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.widget.Switch;
 import android.widget.TimePicker;
-import android.widget.Toast;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.settings.DropDownPreference;
 import com.android.settings.R;
-import com.android.settings.SettingsActivity;
-import com.android.settings.widget.SwitchBar;
 
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
 import java.util.Calendar;
 
-public class ZenModeScheduleRuleSettings extends ZenModeSettingsBase
-        implements SwitchBar.OnSwitchChangeListener {
-    private static final String TAG = ZenModeSettingsBase.TAG;
-    private static final boolean DEBUG = ZenModeSettingsBase.DEBUG;
-
-    private static final String KEY_RULE_NAME = "rule_name";
+public class ZenModeScheduleRuleSettings extends ZenModeRuleSettingsBase {
     private static final String KEY_DAYS = "days";
     private static final String KEY_START_TIME = "start_time";
     private static final String KEY_END_TIME = "end_time";
-    private static final String KEY_ZEN_MODE = "zen_mode";
 
     private static final SimpleDateFormat DAY_FORMAT = new SimpleDateFormat("EEE");
 
     public static final String ACTION = Settings.ACTION_ZEN_MODE_SCHEDULE_RULE_SETTINGS;
-    public static final String EXTRA_RULE_ID = "rule_id";
 
-    private Context mContext;
-    private boolean mDisableListeners;
-    private SwitchBar mSwitchBar;
-    private Preference mRuleName;
     private Preference mDays;
     private TimePickerPreference mStart;
     private TimePickerPreference mEnd;
-    private DropDownPreference mZenMode;
 
-    private String mRuleId;
-    private ZenRule mRule;
     private ScheduleInfo mSchedule;
-    private boolean mDeleting;
 
     @Override
-    protected void onZenModeChanged() {
-        // noop
-    }
-
-    @Override
-    protected void onZenModeConfigChanged() {
-        if (!refreshRuleOrFinish()) {
-            updateControls();
-        }
-    }
-
-    private boolean refreshRuleOrFinish() {
-        mRule = mConfig.automaticRules.get(mRuleId);
-        if (DEBUG) Log.d(TAG, "mRule=" + mRule);
-        mSchedule = mRule != null ? ZenModeConfig.tryParseScheduleConditionId(mRule.conditionId)
+    protected boolean setRule(ZenRule rule) {
+        mSchedule = rule != null ? ZenModeConfig.tryParseScheduleConditionId(rule.conditionId)
                 : null;
-        if (mSchedule == null) {
-            toastAndFinish();
-            return true;
-        }
-        return false;
+        return mSchedule != null;
     }
 
     @Override
-    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
-        if (DEBUG) Log.d(TAG, "onCreateOptionsMenu");
-        inflater.inflate(R.menu.zen_mode_rule, menu);
+    protected String getZenModeDependency() {
+        return mDays.getKey();
     }
 
     @Override
-    public boolean onOptionsItemSelected(MenuItem item) {
-        if (DEBUG) Log.d(TAG, "onOptionsItemSelected " + item.getItemId());
-        if (item.getItemId() == R.id.delete) {
-            showDeleteRuleDialog();
-            return true;
-        }
-        return super.onOptionsItemSelected(item);
-    }
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        mContext = getActivity();
-
-        final Intent intent = getActivity().getIntent();
-        if (DEBUG) Log.d(TAG, "onCreate getIntent()=" + intent);
-        if (intent == null) {
-            Log.w(TAG, "No intent");
-            toastAndFinish();
-            return;
-        }
-
-        mRuleId = intent.getStringExtra(EXTRA_RULE_ID);
-        if (DEBUG) Log.d(TAG, "mRuleId=" + mRuleId);
-        if (refreshRuleOrFinish()) {
-            return;
-        }
-
+    protected void onCreateInternal() {
         addPreferencesFromResource(R.xml.zen_mode_schedule_rule_settings);
         final PreferenceScreen root = getPreferenceScreen();
 
-        setHasOptionsMenu(true);
-
-        mRuleName = root.findPreference(KEY_RULE_NAME);
-        mRuleName.setOnPreferenceClickListener(new OnPreferenceClickListener() {
-            @Override
-            public boolean onPreferenceClick(Preference preference) {
-                showRuleNameDialog();
-                return true;
-            }
-        });
-
         mDays = root.findPreference(KEY_DAYS);
         mDays.setOnPreferenceClickListener(new OnPreferenceClickListener() {
             @Override
@@ -186,10 +103,7 @@
                 if (DEBUG) Log.d(TAG, "onPrefChange start h=" + hour + " m=" + minute);
                 mSchedule.startHour = hour;
                 mSchedule.startMinute = minute;
-                mRule.conditionId = ZenModeConfig.toScheduleConditionId(mSchedule);
-                mRule.condition = null;
-                mRule.snoozing = false;
-                setZenModeConfig(mConfig);
+                updateRule(ZenModeConfig.toScheduleConditionId(mSchedule));
                 return true;
             }
         });
@@ -211,63 +125,12 @@
                 if (DEBUG) Log.d(TAG, "onPrefChange end h=" + hour + " m=" + minute);
                 mSchedule.endHour = hour;
                 mSchedule.endMinute = minute;
-                mRule.conditionId = ZenModeConfig.toScheduleConditionId(mSchedule);
-                mRule.condition = null;
-                mRule.snoozing = false;
-                setZenModeConfig(mConfig);
+                updateRule(ZenModeConfig.toScheduleConditionId(mSchedule));
                 return true;
             }
         });
         root.addPreference(mEnd);
         mEnd.setDependency(mDays.getKey());
-
-        mZenMode = (DropDownPreference) root.findPreference(KEY_ZEN_MODE);
-        mZenMode.addItem(R.string.zen_mode_option_important_interruptions, Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
-        mZenMode.addItem(R.string.zen_mode_option_alarms, Global.ZEN_MODE_ALARMS);
-        mZenMode.addItem(R.string.zen_mode_option_no_interruptions, Global.ZEN_MODE_NO_INTERRUPTIONS);
-        mZenMode.setCallback(new DropDownPreference.Callback() {
-            @Override
-            public boolean onItemSelected(int pos, Object value) {
-                if (mDisableListeners) return true;
-                final int zenMode = (Integer) value;
-                if (zenMode == mRule.zenMode) return true;
-                if (DEBUG) Log.d(TAG, "onPrefChange zenMode=" + zenMode);
-                mRule.zenMode = zenMode;
-                setZenModeConfig(mConfig);
-                return true;
-            }
-        });
-        mZenMode.setOrder(10);  // sort at the bottom of the category
-        mZenMode.setDependency(mDays.getKey());
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-
-        final SettingsActivity activity = (SettingsActivity) getActivity();
-        mSwitchBar = activity.getSwitchBar();
-        mSwitchBar.addOnSwitchChangeListener(this);
-        mSwitchBar.show();
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        mSwitchBar.removeOnSwitchChangeListener(this);
-        mSwitchBar.hide();
-    }
-
-    @Override
-    public void onSwitchChanged(Switch switchView, boolean isChecked) {
-        if (DEBUG) Log.d(TAG, "onSwitchChanged " + isChecked);
-        if (mDisableListeners) return;
-        final boolean enabled = isChecked;
-        if (enabled == mRule.enabled) return;
-        if (DEBUG) Log.d(TAG, "onSwitchChanged enabled=" + enabled);
-        mRule.enabled = enabled;
-        mRule.snoozing = false;
-        setZenModeConfig(mConfig);
     }
 
     private void updateDays() {
@@ -308,28 +171,11 @@
     }
 
     @Override
-    public void onResume() {
-        super.onResume();
-        updateControls();
-    }
-
-    private void updateRuleName() {
-        getActivity().setTitle(mRule.name);
-        mRuleName.setSummary(mRule.name);
-    }
-
-    private void updateControls() {
-        mDisableListeners = true;
-        updateRuleName();
+    protected void updateControlsInternal() {
         updateDays();
         mStart.setTime(mSchedule.startHour, mSchedule.startMinute);
         mEnd.setTime(mSchedule.endHour, mSchedule.endMinute);
-        mZenMode.setSelectedValue(mRule.zenMode);
-        mDisableListeners = false;
         updateEndSummary();
-        if (mSwitchBar != null) {
-            mSwitchBar.setChecked(mRule.enabled);
-        }
     }
 
     @Override
@@ -337,34 +183,6 @@
         return MetricsLogger.NOTIFICATION_ZEN_MODE_SCHEDULE_RULE;
     }
 
-    private void showDeleteRuleDialog() {
-        new AlertDialog.Builder(mContext)
-                .setMessage(getString(R.string.zen_mode_delete_rule_confirmation, mRule.name))
-                .setNegativeButton(R.string.cancel, null)
-                .setPositiveButton(R.string.zen_mode_delete_rule_button, new OnClickListener() {
-                    @Override
-                    public void onClick(DialogInterface dialog, int which) {
-                        mDeleting = true;
-                        mConfig.automaticRules.remove(mRuleId);
-                        setZenModeConfig(mConfig);
-                    }
-                })
-                .show();
-    }
-
-    private void showRuleNameDialog() {
-        new ZenRuleNameDialog(mContext, mRule.name, mConfig.getAutomaticRuleNames()) {
-            @Override
-            public void onOk(String ruleName) {
-                final ZenModeConfig newConfig = mConfig.copy();
-                final ZenRule rule = newConfig.automaticRules.get(mRuleId);
-                if (rule == null) return;
-                rule.name = ruleName;
-                setZenModeConfig(newConfig);
-            }
-        }.show();
-    }
-
     private void showDaysDialog() {
         new AlertDialog.Builder(mContext)
                 .setTitle(R.string.zen_mode_schedule_rule_days)
@@ -375,10 +193,7 @@
                           if (Arrays.equals(days, mSchedule.days)) return;
                           if (DEBUG) Log.d(TAG, "days.onChanged days=" + Arrays.asList(days));
                           mSchedule.days = days;
-                          mRule.conditionId = ZenModeConfig.toScheduleConditionId(mSchedule);
-                          mRule.condition = null;
-                          mRule.snoozing = false;
-                          setZenModeConfig(mConfig);
+                          updateRule(ZenModeConfig.toScheduleConditionId(mSchedule));
                       }
                 })
                 .setOnDismissListener(new OnDismissListener() {
@@ -391,14 +206,6 @@
                 .show();
     }
 
-    private void toastAndFinish() {
-        if (!mDeleting) {
-            Toast.makeText(mContext, R.string.zen_mode_rule_not_found_text, Toast.LENGTH_SHORT)
-                    .show();
-        }
-        getActivity().finish();
-    }
-
     private static class TimePickerPreference extends Preference {
         private final Context mContext;
 
@@ -474,4 +281,5 @@
             boolean onSetTime(int hour, int minute);
         }
     }
+
 }
diff --git a/src/com/android/settings/notification/ZenRuleNameDialog.java b/src/com/android/settings/notification/ZenRuleNameDialog.java
index b0eaaec..8b44e46 100644
--- a/src/com/android/settings/notification/ZenRuleNameDialog.java
+++ b/src/com/android/settings/notification/ZenRuleNameDialog.java
@@ -17,35 +17,71 @@
 package com.android.settings.notification;
 
 import android.app.AlertDialog;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.pm.ServiceInfo;
+import android.net.Uri;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ScheduleInfo;
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.util.ArraySet;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.EditText;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
 
 import com.android.settings.R;
 
+import java.util.List;
+
 public abstract class ZenRuleNameDialog {
+    private static final String TAG = ZenModeSettings.TAG;
+    private static final boolean DEBUG = ZenModeSettings.DEBUG;
+
     private final AlertDialog mDialog;
     private final EditText mEditText;
+    private final RadioGroup mTypes;
     private final ArraySet<String> mExistingNames;
+    private final ServiceListing mServiceListing;
+    private final RuleInfo[] mExternalRules = new RuleInfo[3];
 
-    public ZenRuleNameDialog(Context context, String ruleName, ArraySet<String> existingNames) {
+    public ZenRuleNameDialog(Context context, ServiceListing serviceListing, String ruleName,
+            ArraySet<String> existingNames) {
+        mServiceListing = serviceListing;
         final View v = LayoutInflater.from(context).inflate(R.layout.zen_rule_name, null, false);
         mEditText = (EditText) v.findViewById(R.id.rule_name);
-        mEditText.setText(ruleName);
+        if (ruleName != null) {
+            mEditText.setText(ruleName);
+        }
         mEditText.setSelectAllOnFocus(true);
+        mTypes = (RadioGroup) v.findViewById(R.id.rule_types);
+        if (mServiceListing != null) {
+            bindType(R.id.rule_type_schedule, defaultNewSchedule());
+            bindExternalRules();
+            mServiceListing.addCallback(mServiceListingCallback);
+            mServiceListing.reload();
+        }
         mDialog = new AlertDialog.Builder(context)
                 .setTitle(R.string.zen_mode_rule_name)
                 .setView(v)
                 .setPositiveButton(R.string.okay, new DialogInterface.OnClickListener() {
                     @Override
                     public void onClick(DialogInterface dialog, int which) {
-                        onOk(trimmedText());
+                        onOk(trimmedText(), selectedRuleInfo());
+                    }
+                })
+                .setOnDismissListener(new OnDismissListener() {
+                    @Override
+                    public void onDismiss(DialogInterface dialog) {
+                        if (mServiceListing != null) {
+                            mServiceListing.removeCallback(mServiceListingCallback);
+                        }
                     }
                 })
                 .setNegativeButton(R.string.cancel, null)
@@ -72,17 +108,37 @@
         }
     }
 
-    abstract public void onOk(String ruleName);
-
-    private String trimmedText() {
-        return mEditText.getText() == null ? null : mEditText.getText().toString().trim();
-    }
+    abstract public void onOk(String ruleName, RuleInfo ruleInfo);
 
     public void show() {
         mDialog.show();
         updatePositiveButton();
     }
 
+    private void bindType(int id, RuleInfo ri) {
+        final RadioButton rb = (RadioButton) mTypes.findViewById(id);
+        if (ri == null) {
+            rb.setVisibility(View.GONE);
+            return;
+        }
+        rb.setVisibility(View.VISIBLE);
+        if (ri.caption != null) {
+            rb.setText(ri.caption);
+        }
+        rb.setTag(ri);
+    }
+
+    private RuleInfo selectedRuleInfo() {
+        final int id = mTypes.getCheckedRadioButtonId();
+        if (id == -1) return null;
+        final RadioButton rb = (RadioButton) mTypes.findViewById(id);
+        return (RuleInfo) rb.getTag();
+    }
+
+    private String trimmedText() {
+        return mEditText.getText() == null ? null : mEditText.getText().toString().trim();
+    }
+
     private void updatePositiveButton() {
         final String name = trimmedText();
         final boolean validName = !TextUtils.isEmpty(name)
@@ -90,4 +146,51 @@
         mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(validName);
     }
 
+    private static RuleInfo defaultNewSchedule() {
+        final ScheduleInfo schedule = new ScheduleInfo();
+        schedule.days = ZenModeConfig.ALL_DAYS;
+        schedule.startHour = 22;
+        schedule.endHour = 7;
+        final RuleInfo rt = new RuleInfo();
+        rt.settingsAction = ZenModeScheduleRuleSettings.ACTION;
+        rt.defaultConditionId = ZenModeConfig.toScheduleConditionId(schedule);
+        return rt;
+    }
+
+    private void bindExternalRules() {
+        bindType(R.id.rule_type_2, mExternalRules[0]);
+        bindType(R.id.rule_type_3, mExternalRules[1]);
+        bindType(R.id.rule_type_4, mExternalRules[2]);
+        // show radio group if we have at least one external rule type
+        mTypes.setVisibility(mExternalRules[0] != null ? View.VISIBLE : View.GONE);
+    }
+
+    private final ServiceListing.Callback mServiceListingCallback = new ServiceListing.Callback() {
+        @Override
+        public void onServicesReloaded(List<ServiceInfo> services) {
+            if (DEBUG) Log.d(TAG, "Services reloaded: count=" + services.size());
+            mExternalRules[0] = mExternalRules[1] = mExternalRules[2] = null;
+            int i = 0;
+            for (ServiceInfo si : services) {
+                final RuleInfo ri = ZenModeExternalRuleSettings.getRuleInfo(si);
+                if (ri != null) {
+                    mExternalRules[i] = ri;
+                    i++;
+                    if (i == mExternalRules.length) {
+                        break;
+                    }
+                }
+            }
+            bindExternalRules();
+        }
+    };
+
+    public static class RuleInfo {
+        public String caption;
+        public String settingsAction;
+        public Uri defaultConditionId;
+        public ComponentName serviceComponent;
+        public ComponentName configurationActivity;
+    }
+
 }
\ No newline at end of file