/**
  * If values are passed in, replaces any cached cursor with one containing new values, and then
  * closes the previously cached one (if any, and if not in use) If values are not passed in,
  * removes the row from cache If the row was locked, unlock it
  *
  * @param id the id of the row
  * @param values new ContentValues for the row (or null if row should simply be removed)
  * @param wasLocked whether or not the row was locked; if so, the lock will be removed
  */
 private void unlockImpl(String id, ContentValues values, boolean wasLocked) {
   Cursor c = get(id);
   if (c != null) {
     if (MailActivityEmail.DEBUG && DEBUG_CACHE) {
       LogUtils.d(mLogTag, "=========== Unlocking cache for: " + id);
     }
     if (values != null && !sLockCache) {
       MatrixCursor cursor = getMatrixCursor(id, mBaseProjection, values);
       if (cursor != null) {
         if (MailActivityEmail.DEBUG && DEBUG_CACHE) {
           LogUtils.d(mLogTag, "=========== Recaching with new values: " + id);
         }
         cursor.moveToFirst();
         mLruCache.put(id, cursor);
       } else {
         mLruCache.remove(id);
       }
     } else {
       mLruCache.remove(id);
     }
     // If there are no cursors using the old cached cursor, close it
     if (!sActiveCursors.contains(c)) {
       c.close();
     }
   }
   if (wasLocked) {
     mLockMap.subtract(id);
   }
 }
 private void refreshEntry(Context context, CacheEntry entry)
     throws IOException, MessagingException {
   LogUtils.d(Logging.LOG_TAG, "AuthenticationCache refreshEntry %d", entry.mAccountId);
   try {
     final AuthenticationResult result =
         mAuthenticator.requestRefresh(context, entry.mProviderId, entry.mRefreshToken);
     // Don't set the refresh token here, it's not returned by the refresh response,
     // so setting it here would make it blank.
     entry.mAccessToken = result.mAccessToken;
     entry.mExpirationTime =
         result.mExpiresInSeconds * DateUtils.SECOND_IN_MILLIS + System.currentTimeMillis();
     saveEntry(context, entry);
   } catch (AuthenticationFailedException e) {
     // This is fatal. Clear the tokens and rethrow the exception.
     LogUtils.d(Logging.LOG_TAG, "authentication failed, clearning");
     clearEntry(context, entry);
     throw e;
   } catch (MessagingException e) {
     LogUtils.d(Logging.LOG_TAG, "messaging exception");
     throw e;
   } catch (IOException e) {
     LogUtils.d(Logging.LOG_TAG, "IO exception");
     throw e;
   }
 }
 public synchronized Cursor putCursorImpl(
     Cursor c, String id, String[] projection, CacheToken token) {
   try {
     if (!token.isValid()) {
       if (MailActivityEmail.DEBUG && DEBUG_CACHE) {
         LogUtils.d(mLogTag, "============ Stale token for " + id);
       }
       mStats.mStaleCount++;
       return c;
     }
     if (c != null && Arrays.equals(projection, mBaseProjection) && !sLockCache) {
       if (MailActivityEmail.DEBUG && DEBUG_CACHE) {
         LogUtils.d(mLogTag, "============ Caching cursor for: " + id);
       }
       // If we've already cached this cursor, invalidate the older one
       Cursor existingCursor = get(id);
       if (existingCursor != null) {
         unlockImpl(id, null, false);
       }
       mLruCache.put(id, c);
       return new CachedCursor(c, this, id);
     }
     return c;
   } finally {
     mTokenList.remove(token);
   }
 }
  /**
   * Add a single attachment part to the message
   *
   * <p>This will skip adding attachments if they are already found in the attachments table. The
   * heuristic for this will fail (false-positive) if two identical attachments are included in a
   * single POP3 message. TODO: Fix that, by (elsewhere) simulating an mLocation value based on the
   * attachments position within the list of multipart/mixed elements. This would make every POP3
   * attachment unique, and might also simplify the code (since we could just look at the positions,
   * and ignore the filename, etc.)
   *
   * <p>TODO: Take a closer look at encoding and deal with it if necessary.
   *
   * @param context a context for file operations
   * @param localMessage the attachments will be built against this message
   * @param part a single attachment part from POP or IMAP
   */
  public static void addOneAttachment(
      final Context context, final EmailContent.Message localMessage, final Part part)
      throws MessagingException, IOException {
    final Attachment localAttachment = mimePartToAttachment(part);
    localAttachment.mMessageKey = localMessage.mId;
    localAttachment.mAccountKey = localMessage.mAccountKey;

    if (DEBUG_ATTACHMENTS) {
      LogUtils.d(Logging.LOG_TAG, "Add attachment " + localAttachment);
    }

    // To prevent duplication - do we already have a matching attachment?
    // The fields we'll check for equality are:
    //  mFileName, mMimeType, mContentId, mMessageKey, mLocation
    // NOTE:  This will false-positive if you attach the exact same file, twice, to a POP3
    // message.  We can live with that - you'll get one of the copies.
    final Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, localMessage.mId);
    final Cursor cursor =
        context.getContentResolver().query(uri, Attachment.CONTENT_PROJECTION, null, null, null);
    boolean attachmentFoundInDb = false;
    try {
      while (cursor.moveToNext()) {
        final Attachment dbAttachment = new Attachment();
        dbAttachment.restore(cursor);
        // We test each of the fields here (instead of in SQL) because they may be
        // null, or may be strings.
        if (!TextUtils.equals(dbAttachment.mFileName, localAttachment.mFileName)
            || !TextUtils.equals(dbAttachment.mMimeType, localAttachment.mMimeType)
            || !TextUtils.equals(dbAttachment.mContentId, localAttachment.mContentId)
            || !TextUtils.equals(dbAttachment.mLocation, localAttachment.mLocation)) {
          continue;
        }
        // We found a match, so use the existing attachment id, and stop looking/looping
        attachmentFoundInDb = true;
        localAttachment.mId = dbAttachment.mId;
        if (DEBUG_ATTACHMENTS) {
          LogUtils.d(Logging.LOG_TAG, "Skipped, found db attachment " + dbAttachment);
        }
        break;
      }
    } finally {
      cursor.close();
    }

    // Save the attachment (so far) in order to obtain an id
    if (!attachmentFoundInDb) {
      localAttachment.save(context);
    }

    // If an attachment body was actually provided, we need to write the file now
    saveAttachmentBody(context, part, localAttachment, localMessage.mAccountKey);

    if (localMessage.mAttachments == null) {
      localMessage.mAttachments = new ArrayList<Attachment>();
    }
    localMessage.mAttachments.add(localAttachment);
    localMessage.mFlagAttachment = true;
  }
 /*package*/ boolean remove(CacheToken token) {
   boolean result = super.remove(token);
   if (MailActivityEmail.DEBUG && DEBUG_TOKENS) {
     if (result) {
       LogUtils.d(mLogTag, "============ Removing token for: " + token.mId);
     } else {
       LogUtils.d(mLogTag, "============ No token found for: " + token.mId);
     }
   }
   return result;
 }
  public static void dumpStats() {
    Statistics totals = new Statistics("Totals");

    for (ContentCache cache : sContentCaches) {
      if (cache != null) {
        LogUtils.d(cache.mName, cache.mStats.toString());
        totals.addCacheStatistics(cache);
      }
    }
    LogUtils.d(totals.mName, totals.toString());
  }
