예제 #1
0
 public void retry(long message_id) {
   Common.logError("retry");
   Message msg = TLMessage.get(message_id);
   if (msg == null) return;
   TLMessage.delete(message_id);
   sendMessage(msg);
 }
 public int getUserColor(final long userId, final boolean ignoreCache) {
   if (userId == -1) return Color.TRANSPARENT;
   if (!ignoreCache && mUserColors.indexOfKey(userId) >= 0) return mUserColors.get(userId);
   final int color = mColorPreferences.getInt(Long.toString(userId), Color.TRANSPARENT);
   mUserColors.put(userId, color);
   return color;
 }
 public void saveCategory(Category newCategory) {
   CategoryTree tree = loadCategories();
   if (newCategory.id > 0) {
     LongSparseArray<Category> map = tree.asIdMap();
     Category oldCategory = map.get(newCategory.id);
     if (oldCategory != null) {
       oldCategory.title = newCategory.title;
       Category oldParent = map.get(oldCategory.parentId);
       Category newParent = map.get(newCategory.parentId);
       if (newParent != oldParent) {
         if (oldParent != null) {
           oldParent.removeChild(oldCategory);
         } else {
           tree.removeRoot(oldCategory);
         }
         if (newParent != null) {
           newParent.addChild(oldCategory);
         } else {
           tree.addRoot(oldCategory);
         }
       }
     }
   } else {
     if (newCategory.parentId > 0) {
       LongSparseArray<Category> map = tree.asIdMap();
       Category parent = map.get(newCategory.parentId);
       if (parent != null) {
         parent.addChild(newCategory);
       }
     } else {
       tree.addRoot(newCategory);
     }
   }
   saveCategories(tree);
 }
  public DownloadInfoRunnable getDownload(long id) {

    if (downloads.get(id) != null) {
      return downloads.get(id);
    } else {
      return new DownloadInfoRunnable(manager, id);
    }
  }
예제 #5
0
 private void TL_RpcResult(TL.Object obj) {
   req_msg_id = obj.getLong("req_msg_id");
   TL.Object result = obj.getObject("result");
   Message msg = TLMessage.get(req_msg_id);
   if (msg != null && msg.result != null)
     msg.result.onResultRPC(
         result, msg.param, result.name != null && result.name.equals("rpc_error"));
   process(result);
   TLMessage.delete(req_msg_id);
 }
예제 #6
0
 @Override
 public boolean performItemClick(View view, int position, long id) {
   boolean handled = false;
   boolean dispatchItemClick = true;
   if (mChoiceMode != CHOICE_MODE_NONE) {
     handled = true;
     boolean checkedStateChanged = false;
     if (mChoiceMode == CHOICE_MODE_MULTIPLE
         || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode != null) {
       boolean newValue = !mCheckStates.get(position, false);
       mCheckStates.put(position, newValue);
       if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
         if (newValue) {
           mCheckedIdStates.put(mAdapter.getItemId(position), position);
         } else {
           mCheckedIdStates.delete(mAdapter.getItemId(position));
         }
       }
       if (newValue) {
         mCheckedItemCount++;
       } else {
         mCheckedItemCount--;
       }
       if (mChoiceActionMode != null) {
         mMultiChoiceModeCallback.onItemCheckedStateChanged(
             mChoiceActionMode, position, id, newValue);
         dispatchItemClick = false;
       }
       checkedStateChanged = true;
     } else if (mChoiceMode == CHOICE_MODE_SINGLE) {
       boolean newValue = !mCheckStates.get(position, false);
       if (newValue) {
         mCheckStates.clear();
         mCheckStates.put(position, true);
         if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
           mCheckedIdStates.clear();
           mCheckedIdStates.put(mAdapter.getItemId(position), position);
         }
         mCheckedItemCount = 1;
       } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
         mCheckedItemCount = 0;
       }
       checkedStateChanged = true;
     }
     if (checkedStateChanged) {
       updateOnScreenCheckedViews();
     }
   }
   if (dispatchItemClick) {
     handled |= super.performItemClick(view, position, id);
   }
   return handled;
 }
