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.app.INotificationManager;
     20 import android.app.NotificationManager;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.pm.IPackageManager;
     24 import android.net.Uri;
     25 import android.os.IBinder;
     26 import android.os.IInterface;
     27 import android.os.RemoteException;
     28 import android.os.UserHandle;
     29 import android.provider.Settings;
     30 import android.service.notification.Condition;
     31 import android.service.notification.ConditionProviderService;
     32 import android.service.notification.IConditionProvider;
     33 import android.util.ArrayMap;
     34 import android.util.ArraySet;
     35 import android.util.Slog;
     36 
     37 import com.android.internal.R;
     38 import com.android.internal.annotations.VisibleForTesting;
     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 
     45 public class ConditionProviders extends ManagedServices {
     46 
     47     @VisibleForTesting
     48     static final String TAG_ENABLED_DND_APPS = "dnd_apps";
     49 
     50     private final ArrayList<ConditionRecord> mRecords = new ArrayList<>();
     51     private final ArraySet<String> mSystemConditionProviderNames;
     52     private final ArraySet<SystemConditionProviderService> mSystemConditionProviders
     53             = new ArraySet<>();
     54 
     55     private Callback mCallback;
     56 
     57     public ConditionProviders(Context context, UserProfiles userProfiles, IPackageManager pm) {
     58         super(context, new Object(), userProfiles, pm);
     59         mSystemConditionProviderNames = safeSet(PropConfig.getStringArray(mContext,
     60                 "system.condition.providers",
     61                 R.array.config_system_condition_providers));
     62         mApprovalLevel = APPROVAL_BY_PACKAGE;
     63     }
     64 
     65     public void setCallback(Callback callback) {
     66         mCallback = callback;
     67     }
     68 
     69     public boolean isSystemProviderEnabled(String path) {
     70         return mSystemConditionProviderNames.contains(path);
     71     }
     72 
     73     public void addSystemProvider(SystemConditionProviderService service) {
     74         mSystemConditionProviders.add(service);
     75         service.attachBase(mContext);
     76         registerService(service.asInterface(), service.getComponent(), UserHandle.USER_SYSTEM);
     77     }
     78 
     79     public Iterable<SystemConditionProviderService> getSystemProviders() {
     80         return mSystemConditionProviders;
     81     }
     82 
     83     @Override
     84     protected Config getConfig() {
     85         final Config c = new Config();
     86         c.caption = "condition provider";
     87         c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE;
     88         c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES;
     89         c.xmlTag = TAG_ENABLED_DND_APPS;
     90         c.secondarySettingName = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
     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             pw.print("    mRecords("); pw.print(mRecords.size()); pw.println("):");
    102             for (int i = 0; i < mRecords.size(); i++) {
    103                 final ConditionRecord r = mRecords.get(i);
    104                 if (filter != null && !filter.matches(r.component)) continue;
    105                 pw.print("      "); pw.println(r);
    106                 final String countdownDesc =  CountdownConditionProvider.tryParseDescription(r.id);
    107                 if (countdownDesc != null) {
    108                     pw.print("        ("); pw.print(countdownDesc); pw.println(")");
    109                 }
    110             }
    111         }
    112         pw.print("    mSystemConditionProviders: "); pw.println(mSystemConditionProviderNames);
    113         for (int i = 0; i < mSystemConditionProviders.size(); i++) {
    114             mSystemConditionProviders.valueAt(i).dump(pw, filter);
    115         }
    116     }
    117 
    118     @Override
    119     protected IInterface asInterface(IBinder binder) {
    120         return IConditionProvider.Stub.asInterface(binder);
    121     }
    122 
    123     @Override
    124     protected boolean checkType(IInterface service) {
    125         return service instanceof IConditionProvider;
    126     }
    127 
    128     @Override
    129     public void onBootPhaseAppsCanStart() {
    130         super.onBootPhaseAppsCanStart();
    131         for (int i = 0; i < mSystemConditionProviders.size(); i++) {
    132             mSystemConditionProviders.valueAt(i).onBootComplete();
    133         }
    134         if (mCallback != null) {
    135             mCallback.onBootComplete();
    136         }
    137     }
    138 
    139     @Override
    140     public void onUserSwitched(int user) {
    141         super.onUserSwitched(user);
    142         if (mCallback != null) {
    143             mCallback.onUserSwitched();
    144         }
    145     }
    146 
    147     @Override
    148     protected void onServiceAdded(ManagedServiceInfo info) {
    149         final IConditionProvider provider = provider(info);
    150         try {
    151             provider.onConnected();
    152         } catch (RemoteException e) {
    153             // we tried
    154         }
    155         if (mCallback != null) {
    156             mCallback.onServiceAdded(info.component);
    157         }
    158     }
    159 
    160     @Override
    161     protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
    162         if (removed == null) return;
    163         for (int i = mRecords.size() - 1; i >= 0; i--) {
    164             final ConditionRecord r = mRecords.get(i);
    165             if (!r.component.equals(removed.component)) continue;
    166             mRecords.remove(i);
    167         }
    168     }
    169 
    170     @Override
    171     public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uid) {
    172         if (removingPackage) {
    173             INotificationManager inm = NotificationManager.getService();
    174 
    175             if (pkgList != null && (pkgList.length > 0)) {
    176                 for (String pkgName : pkgList) {
    177                     try {
    178                         inm.removeAutomaticZenRules(pkgName);
    179                         inm.setNotificationPolicyAccessGranted(pkgName, false);
    180                     } catch (Exception e) {
    181                         Slog.e(TAG, "Failed to clean up rules for " + pkgName, e);
    182                     }
    183                 }
    184             }
    185         }
    186         super.onPackagesChanged(removingPackage, pkgList, uid);
    187     }
    188 
    189     @Override
    190     protected boolean isValidEntry(String packageOrComponent, int userId) {
    191         return true;
    192     }
    193 
    194     public ManagedServiceInfo checkServiceToken(IConditionProvider provider) {
    195         synchronized(mMutex) {
    196             return checkServiceTokenLocked(provider);
    197         }
    198     }
    199 
    200     private Condition[] removeDuplicateConditions(String pkg, Condition[] conditions) {
    201         if (conditions == null || conditions.length == 0) return null;
    202         final int N = conditions.length;
    203         final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N);
    204         for (int i = 0; i < N; i++) {
    205             final Uri id = conditions[i].id;
    206             if (valid.containsKey(id)) {
    207                 Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id);
    208                 continue;
    209             }
    210             valid.put(id, conditions[i]);
    211         }
    212         if (valid.size() == 0) return null;
    213         if (valid.size() == N) return conditions;
    214         final Condition[] rt = new Condition[valid.size()];
    215         for (int i = 0; i < rt.length; i++) {
    216             rt[i] = valid.valueAt(i);
    217         }
    218         return rt;
    219     }
    220 
    221     private ConditionRecord getRecordLocked(Uri id, ComponentName component, boolean create) {
    222         if (id == null || component == null) return null;
    223         final int N = mRecords.size();
    224         for (int i = 0; i < N; i++) {
    225             final ConditionRecord r = mRecords.get(i);
    226             if (r.id.equals(id) && r.component.equals(component)) {
    227                 return r;
    228             }
    229         }
    230         if (create) {
    231             final ConditionRecord r = new ConditionRecord(id, component);
    232             mRecords.add(r);
    233             return r;
    234         }
    235         return null;
    236     }
    237 
    238     public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) {
    239         synchronized(mMutex) {
    240             if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions="
    241                     + (conditions == null ? null : Arrays.asList(conditions)));
    242             conditions = removeDuplicateConditions(pkg, conditions);
    243             if (conditions == null || conditions.length == 0) return;
    244             final int N = conditions.length;
    245             for (int i = 0; i < N; i++) {
    246                 final Condition c = conditions[i];
    247                 final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/);
    248                 r.info = info;
    249                 r.condition = c;
    250             }
    251         }
    252         final int N = conditions.length;
    253         for (int i = 0; i < N; i++) {
    254             final Condition c = conditions[i];
    255             if (mCallback != null) {
    256                 mCallback.onConditionChanged(c.id, c);
    257             }
    258         }
    259     }
    260 
    261     public IConditionProvider findConditionProvider(ComponentName component) {
    262         if (component == null) return null;
    263         for (ManagedServiceInfo service : getServices()) {
    264             if (component.equals(service.component)) {
    265                 return provider(service);
    266             }
    267         }
    268         return null;
    269     }
    270 
    271     public Condition findCondition(ComponentName component, Uri conditionId) {
    272         if (component == null || conditionId == null) return null;
    273         synchronized (mMutex) {
    274             final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
    275             return r != null ? r.condition : null;
    276         }
    277     }
    278 
    279     public void ensureRecordExists(ComponentName component, Uri conditionId,
    280             IConditionProvider provider) {
    281         // constructed by convention, make sure the record exists...
    282         final ConditionRecord r = getRecordLocked(conditionId, component, true /*create*/);
    283         if (r.info == null) {
    284             // ... and is associated with the in-process service
    285             r.info = checkServiceTokenLocked(provider);
    286         }
    287     }
    288 
    289     public boolean subscribeIfNecessary(ComponentName component, Uri conditionId) {
    290         synchronized (mMutex) {
    291             final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
    292             if (r == null) {
    293                 Slog.w(TAG, "Unable to subscribe to " + component + " " + conditionId);
    294                 return false;
    295             }
    296             if (r.subscribed) return true;
    297             subscribeLocked(r);
    298             return r.subscribed;
    299         }
    300     }
    301 
    302     public void unsubscribeIfNecessary(ComponentName component, Uri conditionId) {
    303         synchronized (mMutex) {
    304             final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/);
    305             if (r == null) {
    306                 Slog.w(TAG, "Unable to unsubscribe to " + component + " " + conditionId);
    307                 return;
    308             }
    309             if (!r.subscribed) return;
    310             unsubscribeLocked(r);;
    311         }
    312     }
    313 
    314     private void subscribeLocked(ConditionRecord r) {
    315         if (DEBUG) Slog.d(TAG, "subscribeLocked " + r);
    316         final IConditionProvider provider = provider(r);
    317         RemoteException re = null;
    318         if (provider != null) {
    319             try {
    320                 Slog.d(TAG, "Subscribing to " + r.id + " with " + r.component);
    321                 provider.onSubscribe(r.id);
    322                 r.subscribed = true;
    323             } catch (RemoteException e) {
    324                 Slog.w(TAG, "Error subscribing to " + r, e);
    325                 re = e;
    326             }
    327         }
    328         ZenLog.traceSubscribe(r != null ? r.id : null, provider, re);
    329     }
    330 
    331     @SafeVarargs
    332     private static <T> ArraySet<T> safeSet(T... items) {
    333         final ArraySet<T> rt = new ArraySet<T>();
    334         if (items == null || items.length == 0) return rt;
    335         final int N = items.length;
    336         for (int i = 0; i < N; i++) {
    337             final T item = items[i];
    338             if (item != null) {
    339                 rt.add(item);
    340             }
    341         }
    342         return rt;
    343     }
    344 
    345     private void unsubscribeLocked(ConditionRecord r) {
    346         if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r);
    347         final IConditionProvider provider = provider(r);
    348         RemoteException re = null;
    349         if (provider != null) {
    350             try {
    351                 provider.onUnsubscribe(r.id);
    352             } catch (RemoteException e) {
    353                 Slog.w(TAG, "Error unsubscribing to " + r, e);
    354                 re = e;
    355             }
    356             r.subscribed = false;
    357         }
    358         ZenLog.traceUnsubscribe(r != null ? r.id : null, provider, re);
    359     }
    360 
    361     private static IConditionProvider provider(ConditionRecord r) {
    362         return r == null ? null : provider(r.info);
    363     }
    364 
    365     private static IConditionProvider provider(ManagedServiceInfo info) {
    366         return info == null ? null : (IConditionProvider) info.service;
    367     }
    368 
    369     private static class ConditionRecord {
    370         public final Uri id;
    371         public final ComponentName component;
    372         public Condition condition;
    373         public ManagedServiceInfo info;
    374         public boolean subscribed;
    375 
    376         private ConditionRecord(Uri id, ComponentName component) {
    377             this.id = id;
    378             this.component = component;
    379         }
    380 
    381         @Override
    382         public String toString() {
    383             final StringBuilder sb = new StringBuilder("ConditionRecord[id=")
    384                     .append(id).append(",component=").append(component)
    385                     .append(",subscribed=").append(subscribed);
    386             return sb.append(']').toString();
    387         }
    388     }
    389 
    390     public interface Callback {
    391         void onBootComplete();
    392         void onServiceAdded(ComponentName component);
    393         void onConditionChanged(Uri id, Condition condition);
    394         void onUserSwitched();
    395     }
    396 
    397 }
    398