/**
   * Set the requested security level based on the aggregate set of requests. If the set is empty,
   * we release our device administration. If the set is non-empty, we only proceed if we are
   * already active as an admin.
   */
  public void setActivePolicies() {
    DevicePolicyManager dpm = getDPM();
    // compute aggregate set of policies
    Policy aggregatePolicy = getAggregatePolicy();
    // if empty set, detach from policy manager
    if (aggregatePolicy == Policy.NO_POLICY) {
      if (DebugUtils.DEBUG) {
        LogUtils.d(TAG, "setActivePolicies: none, remove admin");
      }
      dpm.removeActiveAdmin(mAdminName);
    } else if (isActiveAdmin()) {
      if (DebugUtils.DEBUG) {
        LogUtils.d(TAG, "setActivePolicies: " + aggregatePolicy);
      }
      // set each policy in the policy manager
      // password mode & length
      dpm.setPasswordQuality(mAdminName, aggregatePolicy.getDPManagerPasswordQuality());
      dpm.setPasswordMinimumLength(mAdminName, aggregatePolicy.mPasswordMinLength);
      // screen lock time
      dpm.setMaximumTimeToLock(mAdminName, aggregatePolicy.mMaxScreenLockTime * 1000);
      // local wipe (failed passwords limit)
      dpm.setMaximumFailedPasswordsForWipe(mAdminName, aggregatePolicy.mPasswordMaxFails);
      // password expiration (days until a password expires).  API takes mSec.
      dpm.setPasswordExpirationTimeout(
          mAdminName, aggregatePolicy.getDPManagerPasswordExpirationTimeout());
      // password history length (number of previous passwords that may not be reused)
      dpm.setPasswordHistoryLength(mAdminName, aggregatePolicy.mPasswordHistory);
      // password minimum complex characters.
      // Note, in Exchange, "complex chars" simply means "non alpha", but in the DPM,
      // setting the quality to complex also defaults min symbols=1 and min numeric=1.
      // We always / safely clear minSymbols & minNumeric to zero (there is no policy
      // configuration in which we explicitly require a minimum number of digits or symbols.)
      dpm.setPasswordMinimumSymbols(mAdminName, 0);
      dpm.setPasswordMinimumNumeric(mAdminName, 0);
      dpm.setPasswordMinimumNonLetter(mAdminName, aggregatePolicy.mPasswordComplexChars);
      // Device capabilities
      try {
        // If we are running in a managed policy, it is a securityException to even
        // call setCameraDisabled(), if is disabled is false. We have to swallow
        // the exception here.
        dpm.setCameraDisabled(mAdminName, aggregatePolicy.mDontAllowCamera);
      } catch (SecurityException e) {
        LogUtils.d(TAG, "SecurityException in setCameraDisabled, nothing changed");
      }

      // encryption required
      dpm.setStorageEncryption(mAdminName, aggregatePolicy.mRequireEncryption);
    }
  }
 /**
  * For all accounts that require password expiration, put them in security hold and wipe their
  * data.
  *
  * @param context context
  * @return true if one or more accounts were wiped
  */
 @VisibleForTesting
 /*package*/ static boolean wipeExpiredAccounts(Context context) {
   boolean result = false;
   Cursor c =
       context
           .getContentResolver()
           .query(Policy.CONTENT_URI, Policy.ID_PROJECTION, HAS_PASSWORD_EXPIRATION, null, null);
   if (c == null) {
     return false;
   }
   try {
     while (c.moveToNext()) {
       long policyId = c.getLong(Policy.ID_PROJECTION_COLUMN);
       long accountId = Policy.getAccountIdWithPolicyKey(context, policyId);
       if (accountId < 0) continue;
       Account account = Account.restoreAccountWithId(context, accountId);
       if (account != null) {
         // Mark the account as "on hold".
         setAccountHoldFlag(context, account, true);
         // Erase data
         Uri uri = EmailProvider.uiUri("uiaccountdata", accountId);
         context.getContentResolver().delete(uri, null, null);
         // Report one or more were found
         result = true;
       }
     }
   } finally {
     c.close();
   }
   return result;
 }
  /**
   * Set the policy for an account atomically; this also removes any other policy associated with
   * the account and sets the policy key for the account. If policy is null, the policyKey is set to
   * 0 and the securitySyncKey to null. Also, update the account object to reflect the current
   * policyKey and securitySyncKey
   *
   * @param context the caller's context
   * @param account the account whose policy is to be set
   * @param policy the policy to set, or null if we're clearing the policy
   * @param securitySyncKey the security sync key for this account (ignored if policy is null)
   */
  public static void setAccountPolicy(
      Context context, Account account, Policy policy, String securitySyncKey) {
    ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();

    // Make sure this is a valid policy set
    if (policy != null) {
      policy.normalize();
      // Add the new policy (no account will yet reference this)
      ops.add(
          ContentProviderOperation.newInsert(Policy.CONTENT_URI)
              .withValues(policy.toContentValues())
              .build());
      // Make the policyKey of the account our newly created policy, and set the sync key
      ops.add(
          ContentProviderOperation.newUpdate(
                  ContentUris.withAppendedId(Account.CONTENT_URI, account.mId))
              .withValueBackReference(AccountColumns.POLICY_KEY, 0)
              .withValue(AccountColumns.SECURITY_SYNC_KEY, securitySyncKey)
              .build());
    } else {
      ops.add(
          ContentProviderOperation.newUpdate(
                  ContentUris.withAppendedId(Account.CONTENT_URI, account.mId))
              .withValue(AccountColumns.SECURITY_SYNC_KEY, null)
              .withValue(AccountColumns.POLICY_KEY, 0)
              .build());
    }

    // Delete the previous policy associated with this account, if any
    if (account.mPolicyKey > 0) {
      ops.add(
          ContentProviderOperation.newDelete(
                  ContentUris.withAppendedId(Policy.CONTENT_URI, account.mPolicyKey))
              .build());
    }

    try {
      context.getContentResolver().applyBatch(EmailContent.AUTHORITY, ops);
      account.refresh(context);
      syncAccount(context, account);
    } catch (RemoteException e) {
      // This is fatal to a remote process
      throw new IllegalStateException("Exception setting account policy.");
    } catch (OperationApplicationException e) {
      // Can't happen; our provider doesn't throw this exception
    }
  }
 public EmailSyncParser(
     final Parser parser,
     final Context context,
     final ContentResolver resolver,
     final Mailbox mailbox,
     final Account account)
     throws IOException {
   super(parser, context, resolver, mailbox, account);
   mMailboxIdAsString = Long.toString(mMailbox.mId);
   if (mAccount.mPolicyKey != 0) {
     mPolicy = Policy.restorePolicyWithId(mContext, mAccount.mPolicyKey);
   } else {
     mPolicy = null;
   }
 }
 /**
  * Find the account with the shortest expiration time. This is always assumed to be the account
  * that forces the password to be refreshed.
  *
  * @return -1 if no expirations, or accountId if one is found
  */
 @VisibleForTesting
 /*package*/ static long findShortestExpiration(Context context) {
   long policyId =
       Utility.getFirstRowLong(
           context,
           Policy.CONTENT_URI,
           Policy.ID_PROJECTION,
           HAS_PASSWORD_EXPIRATION,
           null,
           PolicyColumns.PASSWORD_EXPIRATION_DAYS + " ASC",
           EmailContent.ID_PROJECTION_COLUMN,
           -1L);
   if (policyId < 0) return -1L;
   return Policy.getAccountIdWithPolicyKey(context, policyId);
 }
  /**
   * 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);
    }
  }
  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);
  }
  /**
   * API: Query used to determine if a given policy is "active" (the device is operating at the
   * required security level).
   *
   * <p>This can be used when syncing a specific account, by passing a specific set of policies for
   * that account. Or, it can be used at any time to compare the device state against the aggregate
   * set of device policies stored in all accounts.
   *
   * <p>This method is for queries only, and does not trigger any change in device state.
   *
   * <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). This method will return
   * 'false' as soon as the password expires - 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.
   *
   * @param policy the policies requested, or null to check aggregate stored policies
   * @return zero if the requested policies are active, non-zero bits indicates that more work is
   *     needed (typically, by the user) before the required security polices are fully active.
   */
  public int getInactiveReasons(Policy policy) {
    // select aggregate set if needed
    if (policy == null) {
      policy = getAggregatePolicy();
    }
    // quick check for the "empty set" of no policies
    if (policy == Policy.NO_POLICY) {
      return 0;
    }
    int reasons = 0;
    DevicePolicyManager dpm = getDPM();
    if (isActiveAdmin()) {
      // check each policy explicitly
      if (policy.mPasswordMinLength > 0) {
        if (dpm.getPasswordMinimumLength(mAdminName) < policy.mPasswordMinLength) {
          reasons |= INACTIVE_NEED_PASSWORD;
        }
      }
      if (policy.mPasswordMode > 0) {
        if (dpm.getPasswordQuality(mAdminName) < policy.getDPManagerPasswordQuality()) {
          reasons |= INACTIVE_NEED_PASSWORD;
        }
        if (!dpm.isActivePasswordSufficient()) {
          reasons |= INACTIVE_NEED_PASSWORD;
        }
      }
      if (policy.mMaxScreenLockTime > 0) {
        // Note, we use seconds, dpm uses milliseconds
        if (dpm.getMaximumTimeToLock(mAdminName) > policy.mMaxScreenLockTime * 1000) {
          reasons |= INACTIVE_NEED_CONFIGURATION;
        }
      }
      if (policy.mPasswordExpirationDays > 0) {
        // confirm that expirations are currently set
        long currentTimeout = dpm.getPasswordExpirationTimeout(mAdminName);
        if (currentTimeout == 0
            || currentTimeout > policy.getDPManagerPasswordExpirationTimeout()) {
          reasons |= INACTIVE_NEED_PASSWORD;
        }
        // confirm that the current password hasn't expired
        long expirationDate = dpm.getPasswordExpiration(mAdminName);
        long timeUntilExpiration = expirationDate - System.currentTimeMillis();
        boolean expired = timeUntilExpiration < 0;
        if (expired) {
          reasons |= INACTIVE_NEED_PASSWORD;
        }
      }
      if (policy.mPasswordHistory > 0) {
        if (dpm.getPasswordHistoryLength(mAdminName) < policy.mPasswordHistory) {
          // There's no user action for changes here; this is just a configuration change
          reasons |= INACTIVE_NEED_CONFIGURATION;
        }
      }
      if (policy.mPasswordComplexChars > 0) {
        if (dpm.getPasswordMinimumNonLetter(mAdminName) < policy.mPasswordComplexChars) {
          reasons |= INACTIVE_NEED_PASSWORD;
        }
      }
      if (policy.mRequireEncryption) {
        int encryptionStatus = getDPM().getStorageEncryptionStatus();
        if (encryptionStatus != DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE) {
          reasons |= INACTIVE_NEED_ENCRYPTION;
        }
      }
      if (policy.mDontAllowCamera && !dpm.getCameraDisabled(mAdminName)) {
        reasons |= INACTIVE_NEED_CONFIGURATION;
      }
      // password failures are counted locally - no test required here
      // no check required for remote wipe (it's supported, if we're the admin)

      if (policy.mProtocolPoliciesUnsupported != null) {
        reasons |= INACTIVE_PROTOCOL_POLICIES;
      }

      // If we made it all the way, reasons == 0 here.  Otherwise it's a list of grievances.
      return reasons;
    }
    // return false, not active
    return INACTIVE_NEED_ACTIVATION;
  }
  /**
   * Compute the aggregate policy for all accounts that require it, and record it.
   *
   * <p>The business logic is as follows: min password length take the max password mode take the
   * max (strongest mode) max password fails take the min max screen lock time take the min require
   * remote wipe take the max (logical or) password history take the max (strongest mode) password
   * expiration take the min (strongest mode) password complex chars take the max (strongest mode)
   * encryption take the max (logical or)
   *
   * @return a policy representing the strongest aggregate. If no policy sets are defined, a
   *     lightweight "nothing required" policy will be returned. Never null.
   */
  @VisibleForTesting
  Policy computeAggregatePolicy() {
    boolean policiesFound = false;
    Policy aggregate = new Policy();
    aggregate.mPasswordMinLength = Integer.MIN_VALUE;
    aggregate.mPasswordMode = Integer.MIN_VALUE;
    aggregate.mPasswordMaxFails = Integer.MAX_VALUE;
    aggregate.mPasswordHistory = Integer.MIN_VALUE;
    aggregate.mPasswordExpirationDays = Integer.MAX_VALUE;
    aggregate.mPasswordComplexChars = Integer.MIN_VALUE;
    aggregate.mMaxScreenLockTime = Integer.MAX_VALUE;
    aggregate.mRequireRemoteWipe = false;
    aggregate.mRequireEncryption = false;

    // This can never be supported at this time. It exists only for historic reasons where
    // this was able to be supported prior to the introduction of proper removable storage
    // support for external storage.
    aggregate.mRequireEncryptionExternal = false;

    Cursor c =
        mContext
            .getContentResolver()
            .query(Policy.CONTENT_URI, Policy.CONTENT_PROJECTION, null, null, null);
    Policy policy = new Policy();
    try {
      while (c.moveToNext()) {
        policy.restore(c);
        if (DebugUtils.DEBUG) {
          LogUtils.d(TAG, "Aggregate from: " + policy);
        }
        aggregate.mPasswordMinLength =
            Math.max(policy.mPasswordMinLength, aggregate.mPasswordMinLength);
        aggregate.mPasswordMode = Math.max(policy.mPasswordMode, aggregate.mPasswordMode);
        if (policy.mPasswordMaxFails > 0) {
          aggregate.mPasswordMaxFails =
              Math.min(policy.mPasswordMaxFails, aggregate.mPasswordMaxFails);
        }
        if (policy.mMaxScreenLockTime > 0) {
          aggregate.mMaxScreenLockTime =
              Math.min(policy.mMaxScreenLockTime, aggregate.mMaxScreenLockTime);
        }
        if (policy.mPasswordHistory > 0) {
          aggregate.mPasswordHistory =
              Math.max(policy.mPasswordHistory, aggregate.mPasswordHistory);
        }
        if (policy.mPasswordExpirationDays > 0) {
          aggregate.mPasswordExpirationDays =
              Math.min(policy.mPasswordExpirationDays, aggregate.mPasswordExpirationDays);
        }
        if (policy.mPasswordComplexChars > 0) {
          aggregate.mPasswordComplexChars =
              Math.max(policy.mPasswordComplexChars, aggregate.mPasswordComplexChars);
        }
        aggregate.mRequireRemoteWipe |= policy.mRequireRemoteWipe;
        aggregate.mRequireEncryption |= policy.mRequireEncryption;
        aggregate.mDontAllowCamera |= policy.mDontAllowCamera;
        policiesFound = true;
      }
    } finally {
      c.close();
    }
    if (policiesFound) {
      // final cleanup pass converts any untouched min/max values to zero (not specified)
      if (aggregate.mPasswordMinLength == Integer.MIN_VALUE) aggregate.mPasswordMinLength = 0;
      if (aggregate.mPasswordMode == Integer.MIN_VALUE) aggregate.mPasswordMode = 0;
      if (aggregate.mPasswordMaxFails == Integer.MAX_VALUE) aggregate.mPasswordMaxFails = 0;
      if (aggregate.mMaxScreenLockTime == Integer.MAX_VALUE) aggregate.mMaxScreenLockTime = 0;
      if (aggregate.mPasswordHistory == Integer.MIN_VALUE) aggregate.mPasswordHistory = 0;
      if (aggregate.mPasswordExpirationDays == Integer.MAX_VALUE)
        aggregate.mPasswordExpirationDays = 0;
      if (aggregate.mPasswordComplexChars == Integer.MIN_VALUE) aggregate.mPasswordComplexChars = 0;
      if (DebugUtils.DEBUG) {
        LogUtils.d(TAG, "Calculated Aggregate: " + aggregate);
      }
      return aggregate;
    }
    if (DebugUtils.DEBUG) {
      LogUtils.d(TAG, "Calculated Aggregate: no policy");
    }
    return Policy.NO_POLICY;
  }