예제 #7
0
 @Override
 public void writeToParcel(Parcel out, int flags) {
   super.writeToParcel(out, flags);
   out.writeByte((byte) (inActionMode ? 1 : 0));
   out.writeInt(checkedItemCount);
   out.writeSparseBooleanArray(checkState);
   final int N = checkIdState != null ? checkIdState.size() : 0;
   out.writeInt(N);
   for (int i = 0; i < N; i++) {
     out.writeLong(checkIdState.keyAt(i));
     out.writeInt(checkIdState.valueAt(i));
   }
 }
예제 #8
0
 @Override
 public long[] getCheckedItemIds() {
   if (mChoiceMode == CHOICE_MODE_NONE || mCheckedIdStates == null || mAdapter == null) {
     return new long[0];
   }
   final LongSparseArray<Integer> idStates = mCheckedIdStates;
   final int count = idStates.size();
   final long[] ids = new long[count];
   for (int i = 0; i < count; i++) {
     ids[i] = idStates.keyAt(i);
   }
   return ids;
 }
예제 #9
0
 @Override
 public void setItemChecked(int position, boolean value) {
   if (mChoiceMode == CHOICE_MODE_NONE) {
     return;
   }
   if (value && mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL && mChoiceActionMode == null) {
     mChoiceActionMode = startActionMode(mMultiChoiceModeCallback);
   }
   if (mChoiceMode == CHOICE_MODE_MULTIPLE || mChoiceMode == CHOICE_MODE_MULTIPLE_MODAL) {
     boolean oldValue = mCheckStates.get(position);
     mCheckStates.put(position, value);
     if (mCheckedIdStates != null && mAdapter.hasStableIds()) {
       if (value) {
         mCheckedIdStates.put(mAdapter.getItemId(position), position);
       } else {
         mCheckedIdStates.delete(mAdapter.getItemId(position));
       }
     }
     if (oldValue != value) {
       if (value) {
         mCheckedItemCount++;
       } else {
         mCheckedItemCount--;
       }
     }
     if (mChoiceActionMode != null) {
       final long id = mAdapter.getItemId(position);
       mMultiChoiceModeCallback.onItemCheckedStateChanged(mChoiceActionMode, position, id, value);
     }
   } else {
     boolean updateIds = mCheckedIdStates != null && mAdapter.hasStableIds();
     if (value || isItemChecked(position)) {
       mCheckStates.clear();
       if (updateIds) {
         mCheckedIdStates.clear();
       }
     }
     if (value) {
       mCheckStates.put(position, true);
       if (updateIds) {
         mCheckedIdStates.put(mAdapter.getItemId(position), position);
       }
       mCheckedItemCount = 1;
     } else if (mCheckStates.size() == 0 || !mCheckStates.valueAt(0)) {
       mCheckedItemCount = 0;
     }
   }
   updateOnScreenCheckedViews();
 }
  /**
   * Reads all PGPKeyRing objects from input
   *
   * @param inputData
   * @return
   */
  private void generateListOfKeyrings(InputData inputData) {
    PositionAwareInputStream progressIn = new PositionAwareInputStream(inputData.getInputStream());

    // need to have access to the bufferedInput, so we can reuse it for the possible
    // PGPObject chunks after the first one, e.g. files with several consecutive ASCII
    // armor blocks
    BufferedInputStream bufferedInput = new BufferedInputStream(progressIn);
    try {
      // parse all keyrings
      Iterator<UncachedKeyRing> it = UncachedKeyRing.fromStream(bufferedInput);
      while (it.hasNext()) {
        UncachedKeyRing ring = it.next();
        ImportKeysListEntry item = new ImportKeysListEntry(getContext(), ring);
        mData.add(item);
        mParcelableRings.put(item.hashCode(), new ParcelableKeyRing(ring.getEncoded()));
      }
    } catch (IOException e) {
      Log.e(Constants.TAG, "IOException on parsing key file! Return NoValidKeysException!", e);
      OperationResult.OperationLog log = new OperationResult.OperationLog();
      log.add(OperationResult.LogType.MSG_GET_NO_VALID_KEYS, 0);
      GetKeyResult getKeyResult = new GetKeyResult(GetKeyResult.RESULT_ERROR_NO_VALID_KEYS, log);
      mEntryListWrapper =
          new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mData, getKeyResult);
    }
  }
