summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
Diffstat (limited to 'services')
-rw-r--r--services/audioflinger/AudioResampler.cpp8
-rw-r--r--services/input/Android.mk4
-rw-r--r--services/input/InputReader.h4
-rw-r--r--services/input/PointerController.cpp212
-rw-r--r--services/input/PointerController.h26
-rw-r--r--services/input/SpotController.cpp45
-rw-r--r--services/input/SpotController.h71
-rw-r--r--services/input/SpriteController.cpp472
-rw-r--r--services/input/SpriteController.h251
-rw-r--r--services/input/tests/InputReader_test.cpp4
-rw-r--r--services/java/com/android/server/InputMethodManagerService.java18
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java359
-rw-r--r--services/java/com/android/server/am/ActivityStack.java228
-rw-r--r--services/java/com/android/server/am/BatteryStatsService.java13
-rw-r--r--services/java/com/android/server/am/ProcessRecord.java6
-rw-r--r--services/java/com/android/server/am/ServiceRecord.java20
-rw-r--r--services/java/com/android/server/am/TaskAccessInfo.java35
-rw-r--r--services/java/com/android/server/pm/Settings.java13
-rw-r--r--services/java/com/android/server/pm/UserDetails.java305
-rw-r--r--services/jni/com_android_server_AlarmManagerService.cpp32
-rw-r--r--services/jni/com_android_server_InputManager.cpp34
-rw-r--r--services/jni/com_android_server_UsbService.cpp29
-rw-r--r--services/tests/servicestests/AndroidManifest.xml2
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java106
24 files changed, 1817 insertions, 480 deletions
diff --git a/services/audioflinger/AudioResampler.cpp b/services/audioflinger/AudioResampler.cpp
index 5c3b43fc..dca795c 100644
--- a/services/audioflinger/AudioResampler.cpp
+++ b/services/audioflinger/AudioResampler.cpp
@@ -26,11 +26,15 @@
#include "AudioResamplerSinc.h"
#include "AudioResamplerCubic.h"
+#ifdef __arm__
+#include <machine/cpu-features.h>
+#endif
+
namespace android {
-#ifdef __ARM_ARCH_5E__ // optimized asm option
+#ifdef __ARM_HAVE_HALFWORD_MULTIPLY // optimized asm option
#define ASM_ARM_RESAMP1 // enable asm optimisation for ResamplerOrder1
-#endif // __ARM_ARCH_5E__
+#endif // __ARM_HAVE_HALFWORD_MULTIPLY
// ----------------------------------------------------------------------------
class AudioResamplerOrder1 : public AudioResampler {
diff --git a/services/input/Android.mk b/services/input/Android.mk
index 96431bc..f9f8623f 100644
--- a/services/input/Android.mk
+++ b/services/input/Android.mk
@@ -22,7 +22,9 @@ LOCAL_SRC_FILES:= \
InputManager.cpp \
InputReader.cpp \
InputWindow.cpp \
- PointerController.cpp
+ PointerController.cpp \
+ SpotController.cpp \
+ SpriteController.cpp
LOCAL_SHARED_LIBRARIES := \
libcutils \
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index cf9b13d..9ed1391 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -20,6 +20,7 @@
#include "EventHub.h"
#include "InputDispatcher.h"
#include "PointerController.h"
+#include "SpotController.h"
#include <ui/Input.h>
#include <ui/DisplayInfo.h>
@@ -89,6 +90,9 @@ public:
/* Gets a pointer controller associated with the specified cursor device (ie. a mouse). */
virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) = 0;
+
+ /* Gets a spot controller associated with the specified touch pad device. */
+ virtual sp<SpotControllerInterface> obtainSpotController(int32_t deviceId) = 0;
};
diff --git a/services/input/PointerController.cpp b/services/input/PointerController.cpp
index a4ee295..15effb7 100644
--- a/services/input/PointerController.cpp
+++ b/services/input/PointerController.cpp
@@ -49,8 +49,11 @@ static const nsecs_t FADE_FRAME_INTERVAL = 1000000000LL / 60;
static const float FADE_DECAY_PER_FRAME = float(FADE_FRAME_INTERVAL) / FADE_DURATION;
-PointerController::PointerController(const sp<Looper>& looper, int32_t pointerLayer) :
- mLooper(looper), mPointerLayer(pointerLayer) {
+PointerController::PointerController(const sp<Looper>& looper,
+ const sp<SpriteController>& spriteController) :
+ mLooper(looper), mSpriteController(spriteController) {
+ mHandler = new WeakMessageHandler(this);
+
AutoMutex _l(mLock);
mLocked.displayWidth = -1;
@@ -61,34 +64,20 @@ PointerController::PointerController(const sp<Looper>& looper, int32_t pointerLa
mLocked.pointerY = 0;
mLocked.buttonState = 0;
- mLocked.iconBitmap = NULL;
- mLocked.iconHotSpotX = 0;
- mLocked.iconHotSpotY = 0;
-
mLocked.fadeAlpha = 1;
mLocked.inactivityFadeDelay = INACTIVITY_FADE_DELAY_NORMAL;
- mLocked.wantVisible = false;
mLocked.visible = false;
- mLocked.drawn = false;
- mHandler = new WeakMessageHandler(this);
+ mLocked.sprite = mSpriteController->createSprite();
}
PointerController::~PointerController() {
mLooper->removeMessages(mHandler);
- if (mSurfaceControl != NULL) {
- mSurfaceControl->clear();
- mSurfaceControl.clear();
- }
-
- if (mSurfaceComposerClient != NULL) {
- mSurfaceComposerClient->dispose();
- mSurfaceComposerClient.clear();
- }
+ AutoMutex _l(mLock);
- delete mLocked.iconBitmap;
+ mLocked.sprite.clear();
}
bool PointerController::getBounds(float* outMinX, float* outMinY,
@@ -214,75 +203,11 @@ void PointerController::setInactivityFadeDelay(InactivityFadeDelay inactivityFad
}
void PointerController::updateLocked() {
- bool wantVisibleAndHavePointerIcon = mLocked.wantVisible && mLocked.iconBitmap;
-
- if (wantVisibleAndHavePointerIcon) {
- // Want the pointer to be visible.
- // Ensure the surface is created and drawn.
- if (!createSurfaceIfNeededLocked() || !drawPointerIfNeededLocked()) {
- return;
- }
- } else {
- // Don't want the pointer to be visible.
- // If it is not visible then we are done.
- if (mSurfaceControl == NULL || !mLocked.visible) {
- return;
- }
- }
-
- status_t status = mSurfaceComposerClient->openTransaction();
- if (status) {
- LOGE("Error opening surface transaction to update pointer surface.");
- return;
- }
-
- if (wantVisibleAndHavePointerIcon) {
- status = mSurfaceControl->setPosition(
- mLocked.pointerX - mLocked.iconHotSpotX,
- mLocked.pointerY - mLocked.iconHotSpotY);
- if (status) {
- LOGE("Error %d moving pointer surface.", status);
- goto CloseTransaction;
- }
-
- status = mSurfaceControl->setAlpha(mLocked.fadeAlpha);
- if (status) {
- LOGE("Error %d setting pointer surface alpha.", status);
- goto CloseTransaction;
- }
-
- if (!mLocked.visible) {
- status = mSurfaceControl->setLayer(mPointerLayer);
- if (status) {
- LOGE("Error %d setting pointer surface layer.", status);
- goto CloseTransaction;
- }
-
- status = mSurfaceControl->show(mPointerLayer);
- if (status) {
- LOGE("Error %d showing pointer surface.", status);
- goto CloseTransaction;
- }
-
- mLocked.visible = true;
- }
- } else {
- if (mLocked.visible) {
- status = mSurfaceControl->hide();
- if (status) {
- LOGE("Error %d hiding pointer surface.", status);
- goto CloseTransaction;
- }
-
- mLocked.visible = false;
- }
- }
-
-CloseTransaction:
- status = mSurfaceComposerClient->closeTransaction();
- if (status) {
- LOGE("Error closing surface transaction to update pointer surface.");
- }
+ mLocked.sprite->openTransaction();
+ mLocked.sprite->setPosition(mLocked.pointerX, mLocked.pointerY);
+ mLocked.sprite->setAlpha(mLocked.fadeAlpha);
+ mLocked.sprite->setVisible(mLocked.visible);
+ mLocked.sprite->closeTransaction();
}
void PointerController::setDisplaySize(int32_t width, int32_t height) {
@@ -339,7 +264,7 @@ void PointerController::setDisplayOrientation(int32_t orientation) {
case DISPLAY_ORIENTATION_90:
temp = x;
x = y;
- y = mLocked.displayWidth - x;
+ y = mLocked.displayWidth - temp;
break;
case DISPLAY_ORIENTATION_180:
x = mLocked.displayWidth - x;
@@ -365,106 +290,7 @@ void PointerController::setDisplayOrientation(int32_t orientation) {
void PointerController::setPointerIcon(const SkBitmap* bitmap, float hotSpotX, float hotSpotY) {
AutoMutex _l(mLock);
- if (mLocked.iconBitmap) {
- delete mLocked.iconBitmap;
- mLocked.iconBitmap = NULL;
- }
-
- if (bitmap) {
- mLocked.iconBitmap = new SkBitmap();
- bitmap->copyTo(mLocked.iconBitmap, SkBitmap::kARGB_8888_Config);
- }
-
- mLocked.iconHotSpotX = hotSpotX;
- mLocked.iconHotSpotY = hotSpotY;
- mLocked.drawn = false;
-}
-
-bool PointerController::createSurfaceIfNeededLocked() {
- if (!mLocked.iconBitmap) {
- // If we don't have a pointer icon, then no point allocating a surface now.
- return false;
- }
-
- if (mSurfaceComposerClient == NULL) {
- mSurfaceComposerClient = new SurfaceComposerClient();
- }
-
- if (mSurfaceControl == NULL) {
- mSurfaceControl = mSurfaceComposerClient->createSurface(getpid(),
- String8("Pointer Icon"), 0,
- mLocked.iconBitmap->width(), mLocked.iconBitmap->height(),
- PIXEL_FORMAT_RGBA_8888);
- if (mSurfaceControl == NULL) {
- LOGE("Error creating pointer surface.");
- return false;
- }
- }
- return true;
-}
-
-bool PointerController::drawPointerIfNeededLocked() {
- if (!mLocked.drawn) {
- if (!mLocked.iconBitmap) {
- return false;
- }
-
- if (!resizeSurfaceLocked(mLocked.iconBitmap->width(), mLocked.iconBitmap->height())) {
- return false;
- }
-
- sp<Surface> surface = mSurfaceControl->getSurface();
-
- Surface::SurfaceInfo surfaceInfo;
- status_t status = surface->lock(&surfaceInfo);
- if (status) {
- LOGE("Error %d locking pointer surface before drawing.", status);
- return false;
- }
-
- SkBitmap surfaceBitmap;
- ssize_t bpr = surfaceInfo.s * bytesPerPixel(surfaceInfo.format);
- surfaceBitmap.setConfig(SkBitmap::kARGB_8888_Config, surfaceInfo.w, surfaceInfo.h, bpr);
- surfaceBitmap.setPixels(surfaceInfo.bits);
-
- SkCanvas surfaceCanvas;
- surfaceCanvas.setBitmapDevice(surfaceBitmap);
-
- SkPaint paint;
- paint.setXfermodeMode(SkXfermode::kSrc_Mode);
- surfaceCanvas.drawBitmap(*mLocked.iconBitmap, 0, 0, &paint);
-
- status = surface->unlockAndPost();
- if (status) {
- LOGE("Error %d unlocking pointer surface after drawing.", status);
- return false;
- }
- }
-
- mLocked.drawn = true;
- return true;
-}
-
-bool PointerController::resizeSurfaceLocked(int32_t width, int32_t height) {
- status_t status = mSurfaceComposerClient->openTransaction();
- if (status) {
- LOGE("Error opening surface transaction to resize pointer surface.");
- return false;
- }
-
- status = mSurfaceControl->setSize(width, height);
- if (status) {
- LOGE("Error %d setting pointer surface size.", status);
- return false;
- }
-
- status = mSurfaceComposerClient->closeTransaction();
- if (status) {
- LOGE("Error closing surface transaction to resize pointer surface.");
- return false;
- }
-
- return true;
+ mLocked.sprite->setBitmap(bitmap, hotSpotX, hotSpotY);
}
void PointerController::handleMessage(const Message& message) {
@@ -481,7 +307,7 @@ bool PointerController::unfadeBeforeUpdateLocked() {
sendFadeStepMessageDelayedLocked(getInactivityFadeDelayTimeLocked());
if (isFadingLocked()) {
- mLocked.wantVisible = true;
+ mLocked.visible = true;
mLocked.fadeAlpha = 1;
return true; // update required to effect the unfade
}
@@ -501,11 +327,11 @@ void PointerController::startInactivityFadeDelayLocked() {
}
void PointerController::fadeStepLocked() {
- if (mLocked.wantVisible) {
+ if (mLocked.visible) {
mLocked.fadeAlpha -= FADE_DECAY_PER_FRAME;
if (mLocked.fadeAlpha < 0) {
mLocked.fadeAlpha = 0;
- mLocked.wantVisible = false;
+ mLocked.visible = false;
} else {
sendFadeStepMessageDelayedLocked(FADE_FRAME_INTERVAL);
}
@@ -514,7 +340,7 @@ void PointerController::fadeStepLocked() {
}
bool PointerController::isFadingLocked() {
- return !mLocked.wantVisible || mLocked.fadeAlpha != 1;
+ return !mLocked.visible || mLocked.fadeAlpha != 1;
}
nsecs_t PointerController::getInactivityFadeDelayTimeLocked() {
diff --git a/services/input/PointerController.h b/services/input/PointerController.h
index e1dab5c..d467a5a 100644
--- a/services/input/PointerController.h
+++ b/services/input/PointerController.h
@@ -17,16 +17,14 @@
#ifndef _UI_POINTER_CONTROLLER_H
#define _UI_POINTER_CONTROLLER_H
+#include "SpriteController.h"
+
#include <ui/DisplayInfo.h>
#include <ui/Input.h>
#include <utils/RefBase.h>
#include <utils/Looper.h>
#include <utils/String8.h>
-#include <surfaceflinger/Surface.h>
-#include <surfaceflinger/SurfaceComposerClient.h>
-#include <surfaceflinger/ISurfaceComposer.h>
-
#include <SkBitmap.h>
namespace android {
@@ -86,7 +84,7 @@ public:
INACTIVITY_FADE_DELAY_SHORT = 1,
};
- PointerController(const sp<Looper>& looper, int32_t pointerLayer);
+ PointerController(const sp<Looper>& looper, const sp<SpriteController>& spriteController);
virtual bool getBounds(float* outMinX, float* outMinY,
float* outMaxX, float* outMaxY) const;
@@ -111,9 +109,8 @@ private:
mutable Mutex mLock;
sp<Looper> mLooper;
- int32_t mPointerLayer;
- sp<SurfaceComposerClient> mSurfaceComposerClient;
- sp<SurfaceControl> mSurfaceControl;
+ sp<SpriteController> mSpriteController;
+ sp<WeakMessageHandler> mHandler;
struct Locked {
int32_t displayWidth;
@@ -124,26 +121,17 @@ private:
float pointerY;
uint32_t buttonState;
- SkBitmap* iconBitmap;
- float iconHotSpotX;
- float iconHotSpotY;
-
float fadeAlpha;
InactivityFadeDelay inactivityFadeDelay;
- bool wantVisible;
bool visible;
- bool drawn;
- } mLocked;
- sp<WeakMessageHandler> mHandler;
+ sp<Sprite> sprite;
+ } mLocked;
bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const;
void setPositionLocked(float x, float y);
void updateLocked();
- bool createSurfaceIfNeededLocked();
- bool drawPointerIfNeededLocked();
- bool resizeSurfaceLocked(int32_t width, int32_t height);
void handleMessage(const Message& message);
bool unfadeBeforeUpdateLocked();
diff --git a/services/input/SpotController.cpp b/services/input/SpotController.cpp
new file mode 100644
index 0000000..dffad81
--- /dev/null
+++ b/services/input/SpotController.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "SpotController"
+
+//#define LOG_NDEBUG 0
+
+// Log debug messages about spot updates
+#define DEBUG_SPOT_UPDATES 0
+
+#include "SpotController.h"
+
+#include <cutils/log.h>
+
+namespace android {
+
+// --- SpotController ---
+
+SpotController::SpotController(const sp<Looper>& looper,
+ const sp<SpriteController>& spriteController) :
+ mLooper(looper), mSpriteController(spriteController) {
+ mHandler = new WeakMessageHandler(this);
+}
+
+SpotController::~SpotController() {
+ mLooper->removeMessages(mHandler);
+}
+
+void SpotController:: handleMessage(const Message& message) {
+}
+
+} // namespace android
diff --git a/services/input/SpotController.h b/services/input/SpotController.h
new file mode 100644
index 0000000..1d091d7
--- /dev/null
+++ b/services/input/SpotController.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_SPOT_CONTROLLER_H
+#define _UI_SPOT_CONTROLLER_H
+
+#include "SpriteController.h"
+
+#include <utils/RefBase.h>
+#include <utils/Looper.h>
+
+#include <SkBitmap.h>
+
+namespace android {
+
+/*
+ * Interface for displaying spots on screen that visually represent the positions
+ * of fingers on a touch pad.
+ *
+ * The spot controller is responsible for providing synchronization and for tracking
+ * display orientation changes if needed.
+ */
+class SpotControllerInterface : public virtual RefBase {
+protected:
+ SpotControllerInterface() { }
+ virtual ~SpotControllerInterface() { }
+
+public:
+
+};
+
+
+/*
+ * Sprite-based spot controller implementation.
+ */
+class SpotController : public SpotControllerInterface, public MessageHandler {
+protected:
+ virtual ~SpotController();
+
+public:
+ SpotController(const sp<Looper>& looper, const sp<SpriteController>& spriteController);
+
+private:
+ mutable Mutex mLock;
+
+ sp<Looper> mLooper;
+ sp<SpriteController> mSpriteController;
+ sp<WeakMessageHandler> mHandler;
+
+ struct Locked {
+ } mLocked;
+
+ void handleMessage(const Message& message);
+};
+
+} // namespace android
+
+#endif // _UI_SPOT_CONTROLLER_H
diff --git a/services/input/SpriteController.cpp b/services/input/SpriteController.cpp
new file mode 100644
index 0000000..c6d4390
--- /dev/null
+++ b/services/input/SpriteController.cpp
@@ -0,0 +1,472 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Sprites"
+
+//#define LOG_NDEBUG 0
+
+#include "SpriteController.h"
+
+#include <cutils/log.h>
+#include <utils/String8.h>
+
+#include <SkBitmap.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkPaint.h>
+#include <SkXfermode.h>
+
+namespace android {
+
+// --- SpriteController ---
+
+SpriteController::SpriteController(const sp<Looper>& looper, int32_t overlayLayer) :
+ mLooper(looper), mOverlayLayer(overlayLayer) {
+ mHandler = new WeakMessageHandler(this);
+}
+
+SpriteController::~SpriteController() {
+ mLooper->removeMessages(mHandler);
+
+ if (mSurfaceComposerClient != NULL) {
+ mSurfaceComposerClient->dispose();
+ mSurfaceComposerClient.clear();
+ }
+}
+
+sp<Sprite> SpriteController::createSprite() {
+ return new SpriteImpl(this);
+}
+
+void SpriteController::invalidateSpriteLocked(const sp<SpriteImpl>& sprite) {
+ bool wasEmpty = mInvalidatedSprites.isEmpty();
+ mInvalidatedSprites.push(sprite);
+ if (wasEmpty) {
+ mLooper->sendMessage(mHandler, Message(MSG_UPDATE_SPRITES));
+ }
+}
+
+void SpriteController::disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl) {
+ bool wasEmpty = mDisposedSurfaces.isEmpty();
+ mDisposedSurfaces.push(surfaceControl);
+ if (wasEmpty) {
+ mLooper->sendMessage(mHandler, Message(MSG_DISPOSE_SURFACES));
+ }
+}
+
+void SpriteController::handleMessage(const Message& message) {
+ switch (message.what) {
+ case MSG_UPDATE_SPRITES:
+ doUpdateSprites();
+ break;
+ case MSG_DISPOSE_SURFACES:
+ doDisposeSurfaces();
+ break;
+ }
+}
+
+void SpriteController::doUpdateSprites() {
+ // Collect information about sprite updates.
+ // Each sprite update record includes a reference to its associated sprite so we can
+ // be certain the sprites will not be deleted while this function runs. Sprites
+ // may invalidate themselves again during this time but we will handle those changes
+ // in the next iteration.
+ Vector<SpriteUpdate> updates;
+ size_t numSprites;
+ { // acquire lock
+ AutoMutex _l(mLock);
+
+ numSprites = mInvalidatedSprites.size();
+ for (size_t i = 0; i < numSprites; i++) {
+ const sp<SpriteImpl>& sprite = mInvalidatedSprites.itemAt(i);
+
+ updates.push(SpriteUpdate(sprite, sprite->getStateLocked()));
+ sprite->resetDirtyLocked();
+ }
+ mInvalidatedSprites.clear();
+ } // release lock
+
+ // Create missing surfaces.
+ bool surfaceChanged = false;
+ for (size_t i = 0; i < numSprites; i++) {
+ SpriteUpdate& update = updates.editItemAt(i);
+
+ if (update.state.surfaceControl == NULL && update.state.wantSurfaceVisible()) {
+ update.state.surfaceWidth = update.state.bitmap.width();
+ update.state.surfaceHeight = update.state.bitmap.height();
+ update.state.surfaceDrawn = false;
+ update.state.surfaceVisible = false;
+ update.state.surfaceControl = obtainSurface(
+ update.state.surfaceWidth, update.state.surfaceHeight);
+ if (update.state.surfaceControl != NULL) {
+ update.surfaceChanged = surfaceChanged = true;
+ }
+ }
+ }
+
+ // Resize sprites if needed, inside a global transaction.
+ bool haveGlobalTransaction = false;
+ for (size_t i = 0; i < numSprites; i++) {
+ SpriteUpdate& update = updates.editItemAt(i);
+
+ if (update.state.surfaceControl != NULL && update.state.wantSurfaceVisible()) {
+ int32_t desiredWidth = update.state.bitmap.width();
+ int32_t desiredHeight = update.state.bitmap.height();
+ if (update.state.surfaceWidth < desiredWidth
+ || update.state.surfaceHeight < desiredHeight) {
+ if (!haveGlobalTransaction) {
+ SurfaceComposerClient::openGlobalTransaction();
+ haveGlobalTransaction = true;
+ }
+
+ status_t status = update.state.surfaceControl->setSize(desiredWidth, desiredHeight);
+ if (status) {
+ LOGE("Error %d resizing sprite surface from %dx%d to %dx%d",
+ status, update.state.surfaceWidth, update.state.surfaceHeight,
+ desiredWidth, desiredHeight);
+ } else {
+ update.state.surfaceWidth = desiredWidth;
+ update.state.surfaceHeight = desiredHeight;
+ update.state.surfaceDrawn = false;
+ update.surfaceChanged = surfaceChanged = true;
+
+ if (update.state.surfaceVisible) {
+ status = update.state.surfaceControl->hide();
+ if (status) {
+ LOGE("Error %d hiding sprite surface after resize.", status);
+ } else {
+ update.state.surfaceVisible = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (haveGlobalTransaction) {
+ SurfaceComposerClient::closeGlobalTransaction();
+ }
+
+ // Redraw sprites if needed.
+ for (size_t i = 0; i < numSprites; i++) {
+ SpriteUpdate& update = updates.editItemAt(i);
+
+ if ((update.state.dirty & DIRTY_BITMAP) && update.state.surfaceDrawn) {
+ update.state.surfaceDrawn = false;
+ update.surfaceChanged = surfaceChanged = true;
+ }
+
+ if (update.state.surfaceControl != NULL && !update.state.surfaceDrawn
+ && update.state.wantSurfaceVisible()) {
+ sp<Surface> surface = update.state.surfaceControl->getSurface();
+ Surface::SurfaceInfo surfaceInfo;
+ status_t status = surface->lock(&surfaceInfo);
+ if (status) {
+ LOGE("Error %d locking sprite surface before drawing.", status);
+ } else {
+ SkBitmap surfaceBitmap;
+ ssize_t bpr = surfaceInfo.s * bytesPerPixel(surfaceInfo.format);
+ surfaceBitmap.setConfig(SkBitmap::kARGB_8888_Config,
+ surfaceInfo.w, surfaceInfo.h, bpr);
+ surfaceBitmap.setPixels(surfaceInfo.bits);
+
+ SkCanvas surfaceCanvas;
+ surfaceCanvas.setBitmapDevice(surfaceBitmap);
+
+ SkPaint paint;
+ paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+ surfaceCanvas.drawBitmap(update.state.bitmap, 0, 0, &paint);
+
+ if (surfaceInfo.w > uint32_t(update.state.bitmap.width())) {
+ paint.setColor(0); // transparent fill color
+ surfaceCanvas.drawRectCoords(update.state.bitmap.width(), 0,
+ surfaceInfo.w, update.state.bitmap.height(), paint);
+ }
+ if (surfaceInfo.h > uint32_t(update.state.bitmap.height())) {
+ paint.setColor(0); // transparent fill color
+ surfaceCanvas.drawRectCoords(0, update.state.bitmap.height(),
+ surfaceInfo.w, surfaceInfo.h, paint);
+ }
+
+ status = surface->unlockAndPost();
+ if (status) {
+ LOGE("Error %d unlocking and posting sprite surface after drawing.", status);
+ } else {
+ update.state.surfaceDrawn = true;
+ update.surfaceChanged = surfaceChanged = true;
+ }
+ }
+ }
+ }
+
+ // Set sprite surface properties and make them visible.
+ bool haveTransaction = false;
+ for (size_t i = 0; i < numSprites; i++) {
+ SpriteUpdate& update = updates.editItemAt(i);
+
+ bool wantSurfaceVisibleAndDrawn = update.state.wantSurfaceVisible()
+ && update.state.surfaceDrawn;
+ bool becomingVisible = wantSurfaceVisibleAndDrawn && !update.state.surfaceVisible;
+ bool becomingHidden = !wantSurfaceVisibleAndDrawn && update.state.surfaceVisible;
+ if (update.state.surfaceControl != NULL && (becomingVisible || becomingHidden
+ || (wantSurfaceVisibleAndDrawn && (update.state.dirty & (DIRTY_ALPHA
+ | DIRTY_POSITION | DIRTY_TRANSFORMATION_MATRIX | DIRTY_LAYER
+ | DIRTY_VISIBILITY | DIRTY_HOTSPOT))))) {
+ status_t status;
+ if (!haveTransaction) {
+ status = mSurfaceComposerClient->openTransaction();
+ if (status) {
+ LOGE("Error %d opening transation to update sprite surface.", status);
+ break;
+ }
+ haveTransaction = true;
+ }
+
+ if (wantSurfaceVisibleAndDrawn
+ && (becomingVisible || (update.state.dirty & DIRTY_ALPHA))) {
+ status = update.state.surfaceControl->setAlpha(update.state.alpha);
+ if (status) {
+ LOGE("Error %d setting sprite surface alpha.", status);
+ }
+ }
+
+ if (wantSurfaceVisibleAndDrawn
+ && (becomingVisible || (update.state.dirty & (DIRTY_POSITION
+ | DIRTY_HOTSPOT)))) {
+ status = update.state.surfaceControl->setPosition(
+ update.state.positionX - update.state.hotSpotX,
+ update.state.positionY - update.state.hotSpotY);
+ if (status) {
+ LOGE("Error %d setting sprite surface position.", status);
+ }
+ }
+
+ if (wantSurfaceVisibleAndDrawn
+ && (becomingVisible
+ || (update.state.dirty & DIRTY_TRANSFORMATION_MATRIX))) {
+ status = update.state.surfaceControl->setMatrix(
+ update.state.transformationMatrix.dsdx,
+ update.state.transformationMatrix.dtdx,
+ update.state.transformationMatrix.dsdy,
+ update.state.transformationMatrix.dtdy);
+ if (status) {
+ LOGE("Error %d setting sprite surface transformation matrix.", status);
+ }
+ }
+
+ int32_t surfaceLayer = mOverlayLayer + update.state.layer;
+ if (wantSurfaceVisibleAndDrawn
+ && (becomingVisible || (update.state.dirty & DIRTY_LAYER))) {
+ status = update.state.surfaceControl->setLayer(surfaceLayer);
+ if (status) {
+ LOGE("Error %d setting sprite surface layer.", status);
+ }
+ }
+
+ if (becomingVisible) {
+ status = update.state.surfaceControl->show(surfaceLayer);
+ if (status) {
+ LOGE("Error %d showing sprite surface.", status);
+ } else {
+ update.state.surfaceVisible = true;
+ update.surfaceChanged = surfaceChanged = true;
+ }
+ } else if (becomingHidden) {
+ status = update.state.surfaceControl->hide();
+ if (status) {
+ LOGE("Error %d hiding sprite surface.", status);
+ } else {
+ update.state.surfaceVisible = false;
+ update.surfaceChanged = surfaceChanged = true;
+ }
+ }
+ }
+ }
+
+ if (haveTransaction) {
+ status_t status = mSurfaceComposerClient->closeTransaction();
+ if (status) {
+ LOGE("Error %d closing transaction to update sprite surface.", status);
+ }
+ }
+
+ // If any surfaces were changed, write back the new surface properties to the sprites.
+ if (surfaceChanged) { // acquire lock
+ AutoMutex _l(mLock);
+
+ for (size_t i = 0; i < numSprites; i++) {
+ const SpriteUpdate& update = updates.itemAt(i);
+
+ if (update.surfaceChanged) {
+ update.sprite->setSurfaceLocked(update.state.surfaceControl,
+ update.state.surfaceWidth, update.state.surfaceHeight,
+ update.state.surfaceDrawn, update.state.surfaceVisible);
+ }
+ }
+ } // release lock
+
+ // Clear the sprite update vector outside the lock. It is very important that
+ // we do not clear sprite references inside the lock since we could be releasing
+ // the last remaining reference to the sprite here which would result in the
+ // sprite being deleted and the lock being reacquired by the sprite destructor
+ // while already held.
+ updates.clear();
+}
+
+void SpriteController::doDisposeSurfaces() {
+ // Collect disposed surfaces.
+ Vector<sp<SurfaceControl> > disposedSurfaces;
+ { // acquire lock
+ disposedSurfaces = mDisposedSurfaces;
+ mDisposedSurfaces.clear();
+ } // release lock
+
+ // Release the last reference to each surface outside of the lock.
+ // We don't want the surfaces to be deleted while we are holding our lock.
+ disposedSurfaces.clear();
+}
+
+void SpriteController::ensureSurfaceComposerClient() {
+ if (mSurfaceComposerClient == NULL) {
+ mSurfaceComposerClient = new SurfaceComposerClient();
+ }
+}
+
+sp<SurfaceControl> SpriteController::obtainSurface(int32_t width, int32_t height) {
+ ensureSurfaceComposerClient();
+
+ sp<SurfaceControl> surfaceControl = mSurfaceComposerClient->createSurface(
+ getpid(), String8("Sprite"), 0, width, height, PIXEL_FORMAT_RGBA_8888);
+ if (surfaceControl == NULL) {
+ LOGE("Error creating sprite surface.");
+ return NULL;
+ }
+ return surfaceControl;
+}
+
+
+// --- SpriteController::SpriteImpl ---
+
+SpriteController::SpriteImpl::SpriteImpl(const sp<SpriteController> controller) :
+ mController(controller), mTransactionNestingCount(0) {
+}
+
+SpriteController::SpriteImpl::~SpriteImpl() {
+ AutoMutex _m(mController->mLock);
+
+ // Let the controller take care of deleting the last reference to sprite
+ // surfaces so that we do not block the caller on an IPC here.
+ if (mState.surfaceControl != NULL) {
+ mController->disposeSurfaceLocked(mState.surfaceControl);
+ mState.surfaceControl.clear();
+ }
+}
+
+void SpriteController::SpriteImpl::setBitmap(const SkBitmap* bitmap,
+ float hotSpotX, float hotSpotY) {
+ AutoMutex _l(mController->mLock);
+
+ if (bitmap) {
+ bitmap->copyTo(&mState.bitmap, SkBitmap::kARGB_8888_Config);
+ } else {
+ mState.bitmap.reset();
+ }
+
+ uint32_t dirty = DIRTY_BITMAP;
+ if (mState.hotSpotX != hotSpotX || mState.hotSpotY != hotSpotY) {
+ mState.hotSpotX = hotSpotX;
+ mState.hotSpotY = hotSpotY;
+ dirty |= DIRTY_HOTSPOT;
+ }
+
+ invalidateLocked(dirty);
+}
+
+void SpriteController::SpriteImpl::setVisible(bool visible) {
+ AutoMutex _l(mController->mLock);
+
+ if (mState.visible != visible) {
+ mState.visible = visible;
+ invalidateLocked(DIRTY_VISIBILITY);
+ }
+}
+
+void SpriteController::SpriteImpl::setPosition(float x, float y) {
+ AutoMutex _l(mController->mLock);
+
+ if (mState.positionX != x || mState.positionY != y) {
+ mState.positionX = x;
+ mState.positionY = y;
+ invalidateLocked(DIRTY_POSITION);
+ }
+}
+
+void SpriteController::SpriteImpl::setLayer(int32_t layer) {
+ AutoMutex _l(mController->mLock);
+
+ if (mState.layer != layer) {
+ mState.layer = layer;
+ invalidateLocked(DIRTY_LAYER);
+ }
+}
+
+void SpriteController::SpriteImpl::setAlpha(float alpha) {
+ AutoMutex _l(mController->mLock);
+
+ if (mState.alpha != alpha) {
+ mState.alpha = alpha;
+ invalidateLocked(DIRTY_ALPHA);
+ }
+}
+
+void SpriteController::SpriteImpl::setTransformationMatrix(
+ const SpriteTransformationMatrix& matrix) {
+ AutoMutex _l(mController->mLock);
+
+ if (mState.transformationMatrix != matrix) {
+ mState.transformationMatrix = matrix;
+ invalidateLocked(DIRTY_TRANSFORMATION_MATRIX);
+ }
+}
+
+void SpriteController::SpriteImpl::openTransaction() {
+ AutoMutex _l(mController->mLock);
+
+ mTransactionNestingCount += 1;
+}
+
+void SpriteController::SpriteImpl::closeTransaction() {
+ AutoMutex _l(mController->mLock);
+
+ LOG_ALWAYS_FATAL_IF(mTransactionNestingCount == 0,
+ "Sprite closeTransaction() called but there is no open sprite transaction");
+
+ mTransactionNestingCount -= 1;
+ if (mTransactionNestingCount == 0 && mState.dirty) {
+ mController->invalidateSpriteLocked(this);
+ }
+}
+
+void SpriteController::SpriteImpl::invalidateLocked(uint32_t dirty) {
+ if (mTransactionNestingCount > 0) {
+ bool wasDirty = mState.dirty;
+ mState.dirty |= dirty;
+ if (!wasDirty) {
+ mController->invalidateSpriteLocked(this);
+ }
+ }
+}
+
+} // namespace android
diff --git a/services/input/SpriteController.h b/services/input/SpriteController.h
new file mode 100644
index 0000000..27afb5e
--- /dev/null
+++ b/services/input/SpriteController.h
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef _UI_SPRITES_H
+#define _UI_SPRITES_H
+
+#include <utils/RefBase.h>
+#include <utils/Looper.h>
+
+#include <surfaceflinger/Surface.h>
+#include <surfaceflinger/SurfaceComposerClient.h>
+#include <surfaceflinger/ISurfaceComposer.h>
+
+#include <SkBitmap.h>
+
+namespace android {
+
+/*
+ * Transformation matrix for a sprite.
+ */
+struct SpriteTransformationMatrix {
+ inline SpriteTransformationMatrix() : dsdx(1.0f), dtdx(0.0f), dsdy(0.0f), dtdy(1.0f) { }
+
+ float dsdx;
+ float dtdx;
+ float dsdy;
+ float dtdy;
+
+ inline bool operator== (const SpriteTransformationMatrix& other) {
+ return dsdx == other.dsdx
+ && dtdx == other.dtdx
+ && dsdy == other.dsdy
+ && dtdy == other.dtdy;
+ }
+
+ inline bool operator!= (const SpriteTransformationMatrix& other) {
+ return !(*this == other);
+ }
+};
+
+/*
+ * A sprite is a simple graphical object that is displayed on-screen above other layers.
+ * The basic sprite class is an interface.
+ * The implementation is provided by the sprite controller.
+ */
+class Sprite : public RefBase {
+protected:
+ Sprite() { }
+ virtual ~Sprite() { }
+
+public:
+ /* Sets the bitmap that is drawn by the sprite.
+ * The sprite retains a copy of the bitmap for subsequent rendering. */
+ virtual void setBitmap(const SkBitmap* bitmap, float hotSpotX, float hotSpotY) = 0;
+
+ /* Sets whether the sprite is visible. */
+ virtual void setVisible(bool visible) = 0;
+
+ /* Sets the sprite position on screen, relative to the sprite's hot spot. */
+ virtual void setPosition(float x, float y) = 0;
+
+ /* Sets the layer of the sprite, relative to the system sprite overlay layer.
+ * Layer 0 is the overlay layer, > 0 appear above this layer. */
+ virtual void setLayer(int32_t layer) = 0;
+
+ /* Sets the sprite alpha blend ratio between 0.0 and 1.0. */
+ virtual void setAlpha(float alpha) = 0;
+
+ /* Sets the sprite transformation matrix. */
+ virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix) = 0;
+
+ /* Opens or closes a transaction to perform a batch of sprite updates as part of
+ * a single operation such as setPosition and setAlpha. It is not necessary to
+ * open a transaction when updating a single property.
+ * Calls to openTransaction() nest and must be matched by an equal number
+ * of calls to closeTransaction(). */
+ virtual void openTransaction() = 0;
+ virtual void closeTransaction() = 0;
+};
+
+/*
+ * Displays sprites on the screen.
+ *
+ * This interface is used by PointerController and SpotController to draw pointers or
+ * spot representations of fingers. It is not intended for general purpose use
+ * by other components.
+ *
+ * All sprite position updates and rendering is performed asynchronously.
+ *
+ * Clients are responsible for animating sprites by periodically updating their properties.
+ */
+class SpriteController : public MessageHandler {
+protected:
+ virtual ~SpriteController();
+
+public:
+ SpriteController(const sp<Looper>& looper, int32_t overlayLayer);
+
+ /* Creates a new sprite, initially invisible. */
+ sp<Sprite> createSprite();
+
+private:
+ enum {
+ MSG_UPDATE_SPRITES,
+ MSG_DISPOSE_SURFACES,
+ };
+
+ enum {
+ DIRTY_BITMAP = 1 << 0,
+ DIRTY_ALPHA = 1 << 1,
+ DIRTY_POSITION = 1 << 2,
+ DIRTY_TRANSFORMATION_MATRIX = 1 << 3,
+ DIRTY_LAYER = 1 << 4,
+ DIRTY_VISIBILITY = 1 << 5,
+ DIRTY_HOTSPOT = 1 << 6,
+ };
+
+ /* Describes the state of a sprite.
+ * This structure is designed so that it can be copied during updates so that
+ * surfaces can be resized and redrawn without blocking the client by holding a lock
+ * on the sprites for a long time.
+ * Note that the SkBitmap holds a reference to a shared (and immutable) pixel ref. */
+ struct SpriteState {
+ inline SpriteState() :
+ dirty(0), hotSpotX(0), hotSpotY(0), visible(false),
+ positionX(0), positionY(0), layer(0), alpha(1.0f),
+ surfaceWidth(0), surfaceHeight(0), surfaceDrawn(false), surfaceVisible(false) {
+ }
+
+ uint32_t dirty;
+
+ SkBitmap bitmap;
+ float hotSpotX;
+ float hotSpotY;
+ bool visible;
+ float positionX;
+ float positionY;
+ int32_t layer;
+ float alpha;
+ SpriteTransformationMatrix transformationMatrix;
+
+ sp<SurfaceControl> surfaceControl;
+ int32_t surfaceWidth;
+ int32_t surfaceHeight;
+ bool surfaceDrawn;
+ bool surfaceVisible;
+
+ inline bool wantSurfaceVisible() const {
+ return visible && alpha > 0.0f && !bitmap.isNull() && !bitmap.empty();
+ }
+ };
+
+ /* Client interface for a sprite.
+ * Requests acquire a lock on the controller, update local state and request the
+ * controller to invalidate the sprite.
+ * The real heavy lifting of creating, resizing and redrawing surfaces happens
+ * asynchronously with no locks held except in short critical section to copy
+ * the sprite state before the work and update the sprite surface control afterwards.
+ */
+ class SpriteImpl : public Sprite {
+ protected:
+ virtual ~SpriteImpl();
+
+ public:
+ SpriteImpl(const sp<SpriteController> controller);
+
+ virtual void setBitmap(const SkBitmap* bitmap, float hotSpotX, float hotSpotY);
+ virtual void setVisible(bool visible);
+ virtual void setPosition(float x, float y);
+ virtual void setLayer(int32_t layer);
+ virtual void setAlpha(float alpha);
+ virtual void setTransformationMatrix(const SpriteTransformationMatrix& matrix);
+ virtual void openTransaction();
+ virtual void closeTransaction();
+
+ inline const SpriteState& getStateLocked() const {
+ return mState;
+ }
+
+ inline void resetDirtyLocked() {
+ mState.dirty = 0;
+ }
+
+ inline void setSurfaceLocked(const sp<SurfaceControl>& surfaceControl,
+ int32_t width, int32_t height, bool drawn, bool visible) {
+ mState.surfaceControl = surfaceControl;
+ mState.surfaceWidth = width;
+ mState.surfaceHeight = height;
+ mState.surfaceDrawn = drawn;
+ mState.surfaceVisible = visible;
+ }
+
+ private:
+ sp<SpriteController> mController;
+
+ SpriteState mState; // guarded by mController->mLock
+ uint32_t mTransactionNestingCount; // guarded by mController->mLock
+
+ void invalidateLocked(uint32_t dirty);
+ };
+
+ /* Stores temporary information collected during the sprite update cycle. */
+ struct SpriteUpdate {
+ inline SpriteUpdate() : surfaceChanged(false) { }
+ inline SpriteUpdate(const sp<SpriteImpl> sprite, const SpriteState& state) :
+ sprite(sprite), state(state), surfaceChanged(false) {
+ }
+
+ sp<SpriteImpl> sprite;
+ SpriteState state;
+ bool surfaceChanged;
+ };
+
+ mutable Mutex mLock;
+
+ sp<Looper> mLooper;
+ const int32_t mOverlayLayer;
+ sp<WeakMessageHandler> mHandler;
+
+ sp<SurfaceComposerClient> mSurfaceComposerClient;
+
+ Vector<sp<SpriteImpl> > mInvalidatedSprites; // guarded by mLock
+ Vector<sp<SurfaceControl> > mDisposedSurfaces; // guarded by mLock
+
+ void invalidateSpriteLocked(const sp<SpriteImpl>& sprite);
+ void disposeSurfaceLocked(const sp<SurfaceControl>& surfaceControl);
+
+ void handleMessage(const Message& message);
+ void doUpdateSprites();
+ void doDisposeSurfaces();
+
+ void ensureSurfaceComposerClient();
+ sp<SurfaceControl> obtainSurface(int32_t width, int32_t height);
+};
+
+} // namespace android
+
+#endif // _UI_SPRITES_H
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index 4c5f239..ba8ca9c 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -192,6 +192,10 @@ private:
virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId) {
return mPointerControllers.valueFor(deviceId);
}
+
+ virtual sp<SpotControllerInterface> obtainSpotController(int32_t device) {
+ return NULL;
+ }
};
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index 1455764..7028772 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -1399,6 +1399,24 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
+ public InputMethodSubtype getLastInputMethodSubtype() {
+ synchronized (mMethodMap) {
+ final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
+ // TODO: Handle the case of the last IME with no subtypes
+ if (lastIme == null || TextUtils.isEmpty(lastIme.first)
+ || TextUtils.isEmpty(lastIme.second)) return null;
+ final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
+ if (lastImi == null) return null;
+ try {
+ final int lastSubtypeHash = Integer.valueOf(lastIme.second);
+ return lastImi.getSubtypeAt(getSubtypeIdFromHashCode(
+ lastImi, lastSubtypeHash));
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+ }
+
private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
synchronized (mMethodMap) {
if (token == null) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index f44b889..31b7f86 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -441,8 +441,7 @@ public final class ActivityManagerService extends ActivityManagerNative
/**
* List of intents that were used to start the most recent tasks.
*/
- final ArrayList<TaskRecord> mRecentTasks
- = new ArrayList<TaskRecord>();
+ final ArrayList<TaskRecord> mRecentTasks = new ArrayList<TaskRecord>();
/**
* All of the applications we currently have running organized by name.
@@ -450,8 +449,7 @@ public final class ActivityManagerService extends ActivityManagerNative
* returned by the package manager), and the keys are ApplicationRecord
* objects.
*/
- final ProcessMap<ProcessRecord> mProcessNames
- = new ProcessMap<ProcessRecord>();
+ final ProcessMap<ProcessRecord> mProcessNames = new ProcessMap<ProcessRecord>();
/**
* The currently running heavy-weight process, if any.
@@ -480,8 +478,7 @@ public final class ActivityManagerService extends ActivityManagerNative
* <p>NOTE: This object is protected by its own lock, NOT the global
* activity manager lock!
*/
- final SparseArray<ProcessRecord> mPidsSelfLocked
- = new SparseArray<ProcessRecord>();
+ final SparseArray<ProcessRecord> mPidsSelfLocked = new SparseArray<ProcessRecord>();
/**
* All of the processes that have been forced to be foreground. The key
@@ -2981,7 +2978,10 @@ public final class ActivityManagerService extends ActivityManagerNative
synchronized (this) {
if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) {
- Process.killProcess(app.pid);
+ Slog.w(TAG, "Killing " + app + ": background ANR");
+ EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+ app.processName, app.setAdj, "background ANR");
+ Process.killProcessQuiet(app.pid);
return;
}
@@ -3446,7 +3446,9 @@ public final class ActivityManagerService extends ActivityManagerNative
bringDownServiceLocked(sr, true);
}
}
- Process.killProcess(pid);
+ EventLog.writeEvent(EventLogTags.AM_KILL, pid,
+ app.processName, app.setAdj, "start timeout");
+ Process.killProcessQuiet(pid);
if (mBackupTarget != null && mBackupTarget.app.pid == pid) {
Slog.w(TAG, "Unattached app died before backup, skipping");
try {
@@ -3492,7 +3494,7 @@ public final class ActivityManagerService extends ActivityManagerNative
+ " (IApplicationThread " + thread + "); dropping process");
EventLog.writeEvent(EventLogTags.AM_DROP_PROCESS, pid);
if (pid > 0 && pid != MY_PID) {
- Process.killProcess(pid);
+ Process.killProcessQuiet(pid);
} else {
try {
thread.scheduleExit();
@@ -3890,18 +3892,17 @@ public final class ActivityManagerService extends ActivityManagerNative
}
for (int i=0; i<intents.length; i++) {
Intent intent = intents[i];
- if (intent == null) {
- throw new IllegalArgumentException("Null intent at index " + i);
- }
- if (intent.hasFileDescriptors()) {
- throw new IllegalArgumentException("File descriptors passed in Intent");
- }
- if (type == INTENT_SENDER_BROADCAST &&
- (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
- throw new IllegalArgumentException(
- "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
+ if (intent != null) {
+ if (intent.hasFileDescriptors()) {
+ throw new IllegalArgumentException("File descriptors passed in Intent");
+ }
+ if (type == INTENT_SENDER_BROADCAST &&
+ (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
+ throw new IllegalArgumentException(
+ "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
+ }
+ intents[i] = new Intent(intent);
}
- intents[i] = new Intent(intent);
}
if (resolvedTypes != null && resolvedTypes.length != intents.length) {
throw new IllegalArgumentException(
@@ -4832,11 +4833,6 @@ public final class ActivityManagerService extends ActivityManagerNative
throw new SecurityException(msg);
}
- final boolean canReadFb = (flags&ActivityManager.TASKS_GET_THUMBNAILS) != 0
- && checkCallingPermission(
- android.Manifest.permission.READ_FRAME_BUFFER)
- == PackageManager.PERMISSION_GRANTED;
-
int pos = mMainStack.mHistory.size()-1;
ActivityRecord next =
pos >= 0 ? (ActivityRecord)mMainStack.mHistory.get(pos) : null;
@@ -4876,13 +4872,6 @@ public final class ActivityManagerService extends ActivityManagerNative
ci.id = curTask.taskId;
ci.baseActivity = r.intent.getComponent();
ci.topActivity = top.intent.getComponent();
- if (canReadFb) {
- if (top.state == ActivityState.RESUMED) {
- ci.thumbnail = top.stack.screenshotActivities(top);
- } else if (top.thumbHolder != null) {
- ci.thumbnail = top.thumbHolder.lastThumbnail;
- }
- }
if (top.thumbHolder != null) {
ci.description = top.thumbHolder.lastDescription;
}
@@ -4955,8 +4944,6 @@ public final class ActivityManagerService extends ActivityManagerNative
IPackageManager pm = AppGlobals.getPackageManager();
- ActivityRecord resumed = mMainStack.mResumedActivity;
-
final int N = mRecentTasks.size();
ArrayList<ActivityManager.RecentTaskInfo> res
= new ArrayList<ActivityManager.RecentTaskInfo>(
@@ -5002,74 +4989,121 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ private TaskRecord taskForIdLocked(int id) {
+ final int N = mRecentTasks.size();
+ for (int i=0; i<N; i++) {
+ TaskRecord tr = mRecentTasks.get(i);
+ if (tr.taskId == id) {
+ return tr;
+ }
+ }
+ return null;
+ }
+
public ActivityManager.TaskThumbnails getTaskThumbnails(int id) {
synchronized (this) {
enforceCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER,
- "getTaskThumbnail()");
- ActivityRecord resumed = mMainStack.mResumedActivity;
- final int N = mRecentTasks.size();
- for (int i=0; i<N; i++) {
- TaskRecord tr = mRecentTasks.get(i);
- if (tr.taskId == id) {
- final ActivityManager.TaskThumbnails thumbs
- = new ActivityManager.TaskThumbnails();
- if (resumed != null && resumed.thumbHolder == tr) {
- thumbs.mainThumbnail = resumed.stack.screenshotActivities(resumed);
- } else {
- thumbs.mainThumbnail = tr.lastThumbnail;
- }
- // How many different sub-thumbnails?
- final int NA = mMainStack.mHistory.size();
- int j = 0;
- ThumbnailHolder holder = null;
- while (j < NA) {
- ActivityRecord ar = (ActivityRecord)mMainStack.mHistory.get(j);
- j++;
- if (ar.task == tr) {
- holder = ar.thumbHolder;
- break;
- }
- }
- ArrayList<Bitmap> bitmaps = new ArrayList<Bitmap>();
- thumbs.otherThumbnails = bitmaps;
- ActivityRecord lastActivity = null;
- while (j < NA) {
- ActivityRecord ar = (ActivityRecord)mMainStack.mHistory.get(j);
- j++;
- if (ar.task != tr) {
- break;
- }
- lastActivity = ar;
- if (ar.thumbHolder != holder && holder != null) {
- thumbs.numSubThumbbails++;
- holder = ar.thumbHolder;
- bitmaps.add(holder.lastThumbnail);
- }
- }
- if (lastActivity != null && bitmaps.size() > 0) {
- if (resumed == lastActivity) {
- Bitmap bm = lastActivity.stack.screenshotActivities(lastActivity);
- if (bm != null) {
- bitmaps.remove(bitmaps.size()-1);
- bitmaps.add(bm);
- }
- }
+ "getTaskThumbnails()");
+ TaskRecord tr = taskForIdLocked(id);
+ if (tr != null) {
+ return mMainStack.getTaskThumbnailsLocked(tr);
+ }
+ }
+ return null;
+ }
+
+ public boolean removeSubTask(int taskId, int subTaskIndex) {
+ synchronized (this) {
+ enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS,
+ "removeSubTask()");
+ long ident = Binder.clearCallingIdentity();
+ try {
+ return mMainStack.removeTaskActivitiesLocked(taskId, subTaskIndex) != null;
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ private void removeTaskProcessesLocked(ActivityRecord root) {
+ TaskRecord tr = root.task;
+ Intent baseIntent = new Intent(
+ tr.intent != null ? tr.intent : tr.affinityIntent);
+ ComponentName component = baseIntent.getComponent();
+ if (component == null) {
+ Slog.w(TAG, "Now component for base intent of task: " + tr);
+ return;
+ }
+
+ // Find any running services associated with this app.
+ ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>();
+ for (ServiceRecord sr : mServices.values()) {
+ if (sr.packageName.equals(component.getPackageName())) {
+ services.add(sr);
+ }
+ }
+
+ // Take care of any running services associated with the app.
+ for (int i=0; i<services.size(); i++) {
+ ServiceRecord sr = services.get(i);
+ if (sr.startRequested) {
+ if ((sr.serviceInfo.flags&ServiceInfo.FLAG_STOP_WITH_TASK) != 0) {
+ stopServiceLocked(sr);
+ } else {
+ sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true,
+ sr.makeNextStartId(), baseIntent, -1));
+ if (sr.app != null && sr.app.thread != null) {
+ sendServiceArgsLocked(sr, false);
}
- if (thumbs.numSubThumbbails > 0) {
- thumbs.retriever = new IThumbnailRetriever.Stub() {
- public Bitmap getThumbnail(int index) {
- if (index < 0 || index >= thumbs.otherThumbnails.size()) {
- return null;
- }
- return thumbs.otherThumbnails.get(index);
- }
- };
+ }
+ }
+ }
+
+ // Find any running processes associated with this app.
+ ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>();
+ SparseArray<ProcessRecord> appProcs
+ = mProcessNames.getMap().get(component.getPackageName());
+ if (appProcs != null) {
+ for (int i=0; i<appProcs.size(); i++) {
+ procs.add(appProcs.valueAt(i));
+ }
+ }
+
+ // Kill the running processes.
+ for (int i=0; i<procs.size(); i++) {
+ ProcessRecord pr = procs.get(i);
+ if (pr.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) {
+ Slog.i(TAG, "Killing " + pr + ": remove task");
+ EventLog.writeEvent(EventLogTags.AM_KILL, pr.pid,
+ pr.processName, pr.setAdj, "remove task");
+ Process.killProcessQuiet(pr.pid);
+ } else {
+ pr.waitingToKill = "remove task";
+ }
+ }
+ }
+
+ public boolean removeTask(int taskId, int flags) {
+ synchronized (this) {
+ enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS,
+ "removeTask()");
+ long ident = Binder.clearCallingIdentity();
+ try {
+ ActivityRecord r = mMainStack.removeTaskActivitiesLocked(taskId, -1);
+ if (r != null) {
+ mRecentTasks.remove(r.task);
+
+ if ((flags&ActivityManager.REMOVE_TASK_KILL_PROCESS) != 0) {
+ removeTaskProcessesLocked(r);
}
- return thumbs;
+
+ return true;
}
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
- return null;
+ return false;
}
private final int findAffinityTaskTopLocked(int startIndex, String affinity) {
@@ -5123,21 +5157,18 @@ public final class ActivityManagerService extends ActivityManagerNative
}
final long origId = Binder.clearCallingIdentity();
try {
- int N = mRecentTasks.size();
- for (int i=0; i<N; i++) {
- TaskRecord tr = mRecentTasks.get(i);
- if (tr.taskId == task) {
- if ((flags&ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) {
- mMainStack.mUserLeaving = true;
- }
- if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) {
- // Caller wants the home activity moved with it. To accomplish this,
- // we'll just move the home task to the top first.
- mMainStack.moveHomeToFrontLocked();
- }
- mMainStack.moveTaskToFrontLocked(tr, null);
- return;
+ TaskRecord tr = taskForIdLocked(task);
+ if (tr != null) {
+ if ((flags&ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) {
+ mMainStack.mUserLeaving = true;
+ }
+ if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) {
+ // Caller wants the home activity moved with it. To accomplish this,
+ // we'll just move the home task to the top first.
+ mMainStack.moveHomeToFrontLocked();
}
+ mMainStack.moveTaskToFrontLocked(tr, null);
+ return;
}
for (int i=mMainStack.mHistory.size()-1; i>=0; i--) {
ActivityRecord hr = (ActivityRecord)mMainStack.mHistory.get(i);
@@ -6661,11 +6692,10 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (app.pid > 0 && app.pid != MY_PID) {
handleAppCrashLocked(app);
- Slog.i(ActivityManagerService.TAG, "Killing "
- + app.processName + " (pid=" + app.pid + "): user's request");
+ Slog.i(ActivityManagerService.TAG, "Killing " + app + ": user's request");
EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
app.processName, app.setAdj, "user's request after error");
- Process.killProcess(app.pid);
+ Process.killProcessQuiet(app.pid);
}
}
}
@@ -8946,7 +8976,7 @@ public final class ActivityManagerService extends ActivityManagerNative
+ " in dying process " + proc.processName);
EventLog.writeEvent(EventLogTags.AM_KILL, capp.pid,
capp.processName, capp.setAdj, "dying provider " + proc.processName);
- Process.killProcess(capp.pid);
+ Process.killProcessQuiet(capp.pid);
}
}
@@ -9453,7 +9483,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (si.doneExecutingCount > 0) {
flags |= Service.START_FLAG_REDELIVERY;
}
- r.app.thread.scheduleServiceArgs(r, si.id, flags, si.intent);
+ r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent);
} catch (RemoteException e) {
// Remote process gone... we'll let the normal cleanup take
// care of this.
@@ -9539,11 +9569,8 @@ public final class ActivityManagerService extends ActivityManagerNative
// pending arguments, then fake up one so its onStartCommand() will
// be called.
if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
- r.lastStartId++;
- if (r.lastStartId < 1) {
- r.lastStartId = 1;
- }
- r.pendingStarts.add(new ServiceRecord.StartItem(r, r.lastStartId, null, -1));
+ r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
+ null, -1));
}
sendServiceArgsLocked(r, true);
@@ -9897,11 +9924,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
r.startRequested = true;
r.callStart = false;
- r.lastStartId++;
- if (r.lastStartId < 1) {
- r.lastStartId = 1;
- }
- r.pendingStarts.add(new ServiceRecord.StartItem(r, r.lastStartId,
+ r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
service, targetPermissionUid));
r.lastActivity = SystemClock.uptimeMillis();
synchronized (r.stats.getBatteryStats()) {
@@ -9943,6 +9966,15 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ private void stopServiceLocked(ServiceRecord service) {
+ synchronized (service.stats.getBatteryStats()) {
+ service.stats.stopRunningLocked();
+ }
+ service.startRequested = false;
+ service.callStart = false;
+ bringDownServiceLocked(service, false);
+ }
+
public int stopService(IApplicationThread caller, Intent service,
String resolvedType) {
// Refuse possible leaked file descriptors
@@ -9966,14 +9998,12 @@ public final class ActivityManagerService extends ActivityManagerNative
ServiceLookupResult r = findServiceLocked(service, resolvedType);
if (r != null) {
if (r.record != null) {
- synchronized (r.record.stats.getBatteryStats()) {
- r.record.stats.stopRunningLocked();
- }
- r.record.startRequested = false;
- r.record.callStart = false;
final long origId = Binder.clearCallingIdentity();
- bringDownServiceLocked(r.record, false);
- Binder.restoreCallingIdentity(origId);
+ try {
+ stopServiceLocked(r.record);
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
return 1;
}
return -1;
@@ -10035,7 +10065,7 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- if (r.lastStartId != startId) {
+ if (r.getLastStartId() != startId) {
return false;
}
@@ -10476,7 +10506,7 @@ public final class ActivityManagerService extends ActivityManagerNative
case Service.START_NOT_STICKY: {
// We are done with the associated start arguments.
r.findDeliveredStart(startId, true);
- if (r.lastStartId == startId) {
+ if (r.getLastStartId() == startId) {
// There is no more work, and this service
// doesn't want to hang around if killed.
r.stopIfKilled = true;
@@ -10496,6 +10526,12 @@ public final class ActivityManagerService extends ActivityManagerNative
}
break;
}
+ case Service.START_TASK_REMOVED_COMPLETE: {
+ // Special processing for onTaskRemoved(). Don't
+ // impact normal onStartCommand() processing.
+ r.findDeliveredStart(startId, true);
+ break;
+ }
default:
throw new IllegalArgumentException(
"Unknown service start result: " + res);
@@ -12885,23 +12921,31 @@ public final class ActivityManagerService extends ActivityManagerNative
if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
"Setting process group of " + app.processName
+ " to " + app.curSchedGroup);
- if (true) {
- long oldId = Binder.clearCallingIdentity();
- try {
- Process.setProcessGroup(app.pid, app.curSchedGroup);
- } catch (Exception e) {
- Slog.w(TAG, "Failed setting process group of " + app.pid
- + " to " + app.curSchedGroup);
- e.printStackTrace();
- } finally {
- Binder.restoreCallingIdentity(oldId);
- }
- }
- if (false) {
- if (app.thread != null) {
+ if (app.waitingToKill != null &&
+ app.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) {
+ Slog.i(TAG, "Killing " + app + ": " + app.waitingToKill);
+ EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+ app.processName, app.setAdj, app.waitingToKill);
+ Process.killProcessQuiet(app.pid);
+ } else {
+ if (true) {
+ long oldId = Binder.clearCallingIdentity();
try {
- app.thread.setSchedulingGroup(app.curSchedGroup);
- } catch (RemoteException e) {
+ Process.setProcessGroup(app.pid, app.curSchedGroup);
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed setting process group of " + app.pid
+ + " to " + app.curSchedGroup);
+ e.printStackTrace();
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
+ }
+ }
+ if (false) {
+ if (app.thread != null) {
+ try {
+ app.thread.setSchedulingGroup(app.curSchedGroup);
+ } catch (RemoteException e) {
+ }
}
}
}
@@ -13024,7 +13068,9 @@ public final class ActivityManagerService extends ActivityManagerNative
+ (app.thread != null ? app.thread.asBinder() : null)
+ ")\n");
if (app.pid > 0 && app.pid != MY_PID) {
- Process.killProcess(app.pid);
+ EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+ app.processName, app.setAdj, "empty");
+ Process.killProcessQuiet(app.pid);
} else {
try {
app.thread.scheduleExit();
@@ -13090,7 +13136,9 @@ public final class ActivityManagerService extends ActivityManagerNative
+ (app.thread != null ? app.thread.asBinder() : null)
+ ")\n");
if (app.pid > 0 && app.pid != MY_PID) {
- Process.killProcess(app.pid);
+ EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+ app.processName, app.setAdj, "empty");
+ Process.killProcessQuiet(app.pid);
} else {
try {
app.thread.scheduleExit();
@@ -13147,7 +13195,9 @@ public final class ActivityManagerService extends ActivityManagerNative
+ (app.thread != null ? app.thread.asBinder() : null)
+ ")\n");
if (app.pid > 0 && app.pid != MY_PID) {
- Process.killProcess(app.pid);
+ EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+ app.processName, app.setAdj, "old background");
+ Process.killProcessQuiet(app.pid);
} else {
try {
app.thread.scheduleExit();
@@ -13364,4 +13414,11 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
}
+
+ // Multi-user methods
+
+ public boolean switchUser(int userid) {
+ // TODO
+ return true;
+ }
}
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index e1d380b..9558895 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -21,8 +21,10 @@ import com.android.internal.os.BatteryStatsImpl;
import com.android.server.am.ActivityManagerService.PendingActivityLaunch;
import android.app.Activity;
+import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.IActivityManager;
+import android.app.IThumbnailRetriever;
import static android.app.IActivityManager.START_CLASS_NOT_FOUND;
import static android.app.IActivityManager.START_DELIVERED_TO_TOP;
import static android.app.IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT;
@@ -69,7 +71,7 @@ import java.util.List;
/**
* State and management of a single stack of activities.
*/
-final public class ActivityStack {
+final class ActivityStack {
static final String TAG = ActivityManagerService.TAG;
static final boolean localLOGV = ActivityManagerService.localLOGV;
static final boolean DEBUG_SWITCH = ActivityManagerService.DEBUG_SWITCH;
@@ -135,14 +137,14 @@ final public class ActivityStack {
* The back history of all previous (and possibly still
* running) activities. It contains HistoryRecord objects.
*/
- final ArrayList mHistory = new ArrayList();
+ final ArrayList<ActivityRecord> mHistory = new ArrayList<ActivityRecord>();
/**
* List of running activities, sorted by recent usage.
* The first entry in the list is the least recently used.
* It contains HistoryRecord objects.
*/
- final ArrayList mLRUActivities = new ArrayList();
+ final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<ActivityRecord>();
/**
* List of activities that are waiting for a new activity
@@ -346,7 +348,7 @@ final public class ActivityStack {
final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) {
int i = mHistory.size()-1;
while (i >= 0) {
- ActivityRecord r = (ActivityRecord)mHistory.get(i);
+ ActivityRecord r = mHistory.get(i);
if (!r.finishing && r != notTop) {
return r;
}
@@ -358,7 +360,7 @@ final public class ActivityStack {
final ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) {
int i = mHistory.size()-1;
while (i >= 0) {
- ActivityRecord r = (ActivityRecord)mHistory.get(i);
+ ActivityRecord r = mHistory.get(i);
if (!r.finishing && !r.delayedResume && r != notTop) {
return r;
}
@@ -379,7 +381,7 @@ final public class ActivityStack {
final ActivityRecord topRunningActivityLocked(IBinder token, int taskId) {
int i = mHistory.size()-1;
while (i >= 0) {
- ActivityRecord r = (ActivityRecord)mHistory.get(i);
+ ActivityRecord r = mHistory.get(i);
// Note: the taskId check depends on real taskId fields being non-zero
if (!r.finishing && (token != r) && (taskId != r.task.taskId)) {
return r;
@@ -425,7 +427,7 @@ final public class ActivityStack {
final int N = mHistory.size();
for (int i=(N-1); i>=0; i--) {
- ActivityRecord r = (ActivityRecord)mHistory.get(i);
+ ActivityRecord r = mHistory.get(i);
if (!r.finishing && r.task != cp
&& r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
cp = r.task;
@@ -469,7 +471,7 @@ final public class ActivityStack {
final int N = mHistory.size();
for (int i=(N-1); i>=0; i--) {
- ActivityRecord r = (ActivityRecord)mHistory.get(i);
+ ActivityRecord r = mHistory.get(i);
if (!r.finishing) {
if (r.intent.getComponent().equals(cls)) {
//Slog.i(TAG, "Found matching class!");
@@ -504,6 +506,7 @@ final public class ActivityStack {
}
r.app = app;
+ app.waitingToKill = null;
if (localLOGV) Slog.v(TAG, "Launching: " + r);
@@ -678,7 +681,7 @@ final public class ActivityStack {
}
// Ensure activities are no longer sleeping.
for (int i=mHistory.size()-1; i>=0; i--) {
- ActivityRecord r = (ActivityRecord)mHistory.get(i);
+ ActivityRecord r = mHistory.get(i);
r.setSleeping(false);
}
mGoingToSleepActivities.clear();
@@ -724,7 +727,7 @@ final public class ActivityStack {
// Make sure any stopped but visible activities are now sleeping.
// This ensures that the activity's onStop() is called.
for (int i=mHistory.size()-1; i>=0; i--) {
- ActivityRecord r = (ActivityRecord)mHistory.get(i);
+ ActivityRecord r = mHistory.get(i);
if (r.state == ActivityState.STOPPING || r.state == ActivityState.STOPPED) {
r.setSleeping(true);
}
@@ -862,7 +865,7 @@ final public class ActivityStack {
synchronized (mService) {
int index = indexOfTokenLocked(token);
if (index >= 0) {
- r = (ActivityRecord)mHistory.get(index);
+ r = mHistory.get(index);
mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
if (mPausingActivity == r) {
r.state = ActivityState.PAUSED;
@@ -1024,7 +1027,7 @@ final public class ActivityStack {
ActivityRecord r;
boolean behindFullscreen = false;
for (; i>=0; i--) {
- r = (ActivityRecord)mHistory.get(i);
+ r = mHistory.get(i);
if (DEBUG_VISBILITY) Slog.v(
TAG, "Make visible? " + r + " finishing=" + r.finishing
+ " state=" + r.state);
@@ -1108,7 +1111,7 @@ final public class ActivityStack {
// Now for any activities that aren't visible to the user, make
// sure they no longer are keeping the screen frozen.
while (i >= 0) {
- r = (ActivityRecord)mHistory.get(i);
+ r = mHistory.get(i);
if (DEBUG_VISBILITY) Slog.v(
TAG, "Make invisible? " + r + " finishing=" + r.finishing
+ " state=" + r.state
@@ -1499,7 +1502,7 @@ final public class ActivityStack {
// If starting in an existing task, find where that is...
boolean startIt = true;
for (int i = NH-1; i >= 0; i--) {
- ActivityRecord p = (ActivityRecord)mHistory.get(i);
+ ActivityRecord p = mHistory.get(i);
if (p.finishing) {
continue;
}
@@ -1646,7 +1649,7 @@ final public class ActivityStack {
int replyChainEnd = -1;
int lastReparentPos = -1;
for (int i=mHistory.size()-1; i>=-1; i--) {
- ActivityRecord below = i >= 0 ? (ActivityRecord)mHistory.get(i) : null;
+ ActivityRecord below = i >= 0 ? mHistory.get(i) : null;
if (below != null && below.finishing) {
continue;
@@ -1700,7 +1703,7 @@ final public class ActivityStack {
// bottom of the activity stack. This also keeps it
// correctly ordered with any activities we previously
// moved.
- ActivityRecord p = (ActivityRecord)mHistory.get(0);
+ ActivityRecord p = mHistory.get(0);
if (target.taskAffinity != null
&& target.taskAffinity.equals(p.task.affinity)) {
// If the activity currently at the bottom has the
@@ -1727,7 +1730,7 @@ final public class ActivityStack {
int dstPos = 0;
ThumbnailHolder curThumbHolder = target.thumbHolder;
for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) {
- p = (ActivityRecord)mHistory.get(srcPos);
+ p = mHistory.get(srcPos);
if (p.finishing) {
continue;
}
@@ -1764,7 +1767,7 @@ final public class ActivityStack {
// like these are all in the reply chain.
replyChainEnd = targetI+1;
while (replyChainEnd < mHistory.size() &&
- ((ActivityRecord)mHistory.get(
+ (mHistory.get(
replyChainEnd)).task == task) {
replyChainEnd++;
}
@@ -1774,7 +1777,7 @@ final public class ActivityStack {
}
ActivityRecord p = null;
for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) {
- p = (ActivityRecord)mHistory.get(srcPos);
+ p = mHistory.get(srcPos);
if (p.finishing) {
continue;
}
@@ -1834,7 +1837,7 @@ final public class ActivityStack {
}
ActivityRecord p = null;
for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) {
- p = (ActivityRecord)mHistory.get(srcPos);
+ p = mHistory.get(srcPos);
if (p.finishing) {
continue;
}
@@ -1852,7 +1855,7 @@ final public class ActivityStack {
replyChainEnd = targetI;
}
for (int srcPos=replyChainEnd; srcPos>=targetI; srcPos--) {
- ActivityRecord p = (ActivityRecord)mHistory.get(srcPos);
+ ActivityRecord p = mHistory.get(srcPos);
if (p.finishing) {
continue;
}
@@ -1881,7 +1884,7 @@ final public class ActivityStack {
// below so it remains singleTop.
if (target.info.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) {
for (int j=lastReparentPos-1; j>=0; j--) {
- ActivityRecord p = (ActivityRecord)mHistory.get(j);
+ ActivityRecord p = mHistory.get(j);
if (p.finishing) {
continue;
}
@@ -1922,7 +1925,7 @@ final public class ActivityStack {
// First find the requested task.
while (i > 0) {
i--;
- ActivityRecord r = (ActivityRecord)mHistory.get(i);
+ ActivityRecord r = mHistory.get(i);
if (r.task.taskId == taskId) {
i++;
break;
@@ -1932,7 +1935,7 @@ final public class ActivityStack {
// Now clear it.
while (i > 0) {
i--;
- ActivityRecord r = (ActivityRecord)mHistory.get(i);
+ ActivityRecord r = mHistory.get(i);
if (r.finishing) {
continue;
}
@@ -1944,7 +1947,7 @@ final public class ActivityStack {
ActivityRecord ret = r;
while (i < (mHistory.size()-1)) {
i++;
- r = (ActivityRecord)mHistory.get(i);
+ r = mHistory.get(i);
if (r.task.taskId != taskId) {
break;
}
@@ -1980,6 +1983,28 @@ final public class ActivityStack {
}
/**
+ * Completely remove all activities associated with an existing
+ * task starting at a specified index.
+ */
+ private final void performClearTaskAtIndexLocked(int taskId, int i) {
+ while (i < (mHistory.size()-1)) {
+ ActivityRecord r = mHistory.get(i);
+ if (r.task.taskId != taskId) {
+ // Whoops hit the end.
+ return;
+ }
+ if (r.finishing) {
+ i++;
+ continue;
+ }
+ if (!finishActivityLocked(r, i, Activity.RESULT_CANCELED,
+ null, "clear")) {
+ i++;
+ }
+ }
+ }
+
+ /**
* Completely remove all activities associated with an existing task.
*/
private final void performClearTaskLocked(int taskId) {
@@ -1988,37 +2013,23 @@ final public class ActivityStack {
// First find the requested task.
while (i > 0) {
i--;
- ActivityRecord r = (ActivityRecord)mHistory.get(i);
+ ActivityRecord r = mHistory.get(i);
if (r.task.taskId == taskId) {
i++;
break;
}
}
- // Now clear it.
+ // Now find the start and clear it.
while (i > 0) {
i--;
- ActivityRecord r = (ActivityRecord)mHistory.get(i);
+ ActivityRecord r = mHistory.get(i);
if (r.finishing) {
continue;
}
if (r.task.taskId != taskId) {
// We hit the bottom. Now finish it all...
- while (i < (mHistory.size()-1)) {
- i++;
- r = (ActivityRecord)mHistory.get(i);
- if (r.task.taskId != taskId) {
- // Whoops hit the end.
- return;
- }
- if (r.finishing) {
- continue;
- }
- if (finishActivityLocked(r, i, Activity.RESULT_CANCELED,
- null, "clear")) {
- i--;
- }
- }
+ performClearTaskAtIndexLocked(taskId, i+1);
return;
}
}
@@ -2032,7 +2043,7 @@ final public class ActivityStack {
int i = mHistory.size();
while (i > 0) {
i--;
- ActivityRecord candidate = (ActivityRecord)mHistory.get(i);
+ ActivityRecord candidate = mHistory.get(i);
if (candidate.task.taskId != task) {
break;
}
@@ -2049,9 +2060,9 @@ final public class ActivityStack {
* brought to the front.
*/
private final ActivityRecord moveActivityToFrontLocked(int where) {
- ActivityRecord newTop = (ActivityRecord)mHistory.remove(where);
+ ActivityRecord newTop = mHistory.remove(where);
int top = mHistory.size();
- ActivityRecord oldTop = (ActivityRecord)mHistory.get(top-1);
+ ActivityRecord oldTop = mHistory.get(top-1);
mHistory.add(top, newTop);
oldTop.frontOfTask = false;
newTop.frontOfTask = true;
@@ -2094,7 +2105,7 @@ final public class ActivityStack {
if (DEBUG_RESULTS) Slog.v(
TAG, "Sending result to " + resultTo + " (index " + index + ")");
if (index >= 0) {
- sourceRecord = (ActivityRecord)mHistory.get(index);
+ sourceRecord = mHistory.get(index);
if (requestCode >= 0 && !sourceRecord.finishing) {
resultRecord = sourceRecord;
}
@@ -2576,7 +2587,7 @@ final public class ActivityStack {
// this case should never happen.
final int N = mHistory.size();
ActivityRecord prev =
- N > 0 ? (ActivityRecord)mHistory.get(N-1) : null;
+ N > 0 ? mHistory.get(N-1) : null;
r.setTask(prev != null
? prev.task
: new TaskRecord(mService.mCurTask, r.info, intent), null, true);
@@ -3021,7 +3032,7 @@ final public class ActivityStack {
// Get the activity record.
int index = indexOfTokenLocked(token);
if (index >= 0) {
- ActivityRecord r = (ActivityRecord)mHistory.get(index);
+ ActivityRecord r = mHistory.get(index);
if (fromTimeout) {
reportActivityLaunchedLocked(fromTimeout, r, -1, -1);
@@ -3153,12 +3164,12 @@ final public class ActivityStack {
if (index < 0) {
return false;
}
- ActivityRecord r = (ActivityRecord)mHistory.get(index);
+ ActivityRecord r = mHistory.get(index);
// Is this the last activity left?
boolean lastActivity = true;
for (int i=mHistory.size()-1; i>=0; i--) {
- ActivityRecord p = (ActivityRecord)mHistory.get(i);
+ ActivityRecord p = mHistory.get(i);
if (!p.finishing && p != r) {
lastActivity = false;
break;
@@ -3193,7 +3204,7 @@ final public class ActivityStack {
System.identityHashCode(r),
r.task.taskId, r.shortComponentName, reason);
if (index < (mHistory.size()-1)) {
- ActivityRecord next = (ActivityRecord)mHistory.get(index+1);
+ ActivityRecord next = mHistory.get(index+1);
if (next.task == r.task) {
if (r.frontOfTask) {
// The next activity is now the front of the task.
@@ -3249,7 +3260,7 @@ final public class ActivityStack {
if (mResumedActivity == r) {
boolean endTask = index <= 0
- || ((ActivityRecord)mHistory.get(index-1)).task != r.task;
+ || (mHistory.get(index-1)).task != r.task;
if (DEBUG_TRANSITION) Slog.v(TAG,
"Prepare close transition: finishing " + r);
mService.mWindowManager.prepareAppTransition(endTask
@@ -3391,6 +3402,7 @@ final public class ActivityStack {
// Get rid of any pending idle timeouts.
mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
mHandler.removeMessages(IDLE_TIMEOUT_MSG, r);
+ mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r);
}
private final void removeActivityFromHistoryLocked(ActivityRecord r) {
@@ -3516,7 +3528,7 @@ final public class ActivityStack {
int index = indexOfTokenLocked(token);
if (index >= 0) {
- ActivityRecord r = (ActivityRecord)mHistory.get(index);
+ ActivityRecord r = mHistory.get(index);
if (r.state == ActivityState.DESTROYING) {
final long origId = Binder.clearCallingIdentity();
removeActivityFromHistoryLocked(r);
@@ -3558,7 +3570,7 @@ final public class ActivityStack {
final void moveHomeToFrontLocked() {
TaskRecord homeTask = null;
for (int i=mHistory.size()-1; i>=0; i--) {
- ActivityRecord hr = (ActivityRecord)mHistory.get(i);
+ ActivityRecord hr = mHistory.get(i);
if (hr.isHomeActivity) {
homeTask = hr.task;
break;
@@ -3576,7 +3588,7 @@ final public class ActivityStack {
final int task = tr.taskId;
int top = mHistory.size()-1;
- if (top < 0 || ((ActivityRecord)mHistory.get(top)).task.taskId == task) {
+ if (top < 0 || (mHistory.get(top)).task.taskId == task) {
// nothing to do!
return;
}
@@ -3591,7 +3603,7 @@ final public class ActivityStack {
// Shift all activities with this task up to the top
// of the stack, keeping them in the same internal order.
while (pos >= 0) {
- ActivityRecord r = (ActivityRecord)mHistory.get(pos);
+ ActivityRecord r = mHistory.get(pos);
if (localLOGV) Slog.v(
TAG, "At " + pos + " ckp " + r.task + ": " + r);
if (r.task.taskId == task) {
@@ -3680,7 +3692,7 @@ final public class ActivityStack {
// Shift all activities with this task down to the bottom
// of the stack, keeping them in the same internal order.
while (pos < N) {
- ActivityRecord r = (ActivityRecord)mHistory.get(pos);
+ ActivityRecord r = mHistory.get(pos);
if (localLOGV) Slog.v(
TAG, "At " + pos + " ckp " + r.task + ": " + r);
if (r.task.taskId == task) {
@@ -3714,6 +3726,106 @@ final public class ActivityStack {
return true;
}
+ public ActivityManager.TaskThumbnails getTaskThumbnailsLocked(TaskRecord tr) {
+ TaskAccessInfo info = getTaskAccessInfoLocked(tr.taskId, true);
+ ActivityRecord resumed = mResumedActivity;
+ if (resumed != null && resumed.thumbHolder == tr) {
+ info.mainThumbnail = resumed.stack.screenshotActivities(resumed);
+ } else {
+ info.mainThumbnail = tr.lastThumbnail;
+ }
+ return info;
+ }
+
+ public ActivityRecord removeTaskActivitiesLocked(int taskId, int subTaskIndex) {
+ TaskAccessInfo info = getTaskAccessInfoLocked(taskId, false);
+ if (info.root == null) {
+ Slog.w(TAG, "removeTaskLocked: unknown taskId " + taskId);
+ return null;
+ }
+
+ if (subTaskIndex < 0) {
+ // Just remove the entire task.
+ performClearTaskAtIndexLocked(taskId, info.rootIndex);
+ return info.root;
+ }
+
+ if (subTaskIndex >= info.subtasks.size()) {
+ Slog.w(TAG, "removeTaskLocked: unknown subTaskIndex " + subTaskIndex);
+ return null;
+ }
+
+ // Remove all of this task's activies starting at the sub task.
+ TaskAccessInfo.SubTask subtask = info.subtasks.get(subTaskIndex);
+ performClearTaskAtIndexLocked(taskId, subtask.index);
+ return subtask.activity;
+ }
+
+ public TaskAccessInfo getTaskAccessInfoLocked(int taskId, boolean inclThumbs) {
+ ActivityRecord resumed = mResumedActivity;
+ final TaskAccessInfo thumbs = new TaskAccessInfo();
+ // How many different sub-thumbnails?
+ final int NA = mHistory.size();
+ int j = 0;
+ ThumbnailHolder holder = null;
+ while (j < NA) {
+ ActivityRecord ar = mHistory.get(j);
+ if (!ar.finishing && ar.task.taskId == taskId) {
+ holder = ar.thumbHolder;
+ break;
+ }
+ j++;
+ }
+
+ if (j >= NA) {
+ return thumbs;
+ }
+
+ thumbs.root = mHistory.get(j);
+ thumbs.rootIndex = j;
+
+ ArrayList<TaskAccessInfo.SubTask> subtasks = new ArrayList<TaskAccessInfo.SubTask>();
+ thumbs.subtasks = subtasks;
+ ActivityRecord lastActivity = null;
+ while (j < NA) {
+ ActivityRecord ar = mHistory.get(j);
+ j++;
+ if (ar.finishing) {
+ continue;
+ }
+ if (ar.task.taskId != taskId) {
+ break;
+ }
+ lastActivity = ar;
+ if (ar.thumbHolder != holder && holder != null) {
+ thumbs.numSubThumbbails++;
+ holder = ar.thumbHolder;
+ TaskAccessInfo.SubTask sub = new TaskAccessInfo.SubTask();
+ sub.thumbnail = holder.lastThumbnail;
+ sub.activity = ar;
+ sub.index = j-1;
+ subtasks.add(sub);
+ }
+ }
+ if (lastActivity != null && subtasks.size() > 0) {
+ if (resumed == lastActivity) {
+ TaskAccessInfo.SubTask sub = subtasks.get(subtasks.size()-1);
+ sub.thumbnail = lastActivity.stack.screenshotActivities(lastActivity);
+ }
+ }
+ if (thumbs.numSubThumbbails > 0) {
+ thumbs.retriever = new IThumbnailRetriever.Stub() {
+ public Bitmap getThumbnail(int index) {
+ if (index < 0 || index >= thumbs.subtasks.size()) {
+ return null;
+ }
+ return thumbs.subtasks.get(index).thumbnail;
+ }
+ };
+ }
+ return thumbs;
+ }
+
private final void logStartActivity(int tag, ActivityRecord r,
TaskRecord task) {
EventLog.writeEvent(tag,
diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java
index 963a691..b4fdc9f 100644
--- a/services/java/com/android/server/am/BatteryStatsService.java
+++ b/services/java/com/android/server/am/BatteryStatsService.java
@@ -449,6 +449,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub {
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
boolean isCheckin = false;
+ boolean noOutput = false;
if (args != null) {
for (String arg : args) {
if ("--checkin".equals(arg)) {
@@ -457,10 +458,22 @@ public final class BatteryStatsService extends IBatteryStats.Stub {
synchronized (mStats) {
mStats.resetAllStatsLocked();
pw.println("Battery stats reset.");
+ noOutput = true;
}
+ } else if ("--write".equals(arg)) {
+ synchronized (mStats) {
+ mStats.writeSyncLocked();
+ pw.println("Battery stats written.");
+ noOutput = true;
+ }
+ } else {
+ pw.println("Unknown option: " + arg);
}
}
}
+ if (noOutput) {
+ return;
+ }
if (isCheckin) {
List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(0);
synchronized (mStats) {
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index 353ff6d..e39c239 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -65,6 +65,7 @@ class ProcessRecord {
boolean foregroundServices; // Running any services that are foreground?
boolean bad; // True if disabled in the bad process list
boolean killedBackground; // True when proc has been killed due to too many bg
+ String waitingToKill; // Process is waiting to be killed when in the bg; reason
IBinder forcingToForeground;// Token that is forcing this process to be foreground
int adjSeq; // Sequence id for identifying oom_adj assignment cycles
int lruSeq; // Sequence id for identifying LRU update cycles
@@ -202,8 +203,9 @@ class ProcessRecord {
pw.print(" lastLowMemory=");
TimeUtils.formatDuration(lastLowMemory, now, pw);
pw.print(" reportLowMemory="); pw.println(reportLowMemory);
- if (killedBackground) {
- pw.print(prefix); pw.print("killedBackground="); pw.println(killedBackground);
+ if (killedBackground || waitingToKill != null) {
+ pw.print(prefix); pw.print("killedBackground="); pw.print(killedBackground);
+ pw.print(" waitingToKill="); pw.println(waitingToKill);
}
if (debugging || crashing || crashDialog != null || notResponding
|| anrDialog != null || bad) {
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index 1a617dd..da00c0c 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -84,7 +84,6 @@ class ServiceRecord extends Binder {
boolean startRequested; // someone explicitly called start?
boolean stopIfKilled; // last onStart() said to stop if service killed?
boolean callStart; // last onStart() has asked to alway be called on restart.
- int lastStartId; // identifier of most recent start request.
int executeNesting; // number of outstanding operations keeping foreground.
long executingStart; // start time of last execute request.
int crashCount; // number of times proc has crashed with service running
@@ -96,8 +95,11 @@ class ServiceRecord extends Binder {
String stringName; // caching of toString
+ private int lastStartId; // identifier of most recent start request.
+
static class StartItem {
final ServiceRecord sr;
+ final boolean taskRemoved;
final int id;
final Intent intent;
final int targetPermissionUid;
@@ -108,8 +110,10 @@ class ServiceRecord extends Binder {
String stringName; // caching of toString
- StartItem(ServiceRecord _sr, int _id, Intent _intent, int _targetPermissionUid) {
+ StartItem(ServiceRecord _sr, boolean _taskRemoved, int _id, Intent _intent,
+ int _targetPermissionUid) {
sr = _sr;
+ taskRemoved = _taskRemoved;
id = _id;
intent = _intent;
targetPermissionUid = _targetPermissionUid;
@@ -321,6 +325,18 @@ class ServiceRecord extends Binder {
return null;
}
+ public int getLastStartId() {
+ return lastStartId;
+ }
+
+ public int makeNextStartId() {
+ lastStartId++;
+ if (lastStartId < 1) {
+ lastStartId = 1;
+ }
+ return lastStartId;
+ }
+
public void postNotification() {
final int appUid = appInfo.uid;
final int appPid = app.pid;
diff --git a/services/java/com/android/server/am/TaskAccessInfo.java b/services/java/com/android/server/am/TaskAccessInfo.java
new file mode 100644
index 0000000..5618c1a
--- /dev/null
+++ b/services/java/com/android/server/am/TaskAccessInfo.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import java.util.ArrayList;
+
+import android.app.ActivityManager.TaskThumbnails;
+import android.graphics.Bitmap;
+
+final class TaskAccessInfo extends TaskThumbnails {
+ final static class SubTask {
+ Bitmap thumbnail;
+ ActivityRecord activity;
+ int index;
+ }
+
+ public ActivityRecord root;
+ public int rootIndex;
+
+ public ArrayList<SubTask> subtasks;
+}
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index 11dde75..16b55c3 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -1336,6 +1336,19 @@ final class Settings {
}
mPendingPackages.clear();
+ /*
+ * Make sure all the updated system packages have their shared users
+ * associated with them.
+ */
+ final Iterator<PackageSetting> disabledIt = mDisabledSysPackages.values().iterator();
+ while (disabledIt.hasNext()) {
+ final PackageSetting disabledPs = disabledIt.next();
+ final Object id = getUserIdLPr(disabledPs.userId);
+ if (id != null && id instanceof SharedUserSetting) {
+ disabledPs.sharedUser = (SharedUserSetting) id;
+ }
+ }
+
readStoppedLPw();
mReadMessages.append("Read completed successfully: " + mPackages.size() + " packages, "
diff --git a/services/java/com/android/server/pm/UserDetails.java b/services/java/com/android/server/pm/UserDetails.java
new file mode 100644
index 0000000..2aeed7c
--- /dev/null
+++ b/services/java/com/android/server/pm/UserDetails.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import android.content.pm.UserInfo;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.Xml;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+public class UserDetails {
+ private static final String TAG_NAME = "name";
+
+ private static final String ATTR_FLAGS = "flags";
+
+ private static final String ATTR_ID = "id";
+
+ private static final String TAG_USERS = "users";
+
+ private static final String TAG_USER = "user";
+
+ private static final String TAG = "UserDetails";
+
+ private static final String USER_INFO_DIR = "system/users";
+ private static final String USER_LIST_FILENAME = "userlist.xml";
+
+ private SparseArray<UserInfo> mUsers;
+
+ private final File mUsersDir;
+ private final File mUserListFile;
+
+ /**
+ * Available for testing purposes.
+ */
+ UserDetails(File dataDir) {
+ mUsersDir = new File(dataDir, USER_INFO_DIR);
+ mUsersDir.mkdirs();
+ FileUtils.setPermissions(mUsersDir.toString(),
+ FileUtils.S_IRWXU|FileUtils.S_IRWXG
+ |FileUtils.S_IROTH|FileUtils.S_IXOTH,
+ -1, -1);
+ mUserListFile = new File(mUsersDir, USER_LIST_FILENAME);
+ readUserList();
+ }
+
+ public UserDetails() {
+ this(Environment.getDataDirectory());
+ }
+
+ public List<UserInfo> getUsers() {
+ ArrayList<UserInfo> users = new ArrayList<UserInfo>(mUsers.size());
+ for (int i = 0; i < mUsers.size(); i++) {
+ users.add(mUsers.valueAt(i));
+ }
+ return users;
+ }
+
+ private void readUserList() {
+ mUsers = new SparseArray<UserInfo>();
+ if (!mUserListFile.exists()) {
+ fallbackToSingleUser();
+ return;
+ }
+ FileInputStream fis = null;
+ try {
+ fis = new FileInputStream(mUserListFile);
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(fis, null);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ ;
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ Slog.e(TAG, "Unable to read user list");
+ fallbackToSingleUser();
+ return;
+ }
+
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
+ String id = parser.getAttributeValue(null, ATTR_ID);
+ UserInfo user = readUser(Integer.parseInt(id));
+ if (user != null) {
+ mUsers.put(user.id, user);
+ }
+ }
+ }
+ } catch (IOException ioe) {
+ fallbackToSingleUser();
+ } catch (XmlPullParserException pe) {
+ fallbackToSingleUser();
+ }
+ }
+
+ private void fallbackToSingleUser() {
+ // Create the primary user
+ UserInfo primary = new UserInfo(0, "Primary",
+ UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY);
+ mUsers.put(0, primary);
+
+ writeUserList();
+ writeUser(primary);
+ }
+
+ /*
+ * Writes the user file in this format:
+ *
+ * <user flags="20039023" id="0">
+ * <name>Primary</name>
+ * </user>
+ */
+ private void writeUser(UserInfo userInfo) {
+ try {
+ final File mUserFile = new File(mUsersDir, userInfo.id + ".xml");
+ final FileOutputStream fos = new FileOutputStream(mUserFile);
+ final BufferedOutputStream bos = new BufferedOutputStream(fos);
+
+ // XmlSerializer serializer = XmlUtils.serializerInstance();
+ final XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(bos, "utf-8");
+ serializer.startDocument(null, true);
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+ serializer.startTag(null, TAG_USER);
+ serializer.attribute(null, ATTR_ID, Integer.toString(userInfo.id));
+ serializer.attribute(null, ATTR_FLAGS, Integer.toString(userInfo.flags));
+
+ serializer.startTag(null, TAG_NAME);
+ serializer.text(userInfo.name);
+ serializer.endTag(null, TAG_NAME);
+
+ serializer.endTag(null, TAG_USER);
+
+ serializer.endDocument();
+ } catch (IOException ioe) {
+ Slog.e(TAG, "Error writing user info " + userInfo.id + "\n" + ioe);
+ }
+ }
+
+ /*
+ * Writes the user list file in this format:
+ *
+ * <users>
+ * <user id="0"></user>
+ * <user id="2"></user>
+ * </users>
+ */
+ private void writeUserList() {
+ try {
+ final FileOutputStream fos = new FileOutputStream(mUserListFile);
+ final BufferedOutputStream bos = new BufferedOutputStream(fos);
+
+ // XmlSerializer serializer = XmlUtils.serializerInstance();
+ final XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(bos, "utf-8");
+ serializer.startDocument(null, true);
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+ serializer.startTag(null, TAG_USERS);
+
+ for (int i = 0; i < mUsers.size(); i++) {
+ UserInfo user = mUsers.valueAt(i);
+ serializer.startTag(null, TAG_USER);
+ serializer.attribute(null, ATTR_ID, Integer.toString(user.id));
+ serializer.endTag(null, TAG_USER);
+ Slog.e(TAG, "Wrote user " + user.id + " to userlist.xml");
+ }
+
+ serializer.endTag(null, TAG_USERS);
+
+ serializer.endDocument();
+ } catch (IOException ioe) {
+ Slog.e(TAG, "Error writing user list");
+ }
+ }
+
+ private UserInfo readUser(int id) {
+ int flags = 0;
+ String name = null;
+
+ FileInputStream fis = null;
+ try {
+ File userFile = new File(mUsersDir, Integer.toString(id) + ".xml");
+ fis = new FileInputStream(userFile);
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(fis, null);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ ;
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ Slog.e(TAG, "Unable to read user " + id);
+ return null;
+ }
+
+ if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_USER)) {
+ String storedId = parser.getAttributeValue(null, ATTR_ID);
+ if (Integer.parseInt(storedId) != id) {
+ Slog.e(TAG, "User id does not match the file name");
+ return null;
+ }
+ String flagString = parser.getAttributeValue(null, ATTR_FLAGS);
+ flags = Integer.parseInt(flagString);
+
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ }
+ if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_NAME)) {
+ type = parser.next();
+ if (type == XmlPullParser.TEXT) {
+ name = parser.getText();
+ }
+ }
+ }
+ fis.close();
+
+ UserInfo userInfo = new UserInfo(id, name, flags);
+ return userInfo;
+
+ } catch (IOException ioe) {
+ } catch (XmlPullParserException pe) {
+ }
+ return null;
+ }
+
+ public UserInfo createUser(String name, int flags) {
+ int id = getNextAvailableId();
+ UserInfo userInfo = new UserInfo(id, name, flags);
+ if (!createPackageFolders(id)) {
+ return null;
+ }
+ mUsers.put(id, userInfo);
+ writeUserList();
+ writeUser(userInfo);
+ return userInfo;
+ }
+
+ public void removeUser(int id) {
+ // Remove from the list
+ UserInfo userInfo = mUsers.get(id);
+ if (userInfo != null) {
+ // Remove this user from the list
+ mUsers.remove(id);
+ // Remove user file
+ File userFile = new File(mUsersDir, id + ".xml");
+ userFile.delete();
+ writeUserList();
+ removePackageFolders(id);
+ }
+ }
+
+ private int getNextAvailableId() {
+ int i = 0;
+ while (i < Integer.MAX_VALUE) {
+ if (mUsers.indexOfKey(i) < 0) {
+ break;
+ }
+ i++;
+ }
+ return i;
+ }
+
+ private boolean createPackageFolders(int id) {
+ // TODO: Create data directories for all the packages for a new user, w/ specified user id.
+ return true;
+ }
+
+ private boolean removePackageFolders(int id) {
+ // TODO: Remove all the data directories for the specified user.
+ return true;
+ }
+}
diff --git a/services/jni/com_android_server_AlarmManagerService.cpp b/services/jni/com_android_server_AlarmManagerService.cpp
index aa8c9b3..c9a702a 100644
--- a/services/jni/com_android_server_AlarmManagerService.cpp
+++ b/services/jni/com_android_server_AlarmManagerService.cpp
@@ -2,16 +2,16 @@
**
** Copyright 2006, The Android Open Source Project
**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
**
-** http://www.apache.org/licenses/LICENSE-2.0
+** http://www.apache.org/licenses/LICENSE-2.0
**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
** limitations under the License.
*/
@@ -84,7 +84,7 @@ static void android_server_AlarmManagerService_set(JNIEnv* env, jobject obj, jin
struct timespec ts;
ts.tv_sec = seconds;
ts.tv_nsec = nanoseconds;
-
+
int result = ioctl(fd, ANDROID_ALARM_SET(type), &ts);
if (result < 0)
{
@@ -97,18 +97,18 @@ static jint android_server_AlarmManagerService_waitForAlarm(JNIEnv* env, jobject
{
#ifdef HAVE_ANDROID_OS
int result = 0;
-
+
do
{
result = ioctl(fd, ANDROID_ALARM_WAIT);
} while (result < 0 && errno == EINTR);
-
+
if (result < 0)
{
LOGE("Unable to wait on alarm: %s\n", strerror(errno));
return 0;
}
-
+
return result;
#endif
}
@@ -124,14 +124,6 @@ static JNINativeMethod sMethods[] = {
int register_android_server_AlarmManagerService(JNIEnv* env)
{
- jclass clazz = env->FindClass("com/android/server/AlarmManagerService");
-
- if (clazz == NULL)
- {
- LOGE("Can't find com/android/server/AlarmManagerService");
- return -1;
- }
-
return jniRegisterNativeMethods(env, "com/android/server/AlarmManagerService",
sMethods, NELEM(sMethods));
}
diff --git a/services/jni/com_android_server_InputManager.cpp b/services/jni/com_android_server_InputManager.cpp
index ab2c125..aaa305e 100644
--- a/services/jni/com_android_server_InputManager.cpp
+++ b/services/jni/com_android_server_InputManager.cpp
@@ -36,6 +36,8 @@
#include <input/InputManager.h>
#include <input/PointerController.h>
+#include <input/SpotController.h>
+#include <input/SpriteController.h>
#include <android_os_MessageQueue.h>
#include <android_view_KeyEvent.h>
@@ -163,6 +165,7 @@ public:
virtual nsecs_t getVirtualKeyQuietTime();
virtual void getExcludedDeviceNames(Vector<String8>& outExcludedDeviceNames);
virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId);
+ virtual sp<SpotControllerInterface> obtainSpotController(int32_t deviceId);
/* --- InputDispatcherPolicyInterface implementation --- */
@@ -213,12 +216,16 @@ private:
// System UI visibility.
int32_t systemUiVisibility;
+ // Sprite controller singleton, created on first use.
+ sp<SpriteController> spriteController;
+
// Pointer controller singleton, created and destroyed as needed.
wp<PointerController> pointerController;
} mLocked;
void updateInactivityFadeDelayLocked(const sp<PointerController>& controller);
void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags);
+ void ensureSpriteControllerLocked();
// Power manager interactions.
bool isScreenOn();
@@ -419,18 +426,15 @@ sp<PointerControllerInterface> NativeInputManager::obtainPointerController(int32
sp<PointerController> controller = mLocked.pointerController.promote();
if (controller == NULL) {
- JNIEnv* env = jniEnv();
- jint layer = env->CallIntMethod(mCallbacksObj, gCallbacksClassInfo.getPointerLayer);
- if (checkAndClearExceptionFromCallback(env, "getPointerLayer")) {
- layer = -1;
- }
+ ensureSpriteControllerLocked();
- controller = new PointerController(mLooper, layer);
+ controller = new PointerController(mLooper, mLocked.spriteController);
mLocked.pointerController = controller;
controller->setDisplaySize(mLocked.displayWidth, mLocked.displayHeight);
controller->setDisplayOrientation(mLocked.displayOrientation);
+ JNIEnv* env = jniEnv();
jobject iconObj = env->CallObjectMethod(mCallbacksObj, gCallbacksClassInfo.getPointerIcon);
if (!checkAndClearExceptionFromCallback(env, "getPointerIcon") && iconObj) {
jfloat iconHotSpotX = env->GetFloatField(iconObj, gPointerIconClassInfo.hotSpotX);
@@ -451,6 +455,24 @@ sp<PointerControllerInterface> NativeInputManager::obtainPointerController(int32
return controller;
}
+sp<SpotControllerInterface> NativeInputManager::obtainSpotController(int32_t deviceId) {
+ AutoMutex _l(mLock);
+
+ ensureSpriteControllerLocked();
+ return new SpotController(mLooper, mLocked.spriteController);
+}
+
+void NativeInputManager::ensureSpriteControllerLocked() {
+ if (mLocked.spriteController == NULL) {
+ JNIEnv* env = jniEnv();
+ jint layer = env->CallIntMethod(mCallbacksObj, gCallbacksClassInfo.getPointerLayer);
+ if (checkAndClearExceptionFromCallback(env, "getPointerLayer")) {
+ layer = -1;
+ }
+ mLocked.spriteController = new SpriteController(mLooper, layer);
+ }
+}
+
void NativeInputManager::notifySwitch(nsecs_t when, int32_t switchCode,
int32_t switchValue, uint32_t policyFlags) {
#if DEBUG_INPUT_DISPATCHER_POLICY
diff --git a/services/jni/com_android_server_UsbService.cpp b/services/jni/com_android_server_UsbService.cpp
index 00ee7e3..9cd04f6 100644
--- a/services/jni/com_android_server_UsbService.cpp
+++ b/services/jni/com_android_server_UsbService.cpp
@@ -37,13 +37,6 @@
namespace android
{
-static struct file_descriptor_offsets_t
-{
- jclass mClass;
- jmethodID mConstructor;
- jfieldID mDescriptor;
-} gFileDescriptorOffsets;
-
static struct parcel_file_descriptor_offsets_t
{
jclass mClass;
@@ -167,11 +160,8 @@ static jobject android_server_UsbService_openDevice(JNIEnv *env, jobject thiz, j
int newFD = dup(fd);
usb_device_close(device);
- jobject fileDescriptor = env->NewObject(gFileDescriptorOffsets.mClass,
- gFileDescriptorOffsets.mConstructor);
- if (fileDescriptor != NULL) {
- env->SetIntField(fileDescriptor, gFileDescriptorOffsets.mDescriptor, newFD);
- } else {
+ jobject fileDescriptor = jniCreateFileDescriptor(env, newFD);
+ if (fileDescriptor == NULL) {
return NULL;
}
return env->NewObject(gParcelFileDescriptorOffsets.mClass,
@@ -221,11 +211,8 @@ static jobject android_server_UsbService_openAccessory(JNIEnv *env, jobject thiz
LOGE("could not open %s", DRIVER_NAME);
return NULL;
}
- jobject fileDescriptor = env->NewObject(gFileDescriptorOffsets.mClass,
- gFileDescriptorOffsets.mConstructor);
- if (fileDescriptor != NULL) {
- env->SetIntField(fileDescriptor, gFileDescriptorOffsets.mDescriptor, fd);
- } else {
+ jobject fileDescriptor = jniCreateFileDescriptor(env, fd);
+ if (fileDescriptor == NULL) {
return NULL;
}
return env->NewObject(gParcelFileDescriptorOffsets.mClass,
@@ -260,14 +247,6 @@ int register_android_server_UsbService(JNIEnv *env)
return -1;
}
- clazz = env->FindClass("java/io/FileDescriptor");
- LOG_FATAL_IF(clazz == NULL, "Unable to find class java.io.FileDescriptor");
- gFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
- gFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "()V");
- gFileDescriptorOffsets.mDescriptor = env->GetFieldID(clazz, "descriptor", "I");
- LOG_FATAL_IF(gFileDescriptorOffsets.mDescriptor == NULL,
- "Unable to find descriptor field in java.io.FileDescriptor");
-
clazz = env->FindClass("android/os/ParcelFileDescriptor");
LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor");
gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index f115f42..2fcce78 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -15,7 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.frameworks.servicestests">
+ package="com.android.frameworks.servicestests">
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java b/services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java
new file mode 100644
index 0000000..7b77aac
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/UserDetailsTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import com.android.server.pm.UserDetails;
+
+import android.content.pm.UserInfo;
+import android.os.Debug;
+import android.os.Environment;
+import android.test.AndroidTestCase;
+
+import java.util.List;
+
+/** Test {@link UserDetails} functionality. */
+public class UserDetailsTest extends AndroidTestCase {
+
+ UserDetails mDetails = null;
+
+ @Override
+ public void setUp() throws Exception {
+ mDetails = new UserDetails(Environment.getExternalStorageDirectory());
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ List<UserInfo> users = mDetails.getUsers();
+ // Remove all except the primary user
+ for (UserInfo user : users) {
+ if (!user.isPrimary()) {
+ mDetails.removeUser(user.id);
+ }
+ }
+ }
+
+ public void testHasPrimary() throws Exception {
+ assertTrue(findUser(0));
+ }
+
+ public void testAddUser() throws Exception {
+ final UserDetails details = mDetails;
+
+ UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST);
+ assertTrue(userInfo != null);
+
+ List<UserInfo> list = details.getUsers();
+ boolean found = false;
+ for (UserInfo user : list) {
+ if (user.id == userInfo.id && user.name.equals("Guest 1")
+ && user.isGuest()
+ && !user.isAdmin()
+ && !user.isPrimary()) {
+ found = true;
+ }
+ }
+ assertTrue(found);
+ }
+
+ public void testAdd2Users() throws Exception {
+ final UserDetails details = mDetails;
+
+ UserInfo user1 = details.createUser("Guest 1", UserInfo.FLAG_GUEST);
+ UserInfo user2 = details.createUser("User 2", UserInfo.FLAG_ADMIN);
+
+ assertTrue(user1 != null);
+ assertTrue(user2 != null);
+
+ assertTrue(findUser(0));
+ assertTrue(findUser(user1.id));
+ assertTrue(findUser(user2.id));
+ }
+
+ public void testRemoveUser() throws Exception {
+ final UserDetails details = mDetails;
+
+ UserInfo userInfo = details.createUser("Guest 1", UserInfo.FLAG_GUEST);
+
+ details.removeUser(userInfo.id);
+
+ assertFalse(findUser(userInfo.id));
+ }
+
+ private boolean findUser(int id) {
+ List<UserInfo> list = mDetails.getUsers();
+
+ for (UserInfo user : list) {
+ if (user.id == id) {
+ return true;
+ }
+ }
+ return false;
+ }
+}