/**
   * 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;
    }
  }
  public static void sendMailImpl(Context context, long accountId) {
    /// M: We Can't send mail in low storage state @{
    if (StorageLowState.checkIfStorageLow(context)) {
      LogUtils.e(Logging.LOG_TAG, "Can't send mail due to low storage");
      return;
    }
    /// @}

    /** M: Get the sendable mails count of the account and notify this sending @{ */
    final int count = getSendableMessageCount(context, accountId);
    LogUtils.logFeature(LogTag.SENDMAIL_TAG, "sendable message count [%d]", count);
    if (count <= 0) {
      return;
    }
    SendNotificationProxy.getInstance(context)
        .showSendingNotification(accountId, NotificationController.SEND_MAIL, count);
    /** @} */
    final Account account = Account.restoreAccountWithId(context, accountId);
    TrafficStats.setThreadStatsTag(TrafficFlags.getSmtpFlags(context, account));
    final NotificationController nc = NotificationController.getInstance(context);
    // 1.  Loop through all messages in the account's outbox
    final long outboxId = Mailbox.findMailboxOfType(context, account.mId, Mailbox.TYPE_OUTBOX);
    if (outboxId == Mailbox.NO_MAILBOX) {
      return;
    }
    final ContentResolver resolver = context.getContentResolver();
    final Cursor c =
        resolver.query(
            EmailContent.Message.CONTENT_URI,
            EmailContent.Message.ID_COLUMN_PROJECTION,
            EmailContent.Message.MAILBOX_KEY + "=?",
            new String[] {Long.toString(outboxId)},
            null);
    try {
      // 2.  exit early
      if (c.getCount() <= 0) {
        return;
      }
      final Sender sender = Sender.getInstance(context, account);
      final Store remoteStore = Store.getInstance(account, context);
      final ContentValues moveToSentValues;
      if (remoteStore.requireCopyMessageToSentFolder()) {
        Mailbox sentFolder = Mailbox.restoreMailboxOfType(context, accountId, Mailbox.TYPE_SENT);
        moveToSentValues = new ContentValues();
        moveToSentValues.put(MessageColumns.MAILBOX_KEY, sentFolder.mId);
      } else {
        moveToSentValues = null;
      }

      // 3.  loop through the available messages and send them
      /** M: mark should we cancel the Login Failed Notification. */
      boolean shouldCancelNf = false;
      while (c.moveToNext()) {
        long messageId = -1;
        if (moveToSentValues != null) {
          moveToSentValues.remove(EmailContent.MessageColumns.FLAGS);
        }
        try {
          messageId = c.getLong(0);
          // Don't send messages with unloaded attachments
          if (Utility.hasUnloadedAttachments(context, messageId)) {
            LogUtils.logFeature(
                LogTag.SENDMAIL_TAG, "Can't send #" + messageId + "; unloaded attachments");
            continue;
          }
          sender.sendMessage(messageId);
        } catch (MessagingException me) {
          LogUtils.logFeature(
              LogTag.SENDMAIL_TAG,
              "<<< Smtp send message failed id [%s], exception: %s",
              messageId,
              me);
          // report error for this message, but keep trying others
          if (me instanceof AuthenticationFailedException) {
            shouldCancelNf = false;
            nc.showLoginFailedNotification(account.mId);
          }
          /// M: One mail sent failed
          SendNotificationProxy.getInstance(context)
              .showSendingNotification(account.mId, NotificationController.SEND_FAILED, 1);
          continue;
        }

        /// M: One mail sent complete
        SendNotificationProxy.getInstance(context)
            .showSendingNotification(account.mId, NotificationController.SEND_COMPLETE, 1);

        // 4. move to sent, or delete
        final Uri syncedUri =
            ContentUris.withAppendedId(EmailContent.Message.SYNCED_CONTENT_URI, messageId);
        // Delete all cached files
        AttachmentUtilities.deleteAllCachedAttachmentFiles(context, account.mId, messageId);
        if (moveToSentValues != null) {
          // If this is a forwarded message and it has attachments, delete them, as they
          // duplicate information found elsewhere (on the server).  This saves storage.
          final EmailContent.Message msg =
              EmailContent.Message.restoreMessageWithId(context, messageId);
          if ((msg.mFlags & EmailContent.Message.FLAG_TYPE_FORWARD) != 0) {
            AttachmentUtilities.deleteAllAttachmentFiles(context, account.mId, messageId);
          }
          /// M: un-mark sending status after sending
          final int flags =
              msg.mFlags
                  & ~(EmailContent.Message.FLAG_TYPE_REPLY
                      | EmailContent.Message.FLAG_TYPE_FORWARD
                      | EmailContent.Message.FLAG_TYPE_REPLY_ALL
                      | EmailContent.Message.FLAG_TYPE_ORIGINAL
                      | EmailContent.Message.FLAG_STATUS_SENDING);

          moveToSentValues.put(EmailContent.MessageColumns.FLAGS, flags);
          resolver.update(syncedUri, moveToSentValues, null, null);
        } else {
          AttachmentUtilities.deleteAllAttachmentFiles(context, account.mId, messageId);
          final Uri uri = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, messageId);
          resolver.delete(uri, null, null);
          resolver.delete(syncedUri, null, null);
        }
        shouldCancelNf = true;
      }
      if (shouldCancelNf) {
        nc.cancelLoginFailedNotification(account.mId);
      }
    } catch (MessagingException me) {
      if (me instanceof AuthenticationFailedException) {
        nc.showLoginFailedNotification(account.mId);
      }
      /// M: All mails failed to be sent, caused by fail to get instance of store
      SendNotificationProxy.getInstance(context)
          .showSendingNotification(account.mId, NotificationController.SEND_FAILED, c.getCount());
    } finally {
      c.close();
    }
  }
  /**
   * 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);
  }