예제 #11
0
 @Override
 public void setAdapter(ListAdapter adapter) {
   if (adapter == null) {
     mAdapter = null;
   } else if (mForceHeaderListAdapter
       || mHeaderViewInfos.size() > 0
       || mFooterViewInfos.size() > 0) {
     mAdapter =
         new HeaderViewListAdapter(
             mHeaderViewInfos, mFooterViewInfos, adapter, mListAdapterCallback);
   } else {
     mAdapter = new ListAdapterWrapper(adapter, mListAdapterCallback);
   }
   if (mAdapter != null) {
     mAdapterHasStableIds = mAdapter.hasStableIds();
     if (mChoiceMode != CHOICE_MODE_NONE && mAdapterHasStableIds && mCheckedIdStates == null) {
       mCheckedIdStates = new LongSparseArray<Integer>();
     }
   }
   if (mCheckStates != null) {
     mCheckStates.clear();
   }
   if (mCheckedIdStates != null) {
     mCheckedIdStates.clear();
   }
   super.setAdapter(mAdapter);
 }
 public void deleteCategoryById(long id) {
   if (id > 0) {
     CategoryTree tree = loadCategories();
     LongSparseArray<Category> map = tree.asIdMap();
     Category category = map.get(id);
     if (category != null) {
       Category parent = category.parent;
       if (parent == null) {
         tree.removeRoot(category);
       } else {
         parent.removeChild(category);
       }
       saveCategories(tree);
     }
   }
 }
예제 #13
0
 public void clearUserColor(final long userId) {
   if (userId < 0) return;
   mUserColors.remove(userId);
   final SharedPreferences.Editor editor = mColorPreferences.edit();
   editor.remove(Long.toString(userId));
   editor.apply();
 }
예제 #14
0
  private void TL_RpcError(TL.Object obj) {
    int code = obj.getInt("error_code");
    String msg = obj.getString("error_message");

    Common.logError(String.format("rpc_error: %s (%d)\n", msg, code));

    Message req_msg = TLMessage.get(req_msg_id);
    if (req_msg == null) return;
    Common.logError("message object: " + req_msg.obj.name + ":" + req_msg.obj.type);

    int idx = msg.indexOf("_MIGRATE_");
    if (idx > 0) {
      String type = msg.substring(0, idx);

      String num = msg.substring(idx + 9);
      if ((idx = num.indexOf(":")) > 0) num = num.substring(0, idx);
      int dc_id = Integer.parseInt(num);
      Common.logError("redirect to dc: " + dc_id);

      MTProto m = MTProto.getConnection(dc_id, cb, reuseFlag);
      cb.onRedirect(m);
      if (type.equals("PHONE") || type.equals("NETWORK") || type.equals("USER")) dc_this = dc_id;

      m.sendMessage(req_msg);
    }
  }
예제 #15
0
  private void TL_BadMsgNotification(TL.Object obj) {
    int error_code = obj.getInt("error_code");

    Message msg = TLMessage.get(obj.getLong("bad_msg_id"));
    Common.logError("bad_msg: " + error_code + " " + msg.obj.name + ":" + msg.obj.type);

    if (error_code == 16 || error_code == 17) {
      time_delta = (int) ((cur_message_id >> 32) - Common.getUnixTime());
      last_message_id = 0;
    }

    if (error_code == 32 || error_code == 33) {
      Common.logError("cur seq: " + cur_msg_seq);
      Common.logError("old seq: " + seqno);
      if (!bad_seq) {
        session = GEN_session_id();
        seqno = 0;
        send_ping();
        bad_seq = true;
      }

      // seqno = cur_msg_seq + (cur_msg_seq % 2) + 100;
      //	session = GEN_session_id();
      //	seqno = 0;
    }

    if (obj.id == 0xedab447b) { // bad_server_salt
      server_salt = obj.getLong("new_server_salt");
      dcState.set("server_salt", server_salt);
    }

    retry(obj.getLong("bad_msg_id"));
  }