Ejemplo n.º 7
0
 private static Bitmap createImageThumbnail(InputStream data) {
   try {
     Bitmap bitmap = BitmapFactory.decodeStream(data);
     return bitmap;
   } catch (OutOfMemoryError oome) {
     LogUtils.d(Logging.LOG_TAG, "createImageThumbnail failed with " + oome.getMessage());
     return null;
   } catch (Exception e) {
     LogUtils.d(Logging.LOG_TAG, "createImageThumbnail failed with " + e.getMessage());
     return null;
   }
 }
  /**
   * 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);
    }
  }
  /** Writes a single line to the server using \r\n termination. */
  public void writeLine(String s, String sensitiveReplacement) throws IOException {
    if (MailActivityEmail.DEBUG) {
      if (sensitiveReplacement != null && !Logging.DEBUG_SENSITIVE) {
        LogUtils.d(Logging.LOG_TAG, ">>> " + sensitiveReplacement);
      } else {
        LogUtils.d(Logging.LOG_TAG, ">>> " + s);
      }
    }

    OutputStream out = getOutputStream();
    out.write(s.getBytes());
    out.write('\r');
    out.write('\n');
    out.flush();
  }
Ejemplo n.º 10
0
 public void render(Attachment paramAttachment, boolean paramBoolean) {
   Attachment localAttachment = this.mAttachment;
   this.mAttachment = paramAttachment;
   this.mActionHandler.setAttachment(this.mAttachment);
   if (!paramAttachment.isDownloading()) ;
   for (boolean bool = false; ; bool = this.mSaveClicked) {
     this.mSaveClicked = bool;
     String str = LOG_TAG;
     Object[] arrayOfObject = new Object[6];
     arrayOfObject[0] = paramAttachment.name;
     arrayOfObject[1] = Integer.valueOf(paramAttachment.state);
     arrayOfObject[2] = Integer.valueOf(paramAttachment.destination);
     arrayOfObject[3] = Integer.valueOf(paramAttachment.downloadedSize);
     arrayOfObject[4] = paramAttachment.contentUri;
     arrayOfObject[5] = paramAttachment.contentType;
     LogUtils.d(
         str,
         "got attachment list row: name=%s state/dest=%d/%d dled=%d contentUri=%s MIME=%s",
         arrayOfObject);
     if ((localAttachment == null)
         || (!TextUtils.equals(paramAttachment.name, localAttachment.name)))
       this.mTitle.setText(paramAttachment.name);
     if ((localAttachment == null) || (paramAttachment.size != localAttachment.size)) {
       this.mAttachmentSizeText =
           AttachmentUtils.convertToHumanReadableSize(getContext(), paramAttachment.size);
       this.mDisplayType = AttachmentUtils.getDisplayType(getContext(), paramAttachment);
       updateSubtitleText(null);
     }
     updateActions();
     this.mActionHandler.updateStatus(paramBoolean);
     return;
   }
 }
 public static void alwaysLog(String str) {
   if (!Eas.USER_LOG) {
     LogUtils.d(TAG, str);
   } else {
     log(str);
   }
 }
