| /* |
| * 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<UpdateOp> 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<UpdateOp> 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<UpdateOp> 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<UpdateOp> 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<UpdateOp> 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<UpdateOp> 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); |
| } |
| } |