on xenial help sign in
TouchRegistry: remove null candidates from list of candidates Fixes: #1473492
47c389cDaniel d'Andrada
libs/UbuntuGestures/TouchRegistry.cpp+60−14
@@ -271,6 +271,8 @@ void TouchRegistry::addCandidateOwnerForTouch(int id, QQuickItem *candidate)
271 this, &TouchRegistry::rejectCandidateOwnerForTouch);
271 this, &TouchRegistry::rejectCandidateOwnerForTouch);
272
272
273 touchInfo->candidates.append(candidateInfo);
273 touchInfo->candidates.append(candidateInfo);
274+
275+ connect(candidate, &QObject::destroyed, this, [=](){ pruneNullCandidatesForTouch(id); });
274}
276}
275
277
276void TouchRegistry::addTouchWatcher(int touchId, QQuickItem *watcher)
278void TouchRegistry::addTouchWatcher(int touchId, QQuickItem *watcher)
@@ -296,25 +298,53 @@ void TouchRegistry::removeCandidateOwnerForTouch(int id, QQuickItem *candidate)
296 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
298 Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(id);
297 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
299 if (!touchInfo) { qFatal("TouchRegistry: Failed to find TouchInfo"); }
298
300
299− int indexRemoved = -1;
300
301
301 // TODO: check if the candidate is in fact the owner of the touch
302 // TODO: check if the candidate is in fact the owner of the touch
302
303
304+ bool removed = false;
303− for (int i = 0; i < touchInfo->candidates.count() && indexRemoved == -1; ++i) {
305+ for (int i = 0; i < touchInfo->candidates.count() && !removed; ++i) {
304− CandidateInfo &candidateInfo = touchInfo->candidates[i];
306+ if (touchInfo->candidates[i].item == candidate) {
305− if (candidateInfo.item == candidate) {
307+ removeCandidateOwnerForTouchByIndex(touchInfo, i);
306− Q_ASSERT(i > 0 || candidateInfo.state == CandidateInfo::Undecided);
308+ removed = true;
307− if (i == 0 && candidateInfo.state != CandidateInfo::Undecided) {
308− qCritical("TouchRegistry: touch owner is being removed.");
309− }
310− delete candidateInfo.inactivityTimer;
311− candidateInfo.inactivityTimer = nullptr;
312− touchInfo->candidates.removeAt(i);
313− indexRemoved = i;
314 }
309 }
315 }
310 }
311+}
312+
313+void TouchRegistry::pruneNullCandidatesForTouch(int touchId)
314+{
315+ #if TOUCHREGISTRY_DEBUG
316+ UG_DEBUG << "pruneNullCandidatesForTouch touchId" << touchId;
317+ #endif
318+
319+ Pool<TouchInfo>::Iterator touchInfo = findTouchInfo(touchId);
320+ if (!touchInfo) {
321+ // doesn't matter as touch is already gone.
322+ return;
323+ }
316
324
317− if (indexRemoved == 0) {
325+ int i = 0;
326+ while (i < touchInfo->candidates.count()) {
327+ if (touchInfo->candidates[i].item.isNull()) {
328+ removeCandidateOwnerForTouchByIndex(touchInfo, i);
329+ } else {
330+ ++i;
331+ }
332+ }
333+}
334+
335+void TouchRegistry::removeCandidateOwnerForTouchByIndex(Pool<TouchRegistry::TouchInfo>::Iterator &touchInfo,
336+ int candidateIndex)
337+{
338+ // TODO: check if the candidate is in fact the owner of the touch
339+
340+ Q_ASSERT(candidateIndex < touchInfo->candidates.count());
341+
342+ if (candidateIndex == 0 && touchInfo->candidates[candidateIndex].state != CandidateInfo::Undecided) {
343+ qCritical("TouchRegistry: touch owner is being removed.");
344+ }
345+ removeCandidateHelper(touchInfo, candidateIndex);
346+
347+ if (candidateIndex == 0) {
318 // the top candidate has been removed. if the new top candidate
348 // the top candidate has been removed. if the new top candidate
319 // wants the touch let him know he's now the owner.
349 // wants the touch let him know he's now the owner.
320 if (touchInfo->isOwned()) {
350 if (touchInfo->isOwned()) {
@@ -359,6 +389,7 @@ void TouchRegistry::requestTouchOwnership(int id, QQuickItem *candidate)
359 touchInfo->candidates.append(candidateInfo);
389 touchInfo->candidates.append(candidateInfo);
360 // it's the last one
390 // it's the last one
361 candidateIndex = touchInfo->candidates.count() - 1;
391 candidateIndex = touchInfo->candidates.count() - 1;
392+ connect(candidate, &QObject::destroyed, this, [=](){ pruneNullCandidatesForTouch(id); });
362 }
393 }
363
394
364 // If it's the top candidate it means it's now the owner. Let
395 // If it's the top candidate it means it's now the owner. Let
@@ -430,7 +461,7 @@ void TouchRegistry::rejectCandidateOwnerForTouch(int id, QQuickItem *candidate)
430 QCoreApplication::sendEvent(candidate, &lostOwnershipEvent);
461 QCoreApplication::sendEvent(candidate, &lostOwnershipEvent);
431 }
462 }
432
463
433− touchInfo->candidates.removeAt(rejectedCandidateIndex);
464+ removeCandidateHelper(touchInfo, rejectedCandidateIndex);
434
465
435 if (rejectedCandidateIndex == 0) {
466 if (rejectedCandidateIndex == 0) {
436 // the top candidate has been removed. if the new top candidate
467 // the top candidate has been removed. if the new top candidate
@@ -441,6 +472,21 @@ void TouchRegistry::rejectCandidateOwnerForTouch(int id, QQuickItem *candidate)
441 }
472 }
442}
473}
443
474
475+void TouchRegistry::removeCandidateHelper(Pool<TouchInfo>::Iterator &touchInfo, int candidateIndex)
476+{
477+ {
478+ CandidateInfo &candidateInfo = touchInfo->candidates[candidateIndex];
479+
480+ delete candidateInfo.inactivityTimer;
481+ candidateInfo.inactivityTimer = nullptr;
482+
483+ if (candidateInfo.item) {
484+ disconnect(candidateInfo.item.data(), nullptr, this, nullptr);
485+ }
486+ }
487+ touchInfo->candidates.removeAt(candidateIndex);
488+}
489+
444////////////////////////////////////// TouchRegistry::TouchInfo ////////////////////////////////////
490////////////////////////////////////// TouchRegistry::TouchInfo ////////////////////////////////////
445
491
446TouchRegistry::TouchInfo::TouchInfo(int id)
492TouchRegistry::TouchInfo::TouchInfo(int id)
libs/UbuntuGestures/TouchRegistry.h+4−1
@@ -149,7 +149,6 @@ private:
149 // yet because of undecided candidates higher up.
149 // yet because of undecided candidates higher up.
150 InterimOwner = 2
150 InterimOwner = 2
151 } state;
151 } state;
152− // TODO: Prune candidates that become null and resolve ownership accordingly.
153 QPointer<QQuickItem> item;
152 QPointer<QQuickItem> item;
154 QPointer<UbuntuGestures::CandidateInactivityTimer> inactivityTimer;
153 QPointer<UbuntuGestures::CandidateInactivityTimer> inactivityTimer;
155 };
154 };
@@ -172,6 +171,10 @@ private:
172 QList<QPointer<QQuickItem>> watchers;
171 QList<QPointer<QQuickItem>> watchers;
173 };
172 };
174
173
174+ void pruneNullCandidatesForTouch(int touchId);
175+ void removeCandidateOwnerForTouchByIndex(Pool<TouchInfo>::Iterator &touchInfo, int candidateIndex);
176+ void removeCandidateHelper(Pool<TouchInfo>::Iterator &touchInfo, int candidateIndex);
177+
175 Pool<TouchInfo>::Iterator findTouchInfo(int id);
178 Pool<TouchInfo>::Iterator findTouchInfo(int id);
176
179
177 void deliverTouchUpdatesToUndecidedCandidatesAndWatchers(const QTouchEvent *event);
180 void deliverTouchUpdatesToUndecidedCandidatesAndWatchers(const QTouchEvent *event);
tests/libs/UbuntuGestures/tst_TouchRegistry.cpp+47
@@ -67,6 +67,7 @@ private Q_SLOTS:
67 void rejectingTouchfterItsEnd();
67 void rejectingTouchfterItsEnd();
68 void removeOldUndecidedCandidates();
68 void removeOldUndecidedCandidates();
69 void interimOwnerWontGetUnownedTouchEvents();
69 void interimOwnerWontGetUnownedTouchEvents();
70+ void candidateVanishes();
70
71
71private:
72private:
72 TouchRegistry *touchRegistry;
73 TouchRegistry *touchRegistry;
@@ -817,6 +818,52 @@ void tst_TouchRegistry::interimOwnerWontGetUnownedTouchEvents()
817 QCOMPARE(interimOwner.unownedTouchEvents.count(), 0);
818 QCOMPARE(interimOwner.unownedTouchEvents.count(), 0);
818}
819}
819
820
821+/*
822+ Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1473492
823+
824+ Bug caused by candidates getting deleted before ownership resolution
825+ */
826+void tst_TouchRegistry::candidateVanishes()
827+{
828+ DummyCandidate mainCandidate;
829+ DummyCandidate *missingCandidate = new DummyCandidate;
830+
831+ {
832+ QList<QTouchEvent::TouchPoint> touchPoints;
833+ touchPoints.append(QTouchEvent::TouchPoint(0));
834+ touchPoints[0].setState(Qt::TouchPointPressed);
835+ QTouchEvent touchEvent(QEvent::TouchBegin,
836+ 0 /* device */,
837+ Qt::NoModifier,
838+ Qt::TouchPointPressed,
839+ touchPoints);
840+ touchRegistry->update(&touchEvent);
841+ }
842+
843+ touchRegistry->addCandidateOwnerForTouch(0, &mainCandidate);
844+ touchRegistry->addCandidateOwnerForTouch(0, missingCandidate);
845+
846+ {
847+ QList<QTouchEvent::TouchPoint> touchPoints;
848+ touchPoints.append(QTouchEvent::TouchPoint(0));
849+ touchPoints[0].setState(Qt::TouchPointMoved);
850+ QTouchEvent touchEvent(QEvent::TouchUpdate,
851+ 0 /* device */,
852+ Qt::NoModifier,
853+ Qt::TouchPointMoved,
854+ touchPoints);
855+ touchRegistry->update(&touchEvent);
856+ }
857+
858+ // This candidate suddenly disappears without having removed its candidacy.
859+ delete missingCandidate;
860+ missingCandidate = nullptr;
861+
862+ touchRegistry->requestTouchOwnership(0, &mainCandidate);
863+
864+ QVERIFY(mainCandidate.ownedTouches.contains(0));
865+}
866+
820////////////// TouchMemento //////////
867////////////// TouchMemento //////////
821
868
822TouchMemento::TouchMemento(const QTouchEvent *touchEvent)
869TouchMemento::TouchMemento(const QTouchEvent *touchEvent)