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