예제 #16
0
 public void setUserColor(final long userId, final int color) {
   if (userId < 0) return;
   mUserColors.put(userId, color);
   final SharedPreferences.Editor editor = mColorPreferences.edit();
   editor.putInt(String.valueOf(userId), color);
   editor.apply();
 }
예제 #17
0
 private void TL_MsgsAck(TL.Object obj) {
   TL.Vector msg_ids = obj.getVector("msg_ids");
   for (int i = 0; i < msg_ids.count; i++) {
     Message msg = TLMessage.get(msg_ids.getLong(i));
     if (msg != null) msg.accepted = true;
     // TLMessage.delete(msg_ids.getLong(i));
   }
 }
  @Override
  public View getHeader(RecyclerView parent, int position) {
    long headerId = mAdapter.getHeaderId(position);

    View header = mHeaderViews.get(headerId);
    if (header == null) {
      // TODO - recycle views
      RecyclerView.ViewHolder viewHolder = mAdapter.onCreateHeaderViewHolder(parent);
      mAdapter.onBindHeaderViewHolder(viewHolder, position);
      header = viewHolder.itemView;
      if (header.getLayoutParams() == null) {
        header.setLayoutParams(
            new ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
      }

      int widthSpec;
      int heightSpec;

      if (mOrientationProvider.getOrientation(parent) == LinearLayoutManager.VERTICAL) {
        widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
        heightSpec =
            View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);
      } else {
        widthSpec =
            View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.UNSPECIFIED);
        heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.EXACTLY);
      }

      int childWidth =
          ViewGroup.getChildMeasureSpec(
              widthSpec,
              parent.getPaddingLeft() + parent.getPaddingRight(),
              header.getLayoutParams().width);
      int childHeight =
          ViewGroup.getChildMeasureSpec(
              heightSpec,
              parent.getPaddingTop() + parent.getPaddingBottom(),
              header.getLayoutParams().height);
      header.measure(childWidth, childHeight);
      header.layout(0, 0, header.getMeasuredWidth(), header.getMeasuredHeight());
      mHeaderViews.put(headerId, header);
    }
    return header;
  }
  /** Called when one specific passphrase for keyId timed out. */
  private void removeTimeoutedPassphrase(long keyId) {

    CachedPassphrase cPass = mPassphraseCache.get(keyId);
    if (cPass != null) {
      if (cPass.mPassphrase != null) {
        // clean internal char[] from memory!
        cPass.mPassphrase.removeFromMemory();
      }
      // remove passphrase object
      mPassphraseCache.remove(keyId);
    }

    Log.d(
        Constants.TAG,
        "PassphraseCacheService Timeout of keyId " + keyId + ", removed from memory!");

    updateService();
  }