Ejemplo n.º 12
0
    private boolean parseStore() throws IOException {
      EmailSyncAdapter adapter = new EmailSyncAdapter(mService);
      EasEmailSyncParser parser = new EasEmailSyncParser(this, adapter);
      ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
      boolean res = false;

      while (nextTag(Tags.SEARCH_STORE) != END) {
        if (tag == Tags.SEARCH_STATUS) {
          getValue();
        } else if (tag == Tags.SEARCH_TOTAL) {
          mTotalResults = getValueInt();
        } else if (tag == Tags.SEARCH_RESULT) {
          parseResult(parser, ops);
        } else {
          skipTag();
        }
      }

      try {
        adapter.mContentResolver.applyBatch(EmailContent.AUTHORITY, ops);
        /// M: get the current result count.
        mCurrentResults = ops.size();
        if (Eas.USER_LOG) {
          mService.userLog("Saved " + ops.size() + " search results");
        }
      } catch (RemoteException e) {
        LogUtils.d(Logging.LOG_TAG, "RemoteException while saving search results.");
      } catch (OperationApplicationException e) {
      }

      return res;
    }
 private CacheEntry getEntry(Context context, Account account) {
   CacheEntry entry;
   if (account.isSaved() && !account.isTemporary()) {
     entry = mCache.get(account.mId);
     if (entry == null) {
       LogUtils.d(Logging.LOG_TAG, "initializing entry from database");
       final HostAuth hostAuth = account.getOrCreateHostAuthRecv(context);
       final Credential credential = hostAuth.getOrCreateCredential(context);
       entry =
           new CacheEntry(
               account.mId,
               credential.mProviderId,
               credential.mAccessToken,
               credential.mRefreshToken,
               credential.mExpiration);
       mCache.put(account.mId, entry);
     }
   } else {
     // This account is temporary, just create a temporary entry. Don't store
     // it in the cache, it won't be findable because we don't yet have an account Id.
     final HostAuth hostAuth = account.getOrCreateHostAuthRecv(context);
     final Credential credential = hostAuth.getCredential(context);
     entry =
         new CacheEntry(
             account.mId,
             credential.mProviderId,
             credential.mAccessToken,
             credential.mRefreshToken,
             credential.mExpiration);
   }
   return entry;
 }
