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