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 CountdownConditionProvider mCountdown = new CountdownConditionProvider();
     54     private final DowntimeConditionProvider mDowntime = new DowntimeConditionProvider();
     55 
     56     private Condition mExitCondition;
     57     private ComponentName mExitConditionComponent;
     58 
     59     public ConditionProviders(Context context, Handler handler,
     60             UserProfiles userProfiles, ZenModeHelper zenModeHelper) {
     61         super(context, handler, new Object(), userProfiles);
     62         mZenModeHelper = zenModeHelper;
     63         mZenModeHelper.addCallback(new ZenModeHelperCallback());
     64         loadZenConfig();
     65     }
     66 
     67     @Override
     68     protected Config getConfig() {
     69         Config c = new Config();
     70         c.caption = "condition provider";
     71         c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
     72         c.secureSettingName = Settings.Secure.ENABLED_CONDITION_PROVIDERS;
     73         c.bindPermission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE;
     74         c.settingsAction = Settings.ACTION_CONDITION_PROVIDER_SETTINGS;
     75         c.clientLabel = R.string.condition_provider_service_binding_label;
     76         return c;
     77     }
     78 
     79     @Override
     80     public void dump(PrintWriter pw, DumpFilter filter) {
     81         super.dump(pw, filter);
     82         synchronized(mMutex) {
     83             if (filter == null) {
     84                 pw.print("    mListeners("); pw.print(mListeners.size()); pw.println("):");
     85                 for (int i = 0; i < mListeners.size(); i++) {
     86                     pw.print("      "); pw.println(mListeners.keyAt(i));
     87                 }
     88             }
     89             pw.print("    mRecords("); pw.print(mRecords.size()); pw.println("):");
     90             for (int i = 0; i < mRecords.size(); i++) {
     91                 final ConditionRecord r = mRecords.get(i);
     92                 if (filter != null && !filter.matches(r.component)) continue;
     93                 pw.print("      "); pw.println(r);
     94                 final String countdownDesc =  CountdownConditionProvider.tryParseDescription(r.id);
     95                 if (countdownDesc != null) {
     96                     pw.print("        ("); pw.print(countdownDesc); pw.println(")");
     97                 }
     98             }
     99         }
    100         mCountdown.dump(pw, filter);
    101         mDowntime.dump(pw, filter);
    102     }
    103 
    104     @Override
    105     protected IInterface asInterface(IBinder binder) {
    106         return IConditionProvider.Stub.asInterface(binder);
    107     }
    108 
    109     @Override
    110     public void onBootPhaseAppsCanStart() {
    111         super.onBootPhaseAppsCanStart();
    112         mCountdown.attachBase(mContext);
    113         registerService(mCountdown.asInterface(), CountdownConditionProvider.COMPONENT,
    114                 UserHandle.USER_OWNER);
    115         mDowntime.attachBase(mContext);
    116         registerService(mDowntime.asInterface(), DowntimeConditionProvider.COMPONENT,
    117                 UserHandle.USER_OWNER);
    118         mDowntime.setCallback(new DowntimeCallback());
    119     }
    120 
    121     @Override
    122     protected void onServiceAdded(ManagedServiceInfo info) {
    123         Slog.d(TAG, "onServiceAdded " + info);
    124         final IConditionProvider provider = provider(info);
    125         try {
    126             provider.onConnected();
    127         } catch (RemoteException e) {
    128             // we tried
    129         }
    130         synchronized (mMutex) {
    131             if (info.component.equals(mExitConditionComponent)) {
    132                 // ensure record exists, we'll wire it up and subscribe below
    133                 final ConditionRecord manualRecord =
    134                         getRecordLocked(mExitCondition.id, mExitConditionComponent);
    135                 manualRecord.isManual = true;
    136             }
    137             final int N = mRecords.size();
    138             for(int i = 0; i < N; i++) {
    139                 final ConditionRecord r = mRecords.get(i);
    140                 if (!r.component.equals(info.component)) continue;
    141                 r.info = info;
    142                 // if automatic or manual, auto-subscribe
    143                 if (r.isAutomatic || r.isManual) {
    144                     subscribeLocked(r);
    145                 }
    146             }
    147         }
    148     }
    149 
    150     @Override
    151     protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
    152         if (removed == null) return;
    153         for (int i = mRecords.size() - 1; i >= 0; i--) {
    154             final ConditionRecord r = mRecords.get(i);
    155             if (!r.component.equals(removed.component)) continue;
    156             if (r.isManual) {
    157                 // removing the current manual condition, exit zen
    158                 mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "manualServiceRemoved");
    159             }
    160             if (r.isAutomatic) {
    161                 // removing an automatic condition, exit zen
    162                 mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "automaticServiceRemoved");
    163             }
    164             mRecords.remove(i);
    165         }
    166     }
    167 
    168     public ManagedServiceInfo checkServiceToken(IConditionProvider provider) {
    169         synchronized(mMutex) {
    170             return checkServiceTokenLocked(provider);
    171         }
    172     }
    173 
    174     public void requestZenModeConditions(IConditionListener callback, int relevance) {
    175         synchronized(mMutex) {
    176             if (DEBUG) Slog.d(TAG, "requestZenModeConditions callback=" + callback
    177                     + " relevance=" + Condition.relevanceToString(relevance));
    178             if (callback == null) return;
    179             relevance = relevance & (Condition.FLAG_RELEVANT_NOW | Condition.FLAG_RELEVANT_ALWAYS);
    180             if (relevance != 0) {
    181                 mListeners.put(callback.asBinder(), callback);
    182                 requestConditionsLocked(relevance);
    183             } else {
    184                 mListeners.remove(callback.asBinder());
    185                 if (mListeners.isEmpty()) {
    186                     requestConditionsLocked(0);
    187                 }
    188             }
    189         }
    190     }
    191 
    192     private Condition[] validateConditions(String pkg, Condition[] conditions) {
    193         if (conditions == null || conditions.length == 0) return null;
    194         final int N = conditions.length;
    195         final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N);
    196         for (int i = 0; i < N; i++) {
    197             final Uri id = conditions[i].id;
    198             if (!Condition.isValidId(id, pkg)) {
    199                 Slog.w(TAG, "Ignoring condition from " + pkg + " for invalid id: " + id);
    200                 continue;
    201             }
    202             if (valid.containsKey(id)) {
    203                 Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id);
    204                 continue;
    205             }
    206             valid.put(id, conditions[i]);
    207         }
    208         if (valid.size() == 0) return null;
    209         if (valid.size() == N) return conditions;
    210         final Condition[] rt = new Condition[valid.size()];
    211         for (int i = 0; i < rt.length; i++) {
    212             rt[i] = valid.valueAt(i);
    213         }
    214         return rt;
    215     }
    216 
    217     private ConditionRecord getRecordLocked(Uri id, ComponentName component) {
    218         final int N = mRecords.size();
    219         for (int i = 0; i < N; i++) {
    220             final ConditionRecord r = mRecords.get(i);
    221             if (r.id.equals(id) && r.component.equals(component)) {
    222                 return r;
    223             }
    224         }
    225         final ConditionRecord r = new ConditionRecord(id, component);
    226         mRecords.add(r);
    227         return r;
    228     }
    229 
    230     public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) {
    231         synchronized(mMutex) {
    232             if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions="
    233                     + (conditions == null ? null : Arrays.asList(conditions)));
    234             conditions = validateConditions(pkg, conditions);
    235             if (conditions == null || conditions.length == 0) return;
    236             final int N = conditions.length;
    237             for (IConditionListener listener : mListeners.values()) {
    238                 try {
    239                     listener.onConditionsReceived(conditions);
    240                 } catch (RemoteException e) {
    241                     Slog.w(TAG, "Error sending conditions to listener " + listener, e);
    242                 }
    243             }
    244             for (int i = 0; i < N; i++) {
    245                 final Condition c = conditions[i];
    246                 final ConditionRecord r = getRecordLocked(c.id, info.component);
    247                 r.info = info;
    248                 r.condition = c;
    249                 // if manual, exit zen if false (or failed)
    250                 if (r.isManual) {
    251                     if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
    252                         final boolean failed = c.state == Condition.STATE_ERROR;
    253                         if (failed) {
    254                             Slog.w(TAG, "Exit zen: manual condition failed: " + c);
    255                         } else if (DEBUG) {
    256                             Slog.d(TAG, "Exit zen: manual condition false: " + c);
    257                         }
    258                         mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF,
    259                                 "manualConditionExit");
    260                         unsubscribeLocked(r);
    261                         r.isManual = false;
    262                     }
    263                 }
    264                 // if automatic, exit zen if false (or failed), enter zen if true
    265                 if (r.isAutomatic) {
    266                     if (c.state == Condition.STATE_FALSE || c.state == Condition.STATE_ERROR) {
    267                         final boolean failed = c.state == Condition.STATE_ERROR;
    268                         if (failed) {
    269                             Slog.w(TAG, "Exit zen: automatic condition failed: " + c);
    270                         } else if (DEBUG) {
    271                             Slog.d(TAG, "Exit zen: automatic condition false: " + c);
    272                         }
    273                         mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_OFF,
    274                                 "automaticConditionExit");
    275                     } else if (c.state == Condition.STATE_TRUE) {
    276                         Slog.d(TAG, "Enter zen: automatic condition true: " + c);
    277                         mZenModeHelper.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
    278                                 "automaticConditionEnter");
    279                     }
    280                 }
    281             }
    282         }
    283     }
    284 
    285     public void setZenModeCondition(Condition condition, String reason) {
    286         if (DEBUG) Slog.d(TAG, "setZenModeCondition " + condition);
    287         synchronized(mMutex) {
    288             ComponentName conditionComponent = null;
    289             if (condition != null) {
    290                 if (ZenModeConfig.isValidCountdownConditionId(condition.id)) {
    291                     // constructed by the client, make sure the record exists...
    292                     final ConditionRecord r = getRecordLocked(condition.id,
    293                             CountdownConditionProvider.COMPONENT);
    294                     if (r.info == null) {
    295                         // ... and is associated with the in-process service
    296                         r.info = checkServiceTokenLocked(mCountdown.asInterface());
    297                     }
    298                 }
    299                 if (ZenModeConfig.isValidDowntimeConditionId(condition.id)) {
    300                     // constructed by the client, make sure the record exists...
    301                     final ConditionRecord r = getRecordLocked(condition.id,
    302                             DowntimeConditionProvider.COMPONENT);
    303                     if (r.info == null) {
    304                         // ... and is associated with the in-process service
    305                         r.info = checkServiceTokenLocked(mDowntime.asInterface());
    306                     }
    307                 }
    308             }
    309             final int N = mRecords.size();
    310             for (int i = 0; i < N; i++) {
    311                 final ConditionRecord r = mRecords.get(i);
    312                 final boolean idEqual = condition != null && r.id.equals(condition.id);
    313                 if (r.isManual && !idEqual) {
    314                     // was previous manual condition, unsubscribe
    315                     unsubscribeLocked(r);
    316                     r.isManual = false;
    317                 } else if (idEqual && !r.isManual) {
    318                     // is new manual condition, subscribe
    319                     subscribeLocked(r);
    320                     r.isManual = true;
    321                 }
    322                 if (idEqual) {
    323                     conditionComponent = r.component;
    324                 }
    325             }
    326             if (!Objects.equals(mExitCondition, condition)) {
    327                 mExitCondition = condition;
    328                 mExitConditionComponent = conditionComponent;
    329                 ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, reason);
    330                 saveZenConfigLocked();
    331             }
    332         }
    333     }
    334 
    335     private void subscribeLocked(ConditionRecord r) {
    336         if (DEBUG) Slog.d(TAG, "subscribeLocked " + r);
    337         final IConditionProvider provider = provider(r);
    338         RemoteException re = null;
    339         if (provider != null) {
    340             try {
    341                 Slog.d(TAG, "Subscribing to " + r.id + " with " + provider);
    342                 provider.onSubscribe(r.id);
    343             } catch (RemoteException e) {
    344                 Slog.w(TAG, "Error subscribing to " + r, e);
    345                 re = e;
    346             }
    347         }
    348         ZenLog.traceSubscribe(r != null ? r.id : null, provider, re);
    349     }
    350 
    351     private static <T> ArraySet<T> safeSet(T... items) {
    352         final ArraySet<T> rt = new ArraySet<T>();
    353         if (items == null || items.length == 0) return rt;
    354         final int N = items.length;
    355         for (int i = 0; i < N; i++) {
    356             final T item = items[i];
    357             if (item != null) {
    358                 rt.add(item);
    359             }
    360         }
    361         return rt;
    362     }
    363 
    364     public void setAutomaticZenModeConditions(Uri[] conditionIds) {
    365         setAutomaticZenModeConditions(conditionIds, true /*save*/);
    366     }
    367 
    368     private void setAutomaticZenModeConditions(Uri[] conditionIds, boolean save) {
    369         if (DEBUG) Slog.d(TAG, "setAutomaticZenModeConditions "
    370                 + (conditionIds == null ? null : Arrays.asList(conditionIds)));
    371         synchronized(mMutex) {
    372             final ArraySet<Uri> newIds = safeSet(conditionIds);
    373             final int N = mRecords.size();
    374             boolean changed = false;
    375             for (int i = 0; i < N; i++) {
    376                 final ConditionRecord r = mRecords.get(i);
    377                 final boolean automatic = newIds.contains(r.id);
    378                 if (!r.isAutomatic && automatic) {
    379                     // subscribe to new automatic
    380                     subscribeLocked(r);
    381                     r.isAutomatic = true;
    382                     changed = true;
    383                 } else if (r.isAutomatic && !automatic) {
    384                     // unsubscribe from old automatic
    385                     unsubscribeLocked(r);
    386                     r.isAutomatic = false;
    387                     changed = true;
    388                 }
    389             }
    390             if (save && changed) {
    391                 saveZenConfigLocked();
    392             }
    393         }
    394     }
    395 
    396     public Condition[] getAutomaticZenModeConditions() {
    397         synchronized(mMutex) {
    398             final int N = mRecords.size();
    399             ArrayList<Condition> rt = null;
    400             for (int i = 0; i < N; i++) {
    401                 final ConditionRecord r = mRecords.get(i);
    402                 if (r.isAutomatic && r.condition != null) {
    403                     if (rt == null) rt = new ArrayList<Condition>();
    404                     rt.add(r.condition);
    405                 }
    406             }
    407             return rt == null ? NO_CONDITIONS : rt.toArray(new Condition[rt.size()]);
    408         }
    409     }
    410 
    411     private void unsubscribeLocked(ConditionRecord r) {
    412         if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r);
    413         final IConditionProvider provider = provider(r);
    414         RemoteException re = null;
    415         if (provider != null) {
    416             try {
    417                 provider.onUnsubscribe(r.id);
    418             } catch (RemoteException e) {
    419                 Slog.w(TAG, "Error unsubscribing to " + r, e);
    420                 re = e;
    421             }
    422         }
    423         ZenLog.traceUnsubscribe(r != null ? r.id : null, provider, re);
    424     }
    425 
    426     private static IConditionProvider provider(ConditionRecord r) {
    427         return r == null ? null : provider(r.info);
    428     }
    429 
    430     private static IConditionProvider provider(ManagedServiceInfo info) {
    431         return info == null ? null : (IConditionProvider) info.service;
    432     }
    433 
    434     private void requestConditionsLocked(int flags) {
    435         for (ManagedServiceInfo info : mServices) {
    436             final IConditionProvider provider = provider(info);
    437             if (provider == null) continue;
    438             // clear all stored conditions from this provider that we no longer care about
    439             for (int i = mRecords.size() - 1; i >= 0; i--) {
    440                 final ConditionRecord r = mRecords.get(i);
    441                 if (r.info != info) continue;
    442                 if (r.isManual || r.isAutomatic) continue;
    443                 mRecords.remove(i);
    444             }
    445             try {
    446                 provider.onRequestConditions(flags);
    447             } catch (RemoteException e) {
    448                 Slog.w(TAG, "Error requesting conditions from " + info.component, e);
    449             }
    450         }
    451     }
    452 
    453     private void loadZenConfig() {
    454         final ZenModeConfig config = mZenModeHelper.getConfig();
    455         if (config == null) {
    456             if (DEBUG) Slog.d(TAG, "loadZenConfig: no config");
    457             return;
    458         }
    459         synchronized (mMutex) {
    460             final boolean changingExit = !Objects.equals(mExitCondition, config.exitCondition);
    461             mExitCondition = config.exitCondition;
    462             mExitConditionComponent = config.exitConditionComponent;
    463             if (changingExit) {
    464                 ZenLog.traceExitCondition(mExitCondition, mExitConditionComponent, "config");
    465             }
    466             mDowntime.setConfig(config);
    467             if (config.conditionComponents == null || config.conditionIds == null
    468                     || config.conditionComponents.length != config.conditionIds.length) {
    469                 if (DEBUG) Slog.d(TAG, "loadZenConfig: no conditions");
    470                 setAutomaticZenModeConditions(null, false /*save*/);
    471                 return;
    472             }
    473             final ArraySet<Uri> newIds = new ArraySet<Uri>();
    474             final int N = config.conditionComponents.length;
    475             for (int i = 0; i < N; i++) {
    476                 final ComponentName component = config.conditionComponents[i];
    477                 final Uri id = config.conditionIds[i];
    478                 if (component != null && id != null) {
    479                     getRecordLocked(id, component);  // ensure record exists
    480                     newIds.add(id);
    481                 }
    482             }
    483             if (DEBUG) Slog.d(TAG, "loadZenConfig: N=" + N);
    484             setAutomaticZenModeConditions(newIds.toArray(new Uri[newIds.size()]), false /*save*/);
    485         }
    486     }
    487 
    488     private void saveZenConfigLocked() {
    489         ZenModeConfig config = mZenModeHelper.getConfig();
    490         if (config == null) return;
    491         config = config.copy();
    492         final ArrayList<ConditionRecord> automatic = new ArrayList<ConditionRecord>();
    493         final int automaticN = mRecords.size();
    494         for (int i = 0; i < automaticN; i++) {
    495             final ConditionRecord r = mRecords.get(i);
    496             if (r.isAutomatic) {
    497                 automatic.add(r);
    498             }
    499         }
    500         if (automatic.isEmpty()) {
    501             config.conditionComponents = null;
    502             config.conditionIds = null;
    503         } else {
    504             final int N = automatic.size();
    505             config.conditionComponents = new ComponentName[N];
    506             config.conditionIds = new Uri[N];
    507             for (int i = 0; i < N; i++) {
    508                 final ConditionRecord r = automatic.get(i);
    509                 config.conditionComponents[i] = r.component;
    510                 config.conditionIds[i] = r.id;
    511             }
    512         }
    513         config.exitCondition = mExitCondition;
    514         config.exitConditionComponent = mExitConditionComponent;
    515         if (DEBUG) Slog.d(TAG, "Setting zen config to: " + config);
    516         mZenModeHelper.setConfig(config);
    517     }
    518 
    519     private class ZenModeHelperCallback extends ZenModeHelper.Callback {
    520         @Override
    521         void onConfigChanged() {
    522             loadZenConfig();
    523         }
    524 
    525         @Override
    526         void onZenModeChanged() {
    527             final int mode = mZenModeHelper.getZenMode();
    528             if (mode == Global.ZEN_MODE_OFF) {
    529                 // ensure any manual condition is cleared
    530                 setZenModeCondition(null, "zenOff");
    531             }
    532         }
    533     }
    534 
    535     private class DowntimeCallback implements DowntimeConditionProvider.Callback {
    536         @Override
    537         public void onDowntimeChanged(boolean inDowntime) {
    538             final int mode = mZenModeHelper.getZenMode();
    539             final ZenModeConfig config = mZenModeHelper.getConfig();
    540             // enter downtime
    541             if (inDowntime && mode == Global.ZEN_MODE_OFF && config != null) {
    542                 final Condition condition = mDowntime.createCondition(config.toDowntimeInfo(),
    543                         Condition.STATE_TRUE);
    544                 mZenModeHelper.setZenMode(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, "downtimeEnter");
    545                 setZenModeCondition(condition, "downtime");
    546             }
    547             // exit downtime
    548             if (!inDowntime && mDowntime.isDowntimeCondition(mExitCondition)
    549                     && (mode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
    550                                 || mode == Global.ZEN_MODE_NO_INTERRUPTIONS)) {
    551                 mZenModeHelper.setZenMode(Global.ZEN_MODE_OFF, "downtimeExit");
    552             }
    553         }
    554     }
    555 
    556     private static class ConditionRecord {
    557         public final Uri id;
    558         public final ComponentName component;
    559         public Condition condition;
    560         public ManagedServiceInfo info;
    561         public boolean isAutomatic;
    562         public boolean isManual;
    563 
    564         private ConditionRecord(Uri id, ComponentName component) {
    565             this.id = id;
    566             this.component = component;
    567         }
    568 
    569         @Override
    570         public String toString() {
    571             final StringBuilder sb = new StringBuilder("ConditionRecord[id=")
    572                     .append(id).append(",component=").append(component);
    573             if (isAutomatic) sb.append(",automatic");
    574             if (isManual) sb.append(",manual");
    575             return sb.append(']').toString();
    576         }
    577     }
    578 }
    579