Ejemplo n.º 14
0
  @Override
  public void loadMore(long messageId) throws RemoteException {
    /// M: We Can't load more in low storage state @{
    if (StorageLowState.checkIfStorageLow(mContext)) {
      LogUtils.e(Logging.LOG_TAG, "Can't load more due to low storage");
      return;
    }
    /// @}
    // Load a message for view...
    try {
      // 1. Resample the message, in case it disappeared or synced while
      // this command was in queue
      final EmailContent.Message message =
          EmailContent.Message.restoreMessageWithId(mContext, messageId);
      if (message == null) {
        return;
      }
      if (message.mFlagLoaded == EmailContent.Message.FLAG_LOADED_COMPLETE) {
        // We should NEVER get here
        return;
      }

      // 2. Open the remote folder.
      // TODO combine with common code in loadAttachment
      final Account account = Account.restoreAccountWithId(mContext, message.mAccountKey);
      final Mailbox mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMailboxKey);
      if (account == null || mailbox == null) {
        // mListeners.loadMessageForViewFailed(messageId, "null account or mailbox");
        return;
      }
      TrafficStats.setThreadStatsTag(TrafficFlags.getSyncFlags(mContext, account));

      final Store remoteStore = Store.getInstance(account, mContext);
      final String remoteServerId;
      // If this is a search result, use the protocolSearchInfo field to get the
      // correct remote location
      if (!TextUtils.isEmpty(message.mProtocolSearchInfo)) {
        remoteServerId = message.mProtocolSearchInfo;
      } else {
        remoteServerId = mailbox.mServerId;
      }
      final Folder remoteFolder = remoteStore.getFolder(remoteServerId);
      remoteFolder.open(OpenMode.READ_WRITE);

      // 3. Set up to download the entire message
      final Message remoteMessage = remoteFolder.getMessage(message.mServerId);
      final FetchProfile fp = new FetchProfile();
      fp.add(FetchProfile.Item.BODY);
      remoteFolder.fetch(new Message[] {remoteMessage}, fp, null);

      // 4. Write to provider
      Utilities.copyOneMessageToProvider(
          mContext, remoteMessage, account, mailbox, EmailContent.Message.FLAG_LOADED_COMPLETE);
    } catch (MessagingException me) {
      if (Logging.LOGD) LogUtils.v(Logging.LOG_TAG, "", me);

    } catch (RuntimeException rte) {
      LogUtils.d(Logging.LOG_TAG, "RTE During loadMore");
    }
  }
  /**
   * Parse a message from the server stream.
   *
   * @return the parsed Message
   * @throws IOException
   */
  private EmailContent.Message addParser(final int endingTag)
      throws IOException, CommandStatusException {
    EmailContent.Message msg = new EmailContent.Message();
    msg.mAccountKey = mAccount.mId;
    msg.mMailboxKey = mMailbox.mId;
    msg.mFlagLoaded = EmailContent.Message.FLAG_LOADED_COMPLETE;
    // Default to 1 (success) in case we don't get this tag
    int status = 1;

    while (nextTag(endingTag) != END) {
      switch (tag) {
        case Tags.SYNC_SERVER_ID:
          msg.mServerId = getValue();
          LogUtils.d(TAG, "ServerId: %s", msg.mServerId);
          break;
        case Tags.SYNC_STATUS:
          status = getValueInt();
          break;
        case Tags.SYNC_APPLICATION_DATA:
          addData(msg, tag);
          break;
        default:
          skipTag();
      }
    }
    // For sync, status 1 = success
    if (status != 1) {
      throw new CommandStatusException(status, msg.mServerId);
    }
    return msg;
  }
  @Override
  public void onCreate(Bundle savedState) {
    super.onCreate(savedState);

    parseArguments();
    setBaseUri();

    LogUtils.d(LOG_TAG, "onCreate in ConversationViewFragment (this=%s)", this);
    // Not really, we just want to get a crack to store a reference to the change_folder item
    setHasOptionsMenu(true);

    if (savedState != null) {
      mViewState = savedState.getParcelable(BUNDLE_VIEW_STATE);
      mUserVisible = savedState.getBoolean(BUNDLE_USER_VISIBLE);
      mIsDetached = savedState.getBoolean(BUNDLE_DETACHED, false);
      mHasConversationBeenTransformed =
          savedState.getBoolean(BUNDLE_KEY_HAS_CONVERSATION_BEEN_TRANSFORMED, false);
      mHasConversationTransformBeenReverted =
          savedState.getBoolean(BUNDLE_KEY_HAS_CONVERSATION_BEEN_REVERTED, false);
    } else {
      mViewState = getNewViewState();
      mHasConversationBeenTransformed = false;
      mHasConversationTransformBeenReverted = false;
    }
  }
 /**
  * API: Remote wipe (from server). This is final, there is no confirmation. It will only return to
  * the caller if there is an unexpected failure. The wipe includes external storage.
  */
 public void remoteWipe() {
   DevicePolicyManager dpm = getDPM();
   if (dpm.isAdminActive(mAdminName)) {
     dpm.wipeData(DevicePolicyManager.WIPE_EXTERNAL_STORAGE);
   } else {
     LogUtils.d(Logging.LOG_TAG, "Could not remote wipe because not device admin.");
   }
 }
 private void clearEntry(Context context, CacheEntry entry) {
   LogUtils.d(Logging.LOG_TAG, "clearEntry");
   entry.mAccessToken = "";
   entry.mRefreshToken = "";
   entry.mExpirationTime = 0;
   saveEntry(context, entry);
   mCache.remove(entry.mAccountId);
 }
  protected void onConversationSeen() {
    LogUtils.d(LOG_TAG, "AbstractConversationViewFragment#onConversationSeen()");

    // Ignore unsafe calls made after a fragment is detached from an activity
    final ControllableActivity activity = (ControllableActivity) getActivity();
    if (activity == null) {
      LogUtils.w(LOG_TAG, "ignoring onConversationSeen for conv=%s", mConversation.id);
      return;
    }

    mViewState.setInfoForConversation(mConversation);

    LogUtils.d(
        LOG_TAG, "onConversationSeen() - mSuppressMarkingViewed = %b", mSuppressMarkingViewed);
    // In most circumstances we want to mark the conversation as viewed and read, since the
    // user has read it.  However, if the user has already marked the conversation unread, we
    // do not want a  later mark-read operation to undo this.  So we check this variable which
    // is set in #markUnread() which suppresses automatic mark-read.
    if (!mSuppressMarkingViewed) {
      // mark viewed/read if not previously marked viewed by this conversation view,
      // or if unread messages still exist in the message list cursor
      // we don't want to keep marking viewed on rotation or restore
      // but we do want future re-renders to mark read (e.g. "New message from X" case)
      final MessageCursor cursor = getMessageCursor();
      LogUtils.d(
          LOG_TAG,
          "onConversationSeen() - mConversation.isViewed() = %b, "
              + "cursor null = %b, cursor.isConversationRead() = %b",
          mConversation.isViewed(),
          cursor == null,
          cursor != null && cursor.isConversationRead());
      if (!mConversation.isViewed() || (cursor != null && !cursor.isConversationRead())) {
        // Mark the conversation viewed and read.
        activity
            .getConversationUpdater()
            .markConversationsRead(Arrays.asList(mConversation), true, true);

        // and update the Message objects in the cursor so the next time a cursor update
        // happens with these messages marked read, we know to ignore it
        if (cursor != null && !cursor.isClosed()) {
          cursor.markMessagesRead();
        }
      }
    }
    activity.getListHandler().onConversationSeen();
  }
 public static void log(String tag, String str) {
   if (Eas.USER_LOG) {
     LogUtils.d(tag, str);
     if (Eas.FILE_LOG) {
       FileLogger.log(tag, str);
     }
   }
 }
 public CacheToken add(String id) {
   CacheToken token = new CacheToken(id);
   super.add(token);
   if (MailActivityEmail.DEBUG && DEBUG_TOKENS) {
     LogUtils.d(mLogTag, "============ Taking token for: " + token.mId);
   }
   return token;
 }
 /*package*/ void invalidate() {
   if (MailActivityEmail.DEBUG && DEBUG_TOKENS) {
     LogUtils.d(mLogTag, "============ List invalidated");
   }
   for (CacheToken token : this) {
     token.invalidate();
   }
   clear();
 }
 /**
  * Lock a given row, such that no new valid CacheTokens can be created for the passed-in id.
  *
  * @param id the id of the row to lock
  */
 public synchronized void lock(String id) {
   // Prevent new valid tokens from being created
   mLockMap.add(id);
   // Invalidate current tokens
   int count = mTokenList.invalidateTokens(id);
   if (MailActivityEmail.DEBUG && DEBUG_TOKENS) {
     LogUtils.d(
         mTokenList.mLogTag, "============ Lock invalidated " + count + " tokens for: " + id);
   }
 }
  private void bodyParser(EmailContent.Message msg) throws IOException {
    String bodyType = Eas.BODY_PREFERENCE_TEXT;
    String body = "";
    while (nextTag(Tags.BASE_BODY) != END) {
      switch (tag) {
        case Tags.BASE_TYPE:
          bodyType = getValue();
          break;
        case Tags.BASE_DATA:
          body = getValue();
          break;
        case Tags.BASE_TRUNCATED:
          // M: Message is partial loaded if AirSyncBaseBody::Truncated is True;
          // Otherwise message is complete loaded if False
          String bodyTruncated = getValue();
          if ("1".equals(bodyTruncated) || "true".equals(bodyTruncated)) {
            msg.mFlagLoaded = EmailContent.Message.FLAG_LOADED_PARTIAL;
          } else {
            msg.mFlagLoaded = EmailContent.Message.FLAG_LOADED_COMPLETE;
          }
          LogUtils.d(TAG, "_____________ EAS 12+ body truncated: " + bodyTruncated);
          break;
        default:
          skipTag();
      }
    }
    // We always ask for TEXT or HTML; there's no third option
    if (bodyType.equals(Eas.BODY_PREFERENCE_HTML)) {
      msg.mHtml = body;
    } else {
      msg.mText = body;
    }

    /**
     * M: What if a mail with bodyTruncated tag, but both body html and text are empty? In this
     * case, client should set a suitable load flag: FLAG_LOADED_COMPLETE. @{
     */
    if (TextUtils.isEmpty(msg.mHtml) && TextUtils.isEmpty(msg.mText)) {
      LogUtils.d(TAG, " for empty mailbody, reset load complete tag.");
      msg.mFlagLoaded = EmailContent.Message.FLAG_LOADED_COMPLETE;
    }
    /** @} */
  }
    @Override
    public void onLoadFinished(
        Loader<ObjectCursor<ConversationMessage>> loader, ObjectCursor<ConversationMessage> data) {
      // ignore truly duplicate results
      // this can happen when restoring after rotation
      if (mCursor == data) {
        return;
      } else {
        final MessageCursor messageCursor = (MessageCursor) data;

        // bind the cursor to this fragment so it can access to the current list controller
        messageCursor.setController(AbstractConversationViewFragment.this);

        if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
          LogUtils.d(LOG_TAG, "LOADED CONVERSATION= %s", messageCursor.getDebugDump());
        }

        // We have no messages: exit conversation view.
        if (messageCursor.getCount() == 0
            && (!CursorStatus.isWaitingForResults(messageCursor.getStatus()) || mIsDetached)) {
          if (mUserVisible) {
            onError();
          } else {
            // we expect that the pager adapter will remove this
            // conversation fragment on its own due to a separate
            // conversation cursor update (we might get here if the
            // message list update fires first. nothing to do
            // because we expect to be torn down soon.)
            LogUtils.i(
                LOG_TAG,
                "CVF: offscreen conv has no messages, ignoring update"
                    + " in anticipation of conv cursor update. c=%s",
                mConversation.uri);
          }
          // existing mCursor will imminently be closed, must stop referencing it
          // since we expect to be kicked out soon, it doesn't matter what mCursor
          // becomes
          mCursor = null;
          return;
        }

        // ignore cursors that are still loading results
        if (!messageCursor.isLoaded()) {
          // existing mCursor will imminently be closed, must stop referencing it
          // in this case, the new cursor is also no good, and since don't expect to get
          // here except in initial load situations, it's safest to just ensure the
          // reference is null
          mCursor = null;
          return;
        }
        final MessageCursor oldCursor = mCursor;
        mCursor = messageCursor;
        onMessageCursorLoadFinished(loader, mCursor, oldCursor);
      }
    }
 @Override
 public void dismiss() {
   if (hasFocus()) {
     LogUtils.d(
         LOG_TAG,
         "Oops! ConverationSpecialItem dismiss with Focus %s but isFocusable is %s.",
         hasFocus(),
         isFocusable());
     clearFocus();
   }
 }
  /**
   * Attempts to open a connection using the Uri supplied for connection parameters. Will attempt an
   * SSL connection if indicated.
   */
  public void open() throws MessagingException, CertificateValidationException {
    if (MailActivityEmail.DEBUG) {
      LogUtils.d(
          Logging.LOG_TAG,
          "*** " + mDebugLabel + " open " + getHost() + ":" + String.valueOf(getPort()));
    }

    try {
      SocketAddress socketAddress = new InetSocketAddress(getHost(), getPort());
      if (canTrySslSecurity()) {
        mSocket =
            SSLUtils.getSSLSocketFactory(mContext, mHostAuth, canTrustAllCertificates())
                .createSocket();
      } else {
        mSocket = new Socket();
      }
      mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
      // After the socket connects to an SSL server, confirm that the hostname is as expected
      if (canTrySslSecurity() && !canTrustAllCertificates()) {
        verifyHostname(mSocket, getHost());
      }
      mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
      mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
      mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
    } catch (SSLException e) {
      if (MailActivityEmail.DEBUG) {
        LogUtils.d(Logging.LOG_TAG, e.toString());
      }
      throw new CertificateValidationException(e.getMessage(), e);
    } catch (IOException ioe) {
      if (MailActivityEmail.DEBUG) {
        LogUtils.d(Logging.LOG_TAG, ioe.toString());
      }
      throw new MessagingException(MessagingException.IOERROR, ioe.toString());
    } catch (IllegalArgumentException iae) {
      if (MailActivityEmail.DEBUG) {
        LogUtils.d(Logging.LOG_TAG, iae.toString());
      }
      throw new MessagingException(MessagingException.UNSPECIFIED_EXCEPTION, iae.toString());
    }
  }
 /// M: update UI status, for Loading/LoadMore/NetworkError.
 public void updateLoadingStatus(boolean start) {
   LogUtils.d(
       LogTag.getLogTag(), "updateLoadingStatus show loading progress dialog ? [%s]", start);
   mNetworkError.setVisibility(View.GONE);
   if (start) {
     mLoading.setVisibility(View.VISIBLE);
     mLoadMore.setVisibility(View.GONE);
   } else {
     mLoading.setVisibility(View.GONE);
     mLoadMore.setVisibility(View.VISIBLE);
   }
 }
 @Override
 public void onClick(View v) {
   final int id = v.getId();
   final Folder f = (Folder) v.getTag();
   if (id == R.id.error_action_button) {
     mClickListener.onFooterViewErrorActionClick(f, mErrorStatus);
   } else if (id == R.id.load_more) {
     LogUtils.d(
         LogTag.getLogTag(), "LoadMore triggered folder [%s]", f != null ? f.loadMoreUri : "null");
     mClickListener.onFooterViewLoadMoreClick(f);
   }
 }
 /**
  * Invalidate the entire cache; the arguments are used for logging only, and indicate the write
  * operation that caused the invalidation
  *
  * @param operation a string describing the operation causing the invalidate (or null)
  * @param uri the uri causing the invalidate (or null)
  * @param selection the selection used with the uri (or null)
  */
 public synchronized void invalidate(String operation, Uri uri, String selection) {
   if (DEBUG_CACHE && (operation != null)) {
     LogUtils.d(
         mLogTag,
         "============ INVALIDATED BY " + operation + ": " + uri + ", SELECTION: " + selection);
   }
   mStats.mInvalidateCount++;
   // Close all cached cursors that are no longer in use
   mLruCache.evictAll();
   // Invalidate all current tokens
   mTokenList.invalidate();
 }