ConditionProviders.java revision 6e839b006e102b4c2d9f495fe0551e7668dae61e
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 17package com.android.server.notification; 18 19import android.annotation.NonNull; 20import android.content.ComponentName; 21import android.content.ContentResolver; 22import android.content.Context; 23import android.net.Uri; 24import android.os.Handler; 25import android.os.IBinder; 26import android.os.IInterface; 27import android.os.RemoteException; 28import android.os.UserHandle; 29import android.provider.Settings; 30import android.service.notification.Condition; 31import android.service.notification.ConditionProviderService; 32import android.service.notification.IConditionProvider; 33import android.text.TextUtils; 34import android.util.ArrayMap; 35import android.util.ArraySet; 36import android.util.Slog; 37 38import com.android.internal.R; 39import com.android.server.notification.NotificationManagerService.DumpFilter; 40 41import java.io.PrintWriter; 42import java.util.ArrayList; 43import java.util.Arrays; 44 45public 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