예제 #20
0
 @Override
 public void clearChoices() {
   if (mCheckStates != null) {
     mCheckStates.clear();
   }
   if (mCheckedIdStates != null) {
     mCheckedIdStates.clear();
   }
   mCheckedItemCount = 0;
 }
  private void removeScreenLockPassphrases() {

    for (int i = 0; i < mPassphraseCache.size(); ) {
      CachedPassphrase cPass = mPassphraseCache.valueAt(i);
      if (cPass.mTimeoutMode == TimeoutMode.LOCK) {
        // remove passphrase object
        mPassphraseCache.removeAt(i);
        continue;
      }
      // only do this if we didn't remove at, which continues loop by reducing size!
      i += 1;
    }

    Log.d(
        Constants.TAG,
        "PassphraseCacheService Removing all cached-until-lock passphrases from memory!");

    updateService();
  }
 private void updateService() {
   if (mPassphraseCache.size() > 0) {
     startForeground(Constants.Notification.PASSPHRASE_CACHE, getNotification());
   } else {
     // stop whole service if no cached passphrases remaining
     Log.d(
         Constants.TAG,
         "PassphraseCacheService: No passphrases remaining in memory, stopping service!");
     stopForeground(true);
   }
 }
  private Notification getNotification() {
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
    builder
        .setSmallIcon(R.drawable.ic_stat_notify_24dp)
        .setColor(getResources().getColor(R.color.primary))
        .setContentTitle(
            getResources()
                .getQuantityString(
                    R.plurals.passp_cache_notif_n_keys,
                    mPassphraseCache.size(),
                    mPassphraseCache.size()))
        .setContentText(getString(R.string.passp_cache_notif_touch_to_clear));

    NotificationCompat.InboxStyle inboxStyle = new NotificationCompat.InboxStyle();

    inboxStyle.setBigContentTitle(getString(R.string.passp_cache_notif_keys));

    // Moves events into the big view
    for (int i = 0; i < mPassphraseCache.size(); i++) {
      inboxStyle.addLine(mPassphraseCache.valueAt(i).mPrimaryUserId);
    }

    // Moves the big view style object into the notification object.
    builder.setStyle(inboxStyle);

    Intent intent = new Intent(getApplicationContext(), PassphraseCacheService.class);
    intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);
    PendingIntent clearCachePi =
        PendingIntent.getService(
            getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    // Add cache clear PI to normal touch
    builder.setContentIntent(clearCachePi);

    // Add clear PI action below text
    builder.addAction(
        R.drawable.ic_close_white_24dp, getString(R.string.passp_cache_notif_clear), clearCachePi);

    return builder.build();
  }
예제 #24
0
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // download from net
    if (mSource == R.id.action_discover) {

      mHeaders.append(
          GROUP_IN_THEATERS,
          getActivity().getResources().getString(R.string.film_group_in_theaters));
      mHeaders.append(
          GROUP_MOST_POPULAR,
          getActivity().getResources().getString(R.string.film_group_most_popular));

      List<Film> downloadedFilms = ((App) getActivity().getApplicationContext()).downloadedFilms;
      if (downloadedFilms == null) {
        // Starting Download Service
        mReceiver = new DownloadServiceReceiver(new Handler());
        mReceiver.setReceiver(this);
        Intent intent = new Intent(Intent.ACTION_SYNC, null, getActivity(), DownloadService.class);

        // Send optional extras to Download IntentService
        intent.putExtra("receiver", mReceiver);
        intent.putExtra("groups", new int[] {GROUP_IN_THEATERS, GROUP_MOST_POPULAR});

        getActivity().startService(intent);
      } else {
        setFilms(downloadedFilms);
      }
    } else if (mSource == R.id.action_favourites) {

      mHeaders.append(
          GROUP_FAVOURITES, getActivity().getResources().getString(R.string.film_group_favourites));

      mDatabase = new FilmDatabase(getActivity());
      setFilms(mDatabase.getAll());
    }
  }
  public void setReferrer(long id, String referrer) {
    try {

      DownloadInfoRunnable downloadInfoRunnable = downloads.get(id);
      if (downloadInfoRunnable != null) {
        downloadInfoRunnable.getDownloadExecutor().getApk().setReferrer(referrer);
      } else {
        Log.d("AptoideDownloadService", "Downloadinfo for referrer was null");
      }

    } catch (Exception e) {
      e.printStackTrace();
      Log.d("AptoideDownloadService", "error setting referrer");
    }
  }
