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