Home | History | Annotate | Download | only in notification
      1 /**
      2  * Copyright (c) 2014, The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *     http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.server.notification;
     18 
     19 import android.content.ComponentName;
     20 import android.content.Context;
     21 import android.net.Uri;
     22 import android.os.Handler;
     23 import android.os.IBinder;
     24 import android.os.IInterface;
     25 import android.os.RemoteException;
     26 import android.os.UserHandle;
     27 import android.provider.Settings;
     28 import android.provider.Settings.Global;
     29 import android.service.notification.Condition;
     30 import android.service.notification.ConditionProviderService;
     31 import android.service.notification.IConditionListener;
     32 import android.service.notification.IConditionProvider;
     33 import android.service.notification.ZenModeConfig;
     34 import android.util.ArrayMap;
     35 import android.util.ArraySet;
     36 import android.util.Slog;
     37 
     38 import com.android.internal.R;
     39 import com.android.server.notification.NotificationManagerService.DumpFilter;
     40 
     41 import java.io.PrintWriter;
     42 import java.util.ArrayList;
     43 import java.util.Arrays;
     44 import java.util.Objects;
     45 
     46 public class ConditionProviders extends ManagedServices {
     47     private static final Condition[] NO_CONDITIONS = new Condition[0];
     48 
     49     private final ZenModeHelper mZenModeHelper;
     50     private final ArrayMap<IBinder, IConditionListener> mListeners
     51             = new ArrayMap<IBinder, IConditionListener>();
     52     private final ArrayList<ConditionRecord> mRecords = new ArrayList<ConditionRecord>();
     53     private final ArraySet<String> mSystemConditionProviders;
     54     private final CountdownConditionProvider mCountdown;
     55     private final DowntimeConditionProvider mDowntime;
     56     private final NextAlarmConditionProvider mNextAlarm;
     57     private final NextAlarmTracker mNextAlarmTracker;
     58 
     59     private Condition mExitCondition;
     60     private ComponentName mExitConditionComponent;
     61 
     62     public ConditionProviders(Context context, Handler handler,
     63             UserProfiles userProfiles, ZenModeHelper zenModeHelper) {
     64         super(context, handler, new Object(), userProfiles);
     65         mZenModeHelper = zenModeHelper;
     66         mZenModeHelper.addCallback(new ZenModeHelperCallback());
     67         mSystemConditionProviders = safeSet(PropConfig.getStringArray(mContext,
     68                 "system.condition.providers",
     69                 R.array.config_system_condition_providers));
     70         final boolean countdown = mSystemConditionProviders.contains(ZenModeConfig.COUNTDOWN_PATH);
     71         final boolean downtime = mSystemConditionProviders.contains(ZenModeConfig.DOWNTIME_PATH);
     72         final boolean nextAlarm = mSystemConditionProviders.contains(ZenModeConfig.NEXT_ALARM_PATH);
     73         mNextAlarmTracker = (downtime || nextAlarm) ? new NextAlarmTracker(mContext) : null;
     74         mCountdown = countdown ? new CountdownConditionProvider() : null;
     75         mDowntime = downtime ? new DowntimeConditionProvider(this, mNextAlarmTracker,
     76                 mZenModeHelper) : null;
     77         mNextAlarm = nextAlarm ? new NextAlarmConditionProvider(mNextAlarmTracker) : null;
     78         loadZenConfig();
     79     }
     80 
     81     public boolean isSystemConditionProviderEnabled(String path) {
     82         return mSystemConditionProviders.contains(path);
     83     }
     84 
     85     @Override
     86     protected Config getConfig() {
     87         Config c = new Config();
     88         c.caption = "condition provider";
     89         c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
     90         c.secureSettingName = Settings.Secure.ENABLED_CONDITION_PROVIDERS;
     91         c.bindPermission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
     92         c.settingsAction = Settings.ACTION_CONDITION_PROVIDER_SETTINGS;
     93         c.clientLabel = R.string.condition_provider_service_binding_label;
     94         return c;
     95     }
     96 
     97     @Override
     98     public void dump(PrintWriter pw, DumpFilter filter) {
     99         super.dump(pw, filter);
    100         synchronized(mMutex) {
    101             if (filter == null) {
    102                 pw.print("    mListeners("); pw.print(mListeners.size()); pw.println("):");
    103                 for (int i = 0; i < mListeners.size(); i++) {
    104                     pw.print("      "); pw.println(mListeners.keyAt(i));
    105                 }
    106             }
    107             pw.print("    mRecords("); pw.print(mRecords.size()); pw.println("):");
    108             for (int i = 0; i < mRecords.size(); i++) {
    109                 final ConditionRecord r = mRecords.get(i);
    110                 if (filter != null && !filter.matches(r.component)) continue;
    111                 pw.print("      "); pw.println(r);
    112                 final String countdownDesc =  CountdownConditionProvider.tryParseDescription(r.id);
    113                 if (countdownDesc != null) {
    114                     pw.print("        ("); pw.print(countdownDesc); pw.println(")");
    115                 }
    116             }
    117         }
    118         pw.print("    mSystemConditionProviders: "); pw.println(mSystemConditionProviders);
    119         if (mCountdown != null) {
    120             mCountdown.dump(pw, filter);
    121         }
    122         if (mDowntime != null) {
    123             mDowntime.dump(pw, filter);
    124         }
    125         if (mNextAlarm != null) {
    126             mNextAlarm.dump(pw, filter);
    127         }
    128         if (mNextAlarmTracker != null) {
    129             mNextAlarmTracker.dump(pw, filter);
    130         }
    131     }
    132 
    133     @Override
    134     protected IInterface asInterface(IBinder binder) {
    135         return IConditionProvider.Stub.asInterface(binder);
    136     }
    137 
    138     @Override
    139     public void onBootPhaseAppsCanStart() {
    140         super.onBootPhaseAppsCanStart();
    141         if (mNextAlarmTracker != null) {
    142             mNextAlarmTracker.init();
    143         }
    144         if (mCountdown != null) {
    145             mCountdown.attachBase(mContext);
    146             registerService(mCountdown.asInterface(), CountdownConditionProvider.COMPONENT,
    147                     UserHandle.USER_OWNER);
    148         }
    149         if (mDowntime != null) {
    150             mDowntime.attachBase(mContext);
    151             registerService(mDowntime.asInterface(), DowntimeConditionProvider.COMPONENT,
    152                     UserHandle.USER_OWNER);
    153         }
    154         if (mNextAlarm != null) {
    155             mNextAlarm.attachBase(mContext);
    156             registerService(mNextAlarm.asInterface(), NextAlarmConditionProvider.COMPONENT,
    157                     UserHandle.USER_OWNER);
    158         }
    159     }
    160 
    161     @Override
    162     public void onUserSwitched() {
    163         super.onUserSwitched();
    164         if (mNextAlarmTracker != null) {
    165             mNextAlarmTracker.onUserSwitched();
    166         }
    167     }
    168 
    169     @Override
    170     protected void onServiceAdded(ManagedServiceInfo info) {
    171         final IConditionProvider provider = provider(info);
    172         try {
    173             provider.onConnected();
    174         } catch (RemoteException e) {
    175             // we tried
    176         }
    177         synchronized (mMutex) {
    178             if (info.component.equals(mExitConditionComponent)) {
    179                 // ensure record exists, we'll wire it up and subscribe below
    180                 final ConditionRecord manualRecord =
    181                         getRecordLocked(mExitCondition.id, mExitConditionComponent);
    182                 manualRecord.isManual = true;
    183             }
    184             final int N = mRecords.size();
    185             for(int i = 0; i < N; i++) {
    186                 final ConditionRecord r = mRecords.get(i);
    187                 if (!r.component.equals(info.component)) continue;
    188                 r.info = info;
    189                 // if automatic or manual, auto-subscribe
    190                 if (r.isAutomatic || r.isManual) {
    191                     subscribeLocked(r);
    192                 }
    193             }
    194         }
    195     }
    196 
    197     @Override
    198     protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
    199         if (removed == null) return;
    200         for (int i = mRecords.size() - 1; i >= 0; i--) {
    201             final ConditionRecord r = mRecords.get(i);
    202             if (!r.component.equals(removed.component)) continue;
    203             if (r.isManual) {
    204                 // removing the current manual condition, exit zen
    205                 onManualConditionClearing();
    206                 mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "manualServiceRemoved");
    207             }
    208             if (r.isAutomatic) {
    209                 // removing an automatic condition, exit zen
    210                 mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "automaticServiceRemoved");
    211             }
    212             mRecords.remove(i);
    213         }
    214     }
    215 
    216     public ManagedServiceInfo checkServiceToken(IConditionProvider provider) {
    217         synchronized(mMutex) {
    218             return checkServiceTokenLocked(provider);
    219         }
    220     }
    221 
    222     public void requestZenModeConditions(IConditionListener callback, int relevance) {
    223         synchronized(mMutex) {
    224             if (DEBUG) Slog.d(TAG, "requestZenModeConditions callback=" + callback
    225                     + " relevance=" + Condition.relevanceToString(relevance));
    226             if (callback == null) return;
    227             relevance = relevance & (Condition.FLAG_RELEVANT_NOW | Condition.FLAG_RELEVANT_ALWAYS);
    228             if (relevance != 0) {
    229                 mListeners.put(callback.asBinder(), callback);
    230                 requestConditionsLocked(relevance);
    231             } else {
    232                 mListeners.remove(callback.asBinder());
    233                 if (mListeners.isEmpty()) {
    234                     requestConditionsLocked(0);
    235                 }
    236             }
    237         }
    238     }
    239 
    240     private Condition[] validateConditions(String pkg, Condition[] conditions) {
    241         if (conditions == null || conditions.length == 0) return null;
    242         final int N = conditions.length;
    243         final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N);
    244         for (int i = 0; i < N; i++) {
    245             final Uri id = conditions[i].id;
    246             if (!Condition.isValidId(id, pkg)) {
    247                 Slog.w(TAG, "Ignoring condition from " + pkg + " for invalid id: " + id);
    248                 continue;
    249             }
    250             if (valid.containsKey(id)) {
    251                 Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id);
    252                 continue;
    253             }
    254             valid.put(id, conditions[i]);
    255         }
    256         if (valid.size() == 0) return null;
    257         if (valid.size() == N) return conditions;
    258         final Condition[] rt = new Condition[valid.size()];
    259         for (int i = 0; i < rt.length; i++) {
    260             rt[i] = valid.valueAt(i);
    261         }
    262         return rt;
    263     }
    264 
    265     private ConditionRecord getRecordLocked(Uri id, ComponentName component) {
    266         final int N = mRecords.size();
    267         for (int i = 0; i < N; i++) {
    268             final ConditionRecord r = mRecords.get(i);
    269             if (r.id.equals(id) && r.component.equals(component)) {
    270                 return r;
    271             }
    272         }
    273         final ConditionRecord r = new ConditionRecord(id, component);
    274         mRecords.add(r);
    275         return r;
    276     }
    277 
    278     public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) {
    279         synchronized(mMutex) {
    280             if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions="
    281                     + (conditions == null ? null : Arrays.asList(conditions)));
    282             conditions = validateConditions(pkg, conditions);
    283             if (conditions == null || conditions.length == 0) return;
    284             final int N = conditions.length;
    285             for (IConditionListener listener : mListeners.values()) {
    286                 try {
    287                     listener.onConditionsReceived(conditions);
    288                 } catch (RemoteException e) {
    289                     Slog.w(TAG, "Error sending conditions to listener " + listener, e);
    290                 }
    291             }
    292             for (int i = 0; i < N; i++) {
    293                 final Condition c = conditions[i];
    294                 final ConditionRecord r = getRecordLocked(c.id, info.component);
    295                 final Condition oldCondition = r.condition;
    296                 final boolean conditionUpdate = oldCondition != null && !oldCondition.equals(c);
    297                 r.info = info;
    298                 r.condition = c;
    299                 // if manual, exit zen if false (or failed), update if true (and changed)
    300                 if (r.isManual) {
    301                     if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
    302                         final boolean failed = c.state == Condition.STATE_ERROR;
    303                         if (failed) {
    304                             Slog.w(TAG, "Exit zen: manual condition failed: " + c);
    305                         } else if (DEBUG) {
    306                             Slog.d(TAG, "Exit zen: manual condition false: " + c);
    307                         }
    308                         onManualConditionClearing();
    309                         mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF,
    310                                 "manualConditionExit");
    311                         unsubscribeLocked(r);
    312                         r.isManual = false;
    313                     } else if (c.state == Condition.STATE_TRUE && conditionUpdate) {
    314                         if (DEBUG) Slog.d(TAG, "Current condition updated, still true. old="
    315                                 + oldCondition + " new=" + c);
    316                         setZenModeCondition(c, "conditionUpdate");
    317                     }
    318                 }
    319                 // if automatic, exit zen if false (or failed), enter zen if true
    320                 if (r.isAutomatic) {
    321                     if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
    322                         final boolean failed = c.state == Condition.STATE_ERROR;
    323                         if (failed) {
    324                             Slog.w(TAG, "Exit zen: automatic condition failed: " + c);
    325                         } else if (DEBUG) {
    326                             Slog.d(TAG, "Exit zen: automatic condition false: " + c);
    327                         }
    328                         mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF,
    329                                 "automaticConditionExit");
    330                     } else if (c.state == Condition.STATE_TRUE) {
    331                         Slog.d(TAG, "Enter zen: automatic condition true: " + c);
    332                         mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
    333                                 "automaticConditionEnter");
    334                     }
    335                 }
    336             }
    337         }
    338     }
    339 
    340     private void ensureRecordExists(Condition condition, IConditionProvider provider,
    341             ComponentName component) {
    342         // constructed by convention, make sure the record exists...
    343         final ConditionRecord r = getRecordLocked(condition.id, component);
    344         if (r.info == null) {
    345             // ... and is associated with the in-process service
    346             r.info = checkServiceTokenLocked(provider);
    347         }
    348     }
    349 
    350     public void setZenModeCondition(Condition condition, String reason) {
    351         if (DEBUG) Slog.d(TAG, "setZenModeCondition " + condition + " reason=" + reason);
    352         synchronized(mMutex) {
    353             ComponentName conditionComponent = null;
    354             if (condition != null) {
    355                 if (mCountdown != null && ZenModeConfig.isValidCountdownConditionId(condition.id)) {
    356                     ensureRecordExists(condition, mCountdown.asInterface(),
    357                             CountdownConditionProvider.COMPONENT);
    358                 }
    359                 if (mDowntime != null && ZenModeConfig.isValidDowntimeConditionId(condition.id)) {
    360                     ensureRecordExists(condition, mDowntime.asInterface(),
    361                             DowntimeConditionProvider.COMPONENT);
    362                 }
    363             }
    364             final int N = mRecords.size();
    365             for (int i = 0; i < N; i++) {
    366                 final ConditionRecord r = mRecords.get(i);
    367                 final boolean idEqual = condition != null && r.id.equals(condition.id);
    368                 if (r.isManual && !idEqual) {
    369                     // was previous manual condition, unsubscribe
    370                     unsubscribeLocked(r);
    371                     r.isManual = false;
    372                 } else if (idEqual && !r.isManual) {
    373                     // is new manual condition, subscribe
    374                     subscribeLocked(r);
    375                     r.isManual = true;
    376                 }
    377                 if (idEqual) {
    378                     conditionComponent = r.component;
    379                 }
    380             }
    381             if (!Objects.equals(mExitCondition, condition)) {
    382                 mExitCondition = condition;
    383                 mExitConditionComponent = conditionComponent;
    384                 ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, reason);
    385                 saveZenConfigLocked();
    386             }
    387         }
    388     }
    389 
    390     private void subscribeLocked(ConditionRecord r) {
    391         if (DEBUG) Slog.d(TAG, "subscribeLocked " + r);
    392         final IConditionProvider provider = provider(r);
    393         RemoteException re = null;
    394         if (provider != null) {
    395             try {
    396                 Slog.d(TAG, "Subscribing to " + r.id + " with " + provider);
    397                 provider.onSubscribe(r.id);
    398             } catch (RemoteException e) {
    399                 Slog.w(TAG, "Error subscribing to " + r, e);
    400                 re = e;
    401             }
    402         }
    403         ZenLog.traceSubscribe(r != null ? r.id : null, provider, re);
    404     }
    405 
    406     @SafeVarargs
    407     private static <T> ArraySet<T> safeSet(T... items) {
    408         final ArraySet<T> rt = new ArraySet<T>();
    409         if (items == null || items.length == 0) return rt;
    410         final int N = items.length;
    411         for (int i = 0; i < N; i++) {
    412             final T item = items[i];
    413             if (item != null) {
    414                 rt.add(item);
    415             }
    416         }
    417         return rt;
    418     }
    419 
    420     public void setAutomaticZenModeConditions(Uri[] conditionIds) {
    421         setAutomaticZenModeConditions(conditionIds, true /*save*/);
    422     }
    423 
    424     private void setAutomaticZenModeConditions(Uri[] conditionIds, boolean save) {
    425         if (DEBUG) Slog.d(TAG, "setAutomaticZenModeConditions "
    426                 + (conditionIds == null ? null : Arrays.asList(conditionIds)));
    427         synchronized(mMutex) {
    428             final ArraySet<Uri> newIds = safeSet(conditionIds);
    429             final int N = mRecords.size();
    430             boolean changed = false;
    431             for (int i = 0; i < N; i++) {
    432                 final ConditionRecord r = mRecords.get(i);
    433                 final boolean automatic = newIds.contains(r.id);
    434                 if (!r.isAutomatic && automatic) {
    435                     // subscribe to new automatic
    436                     subscribeLocked(r);
    437                     r.isAutomatic = true;
    438                     changed = true;
    439                 } else if (r.isAutomatic && !automatic) {
    440                     // unsubscribe from old automatic
    441                     unsubscribeLocked(r);
    442                     r.isAutomatic = false;
    443                     changed = true;
    444                 }
    445             }
    446             if (save && changed) {
    447                 saveZenConfigLocked();
    448             }
    449         }
    450     }
    451 
    452     public Condition[] getAutomaticZenModeConditions() {
    453         synchronized(mMutex) {
    454             final int N = mRecords.size();
    455             ArrayList<Condition> rt = null;
    456             for (int i = 0; i < N; i++) {
    457                 final ConditionRecord r = mRecords.get(i);
    458                 if (r.isAutomatic && r.condition != null) {
    459                     if (rt == null) rt = new ArrayList<Condition>();
    460                     rt.add(r.condition);
    461                 }
    462             }
    463             return rt == null ? NO_CONDITIONS : rt.toArray(new Condition[rt.size()]);
    464         }
    465     }
    466 
    467     private void unsubscribeLocked(ConditionRecord r) {
    468         if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r);
    469         final IConditionProvider provider = provider(r);
    470         RemoteException re = null;
    471         if (provider != null) {
    472             try {
    473                 provider.onUnsubscribe(r.id);
    474             } catch (RemoteException e) {
    475                 Slog.w(TAG, "Error unsubscribing to " + r, e);
    476                 re = e;
    477             }
    478         }
    479         ZenLog.traceUnsubscribe(r != null ? r.id : null, provider, re);
    480     }
    481 
    482     private static IConditionProvider provider(ConditionRecord r) {
    483         return r == null ? null : provider(r.info);
    484     }
    485 
    486     private static IConditionProvider provider(ManagedServiceInfo info) {
    487         return info == null ? null : (IConditionProvider) info.service;
    488     }
    489 
    490     private void requestConditionsLocked(int flags) {
    491         for (ManagedServiceInfo info : mServices) {
    492             final IConditionProvider provider = provider(info);
    493             if (provider == null) continue;
    494             // clear all stored conditions from this provider that we no longer care about
    495             for (int i = mRecords.size() - 1; i >= 0; i--) {
    496                 final ConditionRecord r = mRecords.get(i);
    497                 if (r.info != info) continue;
    498                 if (r.isManual || r.isAutomatic) continue;
    499                 mRecords.remove(i);
    500             }
    501             try {
    502                 provider.onRequestConditions(flags);
    503             } catch (RemoteException e) {
    504                 Slog.w(TAG, "Error requesting conditions from " + info.component, e);
    505             }
    506         }
    507     }
    508 
    509     private void loadZenConfig() {
    510         final ZenModeConfig config = mZenModeHelper.getConfig();
    511         if (config == null) {
    512             if (DEBUG) Slog.d(TAG, "loadZenConfig: no config");
    513             return;
    514         }
    515         synchronized (mMutex) {
    516             final boolean changingExit = !Objects.equals(mExitCondition, config.exitCondition);
    517             mExitCondition = config.exitCondition;
    518             mExitConditionComponent = config.exitConditionComponent;
    519             if (changingExit) {
    520                 ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, "config");
    521             }
    522             if (mDowntime != null) {
    523                 mDowntime.setConfig(config);
    524             }
    525             if (config.conditionComponents == null || config.conditionIds == null
    526                     || config.conditionComponents.length != config.conditionIds.length) {
    527                 if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions");
    528                 setAutomaticZenModeConditions(null, false /*save*/);
    529                 return;
    530             }
    531             final ArraySet<Uri> newIds = new ArraySet<Uri>();
    532             final int N = config.conditionComponents.length;
    533             for (int i = 0; i < N; i++) {
    534                 final ComponentName component = config.conditionComponents[i];
    535                 final Uri id = config.conditionIds[i];
    536                 if (component != null && id != null) {
    537                     getRecordLocked(id, component);  // ensure record exists
    538                     newIds.add(id);
    539                 }
    540             }
    541             if (DEBUG) Slog.d(TAG, "loadZenConfig: N=" + N);
    542             setAutomaticZenModeConditions(newIds.toArray(new Uri[newIds.size()]), false /*save*/);
    543         }
    544     }
    545 
    546     private void saveZenConfigLocked() {
    547         ZenModeConfig config = mZenModeHelper.getConfig();
    548         if (config == null) return;
    549         config = config.copy();
    550         final ArrayList<ConditionRecord> automatic = new ArrayList<ConditionRecord>();
    551         final int automaticN = mRecords.size();
    552         for (int i = 0; i < automaticN; i++) {
    553             final ConditionRecord r = mRecords.get(i);
    554             if (r.isAutomatic) {
    555                 automatic.add(r);
    556             }
    557         }
    558         if (automatic.isEmpty()) {
    559             config.conditionComponents = null;
    560             config.conditionIds = null;
    561         } else {
    562             final int N = automatic.size();
    563             config.conditionComponents = new ComponentName[N];
    564             config.conditionIds = new Uri[N];
    565             for (int i = 0; i < N; i++) {
    566                 final ConditionRecord r = automatic.get(i);
    567                 config.conditionComponents[i] = r.component;
    568                 config.conditionIds[i] = r.id;
    569             }
    570         }
    571         config.exitCondition = mExitCondition;
    572         config.exitConditionComponent = mExitConditionComponent;
    573         if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config);
    574         mZenModeHelper.setConfig(config);
    575     }
    576 
    577     private void onManualConditionClearing() {
    578         if (mDowntime != null) {
    579             mDowntime.onManualConditionClearing();
    580         }
    581     }
    582 
    583     private class ZenModeHelperCallback extends ZenModeHelper.Callback {
    584         @Override
    585         void onConfigChanged() {
    586             loadZenConfig();
    587         }
    588 
    589         @Override
    590         void onZenModeChanged() {
    591             final int mode = mZenModeHelper.getZenMode();
    592             if (mode == Global.ZEN_MODE_OFF) {
    593                 // ensure any manual condition is cleared
    594                 setZenModeCondition(null, "zenOff");
    595             }
    596         }
    597     }
    598 
    599     private static class ConditionRecord {
    600         public final Uri id;
    601         public final ComponentName component;
    602         public Condition condition;
    603         public ManagedServiceInfo info;
    604         public boolean isAutomatic;
    605         public boolean isManual;
    606 
    607         private ConditionRecord(Uri id, ComponentName component) {
    608             this.id = id;
    609             this.component = component;
    610         }
    611 
    612         @Override
    613         public String toString() {
    614             final StringBuilder sb = new StringBuilder("ConditionRecord[id=")
    615                     .append(id).append(",component=").append(component);
    616             if (isAutomatic) sb.append(",automatic");
    617             if (isManual) sb.append(",manual");
    618             return sb.append(']').toString();
    619         }
    620     }
    621 }
    622