/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.internal.widget; import static com.android.internal.widget.AdapterHelper.UpdateOp.ADD; import static com.android.internal.widget.AdapterHelper.UpdateOp.MOVE; import static com.android.internal.widget.AdapterHelper.UpdateOp.REMOVE; import static com.android.internal.widget.AdapterHelper.UpdateOp.UPDATE; import com.android.internal.widget.AdapterHelper.UpdateOp; import java.util.List; class OpReorderer { final Callback mCallback; OpReorderer(Callback callback) { mCallback = callback; } void reorderOps(List ops) { // since move operations breaks continuity, their effects on ADD/RM are hard to handle. // we push them to the end of the list so that they can be handled easily. int badMove; while ((badMove = getLastMoveOutOfOrder(ops)) != -1) { swapMoveOp(ops, badMove, badMove + 1); } } private void swapMoveOp(List list, int badMove, int next) { final UpdateOp moveOp = list.get(badMove); final UpdateOp nextOp = list.get(next); switch (nextOp.cmd) { case REMOVE: swapMoveRemove(list, badMove, moveOp, next, nextOp); break; case ADD: swapMoveAdd(list, badMove, moveOp, next, nextOp); break; case UPDATE: swapMoveUpdate(list, badMove, moveOp, next, nextOp); break; } } void swapMoveRemove(List list, int movePos, UpdateOp moveOp, int removePos, UpdateOp removeOp) { UpdateOp extraRm = null; // check if move is nulled out by remove boolean revertedMove = false; final boolean moveIsBackwards; if (moveOp.positionStart < moveOp.itemCount) { moveIsBackwards = false; if (removeOp.positionStart == moveOp.positionStart && removeOp.itemCount == moveOp.itemCount - moveOp.positionStart) { revertedMove = true; } } else { moveIsBackwards = true; if (removeOp.positionStart == moveOp.itemCount + 1 && removeOp.itemCount == moveOp.positionStart - moveOp.itemCount) { revertedMove = true; } } // going in reverse, first revert the effect of add if (moveOp.itemCount < removeOp.positionStart) { removeOp.positionStart--; } else if (moveOp.itemCount < removeOp.positionStart + removeOp.itemCount) { // move is removed. removeOp.itemCount--; moveOp.cmd = REMOVE; moveOp.itemCount = 1; if (removeOp.itemCount == 0) { list.remove(removePos); mCallback.recycleUpdateOp(removeOp); } // no need to swap, it is already a remove return; } // now affect of add is consumed. now apply effect of first remove if (moveOp.positionStart <= removeOp.positionStart) { removeOp.positionStart++; } else if (moveOp.positionStart < removeOp.positionStart + removeOp.itemCount) { final int remaining = removeOp.positionStart + removeOp.itemCount - moveOp.positionStart; extraRm = mCallback.obtainUpdateOp(REMOVE, moveOp.positionStart + 1, remaining, null); removeOp.itemCount = moveOp.positionStart - removeOp.positionStart; } // if effects of move is reverted by remove, we are done. if (revertedMove) { list.set(movePos, removeOp); list.remove(removePos); mCallback.recycleUpdateOp(moveOp); return; } // now find out the new locations for move actions if (moveIsBackwards) { if (extraRm != null) { if (moveOp.positionStart > extraRm.positionStart) { moveOp.positionStart -= extraRm.itemCount; } if (moveOp.itemCount > extraRm.positionStart) { moveOp.itemCount -= extraRm.itemCount; } } if (moveOp.positionStart > removeOp.positionStart) { moveOp.positionStart -= removeOp.itemCount; } if (moveOp.itemCount > removeOp.positionStart) { moveOp.itemCount -= removeOp.itemCount; } } else { if (extraRm != null) { if (moveOp.positionStart >= extraRm.positionStart) { moveOp.positionStart -= extraRm.itemCount; } if (moveOp.itemCount >= extraRm.positionStart) { moveOp.itemCount -= extraRm.itemCount; } } if (moveOp.positionStart >= removeOp.positionStart) { moveOp.positionStart -= removeOp.itemCount; } if (moveOp.itemCount >= removeOp.positionStart) { moveOp.itemCount -= removeOp.itemCount; } } list.set(movePos, removeOp); if (moveOp.positionStart != moveOp.itemCount) { list.set(removePos, moveOp); } else { list.remove(removePos); } if (extraRm != null) { list.add(movePos, extraRm); } } private void swapMoveAdd(List list, int move, UpdateOp moveOp, int add, UpdateOp addOp) { int offset = 0; // going in reverse, first revert the effect of add if (moveOp.itemCount < addOp.positionStart) { offset--; } if (moveOp.positionStart < addOp.positionStart) { offset++; } if (addOp.positionStart <= moveOp.positionStart) { moveOp.positionStart += addOp.itemCount; } if (addOp.positionStart <= moveOp.itemCount) { moveOp.itemCount += addOp.itemCount; } addOp.positionStart += offset; list.set(move, addOp); list.set(add, moveOp); } void swapMoveUpdate(List list, int move, UpdateOp moveOp, int update, UpdateOp updateOp) { UpdateOp extraUp1 = null; UpdateOp extraUp2 = null; // going in reverse, first revert the effect of add if (moveOp.itemCount < updateOp.positionStart) { updateOp.positionStart--; } else if (moveOp.itemCount < updateOp.positionStart + updateOp.itemCount) { // moved item is updated. add an update for it updateOp.itemCount--; extraUp1 = mCallback.obtainUpdateOp(UPDATE, moveOp.positionStart, 1, updateOp.payload); } // now affect of add is consumed. now apply effect of first remove if (moveOp.positionStart <= updateOp.positionStart) { updateOp.positionStart++; } else if (moveOp.positionStart < updateOp.positionStart + updateOp.itemCount) { final int remaining = updateOp.positionStart + updateOp.itemCount - moveOp.positionStart; extraUp2 = mCallback.obtainUpdateOp(UPDATE, moveOp.positionStart + 1, remaining, updateOp.payload); updateOp.itemCount -= remaining; } list.set(update, moveOp); if (updateOp.itemCount > 0) { list.set(move, updateOp); } else { list.remove(move); mCallback.recycleUpdateOp(updateOp); } if (extraUp1 != null) { list.add(move, extraUp1); } if (extraUp2 != null) { list.add(move, extraUp2); } } private int getLastMoveOutOfOrder(List list) { boolean foundNonMove = false; for (int i = list.size() - 1; i >= 0; i--) { final UpdateOp op1 = list.get(i); if (op1.cmd == MOVE) { if (foundNonMove) { return i; } } else { foundNonMove = true; } } return -1; } interface Callback { UpdateOp obtainUpdateOp(int cmd, int startPosition, int itemCount, Object payload); void recycleUpdateOp(UpdateOp op); } }