diff options
author | Roman Birg <roman@cyngn.com> | 2016-04-14 20:06:03 -0700 |
---|---|---|
committer | Roman Birg <roman@cyngn.com> | 2016-04-18 00:13:42 -0700 |
commit | a88d902d7bc88f8b885d06468a8fa5e1498f3b23 (patch) | |
tree | 39e1c5a419d3dd0c84ceaf3ef3d1f90540663497 /packages/SystemUI/src/com/android/systemui/qs | |
parent | 0053be36623525909784d83979a4f9aa42ba17e2 (diff) | |
download | frameworks_base-a88d902d7bc88f8b885d06468a8fa5e1498f3b23.zip frameworks_base-a88d902d7bc88f8b885d06468a8fa5e1498f3b23.tar.gz frameworks_base-a88d902d7bc88f8b885d06468a8fa5e1498f3b23.tar.bz2 |
SystemUI: fix some qs tile race conditions
Whenever setTiles() was called, we assumed all the tile animations had
finished, but if the eager beaver had grabbed a tile and dropped it on the
remove target before waiting for all the animations to complete, a lot of
assumptions are broken and crashes happen. Mainly because tiles aren't removed
from pages and we try to add them to a different page (like if they
needed to be placed a page back).
Implemented cancel for ongoing animations, which we call whenever
setTiles comes in. So all tiles should be in their proper state before
we try to cache/re-add them. Also cleaned up and documented setTiles().
Also, don't try to place the edit tile in the proper place, just add it
to the list if it's not present.
Ticket: CYNGNOS-2472
Change-Id: I5c066abbc16f1fe7173525ea6a8a8b39460461ae
Signed-off-by: Roman Birg <roman@cyngn.com>
Diffstat (limited to 'packages/SystemUI/src/com/android/systemui/qs')
-rw-r--r-- | packages/SystemUI/src/com/android/systemui/qs/QSDragPanel.java | 255 |
1 files changed, 175 insertions, 80 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDragPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSDragPanel.java index 86fc49e..ee47339 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSDragPanel.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSDragPanel.java @@ -19,10 +19,8 @@ package com.android.systemui.qs; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.app.ActivityManager; -import android.app.AlertDialog; import android.content.ContentResolver; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Configuration; @@ -32,12 +30,10 @@ import android.graphics.Color; import android.graphics.Point; import android.graphics.PointF; import android.graphics.PorterDuff; -import android.graphics.drawable.Animatable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.UserHandle; -import android.support.v4.graphics.drawable.DrawableCompat; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.util.ArrayMap; @@ -49,7 +45,6 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.BaseExpandableListAdapter; -import android.widget.EditText; import android.widget.ExpandableListView; import android.widget.ImageView; import android.widget.LinearLayout; @@ -59,17 +54,16 @@ import com.android.systemui.FontSizeUtils; import com.android.systemui.R; import com.android.systemui.cm.UserContentObserver; import com.android.systemui.qs.tiles.EditTile; -import com.android.systemui.qs.tiles.IntentTile; import com.android.systemui.settings.BrightnessController; import com.android.systemui.settings.ToggleSlider; import com.android.systemui.statusbar.phone.QSTileHost; -import com.android.systemui.statusbar.phone.SystemUIDialog; import com.android.systemui.statusbar.policy.BrightnessMirrorController; import com.android.systemui.tuner.QsTuner; import com.viewpagerindicator.CirclePageIndicator; import cyanogenmod.app.StatusBarPanelCustomTile; import cyanogenmod.providers.CMSettings; import org.cyanogenmod.internal.logging.CMMetricsLogger; +import org.cyanogenmod.internal.util.QSConstants; import org.cyanogenmod.internal.util.QSUtils; import java.util.ArrayList; @@ -355,7 +349,7 @@ public class QSDragPanel extends QSPanel implements View.OnDragListener, View.On @Override public boolean hasOverlappingRendering() { - return mClipper.isAnimating() || mEditing; + return mClipper.isAnimating() || mEditing || !mCurrentlyAnimating.isEmpty(); } @Override @@ -465,7 +459,6 @@ public class QSDragPanel extends QSPanel implements View.OnDragListener, View.On mLastRightShift = -1; mQsPanelTop.onStopDrag(); - requestLayout(); } protected View getDropTarget() { @@ -501,14 +494,16 @@ public class QSDragPanel extends QSPanel implements View.OnDragListener, View.On } public void setTiles(final Collection<QSTile<?>> tilesCollection) { - final List<QSTile<?>> tiles = new ArrayList<>(tilesCollection); + // we try to be as efficient as possible here because this can happen while the user + // is in edit mode, or maybe even while tiles are animating + // step 1: stop all animations + // step 2: remove tiles no longer to be used, cache ones that are still valid + // step 3: remove empty viewpager pages + // step 4: generate new tiles, re-add cached ones + if (DEBUG_TILES) { - Log.i(TAG, "setTiles() called with " + "tiles = [" - + tiles + "]"); + Log.i(TAG, "setTiles() called with tiles = [" + tilesCollection + "]"); } - - int currentViewPagerPage = mViewPager.getCurrentItem(); - if (mLastDragRecord != null && mRecords.indexOf(mLastDragRecord) == -1) { // the last removed record might be stored in mLastDragRecord if we just shifted // re-add it to the list so we'll clean it up below @@ -516,25 +511,41 @@ public class QSDragPanel extends QSPanel implements View.OnDragListener, View.On mLastDragRecord = null; } - Map<QSTile<?>, DragTileRecord> recordMap = new ArrayMap<>(); + // step kinda-1 + if (mDraggingRecord != null) { + // dragging record might be animating back, force it to finished position + mDraggingRecord.tileView.animate().cancel(); + } + + int currentViewPagerPage = mViewPager.getCurrentItem(); + int removedPages = 0; + + Map<QSTile<?>, DragTileRecord> cachedRecords = new ArrayMap<>(); ListIterator<TileRecord> iterator = mRecords.listIterator(mRecords.size()); int recordsRemoved = 0; // cleanup current records - while (iterator.hasPrevious()) { + while (iterator.hasPrevious()) { // mRecords DragTileRecord dr = (DragTileRecord) iterator.previous(); - if (dr.page >= 0) { - // clean up view - mPages.get(dr.page).removeView(dr.tileView); - } + // step 1 + dr.tileView.animate().cancel(); - if (tiles.contains(dr.tile)) { + // step 2 + if (tilesCollection.contains(dr.tile)) { if (DEBUG_TILES) { Log.i(TAG, "caching tile: " + dr.tile); } - recordMap.put(dr.tile, dr); + cachedRecords.put(dr.tile, dr); } else { + if (dr.page >= 0) { + if (DEBUG_TILES) { + Log.w(TAG, "removed dr.tileView: " + dr.tileView + " from page: " + + dr.page + " (dest page: " + dr.destinationPage + ")"); + } + + removeTileView(dr.tileView); + } if (DEBUG_TILES) { Log.i(TAG, "removing tile: " + dr.tile); } @@ -543,74 +554,92 @@ public class QSDragPanel extends QSPanel implements View.OnDragListener, View.On iterator.remove(); recordsRemoved++; - if (dr.page >= getCurrentMaxPageCount() - 1) { - final int childCount = mPages.get(dr.page).getChildCount(); - - if (childCount == 0) { - final int currentIndex = mViewPager.getCurrentItem(); - if (currentIndex > 0 && currentViewPagerPage == currentIndex) { - // if we are about to remove the page we are currently on, move back - currentViewPagerPage--; - } - final int pageIndex = dr.page + (mEditing ? 1 : 0); - mPagerAdapter.startUpdate(mViewPager); - mPagerAdapter.destroyItem(mViewPager, pageIndex, mPages.get(dr.page)); - mPagerAdapter.finishUpdate(mViewPager); - mPagerAdapter.notifyDataSetChanged(); - } - } + dr.page = -1; + dr.destinationPage = -1; } - dr.page = -1; - dr.destinationPage = -1; } - // at this point recordMap should have all retained tiles, no new or old tiles - int delta = tiles.size() - recordMap.size() - recordsRemoved; + // at this point cachedRecords should have all retained tiles, no new or old tiles + int delta = tilesCollection.size() - cachedRecords.size() - recordsRemoved; if (DEBUG_TILES) { Log.i(TAG, "record map delta: " + delta); } - mRecords.ensureCapacity(tiles.size()); - mPagerAdapter.notifyDataSetChanged(); + // step 3 + final Iterator<QSPage> pageIterator = mPages.iterator(); + while (pageIterator.hasNext()) { + final QSPage page = pageIterator.next(); + final int viewpagerIndex = page.getPageIndex() + (mEditing ? 1 : 0); + final int childCount = page.getChildCount(); - // even though we explicitly destroy old pages, without this call, - // the viewpager doesn't seem to want to pick up the fact that we have less pages - // and allows "empty" scrolls to the right where there is no page. - mViewPager.setAdapter(mPagerAdapter); + if (DEBUG_TILES) { + Log.d(TAG, "page " + viewpagerIndex + " has " + childCount); + } + if (page.getPageIndex() >= getCurrentMaxPageCount() - 1) { + if (DEBUG_TILES) { + Log.d(TAG, "page : " + page + " has " + childCount + " children"); + } + if (childCount == 0) { + removedPages++; + + page.removeAllViews(); + mPagerAdapter.startUpdate(mViewPager); + mPagerAdapter.destroyItem(mViewPager, viewpagerIndex, page); + mPagerAdapter.finishUpdate(mViewPager); + mPagerAdapter.notifyDataSetChanged(); + } + } + } + + if (removedPages > 0) { + // even though we explicitly destroy old pages, without this call, + // the viewpager doesn't seem to want to pick up the fact that we have less pages + // and allows "empty" scrolls to the right where there is no page. + if (DEBUG_TILES) { + Log.d(TAG, "re-setting adapter, page: " + currentViewPagerPage); + } + mViewPager.setAdapter(mPagerAdapter); + mViewPager.setCurrentItem(Math.min(currentViewPagerPage, mPagerAdapter.getCount()), + false); + mPagerAdapter.notifyDataSetChanged(); + } + + // step 4 + mRecords.ensureCapacity(tilesCollection.size()); + int runningCount = 0; - // add new tiles - for (int i = 0; i < tiles.size(); i++) { - QSTile<?> tile = tiles.get(i); - final int tileDestPage = getPagesForCount(i + 1) - 1; + final Iterator<QSTile<?>> newTileIterator = tilesCollection.iterator(); + while (newTileIterator.hasNext()) { + QSTile<?> tile = newTileIterator.next(); + final int tileDestPage = getPagesForCount(runningCount + 1) - 1; if (DEBUG_TILES) { - Log.d(TAG, "tile at : " + i + ": " + tile + " to dest page: " + tileDestPage); + Log.d(TAG, "tile at : " + runningCount + ": " + tile + + " to dest page: " + tileDestPage); } DragTileRecord record; - if (!recordMap.containsKey(tile)) { + if (!cachedRecords.containsKey(tile)) { if (DEBUG_TILES) { - Log.d(TAG, "tile at: " + i + " not cached, adding it to records"); + Log.d(TAG, "tile at: " + runningCount + " not cached, adding it to records"); } record = makeRecord(tile); record.destinationPage = tileDestPage; - recordMap.put(tile, record); - mRecords.add(i, record); + mRecords.add(runningCount, record); mPagerAdapter.notifyDataSetChanged(); } else { - record = recordMap.get(tile); + record = cachedRecords.get(tile); if (DEBUG_TILES) { - Log.d(TAG, "tile at : " + i + ": cached, restoring: " + record); + Log.d(TAG, "tile at : " + runningCount + ": cached, restoring: " + record); } - int indexOf = mRecords.indexOf(record); - if (indexOf != i) { - if (DEBUG_TILES) { - Log.w(TAG, "moving index of " + record + " from " - + indexOf + " to " + i); - } - Collections.swap(mRecords, indexOf, i); - } + mPages.get(record.page).removeView(record.tileView); + + record.page = -1; record.destinationPage = tileDestPage; + + mRecords.remove(record); + mRecords.add(runningCount, record); + mPagerAdapter.notifyDataSetChanged(); } if (record.page == -1) { // add the view @@ -620,11 +649,9 @@ public class QSDragPanel extends QSPanel implements View.OnDragListener, View.On Log.d(TAG, "added view " + record); } } + runningCount++; } - // restore the visible page - mViewPager.setCurrentItem(currentViewPagerPage, false); - if (isShowingDetail()) { mDetail.bringToFront(); } @@ -718,10 +745,18 @@ public class QSDragPanel extends QSPanel implements View.OnDragListener, View.On return r; } + private void removeTileView(QSTileView v) { + for (QSPage page : mPages) { + page.removeView(v); + page.removeTransientView(v); + } + + } + private void removeDraggingRecord() { // what spec is this tile? String spec = mHost.getSpec(mDraggingRecord.tile); - if (DEBUG_DRAG) { + if (DEBUG_TILES) { Log.w(TAG, "removing tile: " + mDraggingRecord + " with spec: " + spec); } onStopDrag(); @@ -1014,7 +1049,7 @@ public class QSDragPanel extends QSPanel implements View.OnDragListener, View.On } if (originatingTileEvent && !event.getResult()) { // view pager probably ate the event - restoreDraggingTilePosition(v); + restoreDraggingTilePosition(v, null); } break; @@ -1032,15 +1067,27 @@ public class QSDragPanel extends QSPanel implements View.OnDragListener, View.On Log.d(TAG, "dropping on delete target!!"); } if (mDraggingRecord.tile instanceof EditTile) { + final QSTileView editTileView = mDraggingRecord.tileView; + mQsPanelTop.toast(R.string.quick_settings_cannot_delete_edit_tile); - restoreDraggingTilePosition(v); + restoreDraggingTilePosition(v, new Runnable() { + @Override + public void run() { + // move edit tile to the back + final TileRecord editTile = getRecord(editTileView); + if (mRecords.remove(editTile)) { + // we depend on mHost.setTiles() placing it on the end + persistRecords(); + } + } + }); break; } else { mRestored = true; removeDraggingRecord(); } } else { - restoreDraggingTilePosition(v); + restoreDraggingTilePosition(v, null); } break; @@ -1171,7 +1218,7 @@ public class QSDragPanel extends QSPanel implements View.OnDragListener, View.On return false; } - private void restoreDraggingTilePosition(View v) { + private void restoreDraggingTilePosition(View v, final Runnable onAnimationFinishedRunnable) { if (mRestored) { return; } @@ -1251,6 +1298,20 @@ public class QSDragPanel extends QSPanel implements View.OnDragListener, View.On } @Override + public void onAnimationCancel(Animator animation) { + mViewPager.requestDisallowInterceptTouchEvent(false); + removeTransientView(mDraggingRecord.tileView); + mCurrentlyAnimating.remove(mDraggingRecord); + mRestoring = false; + mPagerAdapter.notifyDataSetChanged(); + onStopDrag(); + + if (onAnimationFinishedRunnable != null) { + postOnAnimation(onAnimationFinishedRunnable); + } + } + + @Override public void onAnimationEnd(Animator animation) { mViewPager.requestDisallowInterceptTouchEvent(false); @@ -1265,8 +1326,8 @@ public class QSDragPanel extends QSPanel implements View.OnDragListener, View.On Log.i(TAG, "drag record was attached"); } } - mDraggingRecord.page = mDraggingRecord.destinationPage; targetP.addView(mDraggingRecord.tileView); + mDraggingRecord.page = mDraggingRecord.destinationPage; mDraggingRecord.tileView.setX(mDraggingRecord.destination.x); // reset this to be in the coords of the page, not viewpager anymore @@ -1281,6 +1342,12 @@ public class QSDragPanel extends QSPanel implements View.OnDragListener, View.On mPagerAdapter.notifyDataSetChanged(); } onStopDrag(); + + if (onAnimationFinishedRunnable != null) { + postOnAnimation(onAnimationFinishedRunnable); + } else { + requestLayout(); + } } }); } @@ -1497,10 +1564,16 @@ public class QSDragPanel extends QSPanel implements View.OnDragListener, View.On .y(ti.destination.y) .setListener(new AnimatorListenerAdapter() { @Override + public void onAnimationCancel(Animator animation) { + tilePageSource.removeTransientView(ti.tileView); + mCurrentlyAnimating.remove(ti); + } + + @Override public void onAnimationEnd(Animator animation) { tilePageSource.removeTransientView(ti.tileView); - ti.page = tilePageTarget.getPageIndex(); tilePageTarget.addView(ti.tileView); + ti.page = tilePageTarget.getPageIndex(); ti.tileView.setX(ti.destination.x); ti.tileView.setY(ti.destination.y); @@ -1516,6 +1589,11 @@ public class QSDragPanel extends QSPanel implements View.OnDragListener, View.On .y(ti.destination.y) .setListener(new AnimatorListenerAdapter() { @Override + public void onAnimationCancel(Animator animation) { + mCurrentlyAnimating.remove(ti); + } + + @Override public void onAnimationEnd(Animator animation) { mCurrentlyAnimating.remove(ti); final boolean dual = getPage(ti.destinationPage).dualRecord(ti); @@ -1551,10 +1629,16 @@ public class QSDragPanel extends QSPanel implements View.OnDragListener, View.On .y(last.destination.y) .setListener(new AnimatorListenerAdapter() { @Override + public void onAnimationCancel(Animator animation) { + tilePageSource.removeTransientView(last.tileView); + mCurrentlyAnimating.remove(last); + } + + @Override public void onAnimationEnd(Animator animation) { tilePageSource.removeTransientView(last.tileView); - last.page = tilePageTarget.getPageIndex(); tilePageTarget.addView(last.tileView); + last.page = tilePageTarget.getPageIndex(); last.tileView.setX(last.destination.x); last.tileView.setY(last.destination.y); @@ -1573,6 +1657,11 @@ public class QSDragPanel extends QSPanel implements View.OnDragListener, View.On .y(last.destination.y) .setListener(new AnimatorListenerAdapter() { @Override + public void onAnimationCancel(Animator animation) { + mCurrentlyAnimating.remove(last); + } + + @Override public void onAnimationEnd(Animator animation) { if (DEBUG_DRAG) { Log.i(TAG, "shift finished: " + last); @@ -1654,10 +1743,16 @@ public class QSDragPanel extends QSPanel implements View.OnDragListener, View.On } @Override + public void onAnimationCancel(Animator animation) { + page.removeTransientView(ti.tileView); + mCurrentlyAnimating.remove(ti); + } + + @Override public void onAnimationEnd(Animator animation) { page.removeTransientView(ti.tileView); - ti.page = page.getPageIndex(); page.addView(ti.tileView); + ti.page = page.getPageIndex(); mCurrentlyAnimating.remove(ti); requestLayout(); |