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