content: /**
   * Copyright (c) 2015, 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.content.ComponentName;
  import android.net.Uri;
  import android.service.notification.Condition;
  import android.service.notification.IConditionProvider;
  import android.service.notification.ZenModeConfig;
  import android.service.notification.ZenModeConfig.ZenRule;
  import android.util.ArrayMap;
  import android.util.ArraySet;
  import android.util.Log;
  
  import com.android.internal.annotations.VisibleForTesting;
  
  import java.io.PrintWriter;
  
  /**
   * Helper class for managing active rules from
   * {@link android.service.notification.ConditionProviderService CPSes}.
   */
  public class ZenModeConditions implements ConditionProviders.Callback {
      private static final String TAG = ZenModeHelper.TAG;
      private static final boolean DEBUG = ZenModeHelper.DEBUG;
  
      private final ZenModeHelper mHelper;
      private final ConditionProviders mConditionProviders;
  
      @VisibleForTesting
      protected final ArrayMap<Uri, ComponentName> mSubscriptions = new ArrayMap<>();
  
      public ZenModeConditions(ZenModeHelper helper, ConditionProviders conditionProviders) {
          mHelper = helper;
          mConditionProviders = conditionProviders;
          if (mConditionProviders.isSystemProviderEnabled(ZenModeConfig.COUNTDOWN_PATH)) {
              mConditionProviders.addSystemProvider(new CountdownConditionProvider());
          }
          if (mConditionProviders.isSystemProviderEnabled(ZenModeConfig.SCHEDULE_PATH)) {
              mConditionProviders.addSystemProvider(new ScheduleConditionProvider());
          }
          if (mConditionProviders.isSystemProviderEnabled(ZenModeConfig.EVENT_PATH)) {
              mConditionProviders.addSystemProvider(new EventConditionProvider());
          }
          mConditionProviders.setCallback(this);
      }
  
      public void dump(PrintWriter pw, String prefix) {
          pw.print(prefix); pw.print("mSubscriptions="); pw.println(mSubscriptions);
      }
  
      public void evaluateConfig(ZenModeConfig config, ComponentName trigger,
              boolean processSubscriptions) {
          if (config == null) return;
          if (config.manualRule != null && config.manualRule.condition != null
                  && !config.manualRule.isTrueOrUnknown()) {
              if (DEBUG) Log.d(TAG, "evaluateConfig: clearing manual rule");
              config.manualRule = null;
          }
          final ArraySet<Uri> current = new ArraySet<>();
          evaluateRule(config.manualRule, current, null, processSubscriptions);
          for (ZenRule automaticRule : config.automaticRules.values()) {
              if (automaticRule.component != null) {
                  evaluateRule(automaticRule, current, trigger, processSubscriptions);
                  updateSnoozing(automaticRule);
              }
          }
  
          synchronized (mSubscriptions) {
              final int N = mSubscriptions.size();
              for (int i = N - 1; i >= 0; i--) {
                  final Uri id = mSubscriptions.keyAt(i);
                  final ComponentName component = mSubscriptions.valueAt(i);
                  if (processSubscriptions) {
                      if (!current.contains(id)) {
                          mConditionProviders.unsubscribeIfNecessary(component, id);
                          mSubscriptions.removeAt(i);
                      }
                  }
              }
          }
      }
  
      @Override
      public void onBootComplete() {
          // noop
      }
  
      @Override
      public void onUserSwitched() {
          // noop
      }
  
      @Override
      public void onServiceAdded(ComponentName component) {
          if (DEBUG) Log.d(TAG, "onServiceAdded " + component);
          mHelper.setConfig(mHelper.getConfig(), component, "zmc.onServiceAdded");
      }
  
      @Override
      public void onConditionChanged(Uri id, Condition condition) {
          if (DEBUG) Log.d(TAG, "onConditionChanged " + id + " " + condition);
          ZenModeConfig config = mHelper.getConfig();
          if (config == null) return;
          mHelper.setAutomaticZenRuleState(id, condition);
      }
  
      // Only valid for CPS backed rules
      private void evaluateRule(ZenRule rule, ArraySet<Uri> current, ComponentName trigger,
              boolean processSubscriptions) {
          if (rule == null || rule.conditionId == null) return;
          if (rule.configurationActivity != null) return;
          final Uri id = rule.conditionId;
          boolean isSystemCondition = false;
          for (SystemConditionProviderService sp : mConditionProviders.getSystemProviders()) {
              if (sp.isValidConditionId(id)) {
                  mConditionProviders.ensureRecordExists(sp.getComponent(), id, sp.asInterface());
                  rule.component = sp.getComponent();
                  isSystemCondition = true;
              }
          }
          // ensure that we have a record of the rule if it's backed by an currently alive CPS
          if (!isSystemCondition) {
              final IConditionProvider cp = mConditionProviders.findConditionProvider(rule.component);
              if (DEBUG) Log.d(TAG, "Ensure external rule exists: " + (cp != null) + " for " + id);
              if (cp != null) {
                  mConditionProviders.ensureRecordExists(rule.component, id, cp);
              }
          }
          // empty rule? disable and bail early
          if (rule.component == null && rule.enabler == null) {
              Log.w(TAG, "No component found for automatic rule: " + rule.conditionId);
              rule.enabled = false;
              return;
          }
          if (current != null) {
              current.add(id);
          }
  
          // If the rule is bound by a CPS and the CPS is alive, tell them about the rule
          if (processSubscriptions && ((trigger != null && trigger.equals(rule.component))
                  || isSystemCondition)) {
              if (DEBUG) Log.d(TAG, "Subscribing to " + rule.component);
              if (mConditionProviders.subscribeIfNecessary(rule.component, rule.conditionId)) {
                  synchronized (mSubscriptions) {
                      mSubscriptions.put(rule.conditionId, rule.component);
                  }
              } else {
                  rule.condition = null;
                  if (DEBUG) Log.d(TAG, "zmc failed to subscribe");
              }
          }
          // backfill the rule state from CPS backed components if it's missing
          if (rule.component != null && rule.condition == null) {
              rule.condition = mConditionProviders.findCondition(rule.component, rule.conditionId);
              if (rule.condition != null && DEBUG) Log.d(TAG, "Found existing condition for: "
                      + rule.conditionId);
          }
      }
  
      private boolean updateSnoozing(ZenRule rule) {
          if (rule != null && rule.snoozing && !rule.isTrueOrUnknown()) {
              rule.snoozing = false;
              if (DEBUG) Log.d(TAG, "Snoozing reset for " + rule.conditionId);
              return true;
          }
          return false;
      }
  }
  