예제 #26
0
 public SavedState(Parcel in) {
   super(in);
   inActionMode = in.readByte() != 0;
   checkedItemCount = in.readInt();
   checkState = in.readSparseBooleanArray();
   final int N = in.readInt();
   if (N > 0) {
     checkIdState = new LongSparseArray<Integer>();
     for (int i = 0; i < N; i++) {
       final long key = in.readLong();
       final int value = in.readInt();
       checkIdState.put(key, value);
     }
   }
 }
  private void download(
      long id, Download download, FinishedApk apk, ArrayList<DownloadModel> filesToDownload) {

    DownloadInfoRunnable info = getDownload(id);

    if (download.getCpiUrl() != null) {
      apk.setCpiUrl(download.getCpiUrl());
    }

    if (download.getReferrer() != null) {
      apk.setReferrer(download.getReferrer());
    } else {
      Log.d("AptoideDownloadService", "Creating download with no referrer");
    }

    info.setDownloadExecutor(new DownloadExecutor(apk));
    info.setDownload(download);
    info.setFilesToDownload(filesToDownload);

    boolean update;
    try {
      Aptoide.getContext().getPackageManager().getPackageInfo(download.getPackageName(), 0);
      update = true;
    } catch (PackageManager.NameNotFoundException e) {
      update = false;
    }

    info.setUpdate(update);
    downloads.put(info.getId(), info);
    NotificationCompat.Builder builder = setNotification(info.getId());
    info.setmBuilder(builder);
    info.download();

    if (mBuilder == null) mBuilder = createDefaultNotification();

    startForeground(-3, mBuilder.build());
    startService(new Intent(getApplicationContext(), DownloadService.class));

    startIfStopped();
    Toast.makeText(
            getApplicationContext(),
            getApplicationContext().getString(R.string.starting_download),
            Toast.LENGTH_LONG)
        .show();
  }
예제 #28
0
  public synchronized boolean sendMessage(Message msg) {
    if (msg == null) return false;

    if (!connected || (msg.encrypted && (auth_key == null || auth_key.length == 0)) || bad_seq) {
      Common.logError("add to queue " + connected + " " + msg.encrypted);
      TLMessageQueue.add(msg);
      return false;
    }
    ByteBuffer b = null;

    Common.logInfo("message send: " + msg.obj.name + ":" + msg.obj.type);
    byte[] msg_data = msg.obj.serialize();

    /*
    if (msg_data.length > 255) {
    	Common.logError("compress...");
    	byte[] data = Common.gzipDeflate(msg_data); // TODO: compress message data
    	if (data.length < msg_data.length && data.length > 32)
    		msg_data = TL.newObject("gzip_packed", data).serialize();
    }
    */

    if (msg.encrypted) {
      long message_id = GEN_message_id();
      TLMessage.put(message_id, msg);

      int size_real = 32 + msg_data.length;
      int size_padding = (size_real + 15) / 16 * 16;

      ByteBuffer p = BufferAlloc(size_padding);
      p.putLong(server_salt);
      p.putLong(session);
      p.putLong(message_id);

      if (msg.accept) seqno++;
      p.putInt(seqno);
      if (msg.accept) seqno++;

      p.putInt(msg_data.length);
      p.put(msg_data);
      byte[] data = p.array();
      GEN_random_bytes(data, p.position(), data.length);

      // encrypt data
      byte[] msg_key = Common.ASUB(Common.getSHA1(Common.ASUB(data, 0, size_real)), 4, 16);

      synchronized (aes) {
        aes.prepare(true, msg_key, auth_key);
        data = aes.IGE(data, true);
      }

      // write encrypted packet
      b = BufferAlloc(24 + data.length);
      b.putLong(auth_key_id);
      b.put(msg_key);
      b.put(data);
    } else {
      b = BufferAlloc(20 + msg_data.length);
      b.putLong(0);
      b.putLong(GEN_message_id());
      b.putInt(msg_data.length);
      b.put(msg_data);
    }

    try {
      synchronized (transport) {
        transport.send(b.array());
        return true;
      }
    } catch (Exception e) {
      return false;
    }
  }
