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