/**
   * Handle password expiration - if any accounts appear to have triggered this, put up warnings, or
   * even shut them down.
   *
   * <p>NOTE: If there are multiple accounts with password expiration policies, the device password
   * will be set to expire in the shortest required interval (most secure). The logic in this method
   * operates based on the aggregate setting - irrespective of which account caused the expiration.
   * In other words, all accounts (that require expiration) will run/stop based on the requirements
   * of the account with the shortest interval.
   */
  private void onPasswordExpiring(Context context) {
    // 1.  Do we have any accounts that matter here?
    long nextExpiringAccountId = findShortestExpiration(context);

    // 2.  If not, exit immediately
    if (nextExpiringAccountId == -1) {
      return;
    }

    // 3.  If yes, are we warning or expired?
    long expirationDate = getDPM().getPasswordExpiration(mAdminName);
    long timeUntilExpiration = expirationDate - System.currentTimeMillis();
    boolean expired = timeUntilExpiration < 0;
    final NotificationController nc = NotificationControllerCreatorHolder.getInstance(context);
    if (!expired) {
      // 4.  If warning, simply put up a generic notification and report that it came from
      // the shortest-expiring account.
      nc.showPasswordExpiringNotificationSynchronous(nextExpiringAccountId);
    } else {
      // 5.  Actually expired - find all accounts that expire passwords, and wipe them
      boolean wiped = wipeExpiredAccounts(context);
      if (wiped) {
        nc.showPasswordExpiredNotificationSynchronous(nextExpiringAccountId);
      }
    }
  }
 /** Convenience method; see javadoc below */
 public static void setAccountHoldFlag(Context context, long accountId, boolean newState) {
   Account account = Account.restoreAccountWithId(context, accountId);
   if (account != null) {
     setAccountHoldFlag(context, account, newState);
     if (newState) {
       // Make sure there's a notification up
       final NotificationController nc = NotificationControllerCreatorHolder.getInstance(context);
       nc.showSecurityNeededNotification(account);
     }
   }
 }
  /**
   * API: Sync service should call this any time a sync fails due to isActive() returning false.
   * This will kick off the notify-acquire-admin-state process and/or increase the security level.
   * The caller needs to write the required policies into this account before making this call.
   * Should not be called from UI thread - uses DB lookups to prepare new notifications
   *
   * @param accountId the account for which sync cannot proceed
   */
  public void policiesRequired(long accountId) {
    Account account = Account.restoreAccountWithId(mContext, accountId);
    // In case the account has been deleted, just return
    if (account == null) return;
    if (account.mPolicyKey == 0) return;
    Policy policy = Policy.restorePolicyWithId(mContext, account.mPolicyKey);
    if (policy == null) return;
    if (DebugUtils.DEBUG) {
      LogUtils.d(TAG, "policiesRequired for " + account.mDisplayName + ": " + policy);
    }

    // Mark the account as "on hold".
    setAccountHoldFlag(mContext, account, true);

    // Put up an appropriate notification
    final NotificationController nc = NotificationControllerCreatorHolder.getInstance(mContext);
    if (policy.mProtocolPoliciesUnsupported == null) {
      nc.showSecurityNeededNotification(account);
    } else {
      nc.showSecurityUnsupportedNotification(account);
    }
  }
  /**
   * Callback from EmailBroadcastProcessorService. This provides the workers for the
   * DeviceAdminReceiver calls. These should perform the work directly and not use async threads for
   * completion.
   */
  public static void onDeviceAdminReceiverMessage(Context context, int message) {
    SecurityPolicy instance = SecurityPolicy.getInstance(context);
    switch (message) {
      case DEVICE_ADMIN_MESSAGE_ENABLED:
        instance.onAdminEnabled(true);
        break;
      case DEVICE_ADMIN_MESSAGE_DISABLED:
        instance.onAdminEnabled(false);
        break;
      case DEVICE_ADMIN_MESSAGE_PASSWORD_CHANGED:
        // TODO make a small helper for this
        // Clear security holds (if any)
        Account.clearSecurityHoldOnAllAccounts(context);
        // Cancel any active notifications (if any are posted)
        final NotificationController nc = NotificationControllerCreatorHolder.getInstance(context);

        nc.cancelPasswordExpirationNotifications();
        break;
      case DEVICE_ADMIN_MESSAGE_PASSWORD_EXPIRING:
        instance.onPasswordExpiring(instance.mContext);
        break;
    }
  }
  /**
   * Called from the notification's intent receiver to register that the notification can be cleared
   * now.
   */
  public void clearNotification() {
    final NotificationController nc = NotificationControllerCreatorHolder.getInstance(mContext);

    nc.cancelSecurityNeededNotification();
  }
  public void setAccountPolicy(long accountId, Policy policy, String securityKey, boolean notify) {
    Account account = Account.restoreAccountWithId(mContext, accountId);
    // In case the account has been deleted, just return
    if (account == null) {
      return;
    }
    Policy oldPolicy = null;
    if (account.mPolicyKey > 0) {
      oldPolicy = Policy.restorePolicyWithId(mContext, account.mPolicyKey);
    }

    // If attachment policies have changed, fix up any affected attachment records
    if (oldPolicy != null && securityKey != null) {
      if ((oldPolicy.mDontAllowAttachments != policy.mDontAllowAttachments)
          || (oldPolicy.mMaxAttachmentSize != policy.mMaxAttachmentSize)) {
        Policy.setAttachmentFlagsForNewPolicy(mContext, account, policy);
      }
    }

    boolean policyChanged = (oldPolicy == null) || !oldPolicy.equals(policy);
    if (!policyChanged
        && (TextUtilities.stringOrNullEquals(securityKey, account.mSecuritySyncKey))) {
      LogUtils.d(Logging.LOG_TAG, "setAccountPolicy; policy unchanged");
    } else {
      setAccountPolicy(mContext, account, policy, securityKey);
      policiesUpdated();
    }

    boolean setHold = false;
    final NotificationController nc = NotificationControllerCreatorHolder.getInstance(mContext);
    if (policy.mProtocolPoliciesUnsupported != null) {
      // We can't support this, reasons in unsupportedRemotePolicies
      LogUtils.d(
          Logging.LOG_TAG, "Notify policies for " + account.mDisplayName + " not supported.");
      setHold = true;
      if (notify) {
        nc.showSecurityUnsupportedNotification(account);
      }
      // Erase data
      Uri uri = EmailProvider.uiUri("uiaccountdata", accountId);
      mContext.getContentResolver().delete(uri, null, null);
    } else if (isActive(policy)) {
      if (policyChanged) {
        LogUtils.d(Logging.LOG_TAG, "Notify policies for " + account.mDisplayName + " changed.");
        if (notify) {
          // Notify that policies changed
          nc.showSecurityChangedNotification(account);
        }
      } else {
        LogUtils.d(Logging.LOG_TAG, "Policy is active and unchanged; do not notify.");
      }
    } else {
      setHold = true;
      LogUtils.d(
          Logging.LOG_TAG,
          "Notify policies for " + account.mDisplayName + " are not being enforced.");
      if (notify) {
        // Put up a notification
        nc.showSecurityNeededNotification(account);
      }
    }
    // Set/clear the account hold.
    setAccountHoldFlag(mContext, account, setHold);
  }