| /** |
| * Copyright (c) 2014, The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.notification; |
| |
| import android.app.INotificationManager; |
| import android.app.NotificationManager; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.pm.IPackageManager; |
| import android.net.Uri; |
| import android.os.IBinder; |
| import android.os.IInterface; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.provider.Settings; |
| import android.service.notification.Condition; |
| import android.service.notification.ConditionProviderService; |
| import android.service.notification.IConditionProvider; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.Slog; |
| |
| import com.android.internal.R; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.server.notification.NotificationManagerService.DumpFilter; |
| |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| |
| public class ConditionProviders extends ManagedServices { |
| |
| @VisibleForTesting |
| static final String TAG_ENABLED_DND_APPS = "dnd_apps"; |
| |
| private final ArrayList<ConditionRecord> mRecords = new ArrayList<>(); |
| private final ArraySet<String> mSystemConditionProviderNames; |
| private final ArraySet<SystemConditionProviderService> mSystemConditionProviders |
| = new ArraySet<>(); |
| |
| private Callback mCallback; |
| |
| public ConditionProviders(Context context, UserProfiles userProfiles, IPackageManager pm) { |
| super(context, new Object(), userProfiles, pm); |
| mSystemConditionProviderNames = safeSet(PropConfig.getStringArray(mContext, |
| "system.condition.providers", |
| R.array.config_system_condition_providers)); |
| mApprovalLevel = APPROVAL_BY_PACKAGE; |
| } |
| |
| public void setCallback(Callback callback) { |
| mCallback = callback; |
| } |
| |
| public boolean isSystemProviderEnabled(String path) { |
| return mSystemConditionProviderNames.contains(path); |
| } |
| |
| public void addSystemProvider(SystemConditionProviderService service) { |
| mSystemConditionProviders.add(service); |
| service.attachBase(mContext); |
| registerService(service.asInterface(), service.getComponent(), UserHandle.USER_SYSTEM); |
| } |
| |
| public Iterable<SystemConditionProviderService> getSystemProviders() { |
| return mSystemConditionProviders; |
| } |
| |
| @Override |
| protected Config getConfig() { |
| final Config c = new Config(); |
| c.caption = "condition provider"; |
| c.serviceInterface = ConditionProviderService.SERVICE_INTERFACE; |
| c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_POLICY_ACCESS_PACKAGES; |
| c.xmlTag = TAG_ENABLED_DND_APPS; |
| c.secondarySettingName = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS; |
| c.bindPermission = android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE; |
| c.settingsAction = Settings.ACTION_CONDITION_PROVIDER_SETTINGS; |
| c.clientLabel = R.string.condition_provider_service_binding_label; |
| return c; |
| } |
| |
| @Override |
| public void dump(PrintWriter pw, DumpFilter filter) { |
| super.dump(pw, filter); |
| synchronized(mMutex) { |
| pw.print(" mRecords("); pw.print(mRecords.size()); pw.println("):"); |
| for (int i = 0; i < mRecords.size(); i++) { |
| final ConditionRecord r = mRecords.get(i); |
| if (filter != null && !filter.matches(r.component)) continue; |
| pw.print(" "); pw.println(r); |
| final String countdownDesc = CountdownConditionProvider.tryParseDescription(r.id); |
| if (countdownDesc != null) { |
| pw.print(" ("); pw.print(countdownDesc); pw.println(")"); |
| } |
| } |
| } |
| pw.print(" mSystemConditionProviders: "); pw.println(mSystemConditionProviderNames); |
| for (int i = 0; i < mSystemConditionProviders.size(); i++) { |
| mSystemConditionProviders.valueAt(i).dump(pw, filter); |
| } |
| } |
| |
| @Override |
| protected IInterface asInterface(IBinder binder) { |
| return IConditionProvider.Stub.asInterface(binder); |
| } |
| |
| @Override |
| protected boolean checkType(IInterface service) { |
| return service instanceof IConditionProvider; |
| } |
| |
| @Override |
| public void onBootPhaseAppsCanStart() { |
| super.onBootPhaseAppsCanStart(); |
| for (int i = 0; i < mSystemConditionProviders.size(); i++) { |
| mSystemConditionProviders.valueAt(i).onBootComplete(); |
| } |
| if (mCallback != null) { |
| mCallback.onBootComplete(); |
| } |
| } |
| |
| @Override |
| public void onUserSwitched(int user) { |
| super.onUserSwitched(user); |
| if (mCallback != null) { |
| mCallback.onUserSwitched(); |
| } |
| } |
| |
| @Override |
| protected void onServiceAdded(ManagedServiceInfo info) { |
| final IConditionProvider provider = provider(info); |
| try { |
| provider.onConnected(); |
| } catch (RemoteException e) { |
| Slog.e(TAG, "can't connect to service " + info, e); |
| // we tried |
| } |
| if (mCallback != null) { |
| mCallback.onServiceAdded(info.component); |
| } |
| } |
| |
| @Override |
| protected void onServiceRemovedLocked(ManagedServiceInfo removed) { |
| if (removed == null) return; |
| for (int i = mRecords.size() - 1; i >= 0; i--) { |
| final ConditionRecord r = mRecords.get(i); |
| if (!r.component.equals(removed.component)) continue; |
| mRecords.remove(i); |
| } |
| } |
| |
| @Override |
| public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uid) { |
| if (removingPackage) { |
| INotificationManager inm = NotificationManager.getService(); |
| |
| if (pkgList != null && (pkgList.length > 0)) { |
| for (String pkgName : pkgList) { |
| try { |
| inm.removeAutomaticZenRules(pkgName); |
| inm.setNotificationPolicyAccessGranted(pkgName, false); |
| } catch (Exception e) { |
| Slog.e(TAG, "Failed to clean up rules for " + pkgName, e); |
| } |
| } |
| } |
| } |
| super.onPackagesChanged(removingPackage, pkgList, uid); |
| } |
| |
| @Override |
| protected boolean isValidEntry(String packageOrComponent, int userId) { |
| return true; |
| } |
| |
| public ManagedServiceInfo checkServiceToken(IConditionProvider provider) { |
| synchronized(mMutex) { |
| return checkServiceTokenLocked(provider); |
| } |
| } |
| |
| private Condition[] removeDuplicateConditions(String pkg, Condition[] conditions) { |
| if (conditions == null || conditions.length == 0) return null; |
| final int N = conditions.length; |
| final ArrayMap<Uri, Condition> valid = new ArrayMap<Uri, Condition>(N); |
| for (int i = 0; i < N; i++) { |
| final Uri id = conditions[i].id; |
| if (valid.containsKey(id)) { |
| Slog.w(TAG, "Ignoring condition from " + pkg + " for duplicate id: " + id); |
| continue; |
| } |
| valid.put(id, conditions[i]); |
| } |
| if (valid.size() == 0) return null; |
| if (valid.size() == N) return conditions; |
| final Condition[] rt = new Condition[valid.size()]; |
| for (int i = 0; i < rt.length; i++) { |
| rt[i] = valid.valueAt(i); |
| } |
| return rt; |
| } |
| |
| private ConditionRecord getRecordLocked(Uri id, ComponentName component, boolean create) { |
| if (id == null || component == null) return null; |
| final int N = mRecords.size(); |
| for (int i = 0; i < N; i++) { |
| final ConditionRecord r = mRecords.get(i); |
| if (r.id.equals(id) && r.component.equals(component)) { |
| return r; |
| } |
| } |
| if (create) { |
| final ConditionRecord r = new ConditionRecord(id, component); |
| mRecords.add(r); |
| return r; |
| } |
| return null; |
| } |
| |
| public void notifyConditions(String pkg, ManagedServiceInfo info, Condition[] conditions) { |
| synchronized(mMutex) { |
| if (DEBUG) Slog.d(TAG, "notifyConditions pkg=" + pkg + " info=" + info + " conditions=" |
| + (conditions == null ? null : Arrays.asList(conditions))); |
| conditions = removeDuplicateConditions(pkg, conditions); |
| if (conditions == null || conditions.length == 0) return; |
| final int N = conditions.length; |
| for (int i = 0; i < N; i++) { |
| final Condition c = conditions[i]; |
| final ConditionRecord r = getRecordLocked(c.id, info.component, true /*create*/); |
| r.info = info; |
| r.condition = c; |
| } |
| } |
| final int N = conditions.length; |
| for (int i = 0; i < N; i++) { |
| final Condition c = conditions[i]; |
| if (mCallback != null) { |
| mCallback.onConditionChanged(c.id, c); |
| } |
| } |
| } |
| |
| public IConditionProvider findConditionProvider(ComponentName component) { |
| if (component == null) return null; |
| for (ManagedServiceInfo service : getServices()) { |
| if (component.equals(service.component)) { |
| return provider(service); |
| } |
| } |
| return null; |
| } |
| |
| public Condition findCondition(ComponentName component, Uri conditionId) { |
| if (component == null || conditionId == null) return null; |
| synchronized (mMutex) { |
| final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/); |
| return r != null ? r.condition : null; |
| } |
| } |
| |
| public void ensureRecordExists(ComponentName component, Uri conditionId, |
| IConditionProvider provider) { |
| // constructed by convention, make sure the record exists... |
| final ConditionRecord r = getRecordLocked(conditionId, component, true /*create*/); |
| if (r.info == null) { |
| // ... and is associated with the in-process service |
| r.info = checkServiceTokenLocked(provider); |
| } |
| } |
| |
| public boolean subscribeIfNecessary(ComponentName component, Uri conditionId) { |
| synchronized (mMutex) { |
| final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/); |
| if (r == null) { |
| Slog.w(TAG, "Unable to subscribe to " + component + " " + conditionId); |
| return false; |
| } |
| if (r.subscribed) return true; |
| subscribeLocked(r); |
| return r.subscribed; |
| } |
| } |
| |
| public void unsubscribeIfNecessary(ComponentName component, Uri conditionId) { |
| synchronized (mMutex) { |
| final ConditionRecord r = getRecordLocked(conditionId, component, false /*create*/); |
| if (r == null) { |
| Slog.w(TAG, "Unable to unsubscribe to " + component + " " + conditionId); |
| return; |
| } |
| if (!r.subscribed) return; |
| unsubscribeLocked(r);; |
| } |
| } |
| |
| private void subscribeLocked(ConditionRecord r) { |
| if (DEBUG) Slog.d(TAG, "subscribeLocked " + r); |
| final IConditionProvider provider = provider(r); |
| RemoteException re = null; |
| if (provider != null) { |
| try { |
| Slog.d(TAG, "Subscribing to " + r.id + " with " + r.component); |
| provider.onSubscribe(r.id); |
| r.subscribed = true; |
| } catch (RemoteException e) { |
| Slog.w(TAG, "Error subscribing to " + r, e); |
| re = e; |
| } |
| } |
| ZenLog.traceSubscribe(r != null ? r.id : null, provider, re); |
| } |
| |
| @SafeVarargs |
| private static <T> ArraySet<T> safeSet(T... items) { |
| final ArraySet<T> rt = new ArraySet<T>(); |
| if (items == null || items.length == 0) return rt; |
| final int N = items.length; |
| for (int i = 0; i < N; i++) { |
| final T item = items[i]; |
| if (item != null) { |
| rt.add(item); |
| } |
| } |
| return rt; |
| } |
| |
| private void unsubscribeLocked(ConditionRecord r) { |
| if (DEBUG) Slog.d(TAG, "unsubscribeLocked " + r); |
| final IConditionProvider provider = provider(r); |
| RemoteException re = null; |
| if (provider != null) { |
| try { |
| provider.onUnsubscribe(r.id); |
| } catch (RemoteException e) { |
| Slog.w(TAG, "Error unsubscribing to " + r, e); |
| re = e; |
| } |
| r.subscribed = false; |
| } |
| ZenLog.traceUnsubscribe(r != null ? r.id : null, provider, re); |
| } |
| |
| private static IConditionProvider provider(ConditionRecord r) { |
| return r == null ? null : provider(r.info); |
| } |
| |
| private static IConditionProvider provider(ManagedServiceInfo info) { |
| return info == null ? null : (IConditionProvider) info.service; |
| } |
| |
| private static class ConditionRecord { |
| public final Uri id; |
| public final ComponentName component; |
| public Condition condition; |
| public ManagedServiceInfo info; |
| public boolean subscribed; |
| |
| private ConditionRecord(Uri id, ComponentName component) { |
| this.id = id; |
| this.component = component; |
| } |
| |
| @Override |
| public String toString() { |
| final StringBuilder sb = new StringBuilder("ConditionRecord[id=") |
| .append(id).append(",component=").append(component) |
| .append(",subscribed=").append(subscribed); |
| return sb.append(']').toString(); |
| } |
| } |
| |
| public interface Callback { |
| void onBootComplete(); |
| void onServiceAdded(ComponentName component); |
| void onConditionChanged(Uri id, Condition condition); |
| void onUserSwitched(); |
| } |
| |
| } |