PointerController.cpp revision 778e3b91acad6da04341d439f0c66a4fd09def4f
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "PointerController"
18
19//#define LOG_NDEBUG 0
20
21// Log debug messages about pointer updates
22#define DEBUG_POINTER_UPDATES 0
23
24#include "PointerController.h"
25
26#include <cutils/log.h>
27
28#pragma GCC diagnostic push
29#pragma GCC diagnostic ignored "-Wunused-parameter"
30#include <SkBitmap.h>
31#include <SkCanvas.h>
32#include <SkColor.h>
33#include <SkPaint.h>
34#include <SkXfermode.h>
35#pragma GCC diagnostic pop
36
37namespace android {
38
39// --- PointerController ---
40
41// Time to wait before starting the fade when the pointer is inactive.
42static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL = 15 * 1000 * 1000000LL; // 15 seconds
43static const nsecs_t INACTIVITY_TIMEOUT_DELAY_TIME_SHORT = 3 * 1000 * 1000000LL; // 3 seconds
44
45// Time to spend fading out the spot completely.
46static const nsecs_t SPOT_FADE_DURATION = 200 * 1000000LL; // 200 ms
47
48// Time to spend fading out the pointer completely.
49static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms
50
51// The number of events to be read at once for DisplayEventReceiver.
52static const int EVENT_BUFFER_SIZE = 100;
53
54// --- PointerController ---
55
56PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy,
57        const sp<Looper>& looper, const sp<SpriteController>& spriteController) :
58        mPolicy(policy), mLooper(looper), mSpriteController(spriteController) {
59    mHandler = new WeakMessageHandler(this);
60
61    if (mDisplayEventReceiver.initCheck() == NO_ERROR) {
62        mLooper->addFd(mDisplayEventReceiver.getFd(), Looper::POLL_CALLBACK,
63                       Looper::EVENT_INPUT, this, nullptr);
64    } else {
65        ALOGE("Failed to initialize DisplayEventReceiver.");
66    }
67
68    AutoMutex _l(mLock);
69
70    mLocked.animationPending = false;
71
72    mLocked.displayWidth = -1;
73    mLocked.displayHeight = -1;
74    mLocked.displayOrientation = DISPLAY_ORIENTATION_0;
75
76    mLocked.presentation = PRESENTATION_POINTER;
77    mLocked.presentationChanged = false;
78
79    mLocked.inactivityTimeout = INACTIVITY_TIMEOUT_NORMAL;
80
81    mLocked.pointerFadeDirection = 0;
82    mLocked.pointerX = 0;
83    mLocked.pointerY = 0;
84    mLocked.pointerAlpha = 0.0f; // pointer is initially faded
85    mLocked.pointerSprite = mSpriteController->createSprite();
86    mLocked.pointerIconChanged = false;
87    mLocked.requestedPointerType= mPolicy->getDefaultPointerIconId();
88
89    mLocked.animationFrameIndex = 0;
90    mLocked.lastFrameUpdatedTime = 0;
91
92    mLocked.buttonState = 0;
93
94    mPolicy->loadPointerIcon(&mLocked.pointerIcon);
95
96    loadResources();
97
98    if (mLocked.pointerIcon.isValid()) {
99        mLocked.pointerIconChanged = true;
100        updatePointerLocked();
101    }
102}
103
104PointerController::~PointerController() {
105    mLooper->removeMessages(mHandler);
106
107    AutoMutex _l(mLock);
108
109    mLocked.pointerSprite.clear();
110
111    for (size_t i = 0; i < mLocked.spots.size(); i++) {
112        delete mLocked.spots.itemAt(i);
113    }
114    mLocked.spots.clear();
115    mLocked.recycledSprites.clear();
116}
117
118bool PointerController::getBounds(float* outMinX, float* outMinY,
119        float* outMaxX, float* outMaxY) const {
120    AutoMutex _l(mLock);
121
122    return getBoundsLocked(outMinX, outMinY, outMaxX, outMaxY);
123}
124
125bool PointerController::getBoundsLocked(float* outMinX, float* outMinY,
126        float* outMaxX, float* outMaxY) const {
127    if (mLocked.displayWidth <= 0 || mLocked.displayHeight <= 0) {
128        return false;
129    }
130
131    *outMinX = 0;
132    *outMinY = 0;
133    switch (mLocked.displayOrientation) {
134    case DISPLAY_ORIENTATION_90:
135    case DISPLAY_ORIENTATION_270:
136        *outMaxX = mLocked.displayHeight - 1;
137        *outMaxY = mLocked.displayWidth - 1;
138        break;
139    default:
140        *outMaxX = mLocked.displayWidth - 1;
141        *outMaxY = mLocked.displayHeight - 1;
142        break;
143    }
144    return true;
145}
146
147void PointerController::move(float deltaX, float deltaY) {
148#if DEBUG_POINTER_UPDATES
149    ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY);
150#endif
151    if (deltaX == 0.0f && deltaY == 0.0f) {
152        return;
153    }
154
155    AutoMutex _l(mLock);
156
157    setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY);
158}
159
160void PointerController::setButtonState(int32_t buttonState) {
161#if DEBUG_POINTER_UPDATES
162    ALOGD("Set button state 0x%08x", buttonState);
163#endif
164    AutoMutex _l(mLock);
165
166    if (mLocked.buttonState != buttonState) {
167        mLocked.buttonState = buttonState;
168    }
169}
170
171int32_t PointerController::getButtonState() const {
172    AutoMutex _l(mLock);
173
174    return mLocked.buttonState;
175}
176
177void PointerController::setPosition(float x, float y) {
178#if DEBUG_POINTER_UPDATES
179    ALOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y);
180#endif
181    AutoMutex _l(mLock);
182
183    setPositionLocked(x, y);
184}
185
186void PointerController::setPositionLocked(float x, float y) {
187    float minX, minY, maxX, maxY;
188    if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
189        if (x <= minX) {
190            mLocked.pointerX = minX;
191        } else if (x >= maxX) {
192            mLocked.pointerX = maxX;
193        } else {
194            mLocked.pointerX = x;
195        }
196        if (y <= minY) {
197            mLocked.pointerY = minY;
198        } else if (y >= maxY) {
199            mLocked.pointerY = maxY;
200        } else {
201            mLocked.pointerY = y;
202        }
203        updatePointerLocked();
204    }
205}
206
207void PointerController::getPosition(float* outX, float* outY) const {
208    AutoMutex _l(mLock);
209
210    *outX = mLocked.pointerX;
211    *outY = mLocked.pointerY;
212}
213
214void PointerController::fade(Transition transition) {
215    AutoMutex _l(mLock);
216
217    // Remove the inactivity timeout, since we are fading now.
218    removeInactivityTimeoutLocked();
219
220    // Start fading.
221    if (transition == TRANSITION_IMMEDIATE) {
222        mLocked.pointerFadeDirection = 0;
223        mLocked.pointerAlpha = 0.0f;
224        updatePointerLocked();
225    } else {
226        mLocked.pointerFadeDirection = -1;
227        startAnimationLocked();
228    }
229}
230
231void PointerController::unfade(Transition transition) {
232    AutoMutex _l(mLock);
233
234    // Always reset the inactivity timer.
235    resetInactivityTimeoutLocked();
236
237    // Start unfading.
238    if (transition == TRANSITION_IMMEDIATE) {
239        mLocked.pointerFadeDirection = 0;
240        mLocked.pointerAlpha = 1.0f;
241        updatePointerLocked();
242    } else {
243        mLocked.pointerFadeDirection = 1;
244        startAnimationLocked();
245    }
246}
247
248void PointerController::setPresentation(Presentation presentation) {
249    AutoMutex _l(mLock);
250
251    if (presentation == PRESENTATION_POINTER && mLocked.additionalMouseResources.empty()) {
252        mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
253                                              &mLocked.animationResources);
254    }
255
256    if (mLocked.presentation != presentation) {
257        mLocked.presentation = presentation;
258        mLocked.presentationChanged = true;
259
260        if (presentation != PRESENTATION_SPOT) {
261            fadeOutAndReleaseAllSpotsLocked();
262        }
263
264        updatePointerLocked();
265    }
266}
267
268void PointerController::setSpots(const PointerCoords* spotCoords,
269        const uint32_t* spotIdToIndex, BitSet32 spotIdBits) {
270#if DEBUG_POINTER_UPDATES
271    ALOGD("setSpots: idBits=%08x", spotIdBits.value);
272    for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
273        uint32_t id = idBits.firstMarkedBit();
274        idBits.clearBit(id);
275        const PointerCoords& c = spotCoords[spotIdToIndex[id]];
276        ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f", id,
277                c.getAxisValue(AMOTION_EVENT_AXIS_X),
278                c.getAxisValue(AMOTION_EVENT_AXIS_Y),
279                c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
280    }
281#endif
282
283    AutoMutex _l(mLock);
284
285    mSpriteController->openTransaction();
286
287    // Add or move spots for fingers that are down.
288    for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
289        uint32_t id = idBits.clearFirstMarkedBit();
290        const PointerCoords& c = spotCoords[spotIdToIndex[id]];
291        const SpriteIcon& icon = c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE) > 0
292                ? mResources.spotTouch : mResources.spotHover;
293        float x = c.getAxisValue(AMOTION_EVENT_AXIS_X);
294        float y = c.getAxisValue(AMOTION_EVENT_AXIS_Y);
295
296        Spot* spot = getSpotLocked(id);
297        if (!spot) {
298            spot = createAndAddSpotLocked(id);
299        }
300
301        spot->updateSprite(&icon, x, y);
302    }
303
304    // Remove spots for fingers that went up.
305    for (size_t i = 0; i < mLocked.spots.size(); i++) {
306        Spot* spot = mLocked.spots.itemAt(i);
307        if (spot->id != Spot::INVALID_ID
308                && !spotIdBits.hasBit(spot->id)) {
309            fadeOutAndReleaseSpotLocked(spot);
310        }
311    }
312
313    mSpriteController->closeTransaction();
314}
315
316void PointerController::clearSpots() {
317#if DEBUG_POINTER_UPDATES
318    ALOGD("clearSpots");
319#endif
320
321    AutoMutex _l(mLock);
322
323    fadeOutAndReleaseAllSpotsLocked();
324}
325
326void PointerController::setInactivityTimeout(InactivityTimeout inactivityTimeout) {
327    AutoMutex _l(mLock);
328
329    if (mLocked.inactivityTimeout != inactivityTimeout) {
330        mLocked.inactivityTimeout = inactivityTimeout;
331        resetInactivityTimeoutLocked();
332    }
333}
334
335void PointerController::reloadPointerResources() {
336    AutoMutex _l(mLock);
337
338    loadResources();
339
340    if (mLocked.presentation == PRESENTATION_POINTER) {
341        mLocked.additionalMouseResources.clear();
342        mLocked.animationResources.clear();
343        mPolicy->loadPointerIcon(&mLocked.pointerIcon);
344        mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources,
345                                              &mLocked.animationResources);
346    }
347
348    mLocked.presentationChanged = true;
349    updatePointerLocked();
350}
351
352void PointerController::setDisplayViewport(int32_t width, int32_t height, int32_t orientation) {
353    AutoMutex _l(mLock);
354
355    // Adjust to use the display's unrotated coordinate frame.
356    if (orientation == DISPLAY_ORIENTATION_90
357            || orientation == DISPLAY_ORIENTATION_270) {
358        int32_t temp = height;
359        height = width;
360        width = temp;
361    }
362
363    if (mLocked.displayWidth != width || mLocked.displayHeight != height) {
364        mLocked.displayWidth = width;
365        mLocked.displayHeight = height;
366
367        float minX, minY, maxX, maxY;
368        if (getBoundsLocked(&minX, &minY, &maxX, &maxY)) {
369            mLocked.pointerX = (minX + maxX) * 0.5f;
370            mLocked.pointerY = (minY + maxY) * 0.5f;
371        } else {
372            mLocked.pointerX = 0;
373            mLocked.pointerY = 0;
374        }
375
376        fadeOutAndReleaseAllSpotsLocked();
377    }
378
379    if (mLocked.displayOrientation != orientation) {
380        // Apply offsets to convert from the pixel top-left corner position to the pixel center.
381        // This creates an invariant frame of reference that we can easily rotate when
382        // taking into account that the pointer may be located at fractional pixel offsets.
383        float x = mLocked.pointerX + 0.5f;
384        float y = mLocked.pointerY + 0.5f;
385        float temp;
386
387        // Undo the previous rotation.
388        switch (mLocked.displayOrientation) {
389        case DISPLAY_ORIENTATION_90:
390            temp = x;
391            x = mLocked.displayWidth - y;
392            y = temp;
393            break;
394        case DISPLAY_ORIENTATION_180:
395            x = mLocked.displayWidth - x;
396            y = mLocked.displayHeight - y;
397            break;
398        case DISPLAY_ORIENTATION_270:
399            temp = x;
400            x = y;
401            y = mLocked.displayHeight - temp;
402            break;
403        }
404
405        // Perform the new rotation.
406        switch (orientation) {
407        case DISPLAY_ORIENTATION_90:
408            temp = x;
409            x = y;
410            y = mLocked.displayWidth - temp;
411            break;
412        case DISPLAY_ORIENTATION_180:
413            x = mLocked.displayWidth - x;
414            y = mLocked.displayHeight - y;
415            break;
416        case DISPLAY_ORIENTATION_270:
417            temp = x;
418            x = mLocked.displayHeight - y;
419            y = temp;
420            break;
421        }
422
423        // Apply offsets to convert from the pixel center to the pixel top-left corner position
424        // and save the results.
425        mLocked.pointerX = x - 0.5f;
426        mLocked.pointerY = y - 0.5f;
427        mLocked.displayOrientation = orientation;
428    }
429
430    updatePointerLocked();
431}
432
433void PointerController::updatePointerIcon(int32_t iconId) {
434    AutoMutex _l(mLock);
435    if (mLocked.requestedPointerType != iconId) {
436        mLocked.requestedPointerType = iconId;
437        mLocked.presentationChanged = true;
438        updatePointerLocked();
439    }
440}
441
442void PointerController::setCustomPointerIcon(const SpriteIcon& icon) {
443    AutoMutex _l(mLock);
444
445    const int32_t iconId = mPolicy->getCustomPointerIconId();
446    mLocked.additionalMouseResources[iconId] = icon;
447    mLocked.requestedPointerType = iconId;
448    mLocked.presentationChanged = true;
449
450    updatePointerLocked();
451}
452
453void PointerController::handleMessage(const Message& message) {
454    switch (message.what) {
455    case MSG_INACTIVITY_TIMEOUT:
456        doInactivityTimeout();
457        break;
458    }
459}
460
461int PointerController::handleEvent(int /* fd */, int events, void* /* data */) {
462    if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
463        ALOGE("Display event receiver pipe was closed or an error occurred.  "
464              "events=0x%x", events);
465        return 0; // remove the callback
466    }
467
468    if (!(events & Looper::EVENT_INPUT)) {
469        ALOGW("Received spurious callback for unhandled poll event.  "
470              "events=0x%x", events);
471        return 1; // keep the callback
472    }
473
474    bool gotVsync = false;
475    ssize_t n;
476    nsecs_t timestamp;
477    DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
478    while ((n = mDisplayEventReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
479        for (size_t i = 0; i < static_cast<size_t>(n); ++i) {
480            if (buf[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
481                timestamp = buf[i].header.timestamp;
482                gotVsync = true;
483            }
484        }
485    }
486    if (gotVsync) {
487        doAnimate(timestamp);
488    }
489    return 1;  // keep the callback
490}
491
492void PointerController::doAnimate(nsecs_t timestamp) {
493    AutoMutex _l(mLock);
494
495    mLocked.animationPending = false;
496
497    bool keepFading = doFadingAnimationLocked(timestamp);
498    bool keepBitmapFlipping = doBitmapAnimationLocked(timestamp);
499    if (keepFading || keepBitmapFlipping) {
500        startAnimationLocked();
501    }
502}
503
504bool PointerController::doFadingAnimationLocked(nsecs_t timestamp) {
505    bool keepAnimating = false;
506    nsecs_t frameDelay = timestamp - mLocked.animationTime;
507
508    // Animate pointer fade.
509    if (mLocked.pointerFadeDirection < 0) {
510        mLocked.pointerAlpha -= float(frameDelay) / POINTER_FADE_DURATION;
511        if (mLocked.pointerAlpha <= 0.0f) {
512            mLocked.pointerAlpha = 0.0f;
513            mLocked.pointerFadeDirection = 0;
514        } else {
515            keepAnimating = true;
516        }
517        updatePointerLocked();
518    } else if (mLocked.pointerFadeDirection > 0) {
519        mLocked.pointerAlpha += float(frameDelay) / POINTER_FADE_DURATION;
520        if (mLocked.pointerAlpha >= 1.0f) {
521            mLocked.pointerAlpha = 1.0f;
522            mLocked.pointerFadeDirection = 0;
523        } else {
524            keepAnimating = true;
525        }
526        updatePointerLocked();
527    }
528
529    // Animate spots that are fading out and being removed.
530    for (size_t i = 0; i < mLocked.spots.size(); i++) {
531        Spot* spot = mLocked.spots.itemAt(i);
532        if (spot->id == Spot::INVALID_ID) {
533            spot->alpha -= float(frameDelay) / SPOT_FADE_DURATION;
534            if (spot->alpha <= 0) {
535                mLocked.spots.removeAt(i--);
536                releaseSpotLocked(spot);
537            } else {
538                spot->sprite->setAlpha(spot->alpha);
539                keepAnimating = true;
540            }
541        }
542    }
543    return keepAnimating;
544}
545
546bool PointerController::doBitmapAnimationLocked(nsecs_t timestamp) {
547    std::map<int32_t, PointerAnimation>::const_iterator iter = mLocked.animationResources.find(
548            mLocked.requestedPointerType);
549    if (iter == mLocked.animationResources.end()) {
550        return false;
551    }
552
553    if (timestamp - mLocked.lastFrameUpdatedTime > iter->second.durationPerFrame) {
554        mSpriteController->openTransaction();
555
556        int incr = (timestamp - mLocked.lastFrameUpdatedTime) / iter->second.durationPerFrame;
557        mLocked.animationFrameIndex += incr;
558        mLocked.lastFrameUpdatedTime += iter->second.durationPerFrame * incr;
559        while (mLocked.animationFrameIndex >= iter->second.animationFrames.size()) {
560            mLocked.animationFrameIndex -= iter->second.animationFrames.size();
561        }
562        mLocked.pointerSprite->setIcon(iter->second.animationFrames[mLocked.animationFrameIndex]);
563
564        mSpriteController->closeTransaction();
565    }
566
567    // Keep animating.
568    return true;
569}
570
571void PointerController::doInactivityTimeout() {
572    fade(TRANSITION_GRADUAL);
573}
574
575void PointerController::startAnimationLocked() {
576    if (!mLocked.animationPending) {
577        mLocked.animationPending = true;
578        mLocked.animationTime = systemTime(SYSTEM_TIME_MONOTONIC);
579        mDisplayEventReceiver.requestNextVsync();
580    }
581}
582
583void PointerController::resetInactivityTimeoutLocked() {
584    mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
585
586    nsecs_t timeout = mLocked.inactivityTimeout == INACTIVITY_TIMEOUT_SHORT
587            ? INACTIVITY_TIMEOUT_DELAY_TIME_SHORT : INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL;
588    mLooper->sendMessageDelayed(timeout, mHandler, MSG_INACTIVITY_TIMEOUT);
589}
590
591void PointerController::removeInactivityTimeoutLocked() {
592    mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT);
593}
594
595void PointerController::updatePointerLocked() {
596    mSpriteController->openTransaction();
597
598    mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
599    mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
600
601    if (mLocked.pointerAlpha > 0) {
602        mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha);
603        mLocked.pointerSprite->setVisible(true);
604    } else {
605        mLocked.pointerSprite->setVisible(false);
606    }
607
608    if (mLocked.pointerIconChanged || mLocked.presentationChanged) {
609        if (mLocked.presentation == PRESENTATION_POINTER) {
610            if (mLocked.requestedPointerType== mPolicy->getDefaultPointerIconId()) {
611                mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
612            } else {
613                std::map<int32_t, SpriteIcon>::const_iterator iter =
614                    mLocked.additionalMouseResources.find(mLocked.requestedPointerType);
615                if (iter != mLocked.additionalMouseResources.end()) {
616                    std::map<int32_t, PointerAnimation>::const_iterator anim_iter =
617                            mLocked.animationResources.find(mLocked.requestedPointerType);
618                    if (anim_iter != mLocked.animationResources.end()) {
619                        mLocked.animationFrameIndex = 0;
620                        mLocked.lastFrameUpdatedTime = systemTime(SYSTEM_TIME_MONOTONIC);
621                        startAnimationLocked();
622                    }
623                    mLocked.pointerSprite->setIcon(iter->second);
624                } else {
625                    ALOGW("Can't find the resource for icon id %d", mLocked.requestedPointerType);
626                    mLocked.pointerSprite->setIcon(mLocked.pointerIcon);
627                }
628            }
629        } else {
630            mLocked.pointerSprite->setIcon(mResources.spotAnchor);
631        }
632        mLocked.pointerIconChanged = false;
633        mLocked.presentationChanged = false;
634    }
635
636    mSpriteController->closeTransaction();
637}
638
639PointerController::Spot* PointerController::getSpotLocked(uint32_t id) {
640    for (size_t i = 0; i < mLocked.spots.size(); i++) {
641        Spot* spot = mLocked.spots.itemAt(i);
642        if (spot->id == id) {
643            return spot;
644        }
645    }
646    return NULL;
647}
648
649PointerController::Spot* PointerController::createAndAddSpotLocked(uint32_t id) {
650    // Remove spots until we have fewer than MAX_SPOTS remaining.
651    while (mLocked.spots.size() >= MAX_SPOTS) {
652        Spot* spot = removeFirstFadingSpotLocked();
653        if (!spot) {
654            spot = mLocked.spots.itemAt(0);
655            mLocked.spots.removeAt(0);
656        }
657        releaseSpotLocked(spot);
658    }
659
660    // Obtain a sprite from the recycled pool.
661    sp<Sprite> sprite;
662    if (! mLocked.recycledSprites.isEmpty()) {
663        sprite = mLocked.recycledSprites.top();
664        mLocked.recycledSprites.pop();
665    } else {
666        sprite = mSpriteController->createSprite();
667    }
668
669    // Return the new spot.
670    Spot* spot = new Spot(id, sprite);
671    mLocked.spots.push(spot);
672    return spot;
673}
674
675PointerController::Spot* PointerController::removeFirstFadingSpotLocked() {
676    for (size_t i = 0; i < mLocked.spots.size(); i++) {
677        Spot* spot = mLocked.spots.itemAt(i);
678        if (spot->id == Spot::INVALID_ID) {
679            mLocked.spots.removeAt(i);
680            return spot;
681        }
682    }
683    return NULL;
684}
685
686void PointerController::releaseSpotLocked(Spot* spot) {
687    spot->sprite->clearIcon();
688
689    if (mLocked.recycledSprites.size() < MAX_RECYCLED_SPRITES) {
690        mLocked.recycledSprites.push(spot->sprite);
691    }
692
693    delete spot;
694}
695
696void PointerController::fadeOutAndReleaseSpotLocked(Spot* spot) {
697    if (spot->id != Spot::INVALID_ID) {
698        spot->id = Spot::INVALID_ID;
699        startAnimationLocked();
700    }
701}
702
703void PointerController::fadeOutAndReleaseAllSpotsLocked() {
704    for (size_t i = 0; i < mLocked.spots.size(); i++) {
705        Spot* spot = mLocked.spots.itemAt(i);
706        fadeOutAndReleaseSpotLocked(spot);
707    }
708}
709
710void PointerController::loadResources() {
711    mPolicy->loadPointerResources(&mResources);
712}
713
714
715// --- PointerController::Spot ---
716
717void PointerController::Spot::updateSprite(const SpriteIcon* icon, float x, float y) {
718    sprite->setLayer(Sprite::BASE_LAYER_SPOT + id);
719    sprite->setAlpha(alpha);
720    sprite->setTransformationMatrix(SpriteTransformationMatrix(scale, 0.0f, 0.0f, scale));
721    sprite->setPosition(x, y);
722
723    this->x = x;
724    this->y = y;
725
726    if (icon != lastIcon) {
727        lastIcon = icon;
728        if (icon) {
729            sprite->setIcon(*icon);
730            sprite->setVisible(true);
731        } else {
732            sprite->setVisible(false);
733        }
734    }
735}
736
737} // namespace android
738