예제 #29
0
  /**
   * Get the final moves that we want to upsync to the server, setting the status in the DB for all
   * rows to {@link #STATUS_PROCESSING} that are being updated and to {@link #STATUS_FAILED} for any
   * old updates. Messages whose sequence of pending moves results in a no-op (i.e. the message has
   * been moved back to its original folder) have their moves cleared from the DB without any
   * upsync.
   *
   * @param context A {@link Context}.
   * @param accountId The account we want to update.
   * @return The final moves to send to the server, or null if there are none.
   */
  public static List<MessageMove> getMoves(final Context context, final long accountId) {
    final ContentResolver cr = context.getContentResolver();
    final Cursor c = getCursor(cr, CONTENT_URI, ProjectionMoveQuery.PROJECTION, accountId);
    if (c == null) {
      return null;
    }

    // Collapse any rows in the cursor that are acting on the same message. We know the cursor
    // returned by getRowsToProcess is ordered from oldest to newest, and we use this fact to
    // get the original and final folder for the message.
    LongSparseArray<MessageMove> movesMap = new LongSparseArray();
    try {
      while (c.moveToNext()) {
        final long id = c.getLong(ProjectionMoveQuery.COLUMN_ID);
        final long messageKey = c.getLong(ProjectionMoveQuery.COLUMN_MESSAGE_KEY);
        final String serverId = c.getString(ProjectionMoveQuery.COLUMN_SERVER_ID);
        final long srcFolderKey = c.getLong(ProjectionMoveQuery.COLUMN_SRC_FOLDER_KEY);
        final long dstFolderKey = c.getLong(ProjectionMoveQuery.COLUMN_DST_FOLDER_KEY);
        final String srcFolderServerId =
            c.getString(ProjectionMoveQuery.COLUMN_SRC_FOLDER_SERVER_ID);
        final String dstFolderServerId =
            c.getString(ProjectionMoveQuery.COLUMN_DST_FOLDER_SERVER_ID);
        final MessageMove existingMove = movesMap.get(messageKey);
        if (existingMove != null) {
          if (existingMove.mLastId >= id) {
            LogUtils.w(LOG_TAG, "Moves were not in ascending id order");
          }
          if (!existingMove.mDstFolderServerId.equals(srcFolderServerId)
              || existingMove.mDstFolderKey != srcFolderKey) {
            LogUtils.w(LOG_TAG, "existing move's dst not same as this move's src");
          }
          existingMove.mDstFolderKey = dstFolderKey;
          existingMove.mDstFolderServerId = dstFolderServerId;
          existingMove.mLastId = id;
        } else {
          movesMap.put(
              messageKey,
              new MessageMove(
                  messageKey,
                  serverId,
                  id,
                  srcFolderKey,
                  dstFolderKey,
                  srcFolderServerId,
                  dstFolderServerId));
        }
      }
    } finally {
      c.close();
    }

    // Prune any no-op moves (i.e. messages that have been moved back to the initial folder).
    final int moveCount = movesMap.size();
    final long[] unmovedMessages = new long[moveCount];
    int unmovedMessagesCount = 0;
    final ArrayList<MessageMove> moves = new ArrayList(moveCount);
    for (int i = 0; i < movesMap.size(); ++i) {
      final MessageMove move = movesMap.valueAt(i);
      // We also treat changes without a server id as a no-op.
      if ((move.mServerId == null || move.mServerId.length() == 0)
          || move.mSrcFolderKey == move.mDstFolderKey) {
        unmovedMessages[unmovedMessagesCount] = move.mMessageKey;
        ++unmovedMessagesCount;
      } else {
        moves.add(move);
      }
    }
    if (unmovedMessagesCount != 0) {
      deleteRowsForMessages(cr, CONTENT_URI, unmovedMessages, unmovedMessagesCount);
    }
    if (moves.isEmpty()) {
      return null;
    }
    return moves;
  }
 @Override
 public void invalidate() {
   mHeaderViews.clear();
 }